从vuecli3学习webpack记录(一)vue-cli-serve机制
从vuecli3学习webpack记录(一)vue-cli-serve机制
最近看了看vuecli3,把自己的学习记录下来。
首先看入口npm run dev
即是vue-cli-service serve
,之所以能运行vue-cli-service
命令,就是因为cli3为我们安装了vue-cli-service
(执行的是npm install --save-dev vue-cli-service
),这样可以在node_modules/.bin文件夹里面找到vue-cli-service.js,上面实际上执行的就是node_modules/.bin/service serve
看看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',
'watch',
// serve
'open',
'copy',
'https',
// inspect
'verbose'
]
})
const command = args._[0]
service.run(command, args, rawArgv).catch(err => {
error(err)
process.exit(1)
})
可以看出实际运行的就是run方法 这里的Service就是在node_modules/@vue/cli-service/lib/Service.js文件。 Service是一个class。 先看构造函数
// lib/Service.js
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
process.VUE_CLI_SERVICE = this
this.initialized = false
this.context = context
this.inlineOptions = inlineOptions
this.webpackChainFns = []
this.webpackRawConfigFns = []
this.devServerConfigFns = []
this.commands = {}
// Folder containing the target package.json for plugins
this.pkgContext = context
// package.json containing the plugins
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)
// 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.
this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
return Object.assign(modes, defaultModes)
}, {})
}
然后看run方法
// lib/Service.js 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]) // load env variables, load user config, apply plugins 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) }
可以看出最终执行语句是(后面还会提到的)
const { fn } = command
return fn(args, rawArgv)
而command的fn是是怎么来的呢?这就只能追溯到构造函数了。
主要是看这一句this.plugins = this.resolvePlugins(plugins, useBuiltIn)
// lib/Service.js
resolvePlugins (inlinePlugins, useBuiltIn) {
const idToPlugin = id => ({
id: id.replace(/^.\//, 'built-in:'),
apply: require(id)
})
let plugins
const builtInPlugins = [
'./commands/serve',
'./commands/build',
'./commands/inspect',
'./commands/help',
// config plugins are order sensitive
'./config/base',
'./config/css',
'./config/dev',
'./config/prod',
'./config/app'
].map(idToPlugin)
if (inlinePlugins) {
plugins = useBuiltIn !== false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
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 = () => {}
try {
apply = require(id)
} catch (e) {
warn(`Optional dependency ${id} is not installed.`)
}
return { id, apply }
} else {
return idToPlugin(id)
}
})
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)
})))
}
return plugins
}
我们可以看到这里的builtInPlugins里面的分别对应着commands和config文件夹里面的对应的文件。 根据idToPlugin方法和return { id, apply },我可以知道最后得到的plugins都是形如{ id, apply }的对象了。 比如:
// lib/Service.js
{
id: 'built-in:commands/serve'
apply: require('./commands/serve')
}
调用apply方法就会加载对应文件模块 在上面提到的run方法里面有调用init方法。 init方法如下
// lib/Service.js
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()
this.projectOptions = defaultsDeep(userOptions, defaults())
debug('vue:project-config')(this.projectOptions)
// apply plugins.
this.plugins.forEach(({ id, apply }) => {
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)
}
这里有
// lib/Service.js
this.plugins.forEach(({ id, apply }) => {
apply(new PluginAPI(id, this), this.projectOptions)
})
我们先看apply,就是执行对应的文件,这里我们以常用的./commands/serve.js为例。
// lib/commands/serve.js 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
, '--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
} }, async function serve (args) { ... const compiler = webpack(webpackConfig) const server = new WebpackDevServer(compiler, Object.assign({...}, {...}); ... return new Promise((resolve, reject) => { ... compiler.hooks.done.tap('vue-cli-service serve', stats => {..}) server.listen(port, host, err => { if (err) { reject(err) } }) }) } }
我们主要看看第一个参数api,它就是上面的PluginAPI的实例new PluginAPI(id, this)(这里的this就是service哦):
// /lib/PluginAPI.js
constructor (id, service) {
this.id = id
this.service = service
}
registerCommand (name, opts, fn) {
if (typeof opts === 'function') {
fn = opts
opts = null
}
this.service.commands[name] = { fn, opts: opts || {}}
}
chainWebpack (fn) {
this.service.webpackChainFns.push(fn)
}
这里的registerCommand方法使得我们平时用到的vue-cli-service serve的serve命令得以注册。
vue cli3中在commands文件夹里面的是调用
api.registerCommand
方法,在config文件夹里面的(teserOptions.js和html除外)是调用api.chainWebpack
方法,该方法会将传得的参数(该参数是一个方法)push到this.service.webpackChainFns
数组。
特别提醒下:这里的this.serivce.commands就是Service.js里面的this.commands,不是吗?
之后我们看到service里面的每个commands是形如{ fn, opts: opts || {}}的对象。 对上面的fn有印象吧,一开始我们提到
// /lib/Service.js
const { fn } = command
return fn(args, rawArgv)
上面的api.registerCommand
的第三个参数(函数)就是fn
了。实际我们执行的就是它了。vue-cli-service serve返回的是一个promise
。并且在
compiler.hooks.done.tap('vue-cli-service serve', stats => { ...})
里面resolve该promise。
关于webpack的钩子下篇分析。
- 分类:
- Web前端
相关文章
前端框架vue+wordpress做后端
目前正在利用闲暇时捯饬一下自己的博客,毕竟这么久没有维护了,wordpress是可以用restful API来获取数据的,决定前端用vue实现,目前正在尝试中,今天做了其中很小的一部分,就是博客目录 阅读更多…
Vue在chrome44偶现点击子元素事件无法冒泡
公司的一个项目大致是这样的:一个左侧列表,点击左侧列表的文章标题,右侧展开该文章对应的内容的。 现在的问题出现在极少部分客户有时左侧的标题,无法打开对应的右侧的内容,给人的改进就是‘卡’、点不动、点 阅读更多…
命令式组件Message、Dialog的主流写法分析
这里还是以element-ui为例,那我们就看看里面的Message。 它的dom结构什么的就写在node-modules/element-ui/packages/notification/src/ 阅读更多…
vue多语言的解决方案不只是 vue-i18n,前端+后端完整解决方案
网上搜很多vue多语言的,一般都是介绍vue-i18n怎么使用,当然这是不错的,但是我们如果只是讲这个的话,只是解决了静态文字的多语言化。 这一部分我们也简单讲一下 npm install 阅读更多…
vue-cli设置css不生效
我们有的项目使用的是老的vue-cli脚手架生成的,今天想写点东西,发现.vue文件里面 style 里面写的样式都不生效了,很自然就想到是不是loader的问题。 在这种项目的webpack.ba 阅读更多…
Vue同一路由跳转页面不刷新解决方案及注意事项之二
之前写过一个 《Vue同一路由跳转页面不刷新解决方案及注意事项》 ,在这篇文章里面鞋到了怎么解决这个问题,具体内容可以点击查看,这里简单说一下,就是利用将时间戳传给路由的query,也就导致 $ro 阅读更多…