diff --git "a/source/_posts/\347\224\250 RegularJS \345\274\200\345\217\221\345\260\217\347\250\213\345\272\217 \342\200\224\342\200\224 mpregular \350\247\243\346\236\220.md" "b/source/_posts/\347\224\250 RegularJS \345\274\200\345\217\221\345\260\217\347\250\213\345\272\217 \342\200\224\342\200\224 mpregular \350\247\243\346\236\220.md" new file mode 100644 index 0000000..6a68980 --- /dev/null +++ "b/source/_posts/\347\224\250 RegularJS \345\274\200\345\217\221\345\260\217\347\250\213\345\272\217 \342\200\224\342\200\224 mpregular \350\247\243\346\236\220.md" @@ -0,0 +1,418 @@ + + + +# 用 RegularJS 开发小程序 —— mpregular 解析 + +Mpregular 是基于 RegularJS(简称 Regular) 的小程序开发框架。开发者可以将直接用 RegularJS 开发小程序,或者将现有的 RegularJS 应用通过较少修改移植到小程序上。Mpregular 为 RegularJS 开发者提供了一套跨 h5 和小程序的前端应用解决方案,让开发者能在不同平台有一致的开发体验和开发效率。 + +## 0 序 + +以下是使用 mpregular 前后的效果对比图: + +
+
+

旧版(原生小程序)

+ 1424614_old +
+ +
+

新版(mpregular)

