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.