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">
字符串模版等都不支持
另外还有一些其他问题,最后总结一下
- 1.如何预编译生成
render
function
- 2.如何预编译生成
wxml
,wxss
,wxs
- 3.如何
patch
出diff
- 4.如何更新视图
- 5.如何建立小程序事件代理机制,在事件代理函数中触发与之对应的
vue
组件事件响应 - 6.如何建立
vue
实例与小程序Page
实例关联 - 7.如何建立小程序和
vue
生命周期映射关系,能在小程序生命周期中触发vue
生命周期
platform/mp
的目录结构
.
├── compiler //解决问题1,mpvue-template-compiler源码部分
├── runtime //解决问题3 4 5 6 7
├── util //工具方法
├── entry-compiler.js //mpvue-template-compiler的入口。package.json相关命令会自动生成mpvue-template-compiler这个package。
├── 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
将会生成如下这些文件,文件内容下文慢慢讲来:
// webpack.config.js
{
// ...
entry: {
app: resolve('./src/main.js'), // app 字段被识别为 app 类型
index: resolve('./src/pages/index/main.js'), // 其余字段被识别为 page 类型
'news/home': resolve('./src/pages/news/home/index.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
数据,我们也会做相应的处理,举个实际的例子:
<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
<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="{{ ...$c[0] }}"></template>
</view>
</template>
可能已经注意到了
other-component(:msg="msg")
被转化成了 。mpvue
在运行时会从根组件开始把所有的组件实例数据合并成一个树形的数据,然后通过setData
到appData,$c
是$children
的缩写。至于那个0
则是我们的compiler
处理过后的一个标记,会为每一个子组件打一个特定的不重复的标记。 树形数据结构如下
// 这儿数据结构是一个数组,index 是动态的
{
$child: {
'0'{
// ... root data
$child: {
'0': {
// ... data
msg: 'Hello Vue.js!',
$child: {
// ...data
}
}
}
}
}
}
wxss
这个部分的处理同
web
的处理差异不大,唯一不同在于通过配置生成.css
为.wxs
s ,其中的对于css
的若干处理,在postcss-mpvue-wxss
和px2rpx-loader
这两部分的文档中又详细的介绍
- 推荐和小程序一样,将
app.json/page.json
放到页面入口处,使用copy-webpack-plugin copy
到对应的生成位置。
这部分内容来源于
app
和page
的entry
文件,通常习惯是main.js
,你需要在你的入口文件中export default { config: {} }
,这才能被我们的loader
识别为这是一个配置,需要写成json
文件
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 filled in webpack
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '##455A73',
navigationBarTitleText: '美团汽车票',
navigationBarTextStyle: '##fff'
}
}
};