跨平台不是一个新的话题,它已经被讨论了几十年了。在最近的一些尝试,让我对跨平台有了一些新的想法。在想法真正落地之前,我梳理了一下不同跨平台方案的一些特征,便有了它的几种模式。
故事的开始是这样的,受整洁架构思维的影响,从 2019 年,我便开始一种合适的模式来共享代码模式。我尝试了几种不同的思路:
- 使用 Serverless TypeScript 构建后台应用,通过将领域模型打包成 npm 包、submodule 实现业务共享
- 使用 Golang WASM 构建跨前后端核心域
- 顺带尝试了 Rust WASM,但是 Rust 在这一两里被后端采用的可能性太低,除非原有的是 C 技术栈
- Kotlin 开发后端应用,Kotlin2js 转成前端库,提供给前端使用
除此,还有更伟大的 Chapi,可以将任意语言转换为任意语言。当然了,你知道我只是在 YY。
同一语言
当我们谈论到跨平台的时候,要谈论到桌面操作系统、移动端操作系统。桌面操作系统的跨平台模式和移动端不太一样 —— 桌面端可以使用同一语言。而移动端 Android 主要使用的是 Java、Kotlin,配合游戏开发等使用的 NDK;iOS 主要使用的是 Objective-C、Swift,它们可以直接编译、调用 C 库。
在没有操作系统限制编程语言的时候,我们在同一个世界下,使用着同一种编程语言。
1. 基于库/模式库封装
模式库,是一系列可复用代码的合集,如前端的组件,通用的工具函数等等。
在我还没有接触 Web 开发之前,我是一个 Qt 粉(Qt 是一个跨平台的 C 应用程序开发框架。因为,十几年前对于桌面应用的开发,你并没有太多的选择,要么 GTK 要么 Qt。而我还是一个 KDE 粉,顺带还是一个 OpenSuSE 粉,因为有着最稳定的桌面环境。
过去,CPU 的性能没有这么好,JavaScript 引擎速度没有这么快,Web 浏览器只是个辅助工具。若是想开发跨平台应用,得从底层库开始。
嗯,所以,开发游戏的人们,选择了Qt、wxWidgets、Gtk 等框架,作为应用的基础设施。我习惯于将这样的工具称为模式库,因为它们抽象了各种模式到代码中,否则怎么跨平台呢?
1.1 IDE 封装模式库细节
在有了 IDE 之后,我们已经不关注于这些底层细节了。但是,我们仍然是基于这些模式库。
2. 通过交叉编译构建
交叉编译是指,在一个平台上生成另一个平台上的可执行代码。
在我的大学校园里,我接触最多的就是嵌入式应用的交叉编译,所以我一点儿也不喜欢这个东西。因为它算不上是跨平台的,还依赖于特定 MCU、SoC 的 IDE,我的代码只能运行在特定的平台上。
当我因为贫穷的缘故,我以为我离交叉编译远了——毕竟,你开始一个需要三台机器 Windows、macOS、GNU/Linux,又或者是通过持续集成服务器来做这样的事情。当我只有一台机器的时候,只有卡卡的虚拟机能解决我的矛盾。
直到去年,我使用 Golang 写了 Coca ,我重新认识了一下交叉编译。在 macOS 下,我可以直接编译出可以在 GNU/Linux、Windows 操作系统下运行的
语言运行环境
通过平台封装细节,而后提供语言作为 API 来给外部系统调用。
3. 操作系统之上:语言解释器
这一点实际上是非常容易理解的,比如我们日常使用的 Ruby、Python 等语言,都能归属于此类。
它们封装了操作系统底层的各种细节,提供了各种 API 抽象。除去部分平台特定代码,只需要拿起源码,便能直接到另外一个平台上运行。
而对于那些没有解释器的操作系统来说,可以采用诸如 Pyinstaller 便可以打包成目标平台的可执行文件。
4. 嵌入式运行时
考虑到嵌入式设备的特殊性, 我将嵌入式运行时,视为一个独立的模式。因为在嵌入式设备上跑语言解释器,你一定需要一个操作系统。反过来,针对于不同的硬件情况,还需要定制大量的 API。采用这一类架构模式的开源应用有:采用 Lua 语言的 NodeMCU,采用 JavaScript 语言的 IoT.js 等。
5. 基于应用软件
毫无疑问,这是游戏领域使用 Lua 作为脚本语言,还是 Web 世界被广泛使用的 JavaScript 的一种跨平台架构模式。
浏览器 / Electron
Web 应用,是我们使用最广泛的跨平台应用了。甚至于,你并不需要使用同一个厂商的浏览器,就可以运行起同一个 Web 应用。而这些正是浏览器提供了 JavaScript HTML CSS。JavaScript,是少数几个可以直接抄起记事本就能撸代码,并能跑起来的语言 —— 毕竟操作系统都提供了 Web 浏览器。
而正由于前端技术的速度发展,生态变得日益完善,使得诸如于 Electron 这样的框架,让越来越多的公司采用它来作为桌面应用开发框架,最具代表性的便是:Visual Studio Code。
工具运行时:Emacs
PS:Emacs 即是最好的编辑器,也是最好的操作系统。
除了浏览器之外,Emacs 还内置了一个名为 Emacs Lisp 的直译式脚本语言,通过这个语言来扩展这个操作系统的功能。
毫无疑问这种模式的主要目的是,将平台语言作为扩展的开发语言。
跨语言
构建跨语言平台并不是一件容易的事情。这一部分讲的主要是跨平台移动应用和跨前后端应用。
6. 借助DSL / 语言封装差异
在过去的几年里,跨平台的移动应用框架非常火热,其中呈上升趋势的便是:React Native 和 Flutter。尽管两个框架的运行机制不是很相同,但是考虑到都是框架 语言来封装 Android iOS 平台的差异性,我还是把它们划到同一类。
PS:顺事一吐槽,尽管从架构上说 Flutter 更加优秀,但是它那该死的布局也只有原生应用开发者会喜欢了。
然而,要开发这样一个 DSL 或者语言,并不是一件容易的事情。从某种意义上来说,我们至少需要 Android x 1 iOS x 1 Web x 1 AppDev x 1。
7. 语言转换器
通过 AST 来进行语言转换,再借助于一系列的 wrapper,来封装目标语言上的框架,以实现使用 A 语言开发 B 语言应用的目标。这一点常见于 Web 前端开发领域。
直接从 A 语言转换 B 语言,并没有太大的问题。但是,在转换的时候,我们需要考虑一下核心是什么?
框架 wrapper
这一类的工具过于小众了,而且它永远跟不上前端的变化速度。除此,它的写法可能有些奇怪,举个 Scala.js-React 的示例:
代码语言:javascript复制val Hello =
ScalaComponent.builder[String]("Hello")
.render_P(name => <.div("Hello there ", name))
.build
这……,好丑。
领域模型复用
在我最近的一次 Kotlin2js 的实践中,我发现对于领域模型的转换可能才是语言转换器的核心所在。
即存在一个单独的项目使用 Kotlin 编写,通过它的多平台编译,把它转为其它平台的代码。这样一来,便可以轻松地达到领域模型在其它端的使用。
中间格式/语言
8. 采用虚拟化技术
你知道我在说 JVM,毕竟:Write once, run anywhere。不过 JVM 只是其中的一个,除了它还有 .NET、Parrot 等。
程序语言级别的虚拟化,会将高阶语言转译成一种名为位元组码(Bytecode)的语言,透过虚拟机器转译成为可以直接执行的命令。
嗯,经编译完生成特定的格式后,通过自已的虚拟机就可以转译为可执行命令,就是这么简单直接。
对于一个开发人员来说,我们经常接触到这样的工具,也写过一些。我们也通过它们来做一些 GUI 应用,比如我用得比较多的 ClassyShark。
9. 中间语言
这一类跨平台、跨语言工具并不常用,因为转成中间语言再编译的话,除了微架构,并不常见。
暂存器传递语言(RTL)
这里让我们先用暂存器传递语言作为一个示例,我没有这方面的经验。我隐隐约约觉得存在一些情况,需要它,但是我还没有找到合适的例子证明。
代码语言:javascript复制暂存器传递语言(英语:register transfer language,缩写为 RTL),又译为暂存器转换语言、寄存器转换语言,一种中间语言,使用于编译器中。
(set (reg:SI 140)
(plus:SI (reg:SI 138)
(reg:SI 139)))
GCC 的前端(frontend)会先将程式语言转译成 RTL,之后再利用后端(backend)转化成机器码。
WebAssembly
WebAssembly 是便携式的抽象语法树,被设计来提供比 JavaScript 更快速的编译及运行。对于性能要求高的应用来说,这是一个非常好的技术。有了这一项技术,那么那些使用原生语言开发的桌面应用,就可以更容易地迁移到 Web 平台。
现在,你可以将你的 Golang 编写的代码编译到 WASM,然后提供给 JavaScript 调用了。
10. 代码生成器
我不知道为什么又扯到了这个话题。
我总以为人们会以一种中间 DSL 或者数据格式来作为中间格式,这样一来,可以实现解耦的目的,以适应未来的变化。但是没想到还可能直接生成了对应平台的代码。然后,你拿着代码去各个平台编译一遍。
没的毛病,挺好的,效率更高。
多重平台
事实上,我相信上面的大部分模式,你都是的懵逼。它们都过于 NB,以致于不是一般人能做的。所以,我们就有了多重平台技术。它利用了各种平台提供的能力,以帮助自己更好地构建跨平台能力。
当然了,随之也提升了 debug 的难度。
11. 双重平台/框架
移动端应用的第一大挑战是,面对不同移动平台带来的 API 挑战。所以,Cordova 站了出来,支持了九个平台,现在只剩下了五个。
当我们开发一个基于 Cordova 混合应用时,我们便是基于 WebView Cordova 之上构建我们的应用。大家都已经很熟悉了,这里就不熟悉说明了。
12. 多重平台/框架
即通过一层层的框架和平台,来打造自己的能力。它对于使用者人员来说,可能相当的友好,但是对于开发者来说,不一定如此。举两个例子:
- 小程序应用:微信平台 WebView 小程序框架
- Ionic 应用:WebView Cordova Ionic 框架
嗯,随着层数的上升,调试复杂度会越来越多,也越需要一个尽可能的全才。
结论
没有银弹。