目录
- 课程目标
- 背景
- 双线程架构
- WebView 结构
- 快速渲染 PageFrame
- 编译原理
- Exparser
- 通讯系统
- 生命周期
- 基础库解包
- 跨端框架
- 预编译
- 半编译半运行
- 运行时框架
- 主流技术
- Taro
- uni-app
- 汇总
- 下周安排
课程目标
本次课程主要通过后台管理小程序回顾一下小程序的高阶语法,然后讲解整体小程序流程原理:
- 前端相关的编码规范、设计规范
- 页面切换、生命周期、数据通信等基础知识
- 双线程架构、webview 渲染、PageFrame 模板、通讯系统、生命周期、跨端框架等
- 掘金小册推荐 https://juejin.cn/book/6982013809212784676
背景
https://zh.wikipedia.org/zh-cn/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F
微信小程序于 2017 年上线,当时企业普遍把 H5 作为公众号的引流入口,公众号正火,但是当时由于 H5 不安全、性能不好、占用存储空间大的问题,微信需要设计一个全新的系统进行迭代。
双线程架构
面试官:说说什么是进程?什么是线程?区别? | web前端面试 - 面试官系列
进程:操作系统进行进行资源分配和调度的基本单位
线程:操作系统能够进行运算调度的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行
微信小程序的设计和前端浏览器是不一致的,它隔离了 JS 操作层 和 界面渲染层,前端是互通的,可以通过 window document 任意的调用 dom 元素和执行脚本,但是小程序作为微信的工具,势必不允许像 web 端这样自由的操作空间,所以:
- App Service(逻辑层):单纯执行 JavaScript 的沙箱环境,无法操作 Dom 和访问浏览器 Api ,只能借助 Native 异步查询 DOM 元素并操作,小程序的 WXML 和 WXSS 也会被编译为 JS 脚本进行注入操作。由于小程序内部只有一个 App 实例,通过这个 App 主线程 进行调度
- Web View(渲染层):单纯页面的展示,模拟 app 多页面模式,具有多个 webview ,可以暂时抽象成 浏览器的 tab 页面(并非 VUE 的单页面结构,像 App 多页面结构)
- Native(微信客户端):利用微信缓存机制,提前注入 微信 SDK、接口请求、组件渲染(wx.request、wx.showToast 都是存放在这里)
WebView 结构
- 第一阶段是启动一个 WebView,在iOS和 Android 系统上,操作系统启动WebView都需要一小段时间。
- 第二阶段是在 WebView 中初始化基础库,此时还会进行一些基础库内部优化,以提升页面渲染性能。
- 第三阶段是注入小程序 WXML 结构和 WXSS 样式,使小程序能在接收到页面初始数据之后马上开始渲染页面(这一阶段无法在小程序启动前执行)。
- 每一个页面都独立运行在一个页面层级上。小程序启动时仅有一个页面层级,每次调用
wx.navigateTo
,都会创建一个新的页面层级,wx.navigateBack
会销毁一个页面层级。
- 微信开发者工具底层是 Chrome XWeb 内核,进行开发跨平台的桌面应用(如QQ、WeChat客户端)。
- 左上角选择,
微信开发者工具 -> 调试 -> 调试微信开发者工具
,和谷歌调试界面几乎一模一样(利用这个工具,我们后续可以处理一些内部报错的调试BUG)。 - 接下来,我们利用脚本遍历开发工具的 webview 元素。
// 获取所有的 webview
document.getElementsByTagName('webview')
// 打开调试的 webview
document.getElementsByTagName('webview')[0].showDevTools(true, null)
- webview 的含义如下:当前小程序的页面 两个渲染层 逻辑层 调试器
- 新开一个页面就会新增一个 webview,微信限制最多10个以保证性能问题的底层原因
- 利用命令打开一个 webview 页面进行具体的调试
- 页面的样式、调试库、渲染库、开发工具的配置…
- body 下面的标签就是利用 小程序开发的 Exparser 框架将 dom 转换为自定义组件的一套规则
- 每个 webview 都加载了这么多文件,是如何保证高效运作的?
快速渲染 PageFrame
- 利用 page frame 模板生成 webview 视图。
- 基本的 webview 模板和之前打开的一致,但是包含一些注释占位符,后续会被编译为具体的执行 js 文件。
加载流程如下:
- 首页启动时,即第一次通过
pageframe.html
生成内容后,后台服务会缓存pageframe.html
模板首次生成的html内容。- 非首次新打开页面时,页面请求的
pageframe.html
内容直接走后台缓存。 - 非首次新打开页面时,
pageframe.html
页面引入的外链js资源(如上图所示)走本地缓存。
- 非首次新打开页面时,页面请求的
- 生成 webview 模板后,初始化 webview 地址
http://127.0.0.1:${global.proxyPort}/aboutblank?${c}
空地址,其中${c}
为对应 webview 的 id。 - 监听页面的 ready 操作,注入执行代码脚本生成最终的 webview 地址和执行代码。
编译原理
基础语法不作过多赘述,主要编译原理和rpx动态适配。WXSS/WXML
并不可以直接执行在webview
层进行渲染,通过wcsc/wcc
执行脚本编译为js文件,然后注入webview中,这里可以演示一下。
// help() 查看编译命令
help()
// 编译 WXSS 命令
./wcsc -js index.wxss >> style.js
// 编译 WXML 命令
./wcc -js index.wxml >> test.js
// 执行 $gwx 文件
var decodeName = decodeURI("./pages/home-tab/index.wxml")
var generateFunc = $gwx(decodeName)
generateFunc()
var eps = 1e-4
var transformRPX =
window.__transformRpx__ ||
function (number, newDeviceWidth) {
if (number === 0) return 0
number = (number / BASE_DEVICE_WIDTH) * (newDeviceWidth || deviceWidth)
number = Math.floor(number + eps)
if (number === 0) {
if (deviceDPR === 1 || !isIOS) {
return 1
} else {
return 0.5
}
}
return number
}
-
wcsc 将 WXSS 编译为 JS 文件。
-
JS 文件注入到 WebView 中。
-
逻辑层执行 JS 文件,主要是设备信息获取(宽度、高度、横屏)、特定规则 rpx 适配为 px,写入编译后的 CSS 文件到 head style 头部。
-
WCC 将 WXML 编译为 JS 文件。
-
但是文件作了压缩混淆,本质逻辑执行
$gwx
函数。 -
generateFunc
就是接受动态数据,并生成虚拟 DOM 树的函数,DOM 树已存在的数据直接渲染为 wx-view/wx-text,需要 JS 脚本异步获取的数据采取 tag: virtual 虚拟 DOM 元素进行占位,等到获取后端数据之后直接填充 => 真实 DOM。
Exparser
WebComponents Shadow Dom
Exparser是微信小程序的组件组织框架,内置在小程序基础库中,为小程序提供各种各样的组件支撑。内置组件和自定义组件都有Exparser组织管理。
这部分后面单独抽离一篇文章进行讲解…
通讯系统
在正式讲解小程序的通讯系统前,先来熟悉一下小程序的 发布 - 订阅模式
美团一面:你了解发布-订阅模式吗?「每天搞透一道JS手写题💪Day8」 - 掘金
概述:引入中间平台进行注册和通知,有效解决了观察者维护列表导致的解耦不彻底问题
观察者通过 on 向 EventBus 注册事件,然后 Subject 通过 emit 向 EventBus 发射事件,由 EventBus 来向观察者更新。本质上是维护一个 events 对象,通过事件名注入到 events,每个 事件名 里面都有相应的回调函数,发布之后会分发到相应事件 回调函数 的方法。
class PubSub {
constructor() {
this.events = {};
}
subscribe(event, callback) {
if (typeof event !== 'string') {
throw new Error('Event name must be a string');
}
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
unsubscribe(event, callback) {
if (typeof event !== 'string') {
throw new Error('Event name must be a string');
}
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
publish(event, data) {
if (typeof event !== 'string') {
throw new Error('Event name must be a string');
}
if (!this.events[event]) return;
this.events[event].forEach(callback => callback(data));
}
}
在微信小程序执行过程中,Native层(客户端层)分别向渲染层与逻辑层注入 WeixinJSBridge 以达到线程通讯的目的,前面也提到了 webview 脚本注入的原理,WeixinJSBridge 提供了如下几个方法:
- invoke - 调用 Native API,以api方式调用开发工具提供的基础能力,并提供对应api执行后的回调
- invokeCallbackHandler - Native 传递 invoke 方法回调结果
- on - 用来收集小程序开发者工具触发的事件回调
- publish - 渲染层发布消息,用来向逻辑业务层发送消息,也就是说要调用逻辑层的事件方法
- subscribe - 订阅逻辑层消息
- subscribeHandler - 视图层和逻辑层消息订阅转发
- setCustomPublishHandler - 自定义消息转发
- WXML 渲染为虚拟 DOM,添加
addEventListener
进行事件解析。 - 逻辑层解析的时候会利用 JSBridge subscribe 订阅事件名称到渲染层,执行逻辑还在逻辑层,只是把名称和具体代码在渲染层作了映射。
- 触发事件,渲染层发布消息 publish 携带方法名经过 JSBridge 到达逻辑层。
- 逻辑层执行将 Data 以 JSON 字符串格式的数据经过 JSBridge 渲染给 WXML。
生命周期
onLaunch(App onLaunch)
App 主应用开始初始化,逻辑线程开始执行。onLoad(Object query)
页面加载时触发,一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数。onShow()
页面显示/切入前台时触发。onHide()
页面隐藏/切入后台时触发。如wx.navigateTo
或底部 tab 切换到其他页面,小程序切入后台等。onReady()
页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。onUnload()
页面卸载时触发。如wx.redirectTo
或wx.navigateBack
到其他页面时。
wx.navigateTo
方式是创建新的webview
(添加新的页面栈,新加入的页面从头渲染,之前的页面onHide
隐藏,为了使用navigateBack
走缓存onShow
)。wx.redirectTo
以及wx.navigateBack
是通过更新自身webview
进行页面转换的,所以当前页面会进行卸载操作,并且重新生成新页面。
基础库解包
微信开发者工具里面选择的基础库为微信中的基础库版本(后面标识了兼容性和用户的数量),可用于微信开发者工具内的调试。每个版本的微信客户端都会自带一个版本的小程序基础库,而不是微信客户端带着所有版本的基础库,所以开发时选择的版本尽量和客户端的一致(最新的为好),避免 API 兼容性问题。
接下来,我们利用 openVendor() 找到 基础库进行解析,找到原始的 wxvpkg 然后利用 Github 工具进行反编译出源码,https://github.com/csj5588/wxappUnpacker
构建后的结果在 dist 文件下,本质上还是渲染层和逻辑层两个线程脚本之间的交互流程,后续在深入探讨。
跨端框架
由于小程序语法较为原始,工程化工具不支持,市面上太多平台的小程序需要适配开发,基于此市面上的第三方框架也慢慢进入大众的视野,目前来说流行的就是 taro 和 uni-app。
下面先来介绍下框架的分类:
预编译
本质上是利用 DSL(语法规则) + 语法解析,将一些逻辑转换为小程序支持的语法,但是这样存在一些问题:
- 如果React或者Vue后期再出一些新特性的话,预编译框架都需要进行语法解析扩展编写。
- 兼容问题,比如小程序不支持的一些属性,如果不支持,预编译框架要进行兼容。
半编译半运行
基本上运行时的框架都是参考Vue的框架才可以达到运行时的目的,注意小程序是不支持直接操作DOM元素的,所以只需要将Vue的patch挂载真实DOM流程修改为小程序setData的逻辑,template转换为WXML,style适配下规则就可以。
mpvue
(github地址请参见)是一个使用Vue.js开发小程序的前端框架。框架基于Vue.js
核心,mpvue
修改了Vue.js的runtime和compiler实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套Vue.js
开发体验。
下面我们来看一下源码是如何实现的?
- 修改了 vue 的 patch 阶段进行
this.$updateDataToMP()
方法。 - 在
this.$updateDataToMP()
方法就进行了setData
的一个调用,还进行了diffData
数据的比对和throttle
函数优化双线程通讯的性能。
运行时框架
运行时框架主要解决需要 template 转换为 WXML 这部分逻辑,这部分可以借助小程序的template
模版机制来进行解决。
这样的话我们只需要进行 root 数据的操作即可,操作完成后利用 setData 渲染机制 发送到渲染层进行操作即可,Taro4 我看编译的结果似乎也是按照这样的格式操作的,后续再看看。
主流技术
Taro
Taro 介绍 | Taro 文档
Taro 特点总结
- 采用 TypeScript (TS) 加上部分 Rust 进行开发
- 底层标准参考 微信小程序,兼容性较好,其他平台的兼容性未知
- 对 React 和 Webpack 语法有良好的兼容性支持,Vue 3 加上 Vite 新推出,也可以用
- 调试开发需要下载不同平台的工具,无法直接导出运行
- 在 GitHub 上搜索近两年 stars 30+ 的开源项目,资源只有一页,显示资源较少
- 不支持使用 Vue 3 的 scoped CSS 样式隔离语法,这可能需要更详细的介绍
资源链接
Taro UI | O2Team
NutUI - 移动端组件库
组件库选择建议
- Taro 组件库:由 Taro 维护,但不推荐安装
- NutUI 组件库:多端组件库分离,推荐作为开发的首选
uni-app
uni-app官网
相关资源链接
- uview-plus 3.0 - 全面兼容nvue的uni-app生态框架 - uni-app UI框架
- GitHub - Moonofweisheng/wot-design-uni: 一个基于Vue3+TS开发的uni-app组件库,提供70+高质量组件,支持暗黑模式、国际化和自定义主题。
- 页面 | uni-app官网
uni-app 特点总结
本质上有两种体系:
- 一种是 uniapp + HBuilder 工具构建的一套自己的规范,其中包含:uts(TypeScript 超集)、uni-modules (依赖管理)、uvue(编译语言)、uniCloud(云服务),最终这些被 uni-app x 统一集成,需要重新学习一套标准、时间成本和难度过高,编辑器也不如前端常用的编辑器 VSCode、WebStorm
- 另一种可以借助原本的命令行执行,和其他的前端项目一致,可以借助第三方优质模板,但是注意 app 部分打包仍然需要 HBuilder 开发工具(推荐)
汇总
维度 | Taro | 得分 | Uni-app | 得分 |
---|---|---|---|---|
社区文档 | 清晰明确 | 8 | 较为混乱、自有体系和前端不兼容 | 6 |
上手成本 | VUE 版本上手容易、React 较高 | 8 | 底层采用 VUE 开发,熟悉程度更高 | 9 |
开源项目 pushed:>2023-09-01 stars:>30 | 68 | 7 | 143 | 10 |
Github 活跃 | stars:35.3k+ issues:1.2k + 1w+ | 10 | stars:39.9k+ issues:0.9k + 3k+ | 8 |
三方组件库 | 官方组件库统一,资源优质 NutUI VUE 6k+ | 9 | 官方组件库较为简陋、社区UI生态较差、大部分收费、一部分不敢用 | 5 |
TS 支持 | 兼容性优秀 | 9 | 兼容性优秀 | 8 |
VUE 语法兼容 | 兼容大部分 VUE 语法 | 7 | 兼容大部分 VUE 语法 | 8 |
React 语法兼容 | 支持 | 0 | 不支持 | 0 |
总计 | 🥇🏅🎖️ | 58 | 54 |
跨端开发框架深度横评
下周安排
开源商城项目拆解
- 腾讯有数
- gz-yami - Overview
- EastWorld