npm run build发生了什么?

最近总是感觉对vue的一些用法和语句还是不理解,于是决定撸一下源码,用于加深自己对vue的理解,同时vue主要是通过rollup进行打包编译,因为它相比webpack更加轻量,行了,废话不多说了,开始了!

如上图所示,当我们执行npm runbuild命令的时候,首先package.json会将其解析为node build/build.js,执行这个目录,我们看看这个目录是什么!

代码如果理解起来比较吃力,在文件代码下面会有梳理!

直接进入到build /build.js贴代码!

const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const uglify = require('uglify-js')

if (!fs.existsSync('dist')) {  // 判断是否存在dist文件夹,如果没有则创建一个
  fs.mkdirSync('dist')  // 这也是为什么,当我们执行完build命令后,会出现一个dist文件夹
}

let builds = require('./config').getAllBuilds()  // 引入./config中的文件,然后执行这个文件下的getAllBuilds()方法
//process.argv获得附加的命令行参数 如: node app 127.0.0.1 7001 我们将会得到127.0.0.1
if (process.argv[2]) {  //  主要针对build:ssr和week形式的
  const filters = process.argv[2].split(',')  //  通过逗号分隔成数组
  builds = builds.filter(b => {                                                         //  过滤所以.output.file和._name包含filters内容的
    return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1) // 检测是否又符合条件的,有则返回true没有则是false
  })
} else {  // 说明这块执行的就是单纯的 npm run build
  // filter out weex builds by default  //但也对weex的进行过滤
  builds = builds.filter(b => {    // 过滤输出文件中不包含weex的
    return b.output.file.indexOf('weex') === -1
  })
}   //  综上所述,主要是对builds中的值进行过滤操作


build(builds)

function build (builds) {    //  对拿到的builds进行一个简单的遍历
  let built = 0
  const total = builds.length
  const next = () => {
    buildEntry(builds[built]).then(() => {  //  builds数组从0到最后一个元素执行buildEntry方法
      built++
      if (built < total) {
        next()
      }
    }).catch(logError)
  }

  next()
}

function buildEntry (config) {  // 真正开始通过rollup对其进行编译
  const output = config.output
  const { file, banner } = output
  const isProd = /min\.js$/.test(file)  // 匹配min.js结尾的文件
  return rollup.rollup(config)
    .then(bundle => bundle.generate(output))
    .then(({ code }) => {
      if (isProd) {
        var minified = (banner ? banner + '\n' : '') + uglify.minify(code, {  // 判断生成的js是否需要压缩
          output: {
            ascii_only: true
          },
          compress: {
            pure_funcs: ['makeMap']
          }
        }).code
        return write(file, minified, true)
      } else {
        return write(file, code)
      }
    })
}

function write (dest, code, zip) {  
  return new Promise((resolve, reject) => {
    function report (extra) {  //  必要的时候,在文件中加入console.log
      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
      resolve()
    }

    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()
      }
    })
  })
}

function getSize (code) {
  return (code.length / 1024).toFixed(2) + 'kb'
}

function logError (e) {
  console.log(e)
}

function blue (str) {
  return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
}


复制代码

下面开始对代码进行解析

前两行主要是引入了一些模块,以及对dist文件的判断,相信通过注释,大家一定能看得懂。

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

下面我们看一下这个builds到底是什么,首先我们先看一下config文件夹下的代码

JavaScript 示例:

const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
const version = process.env.VERSION || require('../package.json').version
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version
 //  对版本号的一个注释
const banner =
  '/*!\n' +
  ' * Vue.js v' + version + '\n' +
  ' * (c) 2014-' + new Date().getFullYear() + ' Evan You\n' +
  ' * Released under the MIT License.\n' +
  ' */'
 
const weexFactoryPlugin = {
  intro () {
    return 'module.exports = function weexFactory (exports, document) {'
  },
  outro () {
    return '}'
  }
}

