vue2入口:构建入口

338 阅读3分钟

vue使用过程中可以采用以下两种方式

  • 在vue脚手架中直接使用,参考文档:https://cn.vuejs.org/v2/guide/installation.html
  • 或者在html文件的头部通过静态文件的方式引入: <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

那么问题来了,使用的或者引入的到底是什么?
答:引入的是已经打包好的vue.js文件,通过rollup构建打包所得。

一、构建入口

在vue源码的package.json文件中:

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

通过执行npm run build的时候,会进行scripts/build.js文件的执行,npm run build:ssr和npm run build:weex的时候,将ssr和weex作为参数传入,按照参数构建出不一样的vue.js打包文件。

二、构建函数的执行(核心)

scripts/build.js:

function build (builds) {
  let built = 0
  const total = builds.length
  const next = () => {
    buildEntry(builds[built]).then(() => {
      built++
      if (built < total) {
        next()
      }
    }).catch(logError)
  }

  next()
}

获取当前total=builds.length,可以使得当前total数量的构建都是一个成功后才进行下一个构建,依次有序的进行。buildEntry(builds[built])是真实的rollup构建方法,只有在.then(成功后)并且当前构建索引built还小于总构建数total才再次调用自己next进行下一次的构建。builds是哪儿来的?

三、构建元素的产生

在scripts/build.js文件中有builds的来源

let builds = require('./config').getAllBuilds()

实际是执行scripts/config.js的getAllBuilds方法,即Object.keys(builds).map(genConfig)

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

Object.keys(builds)获取到builds的key值,并通过genConfig方法的将描述式的构建元素重新构建成rollup真正需要的元素。 builds数组如下:

const builds = {
   // ...
   // Runtime+compiler ES modules build (for bundlers)
  '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+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.js'),
    format: 'es',
    transpile: false,
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.min.js'),
    format: 'es',
    transpile: false,
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // ...
}

genConfig数组如下:

function genConfig (name) {
  const opts = builds[name]
  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)
      }
    }
  }
  // ...
  return config
}

四、构建元素的真实路径

我们看到构建元素中有entry: resolve('web/entry-runtime-with-compiler.js')和dest:resolve('dist/vue.esm.js'),这不是真实路径,真实路径由resolve方法获取:

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

其中用到的alias在文件scripts/alias.js中

const resolve = p => path.resolve(__dirname, '../', p)

module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}

入口web/entry-runtime-with-compiler.js'对应的真实路径就是path.resolve(aliases[base], p.slice(base.length + 1)),即src/platforms/web/entry-runtime-with-compiler。这个是带有compiler的版本,主要目的是可以对带有template参数的进行编译处理。

五、构建数组的过滤

最终的builds还会进行再次的过滤

// filter builds via command line arg
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
  })
}

filter builds via command line arg(通过命令行参数来过滤构建数组)。如果,我们通过npm run build:ssr,那么获取到的命令行参数就是ssr,就会只保留输出文件名b.output.file或者当前文件b._name中包含ssr的构建。如果,没有传递参数,就会执行else逻辑,将包含weex的进行过滤。

小结:在构建过程中,将描述式的构建数组转换成rollup可以执行的数组,并通过控制台的参数对当前数组进行过滤,最后通过递归的方式进行构建队列的有序执行。