vue-cli-service源码分析

1. vue-cli-service 解决什么问题?

根据官方文档的描述,vue-cli-servicevue-cli 的运行时依赖。它可以:

  1. 基于webpack构建,内置了合理的默认配置;
  2. 可以通过配置文件进行配置webpack;
  3. 可以通过插件扩展 vue-cli-service 的命令

2. 带着问题看源码

  1. vue-cli-service 主要流程是怎样的?
  2. vue-cli-service 帮我们预设了合理的webpack配置,也支持我们在 vue.config.js 里修改webpack配置,是如何做到webpack配置的缝合的?
  3. vue-cli-service servevue-cli-service build 做了什么?
  4. 如何注册一个新的命令?

3. vue-cli-service 主要流程是怎样的?

3.0 目录结构

├─lib
|  ├─options.js
|  ├─PluginAPI.js
|  ├─Service.js
|  ├─commands
|  |    ├─help.js
|  |    ├─inspect.js
|  |    ├─serve.js
|  |    ├─build
|  |    |   ├─demo-lib-js.html
|  |    |   ├─demo-lib.html
|  |    |   ├─demo-wc.html
|  |    |   ├─entry-lib-no-default.js
|  |    |   ├─entry-lib.js
|  |    |   ├─entry-wc.js
|  |    |   ├─formatStats.js
|  |    |   ├─index.js
|  |    |   ├─resolveAppConfig.js
|  |    |   ├─resolveLibConfig.js
|  |    |   ├─resolveWcConfig.js
|  |    |   ├─resolveWcEntry.js
|  |    |   └setPublicPath.js
├─bin
|  └vue-cli-service.js

注意:这里只展示了部分目录和文件,为了直观显示本文需要讲述的文件以及所在的目录。

3.1 packages\@vue\cli-service\bin\vue-cli-service.js

#!/usr/bin/env node

const { semver, error } = require('@vue/cli-shared-utils')
const requiredVersion = require('../package.json').engines.node

