Vue.js脚手架@vue-cli是如何打包的

首先,要有一个使用@vue-cli打包的Vue.js项目,参考:https://blog.csdn.net/Z_ammo/article/details/103915494

根据package.json的设置:

// 截取package.json
"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  }

打包使用的是vue-cli-service,于是我们找到node_modules/@vue/cli-service(以下操作均在该路径下

打开主文件:/bin/vue-cli-service.js

// 部分截取
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())

const rawArgv = process.argv.slice(2)
// 获取指令参数
const args = require('minimist')(rawArgv, {
  boolean: [
    // build
    'modern',
    'report',
    'report-json',
    'inline-vue',
    'watch',
    // serve
    'open',
    'copy',
    'https',
    // inspect
    'verbose'
  ]
})
// 获取指令
const command = args._[0]

service.run(command, args, rawArgv).catch(err => {
  error(err)
  process.exit(1)
})

首先解释一下:minimist是一个用于处理控制台调用node时的各种参数的模块,其具体使用细节可以参考:https://blog.csdn.net/Z_ammo/article/details/103930392

可以看到还调用了另一个模块Service的run方法

打开lib/Service.js

// Service.run()方法截取
async run (name, args = {}, rawArgv = []) {
  const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

  this.setPluginsToSkip(args)

  this.init(mode)

  args._ = args._ || []
  let command = this.commands[name]
  if (!command && name) {
    error(`command "${name}" does not exist.`)
    process.exit(1)
  }
  if (!command || args.help || args.h) {
    command = this.commands.help
  } else {
    args._.shift() // remove command itself
    rawArgv.shift()
  }
  const { fn } = command
  return fn(args, rawArgv)
}

可以看到,这里调用了command对象的fn方法,并将args和rawArgv传给了它。

而command是this.commands对象中的其中一个元素,元素名为name,我们这里讨论的是“打包”,所以运行的指令为:

npm run build

所以

name = 'build';

所以command是this.commands中的'build'方法。那么this.commands又是什么呢?

它的定义在/lib/PluginAPI.js中

  /**
   * Register a command that will become available as `vue-cli-service [name]`.
   *
   * @param {string} name
   * @param {object} [opts]
   *   {
   *     description: string,
   *     usage: string,
   *     options: { [string]: string }
   *   }
   * @param {function} fn
   *   (args: { [string]: string }, rawArgs: string[]) => ?Promise
   */
  registerCommand (name, opts, fn) {
    if (typeof opts === 'function') {
      fn = opts
      opts = null
    }
    this.service.commands[name] = { fn, opts: opts || {}}
  }

这里写了一个注册函数,用来注册指令名称,我们常用的build,serve,lint就是通过这个方法注册的。

而实际注册指令的文件存储在/lib/commands中,例如'build'文件的存储位置就是/lib/commands/build/index.js

module.exports = (api, options) => {
  api.registerCommand('build', {
    description: 'build for production',
    usage: 'vue-cli-service build [options] [entry|pattern]',
    options: {
      '--mode': `specify env mode (default: production)`,
      '--dest': `specify output directory (default: ${options.outputDir})`,
      '--modern': `build app targeting modern browsers with auto fallback`,
      '--no-unsafe-inline': `build app without introducing inline scripts`,
      '--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
      '--inline-vue': 'include the Vue module in the final bundle of library or web component target',
      '--formats': `list of output formats for library builds (default: ${defaults.formats})`,
      '--name': `name for lib or web-component mode (default: "name" in package.json or entry filename)`,
      '--filename': `file name for output, only usable for 'lib' target (default: value of --name)`,
      '--no-clean': `do not remove the dist directory before building the project`,
      '--report': `generate report.html to help analyze bundle content`,
      '--report-json': 'generate report.json to help analyze bundle content',
      '--skip-plugins': `comma-separated list of plugin names to skip for this run`,
      '--watch': `watch for changes`
    }
  }, async (args, rawArgs) => {
    /* ... */
  })
}

在这里我们可以看到所有的指令参数,而下面的程序是真正调用webpack的打包方法。

async function build (args, api, options) {
  const fs = require('fs-extra')
  const path = require('path')
  const webpack = require('webpack') // 引入webpack
  const { chalk } = require('@vue/cli-shared-utils')
  const formatStats = require('./formatStats')
  const validateWebpackConfig = require('../../util/validateWebpackConfig')
  const {
    log,
    done,
    info,
    logWithSpinner,
    stopSpinner
  } = require('@vue/cli-shared-utils')

  /* ... */

  // 设置webpack参数
  if (args.target === 'lib') { 
    webpackConfig = require('./resolveLibConfig')(api, args, options)
  } else if (
    args.target === 'wc' ||
    args.target === 'wc-async'
  ) {
    webpackConfig = require('./resolveWcConfig')(api, args, options)
  } else {
    webpackConfig = require('./resolveAppConfig')(api, args, options)
  }

  /* ... */
}

以app打包为例,可以看到具体的webpack设置是在/lib/commands/build/resolveAppConfig.js中

  // 部分截取
  if (args.modern) {
    const ModernModePlugin = require('../../webpack/ModernModePlugin')
    if (!args.modernBuild) {
      // Inject plugin to extract build stats and write to disk
      config
        .plugin('modern-mode-legacy')
        .use(ModernModePlugin, [{
          targetDir,
          isModernBuild: false,
          unsafeInline: args['unsafe-inline']
        }])
    } else {
      // Inject plugin to read non-modern build stats and inject HTML
      config
        .plugin('modern-mode-modern')
        .use(ModernModePlugin, [{
          targetDir,
          isModernBuild: true,
          unsafeInline: args['unsafe-inline'],
          // as we may generate an addition file asset (if `no-unsafe-inline` specified)
          // we need to provide the correct directory for that file to place in
          jsDirectory: require('../../util/getAssetPath')(options, 'js')
        }])
    }

生成webpack设置是通过webpack下的ModernModePlugin实现的。

查看webpack模块下的ModernModePlugin.js(注意不在@vue文件夹内)

  // 这里截取legacy模式作为例子,此外还有modern-bundle模式
  applyLegacy (compiler) {
    const ID = `vue-cli-legacy-bundle`
    compiler.hooks.compilation.tap(ID, compilation => {
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
        // 获取参数,写入文件
        await fs.ensureDir(this.targetDir)
        const htmlName = path.basename(data.plugin.options.filename)
        // 注意子路径下的输出文件
        const htmlPath = path.dirname(data.plugin.options.filename)
        const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
        await fs.mkdirp(path.dirname(tempFilename))
        await fs.writeFile(tempFilename, JSON.stringify(data.body))
        cb()
      })
    })
  }

可以看到,在打包的时候,@vue-cli会先生成一个webpack配置文件,然后进行打包。这也就解释了为什么通常情况下@vue-cli脚手架生成的项目里面看不到webpack配置文件。

你可能感兴趣的:(Vue.js,webpack,JavaScript)