首先,要有一个使用@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配置文件。