Vue源码阅读(3):今天来看一下 Vue 项目的源码是如何构建的

 我的开源库:

阅读一个库的源码前,最好先看一下这个项目是如何构建的,尤其是像 Vue 这样代码量大,并且支持多个版本、多个平台的开源库。通过解读项目的构建过程,也可以更加了解整体代码的架构设计。

1,从 package.json 开始

与构建有关的三个命令如下,运行不同的命令,能够进行不同 Vue 版本的构建工作。我们可以看到 "build:ssr" 和 "build:weex" 命令都是执行 "build" 命令完成任务的,只不过后面传递的参数不一样,这些参数在 build/build.js 的执行过程中起到过滤 rollup 配置对象的作用。所以,我们阅读下 build/build.js 的代码就可以了。

"scripts": {
    "build": "node build/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build -- weex"
},

2,build/build.js

构建的代码都在 build/build.js 代码中。

2-1,获取 rollup 配置对象数组,根据命令参数过滤配置对象数组

// builds 是 rollup 配置对象的数组
let builds = require('./config').getAllBuilds()

// 根据命令行传递的参数,过滤掉不需要打包的版本的配置对象。
// package.json 中一共有三个打包命令:
// (1)"build": "node build/build.js",
// (2)"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
// (3)"build:weex": "npm run build -- weex",
// 不同的命令有不同的打包对象,对于不需要打包的配置对象,过滤掉
if (process.argv[2]) {
  const filters = process.argv[2].split(',')
  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
  })
}

首先执行 require('./config') 中的 getAllBuilds 方法,获取 rollup 的配置对象数组,这部分内容在下面说。

然后根据执行命令时候的参数,将配置对象数组中不需要编译的版本过滤掉。

2-2,接下来开始代码的构建工作

// 进行编译操作,传递的参数是 rollup 配置对象的数组
build(builds)

// 对 builds(rollup配置对象数组)进行遍历编译操作
function build (builds) {
  let built = 0
  const total = builds.length
  const next = () => {
    // 针对某一个配置对象,进行编译操作
    buildEntry(builds[built]).then(() => {
      built++
      if (built < total) {
        next()
      }
    }).catch(logError)
  }

  next()
}

// 根据 rollup 配置对象进行编译的方法
function buildEntry (config) {
  const output = config.output
  const { file, banner } = output
  const isProd = /min\.js$/.test(file)
  // 使用 rollup 提供的方法进行编译操作
  return rollup.rollup(config)
    // 编译完成之后,执行 bundle 的 generate 方法,生成目标代码
    // rollup 文章的解释:
    // generate code and a sourcemap
    // const { code, map } = await bundle.generate(outputOptions);
    .then(bundle => bundle.generate(output))
    // 在这里,就能够拿到最终生成的代码了
    .then(({ code }) => {
      // 如果是生成生产环境的代码的话,在这里进行代码的压缩操作
      if (isProd) {
        // 压缩后的代码字符串:minified
        var minified = (banner ? banner + '\n' : '') + uglify.minify(code, {
          output: {
            ascii_only: true
          },
          compress: {
            pure_funcs: ['makeMap']
          }
        }).code
        // 将最终生成的代码写到文件中,并且在控制台打印出日志
        return write(file, minified, true)
      } else {
        // 将最终生成的代码写到文件中,并且在控制台打印出日志
        return write(file, code)
      }
    })
}

构建的入口是 build 函数,在 build 函数中,对 builds 配置对象数组进行遍历执行 buildEntry 函数。

在 buildEntry 函数中借助 rollup 提供的 API 进行代码的构建。

2-3,将生成的代码字符串写到文件系统中

// 将最终生成的代码写到文件中,并且在控制台打印出日志
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()
    }

    // 利用 node 中的 writeFile 将生成的代码写到文件系统中
    fs.writeFile(dest, code, err => {
      if (err) return reject(err)
      if (zip) {
        zlib.gzip(code, (err, zipped) => {
          if (err) return reject(err)
          report(' (gzipped: ' + getSize(zipped) + ')')
        })
      } else {
        report()
      }
    })
  })
}

使用 Node 中的 fs.writeFile() 将生成的代码字符串写到文件系统中。

3,Rollup 配置对象数组的生成

在 2-1 中,生成配置的代码是:let builds = require('./config').getAllBuilds(),所以我们先看一下 build/config.js 中导出的 getAllBuilds 函数。

exports.getAllBuilds = () => Object.keys(builds).map(genConfig)

该函数的返回值就是 Rollup 用于构建的配置对象的数组。

看一下 builds 的内容:

// 不同版本 Vue.js 的基础配置信息
const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.js'),
    format: 'cjs',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.js'),
    format: 'cjs',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime only (ES Modules). Used by bundlers that support ES Modules,
  // e.g. Rollup & Webpack 2
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler CommonJS build (ES Modules)
  'web-full-esm': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.js'),
    format: 'es',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
  ......
}

builds 对象是不同版本 Vue 用于构建的基础配置信息,包括构建入口、构建出口、模块规范等。

然后通过 .map(genConfig) 生成真正能够应用于 Rollup 构建的配置对象数组。

// 根据 builds 中的基础配置信息,生成 rollup 能够运行的配置对象
function genConfig (name) {
  // 取出该版本 Vue 所对应的基础配置对象
  const opts = builds[name]
  // 开始构建 Rollup 配置对象,很简单,取出 opts 中的数据,赋值到 config 对象中即可
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      replace({
        __WEEX__: !!opts.weex,
        __WEEX_VERSION__: weexVersion,
        __VERSION__: version
      }),
      flow(),
      buble(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    }
  }

  if (opts.env) {
    // replace 插件能够替换代码中的字符串
    config.plugins.push(replace({
      'process.env.NODE_ENV': JSON.stringify(opts.env)
    }))
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })

  return config
}

注意:从 builds 中的配置可知,Vue 构建的入口在 src/platforms 中。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值