主题
小程序
1. 登录
unionid
和openid
了解小程序登陆之前, 我们写了解下小程序/公众号登录涉及到两个最关键的用户标识:
OpenId
是⼀个用户对于⼀个小程序/公众号的标识, 开发者可以通过这个标识识别出用户。UnionId
是⼀个用户对于同主体微信小程序/公众号/ APP 的标识, 开发者需要在微信开放平台下绑定相同账号的主体 。开发者可通过UnionId
, 实现多个小程序 、公众号 、甚至APP
之间的数据互通了。
- 关键 Api
wx.login
官⽅提供的登录能⼒wx.checkSession
校验用户当前的session_key
是否有效wx.authorize
提前向用户发起授权请求wx.getUserInfo
获取用户基本信息
- 登录流程设计
利用现有登录体系
直接复用现有系统的登录体系, 只需要在小程序端设计用户名,密码/验证码输⼊页面,便可以简便的实现登录, 只需要保持良好的用户体验即可
利用
OpenId
创建用户体系OpenId 是⼀个⼩程序对于⼀个用户的标识,利用这⼀点我们可以轻松的实现⼀套基于⼩程序的用户体系,值得⼀提的是这种用户体系对用户的打扰最低, 可以实现静默登录 。具体步骤如下:
- ⼩程序客户端通过
wx.login
获取code
- 传递
code
向服务端, 服务端拿到code
调用微信登录凭证校验接⼝ ,微信服务器返回openid
和会话密钥session_key
,此时开发者服务端便可以利用openid
生成用户⼊库, 再向⼩程序客户端返回自定义登录态 - ⼩程序客户端缓存 ( 通过
storage
) 自定义登录态 (token
), 后续调用接⼝时携带该登录态作为用户身份标识即可
- ⼩程序客户端通过
利用
Unionid
创建用户体系如果想实现多个⼩程序, 公众号, 已有登录系统的数据互通, 可以通过获取到用户
unionid
的⽅式建立用户体系 。因为unionid
在同⼀开放平台下的所所有应用都是相同的, 通过unionid
建立的用户体系即可实现全平台数据的互通,更⽅便的接⼊原有的功能,那如何获取unionid
呢,有以下两种⽅式:如果户关注了某个相同主体公众号, 或曾经在某个相同主体
App
、公众号上进行过微信登录授权, 通过wx.login
可以直接获取到unionid
结合
wx.getUserInfo
和<button open-type="getUserInfo"><button/>
这两种⽅式引导用户主动授权, 主动授权后通过返回的信息和服务端交互 (这里有⼀步需要服务端解密数据的过程,很简单,微信提供了示例代码) 即可拿到unionid
建立用户体系, 然后由服务端返回登录态,本地记录即可实现登录, 附上微信提供的最佳实践调用
wx.login
获取code
,然后从微信后端换取到session_key
,用于解密getUserInfo
返回的敏感数据使用
wx.getSetting
获取用户的授权情况- 如果用户已经授权, 直接调用 API
wx.getUserInfo
获取用户最新的信息; - 用户未授权,在界面中显示⼀个按钮提示用户登⼊, 当用户点击并授权后就获取到用户的最新信息
- 如果用户已经授权, 直接调用 API
获取到用户数据后可以进行展示或者发送给自⼰的后端。
WARNING
注意事项
需要获取
unionid
形式的登录体系,在以前 ( 18 年 4 ⽉之前) 是通过以下这种⽅式来实现,但后续微信做了调整 ( 因为⼀进⼊⼩程序, 主动弹起各种授权弹窗的这种形式, 比较容易导致用户流失), 调整为必须使用按钮引导用户主动授权的⽅式, 这次调整对开发者影响较大, 开发者需要注意遵守微信的规则, 并及时和业务⽅沟通业务形式,不要存在侥幸⼼理, 以防造成⼩程序不过审等情况jswx.login(获取code) ===> wx.getUserInfo(用户授权) ===> 获取 unionid
因为⼩程序不存在 cookie 的概念, 登录态必须缓存在本地, 因此强烈建议为登录态设置过期时间
值得⼀提的是如果需要⽀持风控安全校验, 多平台登录等功能, 可能需要加⼊⼀些公共参数,例如 platform , channel , deviceParam 等参数 。在和服务端确定⽅案时,作为前端同学应该及时提出这些合理的建议,设计合理的系统。
openid
,unionid
不要在接⼝中明⽂传输, 这是⼀种危险的⾏为, 同时也很不专业
2. 图片导出
这是⼀种常⻅的引流⽅式,⼀般同时会在图片中附加⼀个⼩程序⼆维码。
基本原理
- 借助
canvas
元素,将需要导出的样式首先在canvas
画布上绘制出来api
基本和h5
保持⼀致,但有轻微差异,使用时注意即可 - 借助微信提供的
canvasToTempFilePath
导出图片, 最后再使用saveImageToPhotosAlbum
( 需要授权) 保存图片到本地
- 借助
如何优雅实现
- 绘制出需要的样式这⼀步是省略不掉的 。但是我们可以封装⼀个绘制库, 包含常⻅图形的绘制,例如矩形, 圆⻆矩形, 圆, 扇形, 三⻆形, ⽂字, 图片减少绘制代码, 只需要提炼出样式信息,便可以轻松的绘制, 最后导出图片存⼊相册 。笔者觉得以下这种⽅式绘制更为优雅清晰⼀些, 其实也可以使用加⼊⼀个 type 参数来指定绘制类型,传⼊的⼀个是样式数组, 实现绘制。
- 结合上⼀步的实现, 如果对于同⼀类型的卡片有多次导出需求的场景,也可以使用自定义组件的⽅式, 封装同⼀类型的卡片为⼀个通用组件,在需要导出图片功能的地⽅, 引⼊该组件即可。
jsclass CanvasKit { constructor() { } drawImg(option = {}) { ... return this } drawRect(option = {}) { return this } drawText(option = {}) { ... return this } static exportImg(option = {}) { ... } } let drawer = new CanvasKit( 'canvasId').drawImg(styleObj1).drawText(styleOb drawer.exportImg()
WARNING
注意事项
- 小程序中无法绘制网络图片到
canvas
上, 需要通过downLoadFile
先下载图片到本地临时文件才可以绘制 - 通常需要绘制⼆维码到导出的图片上, 有⼀种方式导出⼆维码时, 需要携带的参数必须做编码, 而且有具体的长度 ( 32 可见字符) 限制, 可以借助服务端生成短链接 的方式来解决
3. 数据统计
数据统计作为目前⼀种常用的分析用户行为的方式,小程序端也是必不可少的 。小程序采取的曝光,点击数据埋点其实和 h5 原理是⼀样的 。但是埋点作为⼀个和业务逻辑不相关的需求, 我们如果在每⼀个点击事件,每⼀个生命周期加⼊各种埋点代码,则会干扰正常的业务逻辑,和使代码变的臃肿, 笔者提供以下⼏种思路来解决数据埋点
- 设计⼀个埋点 sdk
⼩程序的代码结构是,每⼀个 Page
中都有⼀个 Page
方法,接受⼀个包含生命周期函数,数据的 业务逻辑对象 包装这层数据,借助⼩程序的底层逻辑实现页面的业务逻辑 。通过这个我们可以想到思路,对 Page
进⾏⼀次包装,篡改它的生命周期和点击事件, 混⼊埋点代码,不⼲扰业务逻辑, 只要做⼀些简单的配置即可埋点, 简单的代码实现如下:
js
// 代码仅供理解思路
page = function(params) {
let keys = params.keys()
keys.forEach(v => {
if (v === 'onLoad') {
params [v] = function(options) {
stat() //曝光埋点代码
params [v].call(this, options)
}
} else if (v.includes( 'click')) {
params [v] = funciton(event) {
let data = event.dataset.config
stat(data) // 点击埋点
param[v].call(this)
}
}
})
}
这种思路不光适用于埋点,也可以用来作全局异常处理,请求的统⼀处理等场景。
- 分析接口
对于特殊的⼀些业务, 我们可以采取 接⼝埋点,什么叫接⼝埋点呢?很多情况下, 我们有的 api 并不是多处调用的, 只会在某⼀个特定的页面调用, 通过这个思路我们可以分析出,该接⼝被请求,则这个⾏为被触发了,则完全可以通过服务端日志得出埋点数据,但是这种方式局限性较大, 而且属于分析结果得出过程, 可能存在误差,但可以作为⼀种思路了解⼀下。
- 微信自定义数据分析
微信本身提供的数据分析能⼒ ,微信本身提供了常规分析和自定义分析两种数据分析⽅式,在小程序后台配置即可 。借助小程序数据助手这款小程序可以很⽅便的查看
4. 工程化
- 工程化做什么
目前的前端开发过程, 工程化是必不可少的⼀环,那小程序工程化都需要做些什么呢, 先看下目前小程序开发当中存在哪些问题需要解决:
- 不⽀持
css
预编译器,作为⼀种主流的css
解决⽅案,不论是less
,sass
,stylus
都可以提升css
效率 - 不⽀持引⼊
npm
包 ( 这⼀条,从微信公开课中听闻,微信准备⽀持) - 不⽀持
ES7
等后续的js
特性, 好用的async
await
等特性都⽆法使用 - 不⽀持引⼊外部字体⽂件, 只⽀持
base64
- 没有
eslint
等代码检查工具
- 方案选型
对于目前常用的工程化⽅案, webpack
, rollup
, parcel
等来看,都常用与单页应用的打包和处理, 而小程序天生是“多页应用”并且存在⼀些特定的配置 。根据要解决的问题来看, ⽆非是⽂件的编译,修改,拷贝这些处理,对于这些需求, 我们想到基于流的 gulp
非常的适合处理, 并且相对于 webpack
配置多页应用更加简单 。所以小程序工程化⽅案推荐使用 gulp
- 具体开发思路
通过 gulp
的 task
实现:
- 实时编译
less
⽂件至相应目录 - 引⼊⽀持
async
,await
的运行时⽂件 - 编译字体⽂件为
base64
并生成相应css
⽂件, ⽅便使用 - 依赖分析哪些地⽅引用了
npm
包,将npm
包打成⼀个⽂件,拷贝至相应目录 - 检查代码规范
5. 小程序架构
微信小程序的框架包含两部分 View
视图层 、 AppService
逻辑层。View
层用来渲染页面结构,AppService
层用来逻辑处理 、数据请求 、接口调用。
它们在两个线程里运行。
视图层和逻辑层通过系统层的 JSBridage 进行通信, 逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理
- 视图层使用
WebView
渲染,iOS
中使用自带WKWebView
,在Android
使用腾讯的x5
内核 ( 基于Blink
) 运行。 - 逻辑层使用在
iOS
中使用自带的JSCore
运行,在Android
中使用腾讯的x5
内核 ( 基于Blink
) 运行。 - 开发工具使用
nw.js
同时提供了视图层和逻辑层的运行环境。
6. WXML && WXSS
WXML
- 支持数据绑定
- 支持逻辑算术 、运算
- 支持模板 、引用
- 支持添加事件 (
bindtap
) Wxml
编译器:Wcc
把Wxml
文件 转为js
- 执行方式:
Wcc index.wxml
- 使用
Virtual DOM
, 进行局部更新
WXSS
wxss
编译器:wcsc
把wxss
文件转化为js
- 执行方式:
wcsc index.wxss
尺寸单位 rpx
rpx
( responsive pixel
) : 可以根据屏幕宽度进行自适应 。规定屏幕宽为 750rpx
。公式:
js
const dsWidth = 75 0
export const screenHeightOfRpx = function () {
return 750 / env.screenWidth * env.screenHeight
}
export const rpxToPx = function (rpx) {
return env.screenWidth / 750 * rpx
}
export const pxToRpx = function (px) {
return 750 / env.screenWidth * px
}
样式导入
使用 @import
语句可以导入外联样式表, @import
后跟需要导入的外联样式表的相对路径,用 ;
表示语句结束
内联样式
静态的样式统⼀写到 class
中 。 style
接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style
中, 以免影响渲染速度
全局样式与局部样式
定义在 app.wxss
中的样式为全局样式,作用于每⼀个页面 。在 page
的 wxss
文件中定义的样式为局部样式, 只作用在对应的页面, 并会覆盖 app.wxss
中相同的选择器
7. 小程序的问题
- 小程序仍然使用
WebView
渲染, 并非原生渲染 。 ( 部分原生) - 服务端接口返回的头无法执行, 比如:
Set-Cookie
。 - 依赖浏览器环境的
JS
库不能使用。 - 不能使用
npm
,但是可以自搭构建⼯具或者使用mpvue
。 ( 未来官⽅有计划⽀持) - 不能使用
ES7
, 可以自⼰用babel+webpack
自搭或者使用mpvue
。 - 不⽀持使用自⼰的字体 ( 未来官⽅计划⽀持) 。
- 可以用
base64
的⽅式来使用iconfont
。 - ⼩程序不能发朋友圈 ( 可以通过保存图片到本地,发图片到朋友前 。⼆维码可以使用 B 接⼝ ) 。
- 获取⼆维码/⼩程序接⼝的限制
- 程序推送只能使用“服务通知”而且需要用户主动触发提交
formId
,formId
只有 7 天有效期 。 ( 现在的做法是在每个页面都放⼊form
并且隐藏以此获取更多的formId
。后端使用原则为:优先使用有效期最短的) - ⼩程序大⼩限制
2M
,分包总计不超过8M
- 转发 ( 分享) ⼩程序不能拿到成功结果,原来可以 。链接 ( ⼩游戏造的孽)
- 拿到相同的
unionId
必须绑在同⼀个开放平台下 。开放平台绑定限制:50
个移动应用10
个网站50
个同主体公众号5
个不同主体公众号50
个同主体⼩程序5
个不同主体⼩程序
- 公众号关联⼩程序
- 所有公众号都可以关联⼩程序。
- ⼀个公众号可关联
10
个同主体的⼩程序,3
个不同主体的⼩程序。 - ⼀个⼩程序可关联
500
个公众号。 - 公众号⼀个⽉可新增关联⼩程序
13
次,⼩程序⼀个⽉可新增关联500
次。
- ⼀个公众号关联的 10 个同主体⼩程序和 3 个非同主体⼩程序可以互相跳转
- 品牌搜索不⽀持金融 、医疗
- ⼩程序授权需要用户主动点击
- ⼩程序不提供测试
access_token
- 安卓系统下, ⼩程序授权获取用户信息之后,删除⼩程序再重新获取, 并重新授权,得到旧签名, 导致第⼀次授权失败
- 开发者⼯具上, 授权获取用户信息之后, 如果清缓存选择全部清除,则即使使用了
wx.checkSession
, 并且在session_key
有效期内,授权获取用户信息也会得到新的session_key
8. 授权获取用户信息流程
session_key
有有效期,有效期并没有被告知开发者, 只知道用户越频繁使用小程序,session_key
有效期越长- 在调用
wx.login
时会直接更新session_key
, 导致旧session_key
失效 - 小程序内先调用
wx.checkSession
检查登录态, 并保证没有过期的session_key
不会被更新, 再调用wx.login
获取code
。接着用户授权小程序获取用户信息,小程序拿到加密后的用户数据, 把加密数据和code
传给后端服务 。后端通过code
拿到session_key
并解密数据,将解密后的用户信息返回给小程序
面试题: 先授权获取用户信息再 login 会发生什么?
- 用户授权时, 开放平台使用旧的
session_key
对用户信息进行加密 。调用wx.login
重新登录,会刷新session_key
, 这时后端服务从开放平台获取到新session_key
,但是无法对老session_key
加密过的数据解密,用户信息获取失败 - 在用户信息授权之前先调用
wx.checkSession
呢?wx.checkSession
检查登录态, 并且保证wx.login
不会刷新session_key
,从而让后端服务正确解密数据 。但是这里存在⼀个问题, 如果小程序较长时间不用导致session_key
过期,则wx.login
必定会重新生成session_key
,从而再⼀次导致用户信息解密失败
9. 性能优化
我们知道 view
部分是运行在 webview 上的,所以前端领域的大多数优化方式都有用
加载优化
代码包的大小是最直接影响小程序加载启动速度的因素 。代码包越大不仅下载速度时间长,业务代码注入时间也会变长 。所以最好的优化方式就是减少代码包的大小
小程序加载的三个阶段的表示:
优化方式
- 代码压缩
- 及时清理无用代码和资源文件
- 减少代码包中的图片等资源文件的大⼩和数量
- 分包加载
首屏加载的体验优化建议
- 提前请求:异步数据请求不需要等待页面渲染完成。
- 利用缓存: 利用
storage API
对异步请求数据进行缓存, ⼆次启动时先利用缓存数据渲染页面,在进行后台更新。 - 避免白屏:先展示页面骨架页和基础内容。
- 及时反馈:即时地对需要用户等待的交互操作给出反馈, 避免用户以为⼩程序无响应
使用分包加载优化
- 在构建⼩程序分包项目时,构建会输出⼀个或多个功能的分包, 其中每个分包⼩程序必定含有⼀个主包,所谓的主包, 即放置默认启动页面/
TabBar
页面, 以及⼀些所有分包都需用到公共资源/JS
脚本, 而分包则是根据开发者的配置进行划分 - 在⼩程序启动时, 默认会下载主包并启动主包内页面, 如果用户需要打开分包内某个页面,客户端会把对应分包下载下来,下载完成后再进行展示。
优点:
- 对开发者而言, 能使⼩程序有更大的代码体积,承载更多的功能与服务
- 对用户而言, 可以更快地打开⼩程序, 同时在不影响启动速度前提下使用更多功能限制
- 整个小程序所有分包大小不超过
8M
- 单个分包/主包大小不能超过
2M
- 原生分包加载的配置 假设支持分包的小程序目录结构如下:
md
├── app . js
├── app . json
├── app .wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
│
├── packageB
│ └── pages
│ ├── apple
│ └── banana
│
├── pages
│ ├── index
│ └── logs
└── utils
开发者通过在 app.json
subPackages
字段声明项目分包结构
json
{
"pages": [" pages/ index", " pages/ logs"],
" subPackages": [
{ " root": "packageA", "pages": [" pages/ cat", " pages/dog"] },
{ " root": "packageB", "pages": [" pages/ apple", " pages/ banana"] }
]
}
分包原则
- 声明
subPackages
后,将按subPackages
配置路径进行打包,subPackages
配置路径外的目录将被打包到 app ( 主包) 中 app
( 主包) 也可以有自己的pages
( 即最外层的pages
字段 )subPackage
的根目录不能是另外⼀个subPackage
内的子目录- 首页的
TAB
页面必须在app
( 主包) 内
引用原则
packageA
无法require packageB
JS 文件,但可以require app
、自己package
内的JS
、文件packageA
无法import packageB
的template
,但可以require app
、自己package
内的template
packageA
无法使用packageB
的资源,但可以使用app
、自己package
、内的资源
官方即将推出 分包预加载
独立分包
渲染性能优化
- 每次
setData
的调用都是⼀次进程间通信过程, 通信开销与setData
的数据量正相关。 setData
会引发视图层页面内容的更新, 这⼀耗时操作⼀定时间中会阻塞用户交互。setData
是小程序开发使用最频繁,也是最容易引发性能问题的,避免不当使用setData
- 使用
data
在方法间共享数据, 可能增加setData
传输的数据量。。data
应仅包括与页面渲染相关的数据 - 使用
setData
传输大量数据, 通讯耗时与数据正相关, 页面更新延迟可能造成页面更新开销增加 。仅传输页面中发生变化的数据,使用setData
的特殊key
实现局部更新 。 - 短时间内频繁调用
setData
, 操作卡顿, 交互延迟, 阻塞通信, 页面渲染延迟 。避免不必要的setData
,对连续的setData
调用进行合并。 - 在后台页面进行
setData
,抢占前台页面的渲染资源 。页面切⼊后台后的setData
调用,延迟到页面重新展示时执行。
- 使用
避免不当使用 onPageScroll
- 只在有必要的时候监听
pageScroll
事件 。不监听,则不会派发。 - 避免在
onPageScroll
中执行复杂逻辑 - 避免在
onPageScroll
中频繁调用setData
- 避免滑动时频繁查询节点信息 (
SelectQuery
) 用以判断是否显示, 部分场景建议使用节点布局橡胶状态监听 (inersectionObserver
) 替代
使用自定义组件
在需要频繁更新的场景下, 自定义组件的更新只在组件内部进行,不受页面其他部分内容复杂性影响
10. wepy vs mpvue
数据流管理
相比传统的⼩程序框架, 这个⼀直是我们作为资深开发者比较期望去解决的,在 Web
开发中, 随着 Flux
、 Redux
、 Vuex
等多个数据流⼯具出现,我们也期望在业务复杂的⼩程序中使用
WePY
默认⽀持Redux
,在脚手架生成项目的时候可以内置Mpvue
作为Vue
的移植版本, 当然⽀持Vuex
, 同样在脚手架生成项目的时候可以内置
组件化
WePY
类似Vue
实现了单⽂件组件, 最大的差别是⽂件后缀.wpy
, 只是写法上会有差异
js
export default class Index extends wepy.page {}
Mpvue
作为Vue
的移植版本, ⽀持单⽂件组件,template
、script
和style
都在⼀个.vue
⽂件中,和vue
的写法类似,所以对Vue
开发熟悉的同学会比较适应
工程化
所有的⼩程序开发依赖官⽅提供的开发者⼯具 。开发者⼯具简单直观,对调试⼩程序很有帮助,现在也⽀持腾讯云 (目前我们还没有使用,但是对新的⼀些开发者还是有帮助的), 可以申请测试报告查看⼩程序在真实的移动设备上运⾏性能和运⾏效果,但是它本身没有类似前端⼯程化中的概念和⼯具
wepy
内置了构建, 通过wepy init
命令初始化项目,大致流程如下:wepy-cli
会判断模版是在远程仓库还是在本地, 如果在本地则会立即跳到第3
步,反之继续进⾏ 。- 会从远程仓库下载模版, 并保存到本地。
- 询问开发者
Project name
等问题,依据开发者的回答,创建项目
mpvue
沿用了vue
中推崇的webpack
作为构建⼯具,但同时提供了⼀些自⼰的插件以及配置⽂件的⼀些修改, 比如:- 不再需要
html-webpack-plugin
- 基于
webpack-dev-middleware
修改成webpack-dev-middleware-hard-disk
- 最大的变化是基于
webpack-loader
修改成mpvue-loader
- 但是配置方式还是类似,分环境配置文件, 最终都会编译成小程序支持的目录结构和文件后缀
- 不再需要
11. mpvue
Vue.js
小程序版, fork
自 vuejs/vue@2.4.1
,保留了 vue runtime
能⼒ ,添加了小程序平台的支持 。 mpvue
是⼀个使用 Vue.js
开发小程序的前端框架 。框架基于 Vue.js
核⼼, mpvue
修改了 Vue.js
的 runtime
和 compiler
实现,使其可以运⾏在小程序环境中,从而为小程序开发引⼊了整套 Vue.js
开发体验
框架原理
两个大方向
- 通过
mpvue
提供mp
的runtime
适配小程序 - 通过
mpvue-loader
产出微信小程序所需要的文件结构和模块内容
七个具体问题
要了解 mpvue
原理必然要了解 Vue
原理, 这是大前提
现在假设您对 Vue
原理有个大概的了解
- 由于
Vue
使用了Virtual DOM
,所以Virtual DOM
可以在任何支持JavaScript
语⾔的平台上操作, 譬如说目前Vue
支持浏览器平台或weex
,也可以是mp
(小程序)。那么最后Virtual DOM
如何映射到真实的DOM
节点上呢?vue
为平台做了⼀层适配层, 浏览器平台⻅runtime/node-ops.js
、weex
平台⻅runtime/node-ops.js
,小程序⻅runtime/node-ops.js
。不同平台之间通过适配层对外提供相同的接⼝ ,Virtual DOM
进⾏操作Real DOM
节点的时候, 只需要调用这些适配层的接⼝即可, 而内部实现则不需要关⼼, 它会根据平台的改变而改变 - 所以思路肯定是往增加⼀个
mp
平台的runtime
方向走 。但问题是小程序不能操作DOM
,所以mp
下的node-ops.js
里面的实现都是直接return obj
- 新
Virtual DOM
和旧Virtual DOM
之间需要做⼀个patch
,找出diff
。patch
完了之后的diff
怎么更新视图,也就是如何给这些DOM
加⼊attr
、class
、style
等DOM
属性呢?Vue
中有nextTick
的概念用以更新视图,mpvue
这块对于⼩程序的setData
应该怎么处理呢? - 另外个问题在于⼩程序的
Virtual DOM
怎么生成?也就是怎么将template
编译成render function
。这当中还涉及到运行时-编译器-vs-只包含运行时, 显然如果要提高性能 、减少包大⼩ 、输出wxml
、mpvue
也要提供预编译的能⼒ 。因为要预输出wxml
且没法动态改变DOM
,所以动态组件, 自定义 render ,和<script type="text/x-template">
字符串模版等都不支持
另外还有⼀些其他问题,最后总结⼀下:
- 如何预编译生成
render function
- 如何预编译生成
wxml
,wxss
,wxs
- 如何
patch
出diff
- 如何更新视图
- 如何建立⼩程序事件代理机制,在事件代理函数中触发与之对应的
vue
组件事件响应 - 如何建立
vue
实例与⼩程序Page
实例关联 - 如何建立⼩程序和
vue
生命周期映射关系, 能在⼩程序生命周期中触发vue
生命周期
platform/mp
的目录结构
js
├── compiler //解决问题 1,mpvue-template-compiler 源码部分
├── runtime //解决问题 3 4 5 6 7
├── util // ⼯具方法
├── entry-compiler. js // mpvue-template-compiler 的⼊⼝ 。 package.json 相关命令会自动执行
├── entry-runtime.js //对外提供 Vue 对象, 当然是 mpvue
└── join-code-in-build.js // 编 译 出 SDK 时 的 修 复
mpvue-loader
mpvue-loader
是 vue-loader
的⼀个扩展延伸版, 类似于超集的关系,除了 vue-loader
本身所具备的能⼒之外, 它还会利用 mpvue-template-compiler
生 成 render function
entry
它会从 webpack
的配置中的 entry
开始,分析依赖模块, 并分别打包 。在 entry
中 app
属性及其内容会被打包为微信小程序所需要的 app.js/app .json/app.wxss
, 其余的会生成对应的页面 page.js / page.json / page.wxml / page.wxss
, 如示例的 entry
将会生成如下这些文件,文件内容下文慢慢讲来
js
// webpack.config.js
{
// ...
entry: {
app: resolve('./src/main.js'), // app 字段被识别为 app
index: resolve('./src/pages/index/main.js'), // 其余字段被识别为 pag
'news/home': resolve('./src/pages/news/home/index.js')
} }
js
// 产出⽂件的结构 .
├── app.js
├── app.json
├──· app.wxss
├── components
│ ├── card$74bfae61.wxml
│ ├── index$023eef02.wxml
│ └── news$0699930b.wxml
├── news
│ ├── home.js
│ ├── home.wxml
│ └── home.wxss
├── pages
│ └── index
│ ├── index.js
│ ├── index.wxml
│ └── index.wxss
└── static
├── css
│ ├── app.wxss
│ ├── index.wxss
│ └── news
│ └── home.wxss
└── js
├── app.js
├── index.js
├── manifest.js
├── news
│ └── home.js
└── vendor.js
wxml
每⼀个 .vue
的组件都会被生成为⼀个 wxml
规范的 template
,然后通过 wxml
规范的 import
语法来达到⼀个复用, 同时组件如果涉及到 props
的 data
数据,我们也会做相应的处理,举个实际的例子:
vue
<template>
<div class="my-component" @click="test">
<h1>{{ msg }}</h1>
<other-component :msg="msg"></other-component>
</div>
</template>
<script>
import otherComponent from "./otherComponent.vue";
export default {
components: { otherComponent },
data() {
return { msg: "Hello Vue.js!" };
},
methods: {
test() {},
},
};
</script>
这样⼀个 Vue
的组件的模版部分会⽣成相应的 wxml
vue
<import src="components/other-component$hash.wxml" />
<template name="component$hash">
<view class="my-component" bindtap="handleProxy">
<view class="_h1">{{ msg }}</view>
<template
is="other-component$hash"
wx:if="{{ $c[0] }}"
data="{{ ..}}"
></template>
</view>
</template>
可能已经注意到了 other-component(:msg=“msg”)
被转化成了 。 mpvue
在运⾏时会从根组件开始把所有的组件实例数据合并成⼀个树形的数据,然后通过 setData
到 appData
, $c
是 $children
的缩写。⾄于那个 0
则 是我们的 compiler
处理过后的⼀个标记,会为每⼀个⼦组件打⼀个特定的 不重复的标记。 树形数据结构如下:
js
// 这⼉数据结构是⼀个数组,index 是动态的
{
$child: {
'0'{
// ... root data
$child: {
'0': {
// ... data
msg: 'Hello Vue.js!',
$child: {
// ...data
}
}
}
}
}
}
wxss
这个部分的处理同 web
的处理差异不⼤,唯⼀不同在于通过配置⽣成 .css
为 .wxss
,其中的对于 css
的若⼲处理,在 postcss-mpvuewxss
和 px2rpx-loader
这两部分的⽂档中⼜详细的介绍。推荐和⼩程序⼀样,将 app.json/page.json
放到⻚⾯⼊⼝处,使⽤ copy-webpackplugin copy
到对应的⽣成位置。这部分内容来源于 app
和 page
的 entry
⽂件,通常习惯是 main.js
, 你需要在你的⼊⼝⽂件中 export
default { config: {} }
,这才能被我 们的 loader
识别为这是⼀个配置,需要写成 json
⽂件。
js
import Vue from "vue";
import App from "./app";
const vueApp = new Vue(App);
vueApp.$mount();
// 这个是我们约定的额外的配置
export default {
// 这个字段下的数据会被填充到 app.json / page.json
config: {
pages: ["static/calendar/calendar", "^pages/list/list"], // Will be
window: {
backgroundTextStyle: "light",
navigationBarBackgroundColor: "##455A73",
navigationBarTitleText: "美团汽⻋票",
navigationBarTextStyle: "##fff",
},
},
};