Explore vuE-CLI source code
Ps: suggested with the source code to see, based on @vue/ CLI 4.5.12
When we see vue-cli-service serve and vue-cli-service build, wonder what’s going on?
View the node_modules \ @ vue \ cli – service \ package json
"bin": {
"vue-cli-service": "bin/vue-cli-service.js"},Copy the code
This code does the following: When I install @vue/cli, I will register a vue-cli-service command, that is, we can type vue-cli-service on the console, it will call bin/vue-cli-service.js, The following parameters are also passed to bin/vue-cli-service.js
// bin/vue-cli-service.js
const Service = require('.. /lib/Service')
const service = newService(process.env.VUE_CLI_CONTEXT || process.cwd()) ... .const command = args._[0]
service.run(command, args, rawArgv).catch(err= > {
error(err)
process.exit(1)})Copy the code
Service is a class that defines a command variable (also known as the serve parameter in vue-cli-service serve), which calls the run method in the Service class
/ /.. /lib/Service.js
async run (name, args = {}, rawArgv = []) {
...
this.init(mode)
...
let command = this.commands[name] ... .const { fn } = command
return fn(args, rawArgv)
}
Copy the code
This.init (mode) and let command = this.mands [name]
/ /.. /lib/Service.js
module.exports = class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {...// 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)
...
}
Copy the code
Plugins = this.resolveplugins (plugins, useBuiltIn) is used when a Service class is instantiated
/ /.. /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) ... .return plugins
}
Copy the code
This will eventually return an array of objects containing the builtInPlugins data (because of some processing), each of which is a JSON object, where the apply key holds require(‘./ XXX/XXXX ‘), which will import each file. We only explain./ Commands /serve.
/ /.. /lib/Service.js
init (mode = process.env.VUE_CLI_MODE) {
...
// apply plugins.
this.plugins.forEach(({ id, apply }) = > {
if (this.pluginsToSkip.has(id)) return
apply(new PluginAPI(id, this), this.projectOptions)
})
...
}
Copy the code
Plugins = this.resolveplugins (plugins, useBuiltIn).plugins = this.resolveplugins (plugins, useBuiltIn) It will iterate over and apply to the structure and call it. Actually apply() means I require(‘./commands/serve’)(for example) and call it. So we’ll look at./commands/serve.
/ /.. /lib/commands/serve.js
module.exports = (api, options) = > {
api.registerCommand('serve', {... },async function serve (args) {... })Copy the code
PluginAPI(id, this) in apply(new PluginAPI(id, this), this.projectoptions) is equal to API, Api.registercommand calls new PluginAPI().registerCommand() and then looks at pluginapi.js
/ /.. /lib/PluginAPI.js
class PluginAPI {
constructor (id, service) {
this.id = id
this.service = service // this.service is an instance of service}... registerCommand (name, opts, fn) {if (typeof opts === 'function') {
fn = opts
opts = null
}
this.service.commands[name] = { fn, opts: opts || {}}
}
...
}
Copy the code
In.. /lib/commands/serve.js API. RegisterCommand () is used to call the registerCommand method. Name is serve, opts is some parameters, fn is the method defined.
Finally: this.service.com mands [‘ serve ‘] = {fn, opts: opts | | {}}
/ /.. /lib/Service.js
async run (name, args = {}, rawArgv = []) {
...
this.init(mode)
...
let command = this.commands[name] ... .const { fn } = command
return fn(args, rawArgv)
}
Copy the code
For example, name = ‘serve’, so fn is.. Async function serve (args) method in /lib/commands/serve.js
Vue-cli-serve run serve call method is.. Async function serve (args) method in /lib/commands/serve.js
Quickly ended
/ /.. /lib/commands/serve.js
async function serve (args){...const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')...const webpackConfig = api.resolveWebpackConfig()
...
const compiler = webpack(webpackConfig) // Get the compiled result from webpack according to the configuration.const server = new WebpackDevServer(compiler,Object.assign{ // Add a service to WebpackDevServer. }) server.listen(port, host,err= > {
if (err) {
reject(err)
}
})
}
Copy the code
We can see that vue-CLI will eventually be processed via webpack. One thing to note is that const webpackConfig = api.resolveWebPackConfig (), which is the configuration to load webpack, We talked about the PluginAPI, so let’s move on
/ /.. /lib/PluginAPI.js
resolveWebpackConfig (chainableConfig) {
return this.service.resolveWebpackConfig(chainableConfig)
}
// this.service refers to service because the service instance PluginAPI class also passes itself to the PluginAPI constructor
Copy the code
/ /.. /lib/Service.js
resolveChainableWebpackConfig () {
const chainableConfig = new Config()
// apply chains
this.webpackChainFns.forEach(fn= > fn(chainableConfig))
return chainableConfig
}
resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
...
// get raw config
let config = chainableConfig.toConfig()
...
// Do something with config.return config
}
Copy the code
When we see resolveWebpackConfig method calls the default will call resolveChainableWebpackConfig method, There is a word of this. In see resolveChainableWebpackConfig method webpackChainFns. ForEach (fn = > fn (chainableConfig)) this webpackChainFns is how?
/ /.. /lib/Service.js
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)
Copy the code
Do you remember this one? We only talked about the./commands/serve method, let’s look at./config/base
/ /.. /lib/config/base.js
module.exports = (api, options) = > {
api.chainWebpack(webpackConfig= >{... . webpackConfig .mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
webpackConfig.resolve
.extensions
.merge(['.mjs'.'.js'.'.jsx'.'.vue'.'.json'.'.wasm'])
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
.end()
.alias
.set(The '@', api.resolve('src'))
.set(
'vue$',
options.runtimeCompiler
? 'vue/dist/vue.esm.js'
: 'vue/dist/vue.runtime.esm.js'
)
webpackConfig.resolveLoader
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
webpackConfig.module
.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
// js is handled by cli-plugin-babel ---------------------------------------
// vue-loader --------------------------------------------------------------
const vueLoaderCacheConfig = api.genCacheConfig('vue-loader', {
'vue-loader': require('vue-loader/package.json').version,
/* eslint-disable-next-line node/no-extraneous-require */
'@vue/component-compiler-utils': require('@vue/component-compiler-utils/package.json').version,
'vue-template-compiler': require('vue-template-compiler/package.json').version
})
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader('cache-loader')
.options(vueLoaderCacheConfig)
.end()
.use('vue-loader')
.loader('vue-loader')
.options(Object.assign({
compilerOptions: {
preserveWhitespace: false
}
}, vueLoaderCacheConfig))
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader/lib/plugin'))
// static assets -----------------------------------------------------------
webpackConfig.module
.rule('images')
.test(/\.(png|jpe? g|gif|webp)(\? . *)? $/)
.use('url-loader')
.loader('url-loader')
.options(genUrlLoaderOptions('img'))
// do not base64-inline SVGs.
// https://github.com/facebookincubator/create-react-app/pull/1180
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\? . *)? $/)
.use('file-loader')
.loader('file-loader')
.options({
name: genAssetSubPath('img')
})
webpackConfig.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/)
.use('url-loader')
.loader('url-loader')
.options(genUrlLoaderOptions('media'))
webpackConfig.module
.rule('fonts')
.test(/\.(woff2? |eot|ttf|otf)(\? . *)? $/i)
.use('url-loader')
.loader('url-loader')
.options(genUrlLoaderOptions('fonts'))
// Other common pre-processors ---------------------------------------------
webpackConfig.module
.rule('pug')
.test(/\.pug$/)
.oneOf('pug-vue')
.resourceQuery(/vue/)
.use('pug-plain-loader')
.loader('pug-plain-loader')
.end()
.end()
.oneOf('pug-template')
.use('raw')
.loader('raw-loader')
.end()
.use('pug-plain')
.loader('pug-plain-loader')
.end()
.end()
// shims
webpackConfig.node
.merge({
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false.// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty'.fs: 'empty'.net: 'empty'.tls: 'empty'.child_process: 'empty'
})
const resolveClientEnv = require('.. /util/resolveClientEnv')
webpackConfig
.plugin('define')
.use(require('webpack/lib/DefinePlugin'), [
resolveClientEnv(options)
])
webpackConfig
.plugin('case-sensitive-paths')
.use(require('case-sensitive-paths-webpack-plugin'))
// friendly error plugin displays very confusing errors when webpack
// fails to resolve a loader, so we provide custom handlers to improve it
const { transformer, formatter } = require('.. /util/resolveLoaderError')
webpackConfig
.plugin('friendly-errors')
.use(require('@soda/friendly-errors-webpack-plugin'), [{
additionalTransformers: [transformer],
additionalFormatters: [formatter]
}])
})
}
Copy the code
We can see if this is the same as when we first set up vue with Webpack. Vue-cli helps us to configure a lot of loaders and so on. Let’s look at api.chainWebPack
/ /.. /lib/Service.js
chainWebpack (fn) {
this.service.webpackChainFns.push(fn)
}
Copy the code
WebpackConfig => {… . } this method to save to the this. Service. WebpackChainFns this array, which is in the service of webpackChainFns array, well we look back to the service. Js
/ /.. /lib/Service.js
resolveChainableWebpackConfig () {
const chainableConfig = new Config()
// apply chains
this.webpackChainFns.forEach(fn= > fn(chainableConfig))
return chainableConfig
}
Copy the code
So this. WebpackChainFns stores… /lib/config/base.js, and pass in the chainableConfig. New config () is the new webpack-chain plugin. Vue. Config. js is parsed in loadUserOptions.
So far, it is only a rough process. In fact, it looks more complicated, and some data processing is more complicated, but the logic is clear and clear.