阅读 132

Vue源码构建过程-不同平台&不同环境

整体build过程

  1. 执行npm run build
  2. 得到各平台的rollup配置信息
  3. 通过终端传入的构建平台或环境,过滤掉不需要构建的平台配置
  4. 递归同步构建
  5. 写入文件

build

npm run build

执行命令,会定位到scripts下的build文件,下面来看一下这个文件中做了什么。

首先会判断有没有dist目录,这是存放构建产出物的地方

if (!fs.existsSync('dist')) {
  fs.mkdirSync('dist')
}
复制代码

读取config,获得各个平台或环境的rollup配置信息。

let builds = require('./config').getAllBuilds()
复制代码

下面来看一下config文件做了什么。

来到config定位到getAllBuilds方法

exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
复制代码

getAllBuilds方法返回的是将一个对象的key变成数组并map映射了一下

build它就是存放的打包各个环境或平台的信息

const builds = {
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  },
  'web-runtime-cjs-prod': {},
  'web-full-cjs-dev': {},
  'web-full-cjs-prod': {},
  'web-runtime-esm': {},
  'web-full-esm': { },
  'web-full-esm-browser-dev': {},
  'web-full-esm-browser-prod': {},
  'web-runtime-dev': {},
  'web-runtime-prod': {},
  'web-full-dev': {},
  'web-full-prod': {},
  'web-compiler': {},
  'web-compiler-browser': { },
  'web-server-renderer-dev': {},
  'web-server-renderer-prod': {},
  'web-server-renderer-basic': {},
  'web-server-renderer-webpack-server-plugin': { },
  'web-server-renderer-webpack-client-plugin': {},
  'weex-factory': {},
  'weex-framework': {},
  'weex-compiler': { }
}
复制代码

第一个配置中一共有5个属性

  • entry
  • dest
  • format
  • env
  • banner

entry

entry中调用resolve方法,resolve方法通过对node的path.resolve封装了一下,用于找到构建的入口文件的路径。

const aliases = require('./alias')
const resolve = p => {
    //得到是什么平台:vue有web平台和weex平台
  const base = p.split('/')[0]
  // 通过平台拿到platforms下相应平台的目录,
  if (aliases[base]) {
    //拼接编译入口
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}
复制代码

dest

dest中调用resolve与entry相同,用于找到构建后产出物所在的目录

format

format表示构建完毕产出物的模块化规范是什么,rollup用format来配置,一共有一下几种:

  • amd
  • cjs
  • es
  • iife
  • umd

env

env表示是以生产环境构建还是以开发环境构建,生产环境会进行代码gzip压缩

banner

源码往上翻可以看到这样一段代码

const banner =
  '/*!\n' +
  ` * Vue.js v${version}\n` +
  ` * (c) 2014-${new Date().getFullYear()} Evan You\n` +
  ' * Released under the MIT License.\n' +
  ' */'
复制代码

用于在打包好的文件的顶部添加打包信息。

回到getAllBuilds的话题,现在已经知道builds是什么了,下面调用map方法传入genConfig对原来的配置做了一下映射。

来看一下genConfig做了什么

function genConfig(name) {
  const opts = builds[name]
  // rollup的配置结构
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [flow(), alias(Object.assign({}, aliases, opts.alias))].concat(
      opts.plugins || []
    ),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    },
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }
复制代码

了解rollup的应该都知道,builds中的配置并不是rollup打包的配置信息,所以要把原来的配置映射成rollup的配置,genConfig就是用来做这个的。

现在已经知道在build文件下调用config文件中的getAllBuilds得到的是什么。

接下来,拿到shell中输入的平台或环境信息,并且把不需要打包的builds中的配置过滤掉。

if (process.argv[2]) {
  // 解析shell 参数
  const filters = process.argv[2].split(',')
  //通过shell参数把builds不需要的平台配置过滤掉
  builds = builds.filter(b => {
    return filters.some(
      f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1
    )
  })
} else {
  // filter out weex builds by default
  builds = builds.filter(b => {
    return b.output.file.indexOf('weex') === -1
  })
}
复制代码

执行build函数,开始构建

build(builds)
复制代码

由于存在多个不同或环境的编译构建,vue采用同步promise的方式递归编译。类似于koa的中间件逻辑

function build(builds) {
  let built = 0
  const total = builds.length
  // 通过next 调用buildEntry同步编译,built对应builds中平台入口的索引,当一个编译完成之后,built++,继续启动next调用下一个。
  const next = () => {
    buildEntry(builds[built])
      .then(() => {
        built++
        if (built < total) {
          next()
        }
      })
      .catch(logError)
  }

  next()
}
复制代码

其中调用buildEntry,它是每一次构建入口,在这里会判断是否是生产环境从而在构建过程中使用terser压缩。

function buildEntry(config) {
  const output = config.output
  // file是打包的入口
  const { file, banner } = output
  //isProd 判断是否是生产环境的编译
  const isProd = /(min|prod)\.js$/.test(file)
  return rollup
    .rollup(config)
    .then(bundle => bundle.generate(output))
    .then(({ output: [{ code }] }) => {
      // terser是一个适用于ES6压缩代码的工具,在生产环境对代码进行压缩
      if (isProd) {
        const minified =
          (banner ? banner + '\n' : '') +
          terser.minify(code, {
            toplevel: true,
            output: {
              ascii_only: true
            },
            compress: {
              pure_funcs: ['makeMap']
            }
          }).code
        return write(file, minified, true)
      } else {
        return write(file, code)
      }
    })
}
复制代码

构建完毕之后最终调用write方法写入文件,在写入文件过程中会判断是否是生产环境的构建对产出物进行gzip压缩。

function write(dest, code, zip) {
  return new Promise((resolve, reject) => {
    function report(extra) {
      console.log(
        blue(path.relative(process.cwd(), dest)) +
          ' ' +
          getSize(code) +
          (extra || '')
      )
      resolve()
    }
    // 将编译好的文件写入相应的目录下
    fs.writeFile(dest, code, err => {
      if (err) return reject(err)
      if (zip) {
      //gzip压缩
        zlib.gzip(code, (err, zipped) => {
          if (err) return reject(err)
          report(' (gzipped: ' + getSize(zipped) + ')')
        })
      } else {
        report()
      }
    })
  })
}
复制代码

至此整个编译过程完毕。

关注下面的标签,发现更多相似文章
评论