从编译到运行,跨端解析小程序一

72 阅读7分钟

“我正在参加「掘金·启航计划」”小程序生态如今已经如火如荼地开展开来,自腾讯微信小程序后,各巨头也纷纷建立起自己的小程序。这些小程序的设计原理类似,但是对于开发者来说,开发层面并不互通。在此背景下,效率为先,也就有了各种小程序多端方案

方案探究

小程序跨端技术是编译时运行时的结合方案。

编译时和运行时是软件开发里面用于描述两个不同的软件开发阶段。开发一个程序,程序员首先需要写源代码(Source Code) ,来完成程序的功能。源代码需要被编译成机器可以识别的程序,这个编译过程被称为编译时。用户可以运行编译过的程序,程序运行的过程被称为运行时。

  • 编译时方案 
  •  运行时方案 
  •  编译时和运行时的结合方案

从实现原理上,开源社区的跨端框架大致分为下面两类:

  • compile time 编译时

  • runtime 运行时

  • compile time 编译时的跨端框架,主要的工作量在编译阶段。他们框架约定了一套自己的 DSL ,在编译打包的过程中,利用 babel 工具通过 AST 进行转译,生成符合小程序规则的代码。

这种方式容易出现 BUG ,而且开发限制过多。早期的 Taro 1.0 和 2.0 的版本就是采用的这种方案,下文会有更具体的介绍。

而另外一种runtime 运行时模式, 跨端框架真正的在小程序的逻辑层中运行起来 React 或者是 Vue 的运行时,然后通过适配层,实现自定义渲染器。这种方式比静态编译有天然的优势,所以 Taro 的最新 Next 版本和 Remax 采用的是这种方案。

小程序多端方案最终对外提供的使用方式可以分为: 

  •  类 Vue 风格框架 
  •  类 React 风格框架
  •  自定义 DSL 框架

小程序多端——编译时方案 

顾名思义,编译时方案的工作量主要集中在编译转化环节上。这类多端框架在编译阶段,基于 AST(抽象语法树)技术进行各平台小程序适配。 

 目前社区上存在较多基于 Vue DSL 和 React DSL 的静态编译方案。其实现理念类似,但也有区别,我们分开来看。

Vue DSL 静态编译 

Vue 的设计风格和各小程序设计风格更加接近,因此 Vue DSL 静态编译方案相对容易。Vue 中单文件组件主要由: template script style

  • .wxml 文件,template 文件 .
  • js 文件,.json 文件
  • .wxss 文件

因为小程序基本都可以接受 H5 环境中的 CSS,因此style 部分基本可以直接平滑迁移。template 转换为 .wxml 文件,需要进行 HTML 标签、模版语法的转换。以微信小程序举例,转换目标如下图:

小程序多端——运行时方案 Vue 跨端框架

 Vue 单文件组件的 template 编译过程,而 script 部分的处理会更加困难。试想,对于一段 Vue 代码,我们通过响应式理念监听数据变化,触发视图修改,放到小程序中,多端方案要做的就是监听数据变化,调用 setData() 方法,触发小程序渲染层变化。

 Vue的跨端框架 核心原理都差不多,都是把 Vue 框架拿过来强行的改了一波,借助了 vue 的能力。比如说,vue 的编译打包流程(也就是vue-loader的能力), vue 的响应式双向绑定、虚拟dom、diff 算法。上面这些东西跨端框架都没有修改,直接哪来用的。

那么哪些部分是这些跨端框架自己新加的东西呢?

涉及到 Vue 框架中操作DOM节点的代码。

.vue 单文件中的 script 部分中, 通常会写下面的代码,我们会写一个 Vue 的配置项对象传入到 Vue 构造函数中,然后 new Vue() 会实例化出来一个 vue 实例。

Vue 大部分就是纯粹的 javascript。也就是说,小程序的渲染层里面是完全可以直接运行起来 Vue 的运行时和 React 的运行时的。

但是这样还不够,小程序平台还规定,要在小程序页面中调用 Page() 方法生成一个 page 实例, Page() 方法是小程序官方提供的 API。

在一个小程序的页面中,是必须有 Page() 方法的。微信小程序会在进入一个页面时,扫描该页面中的 .js 文件,如果没有调用 Page() 这个方法,页面会报错。

如下图所示,我们在 <script> 中写的是 new Vue() 这样子的代码,而微信想要的是 Page()

一般在 Vue 单文件组件的 script 部分,我们会使用以下代码:

new Vue({

  data() {},

  methods: {},

  components: {}

})

  举一个例子,以微信小程序为例,微信小程序平台规定,小程序页面中需要有一个 Page() 方法,以生成一个小程序实例,其中 Page() 方法是小程序官方提供的 API。

那么对于业务方写的new Vue()代码,多端平台要手动执行微信小程序平台的 Page(),完成初始化处理,如下:

多端方案内置了 Vue 运行时版,并实例化了一个 Vue 实例,同时在初始阶段调用了小程序平台的 Page() 方法,因此也就有了一个小程序实例

在运行时将 Vue 实例和小程序实例进行关联,以做到:数据变动时,小程序实例能够调用 setData() 方法,进行渲染层更新。

思想确立后,如何实施呢?首先这就需要你对 Vue 原理足够清楚了:Vue 基于响应式,对数据进行监听,在数据改动时,新生成一份虚拟节点 VNode。接下来对比新旧两份虚拟节点,找到 Diff,并进行 patch 操作,最终更新了真实的 DOM 节点

因为小程序架构中,并没有提供操作小程序节点的 API 方法,因此对于小程序多端方案,我们显然不需要进行 Vue 源码中的 patch 操作。又因为小程序隔离了渲染进程(渲染层)和逻辑进程(逻辑层),我们不需要处理渲染层,只需要调用 setData() 方法,更新一份最新的数据就可以了。

因此,借助 Vue 现有的能力,我们秉承“数据部分让 Vue 运行时版接手,渲染部分让小程序架构接手”的理念,就能实现一个类 Vue 风格的多端框架。框架原理如图: