Vue:npm run build 构建过程

580 阅读1分钟

这是我参与更文挑战的第7天,活动详情查看: 更文挑战

在执行npm run build之后项目的构建过程

所有代码

const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const terser = require('terser')

if (!fs.existsSync('dist')) {
  fs.mkdirSync('dist')
}
let builds = require('./config').getAllBuilds()

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

build(builds)

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

  next()
}

function buildEntry (config) {
  const output = config.output
  const { file, banner } = output
  const isProd = /(min|prod)\.js$/.test(file)
  return rollup.rollup(config)
    .then(bundle => bundle.generate(output))
    .then(({ output: [{ code }] }) => {
      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)
      }
    })
}

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) {
        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'
}

代码分析

  • if (!fs.existsSync('dist')) {
      fs.mkdirSync('dist')
    }
    
    • 判断是否已经存在了dist目录,不存在就创建一个目录
  • let builds = require('./config').getAllBuilds()
    
    • 获取./config文件中的所有的配置数据,并将其转换成符合rollup适用的格式

    • 配置文件config.js

      • exports.getAllBuilds = () => Object.keys(builds).map(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)
                    }
                }
            }
            
        const builds = {
          'web-runtime-cjs-dev': {
            entry: resolve('web/entry-runtime.js'), // 入口文件
            dest: resolve('dist/vue.runtime.common.dev.js'),// 出口文件
            format: 'cjs', // 是common.js 或是 es6 啥的格式
            env: 'development',
            banner // 文件上面的注释
          },
          .....其他配置数据
          }
        }
        
        • genConfig主要是对builds中的数据进行配置,使得其符合rollup的所需的个格式
  • 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 {
      builds = builds.filter(b => {
        return b.output.file.indexOf('weex') === -1
      })
    }
    
    • processnode的一个进程相关的全局变量,其中process.argv[0]表示启动进程的可执行文件所在的路径,process.argv[1]表示当前执行的js文件路径 ,process.argv[2]表示其他的命令行参数
    • node scripts/build.js "web-runtime-cjs,web-server-renderer" 其中process.argv[2] 就是web-runtime-cjs,web-server-renderer
    • 在执行npm run build的时候其实是在执行node node scripts/build.js ;那么当前获取到的process.argv[2]是空的,就执行else中的内容,对获取到的build数据进行过滤,过滤掉build的输出文件中包含weex的对象
  • build(builds)
    
    function build (builds) {
      let built = 0
      const total = builds.length
      const next = () => {
        buildEntry(builds[built]).then(() => {
          built++
          if (built < total) {
            next()
          }
        }).catch(logError)
      }
    
      next()
    }
    
    
    • 依次对builds中的配置执行buildEntry,这个next方法实现依次执行。
  • function buildEntry (config) {
      const output = config.output
      const { file, banner } = output
      const isProd = /(min|prod)\.js$/.test(file)
      return rollup.rollup(config)
        .then(bundle => bundle.generate(output))
        .then(({ output: [{ code }] }) => {
          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)
          }
        })
    }
    
    • 获取到配置对象输出的文件名称filebanner文件警告
    • 判断文件名称是否需要进行压缩,需要就将文件进行压缩之后在写入文件
    • 否则的话直接将代码写入就可,其中压缩代码主要是使用的zlib.gzip进行