概述
作为一名前端开发,如果你还停留在应用开发层面,那你就 OUT 了,快来跟我一起探讨下小程序框架本身底层实现的一些技术细节吧,让我们从小程序的运行机制来深度了解小程序。
小程序是基于 WEB 规范,采用 HTML,CSS 和 JS 等搭建的一套框架,微信官方给它们取了一个很牛逼的名字:WXML,WXSS,但本质上还是在整个 WEB 体系之下构建的。
WXML,个人猜测在取这个名字的是微信的 Xml,说到底就是 xml 的一个子集。WXML 采用微信自定义的少量标签 WXSS,大家可以理解为就是自定义的 CSS。实现逻辑部分的 JS 还是通用的 ES 规范,并且 runtime 还是 Webview(IOS WKWEBVIEW, ANDROID X5)。
小程序
小程序目录结构
一个完整的小程序主要由以下几部分组成:
- 一个入口文件:app.js
- 一个全局样式:app.wxss
- 一个全局配置:app.json
- 页面:pages 下,每个页面再按文件夹划分,每个页面 4 个文件
- 视图:wxml,wxss
- 逻辑:js,json(页面配置,不是必须)
注:pages 里面还可以再根据模块划分子目录,孙子目录,只需要在 app.json 里注册时填写路径就行。
小程序打包
开发完成后,我们就可以通过这里可视化的按钮,点击直接打包上传发布,审核通过后用户就可以搜索到了。
那么打包怎么实现的呢?
这就涉及到这个编辑器的实现原理和方式了,它本身也是基于 WEB 技术体系实现的,nwjs+react,nwjs 是什么:简单是说就是 node+webkit,node 提供给我们本地 api 能力,而 webkit 提供给我们 web 能力,两者结合就能让我们使用 JS+HTML 实现本地应用程序。
既然有 nodejs,那上面的打包选项里的功能就好实现了。
ES6 转 ES5:引入 babel-core 的 node 包
CSS 补全:引入 postcss 和 autoprefixer 的 node 包(postcss 和 autoprefixer 的原理看这里)
代码压缩:引入 uglifyjs 的 node 包
注:在 android 上使用的 x5 内核,对 ES6 的支持不好,要兼容的话,要么使用 ES5 的语法或者引入 babel-polyfill 兼容库。
打包后的目录结构
小程序打包后的结构如下:
所有的小程序基本都最后都被打成上面的结构
- WAService.js 框架 JS 库,提供逻辑层基础的 API 能力
- WAWebview.js 框架 JS 库,提供视图层基础的 API 能力
- WAConsole.js 框架 JS 库,控制台
- app-config.js 小程序完整的配置,包含我们通过 app.json 里的所有配置,综合了默认配置型
- app-service.js 我们自己的 JS 代码,全部打包到这个文件
- page-frame.html 小程序视图的模板文件,所有的页面都使用此加载渲染,且所有的 WXML 都拆解为 JS 实现打包到这里
- pages 所有的页面,这个不是我们之前的 wxml 文件了,主要是处理 WXSS 转换,使用 js 插入到 header 区域。
小程序架构
微信小程序的框架包含两部分 View 视图层、App Service 逻辑层,View 层用来渲染页面结构,AppService 层用来逻辑处理、数据请求、接口调用,它们在两个进程(两个 Webview)里运行。
视图层和逻辑层通过系统层的 JSBridage 进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
小程序架构图:
小程序启动时会从 CDN 下载小程序的完整包,一般是数字命名的,如:_-2082693788_4.wxapkg
小程序技术实现
小程序的 UI 视图和逻辑处理是用多个 webview 实现的,逻辑处理的 JS 代码全部加载到一个 Webview 里面,称之为 AppService,整个小程序只有一个,并且整个生命周期常驻内存,而所有的视图(wxml 和 wxss)都是单独的 Webview 来承载,称之为 AppView。所以一个小程序打开至少就会有 2 个 webview 进程,正是因为每个视图都是一个独立的 webview 进程,考虑到性能消耗,小程序不允许打开超过 5 个层级的页面,当然同是也是为了体验更好。
AppService
可以理解 AppService 即一个简单的页面,主要功能是负责逻辑处理部分的执行,底层提供一个 WAService.js 的文件来提供各种 api 接口,主要是以下几个部分:
消息通信封装为 WeixinJSBridge(开发环境为 window.postMessage, IOS 下为 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage,android 下用 WeixinJSCore.invokeHandler)
- 日志组件 Reporter 封装
- wx 对象下面的 api 方法
- 全局的 App,Page,getApp,getCurrentPages 等全局方法
- 还有就是对 AMD 模块规范的实现
然后整个页面就是加载一堆 JS 文件,包括小程序配置 config,上面的 WAService.js(调试模式下有 asdebug.js),剩下就是我们自己写的全部的 js 文件,一次性都加载。
在开发环境下
- 页面模板:app.nw/app/dist/weapp/tpl/appserviceTpl.js
- 配置信息,是直接写入一个 js 变量,__wxConfig。
- 其他配置
线上环境
而在上线后是应用部分会打包为 2 个文件,名称 app-config.json 和 app-service.js,然后微信会打开 webview 去加载。线上部分应该是微信自身提供了相应的模板文件,在压缩包里没有找到。
- WAService.js(底层支持)
- app-config.json(应用配置)
- app-service.js(应用逻辑)
然后运行在 JavaScriptCore 引擎里面。
AppView
这里可以理解为 h5 的页面,提供 UI 渲染,底层提供一个 WAWebview.js 来提供底层的功能,具体如下:
- 消息通信封装为 WeixinJSBridge(开发环境为 window.postMessage, IOS 下为 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage,android 下用 WeixinJSCore.invokeHandler)
- 日志组件 Reporter 封装
- wx 对象下的 api,这里的 api 跟 WAService 里的还不太一样,有几个跟那边功能差不多,但是大部分都是处理 UI 显示相关的方法
- 小程序组件实现和注册
- VirtualDOM,Diff 和 Render UI 实现
- 页面事件触发
在此基础上,AppView 有一个 html 模板文件,通过这个模板文件加载具体的页面,这个模板主要就一个方法,$gwx,主要是返回指定 page 的 VirtualDOM,而在打包的时候,会事先把所有页面的 WXML 转换为 ViirtualDOM 放到模板文件里,而微信自己写了 2 个工具 wcc(把 WXML 转换为 VirtualDOM)和 wcsc(把 WXSS 转换为一个 JS 字符串的形式通过 style 标签 append 到 header 里)。
Service 和 View 通信
使用消息 publish 和 subscribe 机制实现两个 Webview 之间的通信,实现方式就是统一封装一个 WeixinJSBridge 对象,而不同的环境封装的接口不一样,具体实现的技术如下:
windows 环境
通过 window.postMessage 实现(使用 chrome 扩展的接口注入一个 contentScript.js,它封装了 postMessage 方法,实现 webview 之间的通信,并且它也通过 chrome.runtime.connect 方式,也提供了直接操作 chrome native 原生方法的接口)
发送消息:window.postMessage(data, ‘*’);,// data 里指定 webviewID
接收消息:window.addEventListener(‘message’, messageHandler); // 消息处理并分发,同样支持调用 nwjs 的原生能力。
在 contentScript 里面看到一句话,证实了 appservice 也是通过一个 webview 实现的,实现原理上跟 view 一样,只是处理的业务逻辑不一样。
1 | 'webframe' === b ? postMessageToWebPage(a) : 'appservice' === b && postMessageToWebPage(a) |
IOS
通过 WKWebview 的 window.webkit.messageHandlers.NAME.postMessage 实现,微信 navite 代码里实现了两个 handler 消息处理器:
invokeHandler: 调用原生能力
publishHandler: 消息分发
Android
通过 WeixinJSCore.invokeHandler 实现,这个 WeixinJSCore 是微信提供给 JS 调用的接口(native 实现)
invokeHandler: 调用原生能力
publishHandler: 消息分发
微信组件
在 WAWebview.js 里有个对象叫 exparser,它完整的实现小程序里的组件,看具体的实现方式,思路上跟 w3c 的 web components 规范神似,但是具体实现上是不一样的,我们使用的所有组件,都会被提前注册好,在 Webview 里渲染的时候进行替换组装。
exparser 有个核心方法:
registerBehavior: 注册组件的一些基础行为,供组件继承
registerElement:注册组件,跟我们交互接口主要是属性和事件
组件触发事件(带上 webviewID),调用 WeixinJSBridge 的接口,publish 到 native,然后 native 再分发到 AppService 层指定 webviewID 的 Page 注册事件处理方法。
总结
小程序底层还是基于 Webview 来实现的,并没有发明创造新技术,整个框架体系,比较清晰和简单,基于 Web 规范,保证现有技能价值的最大化,只需了解框架规范即可使用已有 Web 技术进行开发。易于理解和开发。
MSSM:对逻辑和 UI 进行了完全隔离,这个跟当前流行的 react,angular,vue 有本质的区别,小程序逻辑和 UI 完全运行在 2 个独立的 Webview 里面,而后面这几个框架还是运行在一个 webview 里面的,如果你想,还是可以直接操作 dom 对象,进行 ui 渲染的。
组件机制:引入组件化机制,但是不完全基于组件开发,跟 vue 一样大部分 UI 还是模板化渲染,引入组件机制能更好的规范开发模式,也更方便升级和维护。
多种节制:不能同时打开超过 5 个窗口,打包文件不能大于 1M,dom 对象不能大于 16000 个等,这些都是为了保证更好的体验。