整体架构
关于小程序转 Web
,MorJS
的思路是将已有的小程序工程直接转成 react
代码。
借助 Webpack
,MorJS
提供了一整套的 loader
,包含 axml-loader
(负责编译 axml
文件)、acss-loader
(负责编译 acss
文件)、js-loader
(负责编译小程序中的 js
文件)。
介绍
上图是小程序转 Web
的整体架构。最上面的四个部分属于内核部分,分别由 编译器、dsl runtime
、api(my)
、全局公共组件 四个模块组成,每一个模块都是独立可替换的。
Compiler(编译器)
编译器模块比较特殊,主要负责将 axml
、acss
、js
、json
等文件编译成模块化的 js
文件,完全是静态编译。至于详细的编译流程下面会细说。
Runtime(运行时)
runtime
提供了小程序在 h5 中的运行时功能,比如:Page
、Component
、App
等全局函数,并且提供完整的生命周期实现,事件的注册、分发(非 react
事件),slot
、block
等全局 DSL
层面组件实现,等等。
API (my.*)
api
模块提供了类似支付宝小程序中 my
一系列 api
的实现,事实上,目前 api
的实现其实相对来说比较分散,在 api
模块并没有提供全部的支付宝小程序 api
的实现,有些 api
在全局公共组件来实现(比如:UI
相关的 api
,下拉刷新等),甚至有些 api
需要开发者自己来实现(比如:登录态的获取等等)。
Components(全局公共组件)
全局公共组件指的是 view
、button
、scroll-view
等支付宝小程序原生提供的组件,在 MorJS
中,这些组件是基于 web-components
来实现的,之所以使用web-components
来实现而不是 react
,是基于以下几个原因:
web-components
组件支持标签选择器。这样就能跟小程序的组件在样式的写法上抹平。web-components
组件天生具备样式隔离的能力,不用担心公共组件的样式会污染全局。web-components
组件支持路由事件,因此小程序中的tap、touch
等事件可以直接自定义抛出。web-components
组件注册以后,可以像原生html
组件一样使用,无需单独引入组件文件。支持原生html
组件的所有方法和功能。
以上几个优势,如果用 react
来实现,虽然在技术上是可以有类似的实现,但是成本就比较高了,远不如 web-components
来的简单直接。
其它
除了以上四个核心模块以外,由于公共组件、api
的实现都是模块化的,尤其是 api
部分,可以轻易的扩展自己的 api
设置重写部分 api
,因此对于接入方来说,可以扩展自己的公共组件、api
。
以上四个模块,除了编译器模块,剩余的三个模块整体才算一个完整的小程序运行时。
运行流程
上图中的运行流程其实跟普通的 react
工程运行流程差不多,仅仅是多了一个针对 axml
文件、acss
文件、js
文件进行额外编译转码的流程,最终还是通过 webpack
进行打包运行。
进一步可以分析出,这样的流程,不管是整个工程级别的转码,还是组件库级别转码,甚至是已有 react
项目直接引入小程序代码,都可以通过上图中的方式直接(loader
方式)或者间接(命令行)运行。
编译流程
不同的文件,编译的方式是不一样的,编译处理的内容也是不一样的。
AXML
整个小程序编译器,就数 axml
文件编译最复杂。整个编译流程如上图所示。
- 将
axml
文件转换成xml
文档树。 - 将
xml
文档树,转换成小程序的ast
。针对不同的节点类型、属性类型做针对性的转换。 3, 将小程序ast
进一步转换成jsx
代码。事实上,有了ast
以后,不仅能转换成react
代码,也可以转换成vue
的代码,甚至也可以转换成基于web-component
的代码,只需要提供不同的dsl runtime
即可。这也是为何上面将dsl runtime
单独作为一个模块的原因。
axml
编译复杂的部分,不在 ast
,而是在 generate
中,各种小程序 dsl
的特性都需要在这里进行转换。
ACSS
acss
编译,其实很简单,目前只是做了三件事:
rpx
支持—— 将rpx
编译成rem
。- 标签样式的支持—— 这是这里仅仅是做了一个标签样式的重命名。之所以这样,是因为在实际
axml
中用的标签比如view
,但是实际注册的标签名是tiga-view
。因此在acss
文件中,对标签样式的重命名也是一个非常重要的功能。 - 条件编译。
JS
最新版本的编译器,极大简化了 JS
文件的编译。目前 JS
编译器仅仅如如下几个事情。
- 条件编译的支持。
import
路径的转换。主要是将绝对路径转换成相对于工程的根目录的相对路径。- 将
Page
或者Component
相关的js
文件,编译成以export default
形式导出的js
文件,之所以这样做,是因为需要将Page
或者Component
全部以组件的形式导出,然后可以被其他js
文件引用。
总结
但从编译器来说,最核心的就是 AXML
的编译,而 acss
和 js
文件 的编译相对来说很简单。