一起来撸个简易的小程序框架

对于小程序框架实现原理 , 在支付宝小程序官方文档上有这样一段描述:

与传统的 H5 应用不同 , 小程序运行架构分为 webview 和 worker 两个部分 。webview 负责渲染 , worker 则负责存储数据和执行业务逻辑 。1.webview 和 worker 之间的通信是异步的 。这意味着当我们调用 setData 时 , 我们的数据并不会立即渲染 , 而是需要从 worker 异步传输到 webview 。2.数据传输时需要序列化为字符串 , 然后通过 evaluateJAVAscript 方式传输 , 数据大小会影响性能 。
概括一下 , 大致意思是小程序框架核心是通过2个线程来完成的 , 主线程负责webView的渲染工作 , worker线程负责js执行 。说到这里 , 你是不是会产生一个疑问:为什么多线程通信损耗性能还要搞多线程呢? 可能大多数人都知道因为Web技术实在是太开放了 , 开发者可以为所欲为 。这种情况在小程序中是不允许的 , 不允许使用<iframe>、不允许 <a> 直接外跳到其他在线网页、不允许开发者触碰DOM、不允许使用某些未知的危险API等 。但是 , 仔细想想其实单线程也有能力来阻止用户操作这些危险动作 , 比如通过全局配置黑名单API、改写框架内部编译机制 , 屏蔽危险操作...
但是却始终无法解决一个问题:如何防止开发者做一些我们想禁用的功能 。因为是一个网页 , 开发者可以执行JS , 可以操作DOM , 可以操作BOM , 可以做一切事情 。so , 我们需要一个沙箱环境 , 来运行我们的js , 这个沙箱环境需要可以屏蔽掉所有的危险动作 。说了这么多 , 大致想法如下:
一起来撸个简易的小程序框架

文章插图
 
关于UI层的渲染 , 有很多实现方式 , 比如通过类似VNode -> diff的自定义渲染方式来实现了一个简易的小程序框架:
【一起来撸个简易的小程序框架】 
function App (props) {const {msg} = props;return () => (<div class="main">{msg}</div>)}render(<App msg="hello world" />)复制代码核心就是通过定义@babel/plugin-transform-react-jsx插件来转换 jsx , 生成Vnode , 再交给Worker通过Diff , 最后通过worker postmsg 来通知渲染进程更新:
let index = 0;// 得到diff差异let diffData = https://www.isolves.com/it/cxkf/ydd/xcx/2020-03-16/diff(0, index, oldVnode, newVnode);// 通知渲染进程更新self.postMessage(JSON.stringify(diffData)); 复制代码有点麻烦?能不能继承现有框架能力?比如Vue、React 。当然可以 , 我们下面就来介绍基于Vue来实现的demo.
实现一个基于Vue的小程序框架有了上面的知识 , 我们先不着急写代码 , 先来捋一下我们需要什么 , 首先我们需要实现这样一个能力:渲染层和逻辑层分离 , emmm 。。。大致我们的小程序是这样的
// page.js 逻辑层export default {data: {msg: 'hello Vox',},create() {console.log(window);setTimeout(() => {this.setData({msg: 'setData',})}, 1000);},}复制代码// page.vxml.js 渲染层export default () => {return '<div>{{msg}}</div>';}复制代码这里的渲染层为啥不是类似于微信或者支付宝小程序 wxml,axml这样的呢?当然可以 , 其实我只是为了方便而已 , 我们可以手写一个webpack loader 来处理一下我们自定义的文件 。这里有兴趣的小伙伴可以尝试一下 。不是本次介绍的核心 。
好了 , 上面是我们想要的功能 , 我们核心是框架 , 框架层要干的事核心有2个:构造worker初始化引擎;构造渲染引擎 。
// index.worker.js 构造workerconst voxWorker = options => {const {config} = options;// Vue生命周期收集const lifeCircleMap = {'lifeCircle:create': [config.create],};// 定义setData方法用于通知UI层渲染更新self.setData = (data) => {console.log('setData called');self.postMessage(JSON.stringify({type: 'update',data,}),null);};// worker构建完成 , 通知渲染层初始化self.postMessage(JSON.stringify({type: 'init',data: config.data,}),null);// 执行生命周期函数self.onmessage = e => {const {type} = JSON.parse(e.data);lifeCircleMap[type].forEach(lifeCircle => lifeCircle.call(self))}}export default voxWorker;复制代码


推荐阅读