在《初识建信小程序引擎》一文中我们了解到,建信小程序是一种新的连接用户与服务的开发方式,可以在建信应用内被便捷的获取和传播,同时具备良好的用户体验。一般的开发工具,都会有一个供用户使用的开发工具链。那么,建信小程序的开发工具链的原理又是怎样的呢?本文将会对以上内容进行介绍。
注:本文基于 @jump-mp/cli 的版本为 1.2.6
@jump-mp/cli 的安装和使用方法详见建信小程序开发文档。
在终端运行
`$ jump -h`
可以看到 @jump-mp/cli 中提供如下功能:
1. `Usage: jump [options]`
2.
3. `Options:`
4. ` **-v, --version** output the version number`
5. ` **-h, --help** display help for command`
6.
7. `Commands:`
8. ` **init** initialize template for jump project`
9. ` **build [options]** build project to be used in android or ios`
10. ` **update** update project dependencies to the latest version`
11. ` **add [options] ** add npm source for miniproject`
12. ` **construct** construct installed npm source for miniproject`
13. ` **help [command]** display help for command`
在上一节中,已经介绍了 @jump-mp/cli
的基本功能。本节将对脚手架 @jump-mp/cli
的整体架构和技术选型进行介绍。
目前较常用的前端开发脚手架工具有 yoeman
、vue-cli
等。
yeoman
搭建项目需要提供 yoeman-generator
。yoeman-generator
本质上就是有 i 个具备完整文件结构的项目样板,用户需要手动地把这些 generator
下载到本地,然后 yoeman
就会根据这些 generator
自动生成各种不同的项目。
vue-cli
提供了相当丰富的选项和设定功能,但是其本质也是从远程仓库把不同的模版拉取到本地,而并非是“本地生成“的黑科技。
综上,目前脚手架的思路大都是建立不同的样板项目,然后脚手架根据用户指令引用样板项目生成实际项目。样板项目可以内置在脚手架中,也可以部署在远程仓库。
为了更好地实现模版定制和功能开发,大前端团队自研了建信小程序脚手架 @jump-mp/cli。脚手架主要使用了以下技术:
node.js
:整个脚手架工具的根本组成部分。commander
: 用于组织和处理命令行的输入。webpack
:用于对一些需要编译的文件进行处理。脚手架的整体架构如下图:
在创建小程序、页面和组件时,小程序的项目、页面和组件的模版(template)都存放在依赖 jump-mp/cli-template
中。每当用户需要创建时,就通过 cli
的命令向 jump-mp/cli-template
中拉取相应的模版并下载到本地。
对于编译、构建等功能,会在后面的小节进行介绍。
最终整个 @jump-mp/cli 的脚手架文件结构如下。
├── bin
│ └── index.js
├── command
│ ├── add.js
│ ├── build.js
│ ├── create.js
│ ├── translate.js
│ └── update.js
├── node_modules
│ └── @jump-mp
│ ├── build
│ └── cli-template
│ ├── component
│ ├── page
│ └── project
└── package.json
其中 bin/index.s
为入口文件,主要用 commander
来构建命令行。commander
的工作流程如下:
在完成小程序的开发后,需要在项目根目录下运行 jump build [option]
来对小程序进行编译打包。下面将介绍建信小程序打包过程中的编译原理。
在《初识建信小程序引擎》一文中我们了解到,为了提升小程序的运行性能、用户体验,建信小程序采用了双线程模式,将运行环境分为视图层(view
层)和逻辑层(service
层)。其中视图层负责页面展示,逻辑层负责小程序业务逻辑。视图层和逻辑层分别由 2 个线程管理:
WebView
进行渲染,视图层使用 Vuejs
作为 MVVM 框架;JsCore
线程运行 JavaScript
脚本。同时,视图层线程和逻辑层线程通信会经由建信客户端(Native
层)做中转,逻辑层发送网络请求也经由 Native
层转发,Native
层也会渲染一些原生的组件、被小程序调用一些原生的 API。
综上,编译器的主要功能是将小程序代码进行编译,将 view
层、service
层、native
层三个部分各自需要的代码进行提取、转换、整合,最终打包成一个可以在建信客户端运行的小程序。
编译器主要使用了以下技术:
node.js
:整个脚手架工具的根本组成部分。gulp
:基于流的自动化构建工具。babel相关
:用于构建 js 代码的 ast 树、修改 js 代码、编译 js 代码。vue-template-compiler
:用于构建 html 代码的 ast 树,修改 html 代码、编译 html 代码。postcss
:用于修改、编译 css 代码。webpack
:用于对一些需要编译的文件进行处理。编译器在工作的过程中,会经历预处理、编译、打包
三个步骤,具体流程如下:
下面将详细描述编译原理和流程。
对于页面/组件的 mxml
页面,编译器做了如下处理:
mxml
会被解析为一颗 ast 抽象语法树
ast 语法树
,将组件名转为小程序内置组件名(例:button->mp-button),将建信小程序语法标签转为 vue
语法标签(例:mp:if->v-if)。ast 抽象语法树
,将 ast
转为使用 vue 框架
的 html
代码。mcss
转为 css
主要做了设备视图分辨率适配
,将 rpx
单位转为 vw
。在生成了 html
中间态文件“使用 vue 框架的 html”
后,还需要生成一个符合 vue 框架
要求的 js
文件,然后将其组合到一个 html
文件(及 page.html)中,该文件才可在使用了 vue
引擎的视图层中被加载,因此我们需要一个这样的 html
文件。举例说明:
jump
小程序:
vue
页面应该是:<template>
<mp-view>
<mp-view>{{title}}</mp-view>
<mp-button @click="clickMe">请点击换标题</mp-button>
<mp-view>
<template>
<script>
export default {
data(){
return {
title: '',
}
}
}
</script>
这里 data
中的变量名不是通过解析 page.js
得到的,因为开发者在编写 page.js
时是没有要求固定格式的,因此无法通过抽象代码树解析的方式拿到 data
的参数,所以这里 data
的变量名是通过解析 html
表达式拿到的。
而没有将 data
中的数据值和 method
方法写到 html
页面中,是因为这部分应由 service 逻辑层
处理,view 层
只需声明变量名即可。
最终再通过 vue
生成的 html
页面应为:
<html>
<head>......</head>
<body>
......
<script>
new Vue({
data() {
return {
title:""
}
},
el: "#app",
template:"<template><mp-view><mp-view>{{title}}</mp-view><mp-button @click="clickMe">请点击换标题</mp-button> <mp-view><template>"
});
</script>
</body>
</html>
如要生成上述页面,首先我们需要拿到data
中的变量名,使用的方法是,通过解析 html
中的表达式来获得变量名。
然后则是构建 html
页面中的 js
代码,这里使用 babel
去构建一个 js
的 ast 抽象语法树
,然后向树中插入 data
和 template
的参数和值。
由于考虑到随着页面功能的增多以及版本的迭代,直接构建目标态代码,在未来版本发生迭代时很可能会导致编译逻辑会跟随修改。为了解决这一问题,我们需要区分出目标态代码的不变部分和可变部分。把不变部分抽象为源对象,把可变部分交由 runtime 运行时框架
处理。这样只需要在 rumtime 层
做版本迭代而不需要编译器修改。
为了提高加载速度,我们在编译阶段做了预处理,把原本在运行时处理的渲染模版提前展开为真实的虚拟节点树。
所以本步骤整体的工作流程如下图:
最终生成的 page.view.js 应为:
1. `let app = window.$$makeVueComponent({`
2. ` data() {`
3. ` return {`
4. ` title: ""`
5. ` };`
6. ` },`
7. ` el: "#app",`
8. ` render: function anonymous() {`
9. ` with(this){return _c('mp-view',[_c('mp-view',[_c('mp-view',[_v(_s(title))]),_v(" "),_c('mp-button',{on:{"click":function($event){return __eventHandlerProxy__('clickMe',$event)}}},[_v("请点击换标题")])],1)],1)}`
10. ` },`
11. ` staticRenderFns: []`
12. ` });`
13. `new Vue(app);`
由于视图 view 层
在加载页面时只会去加载该页面的 html
,因此需要在编译页面时将页面中引入的组件(component)也编译进 html
中。这里的编译流程如下:
编译器会先读取所编译的 page
的 json
文件,如果 json
文件中引入了组件,则将编译后的组件的 js
加入到 page
的 html
中。然后再去读取改组件的 json
文件,查看改组件是否引入了其他组件,一直递归循环,直至所有组件都被编译进 html
文件为止。对于每个页面重复引入的组件,则只编入一次。
在处理需要在 service 逻辑层
运行的 page.js
时,我们不会去改变文件的代码,只是对 page.js
用 webpack
做编译打包,然后汇总到 app-service.js
文件中。
建信小程序支持引入第三方 npm 依赖。这里不再对 npm 的功能进行介绍,可以参考官方 npm 文档。
建信小程序引入 npm 依赖
,需要添加依赖
和构建依赖
两个步骤。
其中,构建依赖
的目的是,将安装在 node_modules
下的依赖进行编译构建,然后生成到项目根目录下的 npm_miniprogram
文件夹下,以供小程序在开发过程中进行引入。
在使用 npm 依赖
时需要构建依赖,是因为在小程序编译时,无法在未指定路径的情况下将 require
的 node_modules
下的依赖编译进去,所以需要构建这个步骤,将依赖进行预编译,然后统一到一个目录下。
npm 依赖
的构建原理如下图所示:
package.json
, 如果 devDependencies/dependencies
不为空,则小程序中有使用 npm 依赖
,需要构建。node_modules
中找到小程序引入的依赖,查看该依赖是否指明小程序的引入入口(miniprogram_dist
目录),如果有则直接将其复制到 miniprogram_npm
路径下,如果没有则使用 webpack
由 main
入口进行编译,编译结果保存到 miniprogram_npm
路径。npm 依赖
重复第一步的步骤,直到所有依赖及依赖的依赖构建完成。本文主要对建信小程序的开发工具链 @jump-mp/cli
进行了介绍,并对其脚手架开发
、编译器
、npm 构建
等功能的原理进行了介绍。
欢迎大家试用和使用建信小程序进行开发应用(开发文档详见文末链接),您有任何意见和想法都可以与我们联系,进行相关技术交流。
建信小程序开发文档:http://docs.ccbft.cloud:8888/jump-mp/
本文转载自:金科优源汇(ID:jkyyh2020)
原文链接:建信小程序开发工具链原理浅析
领取专属 10元无门槛券
私享最新 技术干货