+ 1424614_new +
+
+ +## 1 为何而生 + +### 1.1 原生小程序开发 + +小程序本身提供的特性相对简单,在开发复杂应用的时候,用原生小程序进行开发就会显得比较吃力。为了更好支持复杂应用,小程序也推出自定义组件、wxs 等新特性,但这些新特性无形中又会给开发者带来一定的学习成本。另外,小程序的开发规范和通常的 web 应用的开发规范有着较大差异,如果需要同时在两端上开发同样功能的应用,则要求投入双倍的人力,无疑大大增加了开发和维护的成本。 + +### 1.2 考拉前端业务现状 + +目前考拉的 wap 前台页面大部分都是采用 RegularJS 开发的,包括 wap 首页、详情页,因此考拉的前端们都拥有丰富的 RegularJS 开发经验,RegularJS 可谓是我们最熟悉的前端开发框架之一。相比之下,熟悉小程序的开发就比较少了。微信是一个庞大的流量入口,最近小程序又掀起了一波热潮,伴随而来的就是小程序相关业务的增加。我们不仅需要把现有前台页面迁移到小程序,还需要开发和维护跨小程序和 wap 两端的业务。因此,我们迫切需要一个能够支撑当前业务的解决方案,保证我们的开发效率,降低开发和维护成本。 + +### 1.3 业界的解决方案 + +业界关于小程序也有许多解决方案。 + +小程序官方很早就推出了一个组件化解决方案 —— Wepy,它有自己的一套语法规范,构建时将 Wepy 代码编译转换为小程序代码。它强依赖小程序自身的特性,因此受小程序自身特性所限,开发规范与考拉当前的前端技术栈差异较大,并不适用。 + +京东的凹凸实验室推出了新的跨端开发框架 Taro,它是一个 React-like 的开发框架,有完善的配套设施,支持大部分 React 特性。但 Taro 是在 mpregular 开发完成后才出来,而且不符合我们当前的技术栈。 + +美团今年早些时候推出 mpvue,一个基于 Vue 实现的小程序开发框架,Vue 开发者看到这个框架以后欢天喜地,Github 上 star 数迅速攀升。对此,我们也做了一些调研,它不仅支持了大部分 Vue 的特性,而且有完善的文档教程、配套设施,可以说是一个非常完善的解决方案。但我们当前存在的大量 RegularJS 页面到小程序的迁移需求,在这一场景面前,mpvue 显得无能为力。 + +纵观业界的解决方案,都很难满足我们当前的需求。我们受到了 mpvue 的启发,并借鉴它的基本设计思想(包括名称...),决定对 RegularJS 进行了改造,开发 mpregular 这一个基于 RegularJS 的小程序开发框架。 + +## 2 框架特性 + +既然是基于 RegularJS 实现的框架,语法规范必然是与 RegularJS 基本一致。在开发的的时候,基本上只要遵循 RegularJS 的开发规范进行即可,大大降低了 RegularJS 开发者的学习成本。 + +### 2.1 生命周期 + +小程序有 App 和 Page 两个重要的概念,但通常业务代码是写在 Page 里的,这里就以小程序页面为例。开发者在开发小程序页面的时候,基本只需要了解 Regular 实例的生命周期。小程序 Page 的 `onLoad`、`onReady` 已经通过 mpregular 与 Regular 的实例生命周期绑定在一起了。页面 url 的 querystring 也可以通过 `this.$mp.options` 获取。 `onShow`、`onPageScroll` 等小程序特有的生命周期钩子都同样绑定到 Regular 实例上。 + +```html + + +``` + +### 2.2 语法和特性 + +mpregular 支持 RegularJS 的语法和大部分特性。例如: + +```html + +``` + +上述模版中的语法可以直接在小程序上执行。除此以外,mpregular 还支持 r-html、r-hide、{#include this.$body }、filter 等特性。这些特性在现有业务代码中被大量使用,因此在迁移现有代码时,几乎可以原封不动地拷贝过来(除非原有代码中包含大量 DOM 操作...)。 + +mpreguar 支持的特性: + +- RegularJS 基本语法,包括 {#list},{#if}, {#include this.$body } +- filter +- r-model +- r-hide +- r-html +- r-class(即将支持) +- r-style(即将支持) + +相比于原生小程序和业界其他框架而言,mpregular 给 RegularJS 开发者提供了他们更熟悉的开发模式,支持更多的特性,对模版的处理能力进一步增强,更适应于我们当前复杂应用的业务场景。 + +特性 | 小程序 | mpvue | mpregular | +:--- |:--- |:---|:--- +语法规范 | 原生小程序 | Vue 规范 | RegularJS 规范 +组件化 | 小程序组件化规范 | Vue 组件 | RegularJS 组件 +computed 计算属性 | ❌ | ⭕ | ⭕️ +model 双向绑定 | ❌ | ⭕ | ⭕️ +slot 插槽 | ❌ | ⭕ | ⭕️ +filter 过滤器 | ❌ | ❌ | ⭕️ +html 富文本 | ❌ | ❌ | ⭕️ +复杂表达式插值 | ❌ | ❌ | ⭕️ +小程序分包 | ⭕ | ❌ | ⭕️ + +## 3 基本原理 + +小程序在结构上主要有 Service(JavascriptCore) 和 View(WebView) 两部分组成,分别运行在独立的环境上,之间不具备共享数据通道,二者的通信方式是将数据封装在 js 脚本后传递。Page 实例就在 Service 中,通过 setData 方法将数据传递到 View。View 则通过事件绑定将视图层触发的事件传递给 Service。 + +
+ 小程序 +
+ +Regular 是基于 Living Template 实现的,它使用一个内建 DSL 将模版字符串解析成 AST,然后在编译阶段结合数据模型将 AST 进行递归遍历,并在这个遍历过程中生成 DOM 节点,同时完成插值、指令等的绑定,实现 DOM 与数据的链接。 + +
+ RegularJS +
+ +Mpregular 要做的就是将 Regular 的视图层从 DOM 替换成小程序的 View。在小程序中不能直接操作 View 中的 DOM 节点,而是需要通过小程序的 Service 层 `setData` 方法去更新 View 的数据。 + +构建时,mpregular 会将 Regular 的模版字符串预先编译成小程序的模版 .wxml,通过小程序的 Service 与小程序的 View 建立联系,实现数据更新和事件监听。由于小程序中无法使用 `eval` 和 `new Function` 等操作,所以 mpregular 会在构建阶段预先生成 AST ,运行时从源码中读取 AST。在执行 `this.$update` 时把更新数据通知 Service,调用 `setData` 完成视图更新。View 触发的事件会被代理到 Service 的 `proxyEvent` 方法,这个方法会在 RegularVM 中找到对应的事件处理函数并执行。 + +
+ mpregular +
+ +Mpregular 要做的,就是在 Regular 实例和小程序 Service 之间建立联系,完成生命周期绑定、数据更新、事件代理等工作。 + +### 3.1 生命周期 + +小程序中通过调用 Page 方法注册页面,而页面加载时创建的页面实例 `PageVM` 就是 mpregular 与小程序建立连接的通道。 + +Mpregular 在定义页面入口的 Regular 组件时去调用 Page 方法注册页面,并将 Page 的生命周期钩子与 Regular 的生命周期进行绑定。 + +```javascript +page.init = function(config) { + Page({ + onLoad(options) { + this.rootVM = initRootVM(this, config); + callHook(this.rootVM,'onLoad'); + }, + onReady() { + callHook(this.rootVM,'onReady', options); + initDataToMP(this.rootVM); + } + }) +} +``` + +在 Page 实例化(页面加载)时,会触发 `onLoad` 钩子,此时会对这个页面对应的 Regular 入口组件进行实例化,并将 `PageVM` 和 `RegularVM` 绑定在一起。由于每个页面只有一个 `PageVM`,所以 `PageVM` 会与 `RegularVM.$root` 进行绑定,之后 Regular 的逻辑会利用 `RegularVM.$root` 所绑定的 `PageVM` 与小程序进行通信。当页面初次渲染完成后,会触发 `onReady` 钩子,对应于 Regular 的 `init`。当页面的其他钩子函数触发时,如 `onShow`、`onHide`,`PageVM` 会通过 `callHook` 方法调用 `RegularVM` 上定义的同名方法。在页面退出销毁时,`onUnload` 中则会触发 `RegularVM` 的 `destroy` 方法,将页面绑定对应的 Regular 实例销毁。 + +
+ lifecycle +
+ +### 3.2 模版转换 + +由于 Regular 的模版语法与小程序模版语法不一样,所以在构建阶段,mpregular-loader 会把 Regular 的模版字符串转换成小程序的 .wxml,不仅会对标签进行转换,还会对模版的语法、子组件模版进行处理。所定义的每个 Regular 组件,包括入口组件,都会被转换成一个个模版片段,存放到对应的 .wxml 文件中,并用 `