0%

小程序实现原理解析

概述

作为一名前端开发,如果你还停留在应用开发层面,那你就 OUT 了,快来跟我一起探讨下小程序框架本身底层实现的一些技术细节吧,让我们从小程序的运行机制来深度了解小程序。
小程序是基于 WEB 规范,采用 HTML,CSS 和 JS 等搭建的一套框架,微信官方给它们取了一个很牛逼的名字:WXML,WXSS,但本质上还是在整个 WEB 体系之下构建的。
WXML,个人猜测在取这个名字的是微信的 Xml,说到底就是 xml 的一个子集。WXML 采用微信自定义的少量标签 WXSS,大家可以理解为就是自定义的 CSS。实现逻辑部分的 JS 还是通用的 ES 规范,并且 runtime 还是 Webview(IOS WKWEBVIEW, ANDROID X5)。

小程序

小程序目录结构

image

一个完整的小程序主要由以下几部分组成:

  • 一个入口文件:app.js
  • 一个全局样式:app.wxss
  • 一个全局配置:app.json
  • 页面:pages 下,每个页面再按文件夹划分,每个页面 4 个文件
  • 视图:wxml,wxss
  • 逻辑:js,json(页面配置,不是必须)

注:pages 里面还可以再根据模块划分子目录,孙子目录,只需要在 app.json 里注册时填写路径就行。

小程序打包

开发完成后,我们就可以通过这里可视化的按钮,点击直接打包上传发布,审核通过后用户就可以搜索到了。

image

那么打包怎么实现的呢?
这就涉及到这个编辑器的实现原理和方式了,它本身也是基于 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 兼容库。

打包后的目录结构

小程序打包后的结构如下:

image

所有的小程序基本都最后都被打成上面的结构

  1. WAService.js 框架 JS 库,提供逻辑层基础的 API 能力
  2. WAWebview.js 框架 JS 库,提供视图层基础的 API 能力
  3. WAConsole.js 框架 JS 库,控制台
  4. app-config.js 小程序完整的配置,包含我们通过 app.json 里的所有配置,综合了默认配置型
  5. app-service.js 我们自己的 JS 代码,全部打包到这个文件
  6. page-frame.html 小程序视图的模板文件,所有的页面都使用此加载渲染,且所有的 WXML 都拆解为 JS 实现打包到这里
  7. pages 所有的页面,这个不是我们之前的 wxml 文件了,主要是处理 WXSS 转换,使用 js 插入到 header 区域。

小程序架构

微信小程序的框架包含两部分 View 视图层、App Service 逻辑层,View 层用来渲染页面结构,AppService 层用来逻辑处理、数据请求、接口调用,它们在两个进程(两个 Webview)里运行。
视图层和逻辑层通过系统层的 JSBridage 进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

小程序架构图:

image

小程序启动时会从 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)

  1. 日志组件 Reporter 封装
  2. wx 对象下面的 api 方法
  3. 全局的 App,Page,getApp,getCurrentPages 等全局方法
  4. 还有就是对 AMD 模块规范的实现

然后整个页面就是加载一堆 JS 文件,包括小程序配置 config,上面的 WAService.js(调试模式下有 asdebug.js),剩下就是我们自己写的全部的 js 文件,一次性都加载。

在开发环境下

  1. 页面模板:app.nw/app/dist/weapp/tpl/appserviceTpl.js
  2. 配置信息,是直接写入一个 js 变量,__wxConfig。
  3. 其他配置

image

线上环境

而在上线后是应用部分会打包为 2 个文件,名称 app-config.json 和 app-service.js,然后微信会打开 webview 去加载。线上部分应该是微信自身提供了相应的模板文件,在压缩包里没有找到。

  1. WAService.js(底层支持)
  2. app-config.json(应用配置)
  3. app-service.js(应用逻辑)

然后运行在 JavaScriptCore 引擎里面。

AppView

这里可以理解为 h5 的页面,提供 UI 渲染,底层提供一个 WAWebview.js 来提供底层的功能,具体如下:

  1. 消息通信封装为 WeixinJSBridge(开发环境为 window.postMessage, IOS 下为 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage,android 下用 WeixinJSCore.invokeHandler)
  2. 日志组件 Reporter 封装
  3. wx 对象下的 api,这里的 api 跟 WAService 里的还不太一样,有几个跟那边功能差不多,但是大部分都是处理 UI 显示相关的方法
  4. 小程序组件实现和注册
  5. VirtualDOM,Diff 和 Render UI 实现
  6. 页面事件触发

在此基础上,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: 消息分发

image

Android

通过 WeixinJSCore.invokeHandler 实现,这个 WeixinJSCore 是微信提供给 JS 调用的接口(native 实现)
invokeHandler: 调用原生能力
publishHandler: 消息分发

微信组件

在 WAWebview.js 里有个对象叫 exparser,它完整的实现小程序里的组件,看具体的实现方式,思路上跟 w3c 的 web components 规范神似,但是具体实现上是不一样的,我们使用的所有组件,都会被提前注册好,在 Webview 里渲染的时候进行替换组装。
exparser 有个核心方法:
registerBehavior: 注册组件的一些基础行为,供组件继承
registerElement:注册组件,跟我们交互接口主要是属性和事件

image

组件触发事件(带上 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 个等,这些都是为了保证更好的体验。