mpvue 与小程序结合的原理
mpvue 如何生成小程序源码
mpvue 生成小程序源码主要在构建阶段完成,下面我们就通过 dev 和 build 两种构建方式,具体分析 mpvue 的构建流程。
mpvue dev 模式构建流程
mpvue 转换成小程序源码主要依靠构建工具 webpack,我们打开 package.json 查看 scripts:
{
"scripts": {
"dev:wx": "node build/dev-server.js wx",
"start:wx": "npm run dev:wx",
"build:wx": "node build/build.js wx",
"dev:swan": "node build/dev-server.js swan",
"start:swan": "npm run dev:swan",
"build:swan": "node build/build.js swan",
"dev:tt": "node build/dev-server.js tt",
"start:tt": "npm run dev:tt",
"build:tt": "node build/build.js tt",
"dev:my": "node build/dev-server.js my",
"start:my": "npm run dev:my",
"build:my": "node build/build.js my",
"dev": "node build/dev-server.js wx",
"start": "npm run dev",
"build": "node build/build.js wx",
"lint": "eslint --ext .js,.vue src"
}
}
scripts 中 mpvue 提供了大量构建指令,开发时我们选择 dev 相关的指令,发布时我们则需要选择 build 相关的指令。dev 后是小程序对应的平台代码,它们的对应关系如下:
- wx:微信小程序
- my:支付宝小程序
- swan:百度小程序
- tt:头条小程序
如果希望开发百度小程序,则指令为:npm run dev:swan,打包头条小程序指令为:npm run build:tt,以此类推。这里我们仍然以微信小程序为例,着重分析 dev 的过程。微信小程序的开发指令为:
"dev:wx": "node build/dev-server.js wx",
它实际执行的指令为:
node build/dev-server.js wx
这里使用 node 命令运行了 build/dev-server.js 文件,并且传入了一个参数 wx。dev-server.js 文件中做了两件非常重要的事情:
- 生成 webpack 配置文件,完成一次 webpack 构建;
- 监听微信小程序文件变化,文件发生变化后重新构建。
mpvue 将它的源码写得比较复杂,实际上它只需要以下代码即可正常工作:
/* build/dev-server.js */
require('./check-versions')()
process.env.PLATFORM = process.argv[process.argv.length - 1] || 'wx'
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var webpack = require('webpack')
var webpackConfig = require('./webpack.dev.conf')
var compiler = webpack(webpackConfig)
module.exports = new Promise((resolve, reject) => {
require('webpack-dev-middleware-hard-disk')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
})
上述代码中的核心部分如下:
var compiler = webpack(webpackConfig)
上述代码以 API 形式调用了 webpack 构建函数,传入了 webpack 的配置信息 webpackConfig。调用成功后将对现有 mpvue 源码进行构建,在 dist/wx 目录下生成小程序源码。下面的代码将实现 mpvue 源码的修改监听:
require('webpack-dev-middleware-hard-disk')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
关于 webpack-dev-middleware-hard-disk 插件的用法,大家可以查看 github 官方源码库,这是一个 webpack 官方提供的插件,主要解决 webpack 文件监听问题,当我们的 mpvue 代码发生变化时,它会监听代码的变化,并且重新构建,它的更新机制是增量更新,不会整体重新打包。
mpvue build 模式构建流程
接下来我们查看 build 对应的指令:
"build:wx": "node build/build.js wx",
当我们执行 npm run build:wx 实际运行指令为:node build/build.js wx。dev 模式和 build 模式最主要的差别是会对代码进行压缩,减小小程序源码的体积,从而提升访问性能。build/build.js 的关键源码如下:
/* build/build.js */
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
rm(path.join(config.build.assetsRoot, '*'), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
// ...
})
})
build 阶段的主要工作是两件:
- 完成构建前的初始化工作,并删除构建目录;
- 生成 webpack 配置文件,并完成构建。
build 模式不会监听文件的变化,它是从发布的角度考虑问题,以程序体积最小化为出发点,所以对 js 和 css 进行了压缩处理。
mpvue 代码转化核心 loader 和 plugin
在 mpvue 构建过程中,通过 webpack 的 loader 和 plugin 完成代码的解析、文件分割、资源打包等工作。下面我们分析这一过程:
mpvue-loader
mpvue-loader 的主要工作是完成对 .vue 文件的解析,它会对每一个 .vue 文件进行解析,并完成文件分割,它的配置信息如下:
{
test: /\.vue$/,
loader: 'mpvue-loader',
options: vueLoaderConfig
}
vueLoaderConfig 会根据我们传入的平台参数不同,分别给予不同的文件后缀:
var fileExtConfig = {
swan: {
template: 'swan',
script: 'js',
style: 'css',
platform: 'swan'
},
tt: {
template: 'ttml',
script: 'js',
style: 'ttss',
platform: 'tt'
},
wx: {
template: 'wxml',
script: 'js',
style: 'wxss',
platform: 'wx'
},
my: {
template: 'axml',
script: 'js',
style: 'acss',
platform: 'my'
}
}
在解析过程中,它会依次分析一个 .vue 文件的 template、script 和 style 部分,比如下面的源码:
<template>
<div>{{message}}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Mpvue'
}
}
}
</script>
将被 mpvue-loader 转化为如下代码:
/* script */
import __vue_script__ from "!!babel-loader!../../../node_modules/_mpvue-loader@2.0.1@mpvue-loader/lib/selector?type=script&index=0!./index.vue"
/* template */
import __vue_template__ from "!!../../../node_modules/_mpvue-loader@2.0.1@mpvue-loader/lib/template-compiler/index?{\"id\":\"data-v-65f59a68\",\"hasScoped\":false,\"transformToRequire\":{\"video\":\"src\",\"source\":\"src\",\"img\":\"src\",\"image\":\"xlink:href\"},\"fileExt\":{\"template\":\"wxml\",\"script\":\"js\",\"style\":\"wxss\",\"platform\":\"wx\"}}!../../../node_modules/_mpvue-loader@2.0.1@mpvue-loader/lib/selector?type=template&index=0!./index.vue"
/* styles */
var __vue_styles__ = injectStyle
/* scopeId */
var __vue_scopeId__ = null
/* moduleIdentifier (server only) */
var __vue_module_identifier__ = null
var Component = normalizeComponent(
__vue_script__,
__vue_template__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)
Component.options.__file = "src/pages/index/index.vue"
export default Component.exports
上述代码是 mpvue-loader 转换后代码的核心产物,我们暂时不需要明白它的具体含义,只需要知道 mpvue-loader 会将 .vue 源码编译为一个 js 文件,并且在该文件中明确分割了 script、tepmlate 和 styles 三个部分。除此之外,mpvue-loader 内部会直接调用 loader 完成对三种类型文件的解析,最终 webpack 会将三种不同类型的代码输出到 wxml、js 和 wxss 文件中。可以说 mpvue-loader 是整个 mpvue 的内核,它完成了小程序源码的转换。不过这里面还有很多细节,比如 mpvue-loader 是如何完成三种类型代码的识别和转换,这些内容我们将会在源码一章为大家揭开答案,目前我们只需要明白 mpvue-loader 做了这些工作即可。
DefinePlugin
DefinePlugin 插件定义了全局的 mpvue 和 mpvuePlatform 对象:
new webpack.DefinePlugin({
'mpvue': 'global.mpvue',
'mpvuePlatform': 'global.mpvuePlatform'
}),
这样我们就可以在代码中直接使用 mpvue 或 mpvuePlatform 对象。
CopyWebpackPlugin
CopyWebpackPlugin 的用途是在构建过程中进行文件拷贝,它的配置如下:
new CopyWebpackPlugin([{
from: '**/*.json',
to: ''
}], {
context: 'src/'
}),
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: path.resolve(config.build.assetsRoot, './static'),
ignore: ['.*']
}
])
第一段 CopyWebpackPlugin 完成了将 src 下的所有 json 配置文件拷贝到 dist/wx 根目录下,这说明 json 文件并没有进行任何构建,仅仅做了平移。第二段 CopyWebpackPlugin 完成了资源文件的拷贝,它将代码目录下的 static 文件夹直接拷贝到 dist/wx/static 目录下,所以当我们有内置资源需求的时候,可以放置到 static 目录下。
CommonsChunkPlugin
CommonsChunkPlugin 是 webpack 的一个核心插件,它的主要功能是完成代码分割,减少单个 js 代码文件的体积,在未使用 CommonsChunkPlugin 时,我们每个 js 文件中都将包含所有引用的库,这样单个 js 文件体积非常庞大,而且会造成重复引用的问题,使用 CommonsChunkPlugin 插件后,它会将共共的 js 库打包到一个文件中,并可以根据我们设定的需求完成代码分割,下面是 dev 模式下代码分割配置:
new webpack.optimize.CommonsChunkPlugin({
name: 'common/vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf('node_modules') >= 0
) || count > 1
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'common/manifest',
chunks: ['common/vendor']
}),
上述配置将在构建时创建 common 目录,包含两个 js 文件,分别为 manifest.js 和 vendor.js,manifest.js 中将包含很多 webpack 模块的通用方法,vendor.js 中将包含通用第三方 js 库,如 mpvue 的 runtime 部分源码等。这样做的好处是将通用代码进行提取和分割,使得单个 js 文件体积减小,并且提供代码的复用程度,减少重复代码。