const aliases = require('./alias')   // alias是对文件真实路径的一个映射
const resolve = p => {
  const base = p.split('/')[0]   // 获取第一个/前的名字
  if (aliases[base]) {  //  判断aliases中是否有这个名字,同时获取它的映射路径
    return path.resolve(aliases[base], p.slice(base.length + 1))   //  p.slice(base.length + 1) 为 ‘/’ 后的名字
  } else {
    return path.resolve(__dirname, '../', p)  //  即dist目录下
  }
} 
 // entry为入口,对应rollup的input  dest为出口,对应rollup的output  format为格式  banner上面有过解释,为版本信息注释
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
  },
  // runtime-only production build (Browser)
  'web-runtime-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.min.js'),
    format: 'umd',
    env: 'production',
    banner
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler production build  (Browser)
  'web-full-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.min.js'),
    format: 'umd',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // Web compiler (CommonJS).
  'web-compiler': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)
  },
  // Web compiler (UMD for in-browser use).
  'web-compiler-browser': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/browser.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'VueTemplateCompiler',
    plugins: [node(), cjs()]
  },
  // Web server renderer (CommonJS).
  'web-server-renderer': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-basic-renderer': {
    entry: resolve('web/entry-server-basic-renderer.js'),
    dest: resolve('packages/vue-server-renderer/basic.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'renderVueComponentToString',
    plugins: [node(), cjs()]
  },
  'web-server-renderer-webpack-server-plugin': {
    entry: resolve('server/webpack-plugin/server.js'),
    dest: resolve('packages/vue-server-renderer/server-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-webpack-client-plugin': {
    entry: resolve('server/webpack-plugin/client.js'),
    dest: resolve('packages/vue-server-renderer/client-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  // Weex runtime factory
  'weex-factory': {
    weex: true,
    entry: resolve('weex/entry-runtime-factory.js'),
    dest: resolve('packages/weex-vue-framework/factory.js'),
    format: 'cjs',
    plugins: [weexFactoryPlugin]
  },
  // Weex runtime framework (CommonJS).
  'weex-framework': {
    weex: true,
    entry: resolve('weex/entry-framework.js'),
    dest: resolve('packages/weex-vue-framework/index.js'),
    format: 'cjs'
  },
  // Weex compiler (CommonJS). Used by Weex's Webpack loader.
  'weex-compiler': {
    weex: true,
    entry: resolve('weex/entry-compiler.js'),
    dest: resolve('packages/weex-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)
  }
}

function genConfig (name) {   ///  主要是通过我们上面的builds对应到rollup格式的一个转换,如把entry转换为input
  const opts = builds[name]
  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) {
    config.plugins.push(replace({
      'process.env.NODE_ENV': JSON.stringify(opts.env)
    }))
  }

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

  return config
}

if (process.env.TARGET) {  //  拿到用户环境信息中的TARGET
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)   
  //  取到builds下的所有索引,然后遍历执行genConfig方法
}
复制代码

下面我们开始最整个打包编译进行梳理

当我们执行build文件时,在引入模块之后,会在./config中拿到builds,builds是什么呢?

在config文件下的尾部有这样一段代码

我们可以清晰的看到该在最后一行对builds拿到了所以的keys进行了一个遍历,执行genConfig方法

通过上图可以看出来,builds是含有一个个文件信息的对象,相当于是对rollup参数的一个映射,其中entry为入口,对应rollup的input,dest为出口,对应rollup的output ,format为格式,banner为版本注释

这个对象通过key传给了genConfig方法,genConfig又是什么?我们看一下:

不难看出,genConfig方法就是一个将builds对象转化为rollup使用的格式的方法,就这样含有rollup信息的格式,吐给了builds文件中的变量builds,也就是我们最开始提到的。

然后我们接着看build文件的代码

接下来通过拿到process.argv[2],进行了一个过滤,过滤掉不需要编译的文件,注释说的已经够详细了,不想在过多解释了。

接下来会执行build()方法,可以看的出来,build方法是一个循环,循环执行buildEntry()方法

buildEntry则是真正的开始执行rollup,对返回的builds进行编译,然后生成对应的文件。

就这样整个编译过程就执行完了,觉得有帮助的,点个小心心吧!

Logo

前往低代码交流专区

更多推荐