Skip to content
微信公众号

mpvue 与小程序结合的原理

mpvue 如何生成小程序源码

mpvue 生成小程序源码主要在构建阶段完成,下面我们就通过 dev 和 build 两种构建方式,具体分析 mpvue 的构建流程。

mpvue dev 模式构建流程

mpvue 转换成小程序源码主要依靠构建工具 webpack,我们打开 package.json 查看 scripts:

js
{
  "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 的过程。微信小程序的开发指令为:

shell
"dev:wx": "node build/dev-server.js wx",

它实际执行的指令为:

shell
node build/dev-server.js wx

这里使用 node 命令运行了 build/dev-server.js 文件,并且传入了一个参数 wx。dev-server.js 文件中做了两件非常重要的事情:

  • 生成 webpack 配置文件,完成一次 webpack 构建;
  • 监听微信小程序文件变化,文件发生变化后重新构建。

mpvue 将它的源码写得比较复杂,实际上它只需要以下代码即可正常工作:

js
/* 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
  })
})

上述代码中的核心部分如下:

js
var compiler = webpack(webpackConfig)

上述代码以 API 形式调用了 webpack 构建函数,传入了 webpack 的配置信息 webpackConfig。调用成功后将对现有 mpvue 源码进行构建,在 dist/wx 目录下生成小程序源码。下面的代码将实现 mpvue 源码的修改监听:

js
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 对应的指令:

shell
"build:wx": "node build/build.js wx",

当我们执行 npm run build:wx 实际运行指令为:node build/build.js wx。dev 模式和 build 模式最主要的差别是会对代码进行压缩,减小小程序源码的体积,从而提升访问性能。build/build.js 的关键源码如下:

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 文件进行解析,并完成文件分割,它的配置信息如下:

js
{
  test: /\.vue$/,
  loader: 'mpvue-loader',
  options: vueLoaderConfig
}

vueLoaderConfig 会根据我们传入的平台参数不同,分别给予不同的文件后缀:

js
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 部分,比如下面的源码:

vue
<template>
  <div>{{message}}</div>
</template>

<script>
  export default {
    data() {
      return {
        message: 'Hello Mpvue'
      }
    }
  }
</script>

将被 mpvue-loader 转化为如下代码:

js
/* 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 对象:

js
new webpack.DefinePlugin({
	'mpvue': 'global.mpvue',
	'mpvuePlatform': 'global.mpvuePlatform'
}),

这样我们就可以在代码中直接使用 mpvue 或 mpvuePlatform 对象。

CopyWebpackPlugin

CopyWebpackPlugin 的用途是在构建过程中进行文件拷贝,它的配置如下:

js
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 模式下代码分割配置:

js
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 文件体积减小,并且提供代码的复用程度,减少重复代码。

本站总访问量次,本站总访客数人次
Released under the MIT License.