本系列一共6篇文章
- Vue CLI 源码探索 [开篇] 整体介绍下 Vue CLI
- Vue CLI 源码探索 [一] @vue/cli 包的概览,已经第一个命令 vue create
- Vue CLI 源码探索 [二] 涉及三个命令(vue add/invoke/inspect)
- Vue CLI 源码探索 [三] 和想象中不太一样的 vue serve/build
- Vue CLI 源码探索 [四] 其他命令(vue init/outdated/upgrade/migrate/info//--help)
- Vue CLI 源码探索 [五] 分析下 Vue CLI 中测试相关的内容
- Vue CLI 源码探索 [六] 探索下 Vue CLI 的插件机制,内容较多,请慢慢看。涉及如下插件(@vue/cli-plugin-vuex/router/babel/typescript/eslint)
下面正文开始啦 ^_^
vue serve [entry]
serve a .js or .vue file in development mode with zero config
在开发模式下零配置的调试一个 .js 或者 .vue 文件
调试配置
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/packages/@vue/cli/bin/vue",
// 对应着在 test 目录下新建了 App.vue 文件
"args": ["serve", "packages/test/App.vue"]
}
]
}
源码探索
命令注册
program
.command('serve [entry]')
.description('serve a .js or .vue file in development mode with zero config')
.option('-o, --open', 'Open browser')
.option('-c, --copy', 'Copy local url to clipboard')
.option('-p, --port <port>', 'Port used by the server (default: 8080 or next available port)')
.action((entry, cmd) => {
// 载入了 @vue/cli-service-global 这个包,并调用 serve 方法
loadCommand('serve', '@vue/cli-service-global').serve(entry, cleanArgs(cmd))
})
@vue/cli-service-global
serve 方法就两行,加载入口,然后实例化服务,执行 run('serve', args) 方法
exports.serve = (_entry, args) => {
const entry = resolveEntry(_entry, 'serve')
createService(entry).run('serve', args)
}
看下创建服务的代码
function createService (entry, asLib) {
// 实例化 Service
return new Service(context, {
projectOptions: {
compiler: true,
lintOnSave: 'default'
},
plugins: [
babelPlugin,
eslintPlugin,
globalConfigPlugin(context, entry, asLib)
]
})
}
被实例化的 Serice 是 const Service = require('@vue/cli-service') 引入进来的,所以后面最终做事的还是 @vue/cli-servcie 类, @vue/cli-service 的分析在 vue inspect 中已经提到了,不赘述。
@vue/cli-service/lib/commands/serve.js
我们这里着重看下 serve 命令回调函数中的逻辑,
const url = require('url')
const { chalk } = require('@vue/cli-shared-utils')
// 这里载入了 webpack
const webpack = require('webpack')
// 这里载入了 webpack-dev-server
const WebpackDevServer = require('webpack-dev-server')
// 这个portfinder 解决了多个项目需要手动修改 port 的痛点
const portfinder = require('portfinder')
const prepareURLs = require('../util/prepareURLs')
const prepareProxy = require('../util/prepareProxy')
const launchEditorMiddleware = require('launch-editor-middleware')
const validateWebpackConfig = require('../util/validateWebpackConfig')
const isAbsoluteUrl = require('../util/isAbsoluteUrl')
// 这里设置了webpack入口
const entry = args._[0]
if (entry) {
webpackConfig.entry = {
app: api.resolve(entry)
}
}
// 这里通过 portfinder 获取了可用端口
const port = await portfinder.getPortPromise()
// 这里创建了 webpack 实例
const compiler = webpack(webpackConfig)
// 实例创建了 WebpackDevServer 实例
const server = new WebpackDevServer(compiler, Object.assign({
logLevel: 'silent',
clientLogLevel: 'silent',
historyApiFallback: {
disableDotRule: true,
rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages)
},
contentBase: api.resolve('public'),
watchContentBase: !isProduction,
hot: !isProduction,
injectClient: false,
compress: isProduction,
publicPath: options.publicPath,
overlay: isProduction // TODO disable this
? false
: { warnings: false, errors: true }
}, projectDevServerOptions, {
https: useHttps,
proxy: proxySettings,
// eslint-disable-next-line no-shadow
before (app, server) {
// launch editor support.
// this works with vue-devtools & @vue/cli-overlay
app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
`To specify an editor, specify the EDITOR env variable or ` +
`add "editor" field to your Vue project config.\n`
)))
// allow other plugins to register middlewares, e.g. PWA
api.service.devServerConfigFns.forEach(fn => fn(app, server))
// apply in project middlewares
projectDevServerOptions.before && projectDevServerOptions.before(app, server)
},
// avoid opening browser
open: false
}))
// todo 这里的这段代码在 webpack 相关阅读完成后再回头看
compiler.hooks.done.tap('vue-cli-service serve', stats => {
小插曲
调试的过程中报这个错误,官方已经将该问题修复了
Module build failed (from ./node_modules/eslint-loader/index.js):
Error: BaseConfig:
Configuration for rule "no-debugger" is invalid:
Severity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '"process.env.NODE_ENV === "production" ? "error" : "off""').
at validateRuleOptions (/Users/didi/Documents/project/llccing-demo/vue-cli/node_modules/eslint/lib/shared/
...
vue build [entry]
build a .js or .vue file in production mode with zero config
在生产模式下零配置的构建一个 .js 或者 .vue 文件
源码探索
命令注册
program
.command('build [entry]')
.description('build a .js or .vue file in production mode with zero config')
.option('-t, --target <target>', 'Build target (app | lib | wc | wc-async, default: app)')
.option('-n, --name <name>', 'name for lib or web-component mode (default: entry filename)')
.option('-d, --dest <dir>', 'output directory (default: dist)')
.action((entry, cmd) => {
// 载入 @vue/cli-service-global,执行 build 方法
loadCommand('build', '@vue/cli-service-global').build(entry, cleanArgs(cmd))
})
@vue/cli-service-global
build 方法的逻辑很简单,解析入口,创建 Service 实例再执行 run 方法。
exports.build = (_entry, args) => {
const entry = resolveEntry(_entry, 'build')
const asLib = args.target && args.target !== 'app'
if (asLib) {
args.entry = entry
}
return createService(entry, asLib).run('build', args)
}
@vue/cli-service/lib/commands/build/index.js
Service 实例的 run 方法传入 build 参数,执行的是 build 命令。
命令注册
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})`,
...
}
}, () => {
// 这里主要是处理了默认配置,然后会走到 build 函数
await build(Object.assign({}, args, {
modernBuild: false,
keepAlive: true
}), api, options)
}
)
我们看下 build 函数
// 这里载入了 webpack 配置
webpackConfig = require('./resolveAppConfig')(api, args, options)
// report 选项用到了 webpack-bundle-analyzer 插件,便于查看项目的资源依赖及大小
if (args.report || args['report-json']) {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
modifyConfig(webpackConfig, config => {
const bundleName = args.target !== 'app'
? config.output.filename.replace(/\.js$/, '-')
: isLegacyBuild ? 'legacy-' : ''
config.plugins.push(new BundleAnalyzerPlugin({
logLevel: 'warn',
openAnalyzer: false,
analyzerMode: args.report ? 'static' : 'disabled',
reportFilename: `${bundleName}report.html`,
statsFilename: `${bundleName}report.json`,
generateStatsFile: !!args['report-json']
}))
})
}
// 这里执行了webpack的构建
webpack(webpackConfig, (err, stats) => {
感谢阅读
感谢你阅读到这里,翻译的不好的地方,还请指点。希望我的内容能让你受用,再次感谢。by llccing 千里