小程序开发框架解析
小程序开发有一个”行业特产”的痛点:同一个业务要在微信、支付宝、百度等多个平台上线,每个平台有自己的 DSL、自己的 API、自己的组件体系。如果每个平台写一套代码,维护成本直接乘以 N。
跨端框架试图解决这个问题——用一套 DSL(通常是 React 或 Vue 语法)写代码,编译或运行时转换为各平台的小程序代码。这篇文章从行业小程序的差异对比开始,然后拆解编译时(Rax compile)和运行时(Rax runtime + kbone)两种方案的原理。
行业小程序对比
产品及定位
- 微信:在微信内被便捷地获取和传播的连接⽤户与服务的⽅式,同时具有出⾊的使⽤体验;
- ⽀付宝:运⾏在⽀付宝客户端,可以被便捷地获取和传播,为终端⽤户提供更优的⽤户体验;
- 淘宝:服务移动开发者的平台,帮助开发者构建⾃⼰的业务阵地,并提供良好的⽤户体验;
- 百度:依托以百度App为代表的全域流量,通过百度AI开放式赋能,精准连接⽤户,业界⾸家开放⽣态,让开发者重回业务理解和创意赛道;
总结:
- ⼩程序的⽬标是万事万物皆可⼩程序;
- 超级APP的崛起为⼩程序提供⽣存⼟壤 -> 圈地运营;
- 云计算发展,Saas标准化服务输出,降低了品牌建站的成本-> FaaS;
官⽅⽂档
- 微信:微信公众平台-⼩程序—⽂档
- ⽀付宝:蚂蚁⾦服开发平台-⼩程序—⽂档
- 淘宝:淘宝开发者平台—⽂档
- 百度:智能⼩程序 — ⽂档
IDE
⽂件结构
- 微信⼩程序
1 | ## 微信⼩程序 |
- 支付宝小程序
1 | ## ⽀付宝⼩程序 |
- ⼿淘⼩程序
1 | ## ⼿淘⼩程序:基于轻框架 |
- 百度⼩程序
1 | ## 百度⼩程序 |
⽣命周期
- 微信⼩程序
1 |
|
- ⽀付宝⼩程序
1 |
|
- 淘宝⼩程序
1 | // 不基于框架:跟⽀付宝⼩程序语法⼏乎完全⼀样 |
1 | // 基于框架,类Rax框架,html、js、css在同⼀⽂件中,通过sfc2mp转为⽀付宝⼩程序 |
- 百度⼩程序
1 | Page({ |
视图层
数据绑定
- 微信:指令以wx:开头
1 | <view wx:if="{{condition}}"> </view> |
- ⽀付宝:指令以a:开头
1 | <view a:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view> |
- 淘宝:完全同⽀付宝
- 百度
1 | Page({ |
样式⽀持
全部⽀持rpx逻辑单位
组件
⽬前以微信⼩程序⽀持最为完善
- 微信⼩程序
- picker-view:滚动选择器
- functional-page-navigator:跳转⾄插件功能⻚
- live-push:实时⾳视频录制
- ad:banner⼴告
- official-account:公众号关注组件
- ⽀付宝⼩程序
- 缺少movable-area
- 缺少cover-view
- 缺少rich-text
- 缺少audio
- 缺少video
- 缺少camera
- 缺少live-player
- 缺少live-pusher
- 缺少ad
- 缺少open-data
- 淘宝
- 缺少movable-area
- 缺少cover-view
- 缺少rich-text
- 缺少camera
- 缺少live-player
- 缺少live-pusher
- 缺少canvas
- 缺少web-view
- 缺少ad
- 缺少open-data
- 缺少offical-account
- 百度
- animation-view:动画组件
- 缺少live-pusher
总结
⼩程序底层⽅案都是⼀致的,只不过在⽀持程度等有所不同。
以⽀付宝⼩程序为例:
- ⼩程序分别运⾏在 worker(JSEngine) 以及 render 渲染层中, render 可以有多个, worker 只有⼀个,⽅便 app 数据在⻚⾯间的共享和交互;(渲染层 & 逻辑层)
- worker 运⾏⼩程序的逻辑处理代码,包括事件处理,api 调⽤以及框架的⽣命周期管理;(逻辑层功能)
- render 运⾏⼩程序的渲染代码,主要包括模版/样式和框架的跨终端的 js 组件或 native 组件,获取逻辑层的数据进⾏渲染;(渲染层功能)
- worker 和所有的 render 都建⽴连接,将需要渲染的数据传递给对应的 render 进⾏渲染,worker 也会将 api 调⽤转给 native SDK 处理;(Hybrid通信)
- render 则将组件的触发事件送到对应的 worker 处理,同时接受 worker 的 setData 调⽤ React 重新渲染。 render 可以看作⼀个⽆状态的渲染终端,状态统统保留在 app 级别的 worker ⾥⾯;(渲染层&逻辑层交互)
小程序跨端框架介绍
原⽣⼩程序开发
原⽣⼩程序适⽤于:需求明确只在指定⼩程序⼀端进⾏,保证最⼤程度的避免多端框架兼容带来的莫名 bug。
多端小程序开发主要有两条技术路线——编译时和运行时。这里只讨论 React 语法的跨端框架。
编译时方案
把开发者写的类 React 代码解析成 AST,通过语法分析转换为可运行的小程序代码。代表:Taro 1/2、Nanachi、Rax(compile mode)。
以 Rax 编译时为例,链路分为五个模块:
- CLI:入口,读取用户代码,传入 webpack 执行依赖分析,提供
watch和build两个指令 - Loader(jsx2mp-loader):按文件角色分发——
app-loader处理 app.js 和配置抹平、page-loader处理页面组件、component-loader处理组件、script-loader处理依赖路径 - Compiler(jsx-compiler):AST 转换核心——词法分析 → 语法分析 → 代码生成。输入类 React 代码,输出小程序可执行代码 + 模板
- Runtime(jsx2mp-runtime):运行时垫片,桥接小程序 Page/Component 和 Rax API
- Universal:多端统一的组件和 API 基础服务
CLI 如何解决小程序入口问题
原生小程序中入口文件 app.js 并没有声明依赖——pages 是在 app.json 中注册的。Rax 采用统一的工程目录,app.json 中通过 routes 声明路由和页面:
1 | ├── src |
CLI 读取 routes 中引用的所有 pages 文件和 app.js 作为 entry,webpack 依次遍历依赖,交由对应 loader 处理。
Loader 体系
- app-loader:处理 app.js,抹平支付宝/微信两端的
window配置差异 - page-loader:处理页面组件,解析引用的组件写入
usingComponents,并将这些组件加入依赖分析链 - component-loader:处理组件,同样解析子组件依赖并写入
usingComponents - script-loader:处理 npm 包路径,搜集依赖并进行 babel 编译;对于第三方原生小程序 npm 包,读取同目录 json 中的
usingComponents加入依赖链 - file-loader:处理图片等静态资源
Compiler:jsx-compiler
编译流程:词法分析(tokenizing)→ 语法分析(parsing)→ 代码生成(generate)。
输入一段类 React 代码,输出包含 ast、code、template、config、style、usingComponents 等完整的小程序产物。
运行时方案
代表:Remax、Taro 3、Rax(runtime mode)。
起点的关键技术是 kbone——在小程序的逻辑层(appService)适配一套轻量 DOM/BOM API,在内存中维护一棵 DOM tree,每当你通过前端框架操作 DOM 时,实际修改的是这棵内存中的树。每次操作后(有节流)把 DOM tree 同步到渲染层(webView),通过 WXML 自定义组件递归渲染。
关键技巧:小程序自定义组件支持递归引用自身。利用这个特性,可以将任意层级、任意节点数量的 DOM 树映射为一棵自定义组件树。
Rax 的运行时方案类似 kbone:有一个 driver-miniapp 作为小程序 Driver,miniapp-render 库模拟 DOM/BOM API,事件系统基于 EventTarget 基类实现完整的事件派发机制——逻辑层 DOM 节点继承 EventTarget,通过唯一 nodeId 收集绑定事件,视图层模板上的每个组件监听所有可触发事件,通过 event.currentTarget.dataset.nodeId 定位目标节点。
框架选择:Rax vs Taro
API 设计
每个平台总有一些无法抹平的差异。好的框架设计应该让平台特有属性不侵入基础框架本身。
对比 Taro 和 Rax 的页面组件写法:
Taro:内置了 componentDidShow、componentDidHide 等小程序特有生命周期,以及 config 静态属性设置页面标题。
Rax:没有这些非 React 标准的 API,改用 addNativeEventListener / removeNativeEventListener 等更接近 W3C 标准的接口。页面配置不在组件实例上,而在 app.json 中。
从”保持 React 纯净性”的角度,Rax 的设计更克制——它不往组件上挂不属于 React 的属性和生命周期。
多端组件协议
- Taro:组件统一在项目中编译产出为小程序代码
- Rax:构建产物符合小程序语法,可以直接在原声小程序项目中使用,支持渐进式迁移
构建体系
Rax 基于 build-scripts 和 webpack-chain 提供灵活的插件体系。编译时方案通过 webpack loader 调用 jsx-compiler 做 AST 转换,运行时方案直接复用 Web 端的 webpack 配置。
主流框架对比
选择编译时还是运行时,本质上是一个”性能 vs 灵活性”的权衡。编译时性能更好但语法受限,运行时几乎无语法限制但有性能开销。Rax 选择了”双引擎”——默认运行时,局部高性能场景(如长列表)使用编译时组件,试图在两个极端之间找到平衡。