if (!semver.satisfies(process.version, requiredVersion, { includePrerelease: true })) {
  error(
    `You are using Node ${process.version}, but vue-cli-service ` +
    `requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
  )
  process.exit(1)
}

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
    // FIXME: --no-module, --no-unsafe-inline, no-clean, etc.
    '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)
})

从入口可以看出,vue-cli-service 的核心是使用了 Service 类,实例化并调用run方法。下面我们看看 Service 类。

3.2 packages\@vue\cli-service\lib\Service.js

// ...
module.exports = class Service {
  constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
    process.VUE_CLI_SERVICE = this
    this.initialized = false
    // 项目的工作路径
    this.context = context
    this.inlineOptions = inlineOptions
    // webpack配置相关
    this.webpackChainFns = []
    // webpack配置相关
    this.webpackRawConfigFns = []
    this.devServerConfigFns = []
    this.commands = {}
    // Folder containing the target package.json for plugins
    this.pkgContext = context
    // package.json containing the plugins
    // 解析package.json
    this.pkg = this.resolvePkg(pkg)
    // If there are inline plugins, they will be used instead of those
    // found in package.json.
    // When useBuiltIn === false, built-in plugins are disabled. This is mostly
    // for testing.
    // 解析插件
    this.plugins = this.resolvePlugins(plugins, useBuiltIn)
    // pluginsToSkip will be populated during run()
    // 需要跳过的插件
    this.pluginsToSkip = new Set()
    // resolve the default mode to use for each command
    // this is provided by plugins as module.exports.defaultModes
    // so we can get the information without actually applying the plugin.
    // 模式,基于每个插件设置的defaultModes来决定
    this.modes = this.plugins.reduce((modes, { apply: { defaultModes } }) => {
      return Object.assign(modes, defaultModes)
    }, {})
  }
// ...

构造函数,初始化变量,下面看看 this.resolvePlugins 初始化插件是如何实现的。


  resolvePlugins (inlinePlugins, useBuiltIn) {
    const idToPlugin = (id, absolutePath) => ({
      id: id.replace(/^.\//, 'built-in:'),
      apply: require(absolutePath || id)
    })

    let plugins
    // 内建插件
    const builtInPlugins = [
      './commands/serve',
      './commands/build',
      './commands/inspect',
      './commands/help',
      // config plugins are order sensitive
      './config/base',
      './config/assets',
      './config/css',
      './config/prod',
      './config/app'
    ].map((id) => idToPlugin(id))
    // 行内插件
    if (inlinePlugins) {
      plugins = useBuiltIn !== false
        ? builtInPlugins.concat(inlinePlugins)
        : inlinePlugins
    } else {
    // 在package.json符合@vue/cli-plugin-xxx规范的插件
      const projectPlugins = Object.keys(this.pkg.devDependencies || {})
        .concat(Object.keys(this.pkg.dependencies || {}))
        .filter(isPlugin)
        .map(id => {
          if (
            this.pkg.optionalDependencies &&
            id in this.pkg.optionalDependencies
          ) {
            let apply = loadModule(id, this.pkgContext)
            if (!apply) {
              warn(`Optional dependency ${id} is not installed.`)
              apply = () => {}
            }

            return { id, apply }
          } else {
            return idToPlugin(id, resolveModule(id, this.pkgContext))
          }
        })

      plugins = builtInPlugins.concat(projectPlugins)
    }
    // 本地插件
    // Local plugins
    if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
      const files = this.pkg.vuePlugins.service
      if (!Array.isArray(files)) {
        throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
      }
      plugins = plugins.concat(files.map(file => ({
        id: `local:${file}`,
        apply: loadModule(`./${file}`, this.pkgContext)
      })))
    }
    debug('vue:plugins')(plugins)

    const orderedPlugins = sortPlugins(plugins)
    debug('vue:plugins-ordered')(orderedPlugins)

    return orderedPlugins
  }
// @vue\cli-shared-utils\lib\pluginResolution.js
const pluginRE = /^(@vue\/|vue-|@[\w-]+(\.)?[\w-]+\/vue-)cli-plugin-/
exports.isPlugin = id => pluginRE.test(id)

可以看到,插件根据引入的位置可以分为四种:内置插件、行内插件、本地插件、在package.json符合 pluginRE 正则的插件。然后用 idToPlugin 封装成 {id: 插件id, apply: 插件方法}。下面先看看 run 方法,再看看内置插件 commands/serve是如何实现的。


  async run (name, args = {}, rawArgv = []) {
    // resolve mode
    // prioritize inline --mode
    // fallback to resolved default modes from plugins or development if --watch is defined
    const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

    // --skip-plugins arg may have plugins that should be skipped during init()
    // 根据入参来决定跳过的插件
    this.setPluginsToSkip(args)

    // load env variables, load user config, apply plugins
    // 初始化:加载环境变量、加载用户配置,执行插件
    await this.init(mode)

    args._ = args._ || []
    // 根据name来筛选命令
    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)
  }

run 方法,主要做了以下事情:通过参数设置跳过的插件,通过 this.init 来初始化(加载环境变量、加载用户配置,执行插件),执行命令方法。下面看看 this.init 的实现


init (mode = process.env.VUE_CLI_MODE) {
    if (this.initialized) {
      return
    }
    this.initialized = true
    this.mode = mode

    // load mode .env
    if (mode) {
      this.loadEnv(mode)
    }
    // load base .env
    this.loadEnv()

    // load user config
    const userOptions = this.loadUserOptions()
    const loadedCallback = (loadedUserOptions) => {
      this.projectOptions = defaultsDeep(loadedUserOptions, defaults())

      debug('vue:project-config')(this.projectOptions)

      // apply plugins.
      this.plugins.forEach(({ id, apply }) => {
        if (this.pluginsToSkip.has(id)) return
        apply(new PluginAPI(id, this), this.projectOptions)
      })

      // apply webpack configs from project config file
      if (this.projectOptions.chainWebpack) {
        this.webpackChainFns.push(this.projectOptions.chainWebpack)
      }
      if (this.projectOptions.configureWebpack) {
        this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
      }
    }

    if (isPromise(userOptions)) {
      return userOptions.then(loadedCallback)
    } else {
      return loadedCallback(userOptions)
    }
  }

可以看到 this.init 加载环境变量、加载用户配置,执行插件。其中在执行插件的时候,api传入了 PluginAPI 实例,它是插件的一些api,下面先贴一下代码,后面在讲插件的时候会用到。

3.3 packages\@vue\cli-service\lib\PluginAPI.js

const path = require('path')
const hash = require('hash-sum')
const { semver, matchesPluginId } = require('@vue/cli-shared-utils')

// Note: if a plugin-registered command needs to run in a specific default mode,
// the plugin needs to expose it via `module.exports.defaultModes` in the form
// of { [commandName]: mode }. This is because the command mode needs to be
// known and applied before loading user options / applying plugins.

class PluginAPI {
  /**
   * @param {string} id - Id of the plugin.
   * @param {Service} service - A vue-cli-service instance.
   */
  constructor (id, service) {
    this.id = id
    this.service = service
  }

  get version () {
    return require('../package.json').version
  }

  assertVersion (range) {
    if (typeof range === 'number') {
      if (!Number.isInteger(range)) {
        throw new Error('Expected string or integer value.')
      }
      range = `^${range}.0.0-0`
    }
    if (typeof range !== 'string') {
      throw new Error('Expected string or integer value.')
    }

    if (semver.satisfies(this.version, range, { includePrerelease: true })) return

    throw new Error(
      `Require @vue/cli-service "${range}", but was loaded with "${this.version}".`
    )
  }

  /**
   * Current working directory.
   */
  getCwd () {
    return this.service.context
  }

  /**
   * Resolve path for a project.
   *
   * @param {string} _path - Relative path from project root
   * @return {string} The resolved absolute path.
   */
  resolve (_path) {
    return path.resolve(this.service.context, _path)
  }

  /**
   * Check if the project has a given plugin.
   *
   * @param {string} id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix
   * @return {boolean}
   */
  hasPlugin (id) {
    return this.service.plugins.some(p => matchesPluginId(id, p.id))
  }

  /**
   * 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 || {} }
  }

  /**
   * Register a function that will receive a chainable webpack config
   * the function is lazy and won't be called until `resolveWebpackConfig` is
   * called
   *
   * @param {function} fn
   */
  chainWebpack (fn) {
    this.service.webpackChainFns.push(fn)
  }

  /**
   * Register
   * - a webpack configuration object that will be merged into the config
   * OR
   * - a function that will receive the raw webpack config.
   *   the function can either mutate the config directly or return an object
   *   that will be merged into the config.
   *
   * @param {object | function} fn
   */
  configureWebpack (fn) {
    this.service.webpackRawConfigFns.push(fn)
  }

  /**
   * Register a dev serve config function. It will receive the express `app`
   * instance of the dev server.
   *
   * @param {function} fn
   */
  configureDevServer (fn) {
    this.service.devServerConfigFns.push(fn)
  }

  /**
   * Resolve the final raw webpack config, that will be passed to webpack.
   *
   * @param {ChainableWebpackConfig} [chainableConfig]
   * @return {object} Raw webpack config.
   */
  resolveWebpackConfig (chainableConfig) {
    return this.service.resolveWebpackConfig(chainableConfig)
  }

  /**
   * Resolve an intermediate chainable webpack config instance, which can be
   * further tweaked before generating the final raw webpack config.
   * You can call this multiple times to generate different branches of the
   * base webpack config.
   * See https://github.com/mozilla-neutrino/webpack-chain
   *
   * @return {ChainableWebpackConfig}
   */
  resolveChainableWebpackConfig () {
    return this.service.resolveChainableWebpackConfig()
  }

  /**
   * Generate a cache identifier from a number of variables
   */
  genCacheConfig (id, partialIdentifier, configFiles = []) {
    const fs = require('fs')
    const cacheDirectory = this.resolve(`node_modules/.cache/${id}`)

    // replace \r\n to \n generate consistent hash
    const fmtFunc = conf => {
      if (typeof conf === 'function') {
        return conf.toString().replace(/\r\n?/g, '\n')
      }
      return conf
    }

    const variables = {
      partialIdentifier,
      'cli-service': require('../package.json').version,
      env: process.env.NODE_ENV,
      test: !!process.env.VUE_CLI_TEST,
      config: [
        fmtFunc(this.service.projectOptions.chainWebpack),
        fmtFunc(this.service.projectOptions.configureWebpack)
      ]
    }

    try {
      variables['cache-loader'] = require('cache-loader/package.json').version
    } catch (e) {
      // cache-loader is only intended to be used for webpack 4
    }

    if (!Array.isArray(configFiles)) {
      configFiles = [configFiles]
    }
    configFiles = configFiles.concat([
      'package-lock.json',
      'yarn.lock',
      'pnpm-lock.yaml'
    ])

    const readConfig = file => {
      const absolutePath = this.resolve(file)
      if (!fs.existsSync(absolutePath)) {
        return
      }

      if (absolutePath.endsWith('.js')) {
        // should evaluate config scripts to reflect environment variable changes
        try {
          return JSON.stringify(require(absolutePath))
        } catch (e) {
          return fs.readFileSync(absolutePath, 'utf-8')
        }
      } else {
        return fs.readFileSync(absolutePath, 'utf-8')
      }
    }

    variables.configFiles = configFiles.map(file => {
      const content = readConfig(file)
      return content && content.replace(/\r\n?/g, '\n')
    })

    const cacheIdentifier = hash(variables)
    return { cacheDirectory, cacheIdentifier }
  }
}

module.exports = PluginAPI

4. vue-cli-service servevue-cli-service build 做了什么?

4.1 内置插件serve做了什么?

const {
    info,
    error,
    hasProjectYarn,
    hasProjectPnpm,
    IpcMessenger
  } = require('@vue/cli-shared-utils')
  
  const defaults = {
    host: '0.0.0.0',
    port: 8080,
    https: false
  }
  
  /** @type {import('@vue/cli-service').ServicePlugin} */
  module.exports = (api, options) => {
    api.registerCommand('serve', {
      description: 'start development server',
      usage: 'vue-cli-service serve [options] [entry]',
      options: {
        '--open': `open browser on server start`,
        '--copy': `copy url to clipboard on server start`,
        '--stdin': `close when stdin ends`,
        '--mode': `specify env mode (default: development)`,
        '--host': `specify host (default: ${defaults.host})`,
        '--port': `specify port (default: ${defaults.port})`,
        '--https': `use https (default: ${defaults.https})`,
        '--public': `specify the public network URL for the HMR client`,
        '--skip-plugins': `comma-separated list of plugin names to skip for this run`
      }
    }, async function serve (args) {
      info('Starting development server...')
      // ...
      // configs that only matters for dev server
      api.chainWebpack(webpackConfig => {
        // ...
      })
      // resolve webpack config
      const webpackConfig = api.resolveWebpackConfig()
      // check for common config errors
      validateWebpackConfig(webpackConfig, api, options)
      // ...
      // create compiler
      const compiler = webpack(webpackConfig)
  
      // handle compiler error
      compiler.hooks.failed.tap('vue-cli-service serve', msg => {
        error(msg)
        process.exit(1)
      })
  
      // create server
      const server = new WebpackDevServer(Object.assign({
        // ...
      }), compiler)
      // ...
      return new Promise((resolve, reject) => {
        // ...  
            resolve({
              server,
              url: localUrlForBrowser
            })
        // ...
        server.start().catch(err => reject(err))
      })
    })
  }
  // ...
  module.exports.defaultModes = {
    serve: 'development'
  }

调用 PluginAPIregisterCommand 方法注册命令,传了三个参数:命令名称、命令的用法配置、命令函数。注册命令其实就是在 service实例 commands 变量添加一个字段,以 {fn: 命令方法, otps: 命令使用方法选项} 的方式存起来

// packages\@vue\cli-service\lib\PluginAPI.js

  chainWebpack (fn) {
    this.service.webpackChainFns.push(fn)
  }
  resolveWebpackConfig (chainableConfig) {
    return this.service.resolveWebpackConfig(chainableConfig)
  }
// packages\@vue\cli-service\lib\Service.js

  resolveChainableWebpackConfig () {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
  }

  resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
    if (!this.initialized) {
      throw new Error('Service must call init() before calling resolveWebpackConfig().')
    }
    // get raw config
    let config = chainableConfig.toConfig()
    const original = config
    // apply raw config fns
    this.webpackRawConfigFns.forEach(fn => {
      if (typeof fn === 'function') {
        // function with optional return value
        const res = fn(config)
        if (res) config = merge(config, res)
      } else if (fn) {
        // merge literal values
        config = merge(config, fn)
      }
    })

    // #2206 If config is merged by merge-webpack, it discards the __ruleNames
    // information injected by webpack-chain. Restore the info so that
    // vue inspect works properly.
    if (config !== original) {
      cloneRuleNames(
        config.module && config.module.rules,
        original.module && original.module.rules
      )
    }

    // check if the user has manually mutated output.publicPath
    const target = process.env.VUE_CLI_BUILD_TARGET
    if (
      !process.env.VUE_CLI_TEST &&
      (target && target !== 'app') &&
      config.output.publicPath !== this.projectOptions.publicPath
    ) {
      throw new Error(
        `Do not modify webpack output.publicPath directly. ` +
        `Use the "publicPath" option in vue.config.js instead.`
      )
    }

    if (
      !process.env.VUE_CLI_ENTRY_FILES &&
      typeof config.entry !== 'function'
    ) {
      let entryFiles
      if (typeof config.entry === 'string') {
        entryFiles = [config.entry]
      } else if (Array.isArray(config.entry)) {
        entryFiles = config.entry
      } else {
        entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => {
          return allEntries.concat(curr)
        }, [])
      }

      entryFiles = entryFiles.map(file => path.resolve(this.context, file))
      process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles)
    }

    return config
  }

serve的命令方法,其实就做了两件事:

  1. 通过 api.chainWebpack 注入内置的webpack命令,之后通过 api.resolveWebpackConfig 来解析webpack配置,并通过 validateWebpackConfig 方法来验证webpack配置格式是否正确,并执行 webpack
  2. 创建 WebpackDevServer

4.2 如何实现webpack配置的缝合?

// packages\@vue\cli-service\lib\PluginAPI.js

  registerCommand (name, opts, fn) {
    if (typeof opts === 'function') {
      fn = opts
      opts = null
    }
    this.service.commands[name] = { fn, opts: opts || {} }
  }
// packages\@vue\cli-service\lib\Service.js

  resolveChainableWebpackConfig () {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
  }
  resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
    if (!this.initialized) {
      throw new Error('Service must call init() before calling resolveWebpackConfig().')
    }
    // get raw config
    let config = chainableConfig.toConfig()
    const original = config
    // apply raw config fns
    this.webpackRawConfigFns.forEach(fn => {
      if (typeof fn === 'function') {
        // function with optional return value
        const res = fn(config)
        if (res) config = merge(config, res)
      } else if (fn) {
        // merge literal values
        config = merge(config, fn)
      }
    })
    // ...
    return config
  }

可以看出,service 就是通过 webpackChainFnswebpackRawConfigFns 记录webpack配置,前者通过 webpack-chain 来合并配置,而后者通过 webpack-merge 来合并配置。
此时我们回头看 Serviceinit,加载用户配置:

// packages\@vue\cli-service\lib\Service.js

  init (mode = process.env.VUE_CLI_MODE) {
    // ...
    // load user config
    // 加载用户配置,就是vue.config.js
    const userOptions = this.loadUserOptions()
    const loadedCallback = (loadedUserOptions) => {
      this.projectOptions = defaultsDeep(loadedUserOptions, defaults())
      // apply plugins.
      // 注册插件,放到this.service.commands下 
      this.plugins.forEach(({ id, apply }) => {
        if (this.pluginsToSkip.has(id)) return
        apply(new PluginAPI(id, this), this.projectOptions)
      })
      // apply webpack configs from project config file
      // 把用户的webpack配置保存起来
      if (this.projectOptions.chainWebpack) {
        this.webpackChainFns.push(this.projectOptions.chainWebpack)
      }
      if (this.projectOptions.configureWebpack) {
        this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
      }
    }
    if (isPromise(userOptions)) {
      return userOptions.then(loadedCallback)
    } else {
      return loadedCallback(userOptions)
    }
  }

也就是说,初始化的时候,先加载本地配置,加载完了,先插件的注册,用户的webpack命令把它们分别存到 webpackChainFnswebpackRawConfigFns,然后执行命令的时候,再把内置的命令push进去。

// packages\@vue\cli-service\lib\Service.js
      // apply plugins.
      this.plugins.forEach(({ id, apply }) => {
        if (this.pluginsToSkip.has(id)) return
        apply(new PluginAPI(id, this), this.projectOptions)
      })

      // apply webpack configs from project config file
      if (this.projectOptions.chainWebpack) {
        this.webpackChainFns.push(this.projectOptions.chainWebpack)
      }
      if (this.projectOptions.configureWebpack) {
        this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
      }

4.3 serve内置webpack配置了什么?

 // configs that only matters for dev server
    api.chainWebpack(webpackConfig => {
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
        if (!webpackConfig.get('devtool')) {
          webpackConfig
            .devtool('eval-cheap-module-source-map')
        }

        // https://github.com/webpack/webpack/issues/6642
        // https://github.com/vuejs/vue-cli/issues/3539
        webpackConfig
          .output
            .globalObject(`(typeof self !== 'undefined' ? self : this)`)

        if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) {
          // the default progress plugin won't show progress due to infrastructreLogging.level
          webpackConfig
            .plugin('progress')
            .use(require('progress-webpack-plugin'))
        }
      }
    })

    // resolve webpack config
    const webpackConfig = api.resolveWebpackConfig()

    // check for common config errors
    validateWebpackConfig(webpackConfig, api, options)

    // load user devServer options with higher priority than devServer
    // in webpack config
    const projectDevServerOptions = Object.assign(
      webpackConfig.devServer || {},
      options.devServer
    )

    // expose advanced stats
    if (args.dashboard) {
      const DashboardPlugin = require('../webpack/DashboardPlugin')
      webpackConfig.plugins.push(new DashboardPlugin({
        type: 'serve'
      }))
    }

    // entry arg
    const entry = args._[0]
    if (entry) {
      webpackConfig.entry = {
        app: api.resolve(entry)
      }
    }

serve命令webpack配置了:

  1. 默认配置sourcemap:eval-cheap-module-source-map
  2. 配置output的globalObject为self/this,一般用途是作为library输出,尤其是 umd 标准,这个全局对象在node.js/浏览器上需要指定为 this,类似web的目标则需要指定为 self。所以可以通过这样去赋值:(typeof self !== 'undefined' ? self : this)
  3. 使用插件 progress-webpack-plugin 显示进度
  4. 如果参数带了 dashboard,则加载插件 DashboardPlugin
  5. 配置entry

剩下的就是配置 webpackDevServer,就不展开了。同理,我们可以看看 build 命令内置了哪些webpack配置

4.4 build内置webpack配置了什么?

// packages\@vue\cli-service\lib\commands\build\index.js

const defaults = {
  clean: true,
  target: 'app',
  module: true,
  formats: 'commonjs,umd,umd-min'
}

const buildModes = {
  lib: 'library',
  wc: 'web component',
  'wc-async': 'web component (async)'
}

const modifyConfig = (config, fn) => {
  if (Array.isArray(config)) {
    config.forEach(c => fn(c))
  } else {
    fn(config)
  }
}

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})`,
      '--no-module': `build app without generating 
                    
                    

你可能感兴趣的:(vue.jsvue-cli4)