從vuecli3學習webpack記錄(四)vue是怎麼進行預設配置的

談笑斗酒發表於2020-04-04

原文地址:從vuecli3學習webpack記錄(四)vue是怎麼進行預設配置的

在我們講到從vuecli3學習webpack記錄(一)vue-cli-serve機制

vue cli3中在commands資料夾裡面的是呼叫api.registerCommand方法,在config資料夾裡面的(teserOptions.js和html除外)是呼叫api.chainWebpack方法,該方法會將傳得的引數(該引數是一個方法)push到this.service.webpackChainFns陣列。

今天就展開看看裡面具體是什麼。 1.this.service其實就是cli3裡面的Service(node_modules/@vue/cli-service/lib/Service.js)的例項,通過api.registerCommand方法將對應的serve(就是npm run serve那個serve)等command加入到this.commands這個物件屬性裡面,通過api.chainWebpack方法將app、base等webpack配置加入到this.webpackChainFns這個陣列屬性裡面。 2.上面的api其實是PluginApi(node_modules/@vue/cli-service/lib/PluginApi.js)的例項, 部分程式碼如下

// node_modules/@vue/cli-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)
  }
複製程式碼

cli3幫我們配置的預設配置是怎麼進去的呢? 在此之前,我們先看看這些配置長什麼樣吧,以node_modules/@vue/cli-service/lib/config/base.js為例 我們一般在webpack裡面是這樣配置它

module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      }
    ]
}
```
而在cli3裡面這樣配置
````javascript
module.exports = (api, options) => {
  api.chainWebpack(webpackConfig => {
		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'))

		webpackConfig.module
			  .rule('images')
				.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
				.use('url-loader')
				  .loader('url-loader')
				  .options(genUrlLoaderOptions('img'))
	 })
}
複製程式碼

可以看出webpack的配置可以以鏈式呼叫的方式新增,這樣就可以以更加靈活的函式式新增配置了。 webpackConfig是什麼鬼,這就要再次回到Service.js了,看它是怎執行webpackChainFns陣列裡面的函式了

const Config = require('webpack-chain')

resolveChainableWebpackConfig () {
    const chainableConfig = new Config()
    // apply chains
    this.webpackChainFns.forEach(fn => fn(chainableConfig))
    return chainableConfig
}
複製程式碼

再看看webpack-chain

const ChainedMap = require('./ChainedMap');
const ChainedSet = require('./ChainedSet');
const Resolve = require('./Resolve');
const ResolveLoader = require('./ResolveLoader');
const Output = require('./Output');
const DevServer = require('./DevServer');
const Plugin = require('./Plugin');
const Module = require('./Module');
const Optimization = require('./Optimization');
const Performance = require('./Performance');

module.exports = class extends ChainedMap {
  constructor() {
    super();
    this.devServer = new DevServer(this);
    this.entryPoints = new ChainedMap(this);
    this.module = new Module(this);
    this.node = new ChainedMap(this);
    this.optimization = new Optimization(this);
    this.output = new Output(this);
    this.performance = new Performance(this);
    this.plugins = new ChainedMap(this);
    this.resolve = new Resolve(this);
    this.resolveLoader = new ResolveLoader(this);
    this.extend([
      'amd',
      'bail',
      'cache',
      'context',
      'devtool',
      'externals',
      'loader',
      'mode',
      'parallelism',
      'profile',
      'recordsInputPath',
      'recordsPath',
      'recordsOutputPath',
      'stats',
      'target',
      'watch',
      'watchOptions',
    ]);
  }
  
  toConfig() {
    const entryPoints = this.entryPoints.entries() || {};

    return this.clean(
      Object.assign(this.entries() || {}, {
        node: this.node.entries(),
        output: this.output.entries(),
        resolve: this.resolve.toConfig(),
        resolveLoader: this.resolveLoader.toConfig(),
        devServer: this.devServer.toConfig(),
        module: this.module.toConfig(),
        optimization: this.optimization.entries(),
        plugins: this.plugins.values().map(plugin => plugin.toConfig()),
        performance: this.performance.entries(),
        entry: Object.keys(entryPoints).reduce(
          (acc, key) =>
            Object.assign(acc, { [key]: entryPoints[key].values() }),
          {}
        ),
      })
    );
  }
}
複製程式碼

原來它針對webpack的配置裡面的每一個大項都設定了不同的屬性,並且分配以不同的方式實現。我們還可以看到裡面有個toConfig方法,它會將最終的配置返回為我們熟悉的物件形式 的wepback配置,畢竟webpack只認這種配置。 下面就以this.module為例吧,因為看到它的鏈式呼叫裡面有個end方法,會讓鏈式呼叫調回去,方便執行後續的use('vue-loader')

// node_modules/webpack-chain/src/Module.js
module.exports = class extends ChainedMap {
  constructor(parent) {
    super(parent);
    this.rules = new ChainedMap(this);
    this.defaultRules = new ChainedMap(this);
    this.extend(['noParse']);
  }
}
複製程式碼

Module繼承ChainMap,而ChainMap又繼承自Chainable,我們要看的end方法就是在這裡

// node_modules/webpack-chain/src/Chainable.js
module.exports = class {
  constructor(parent) {
    this.parent = parent;
  }

  batch(handler) {
    handler(this);
    return this;
  }

  end() {
    return this.parent;
  }
};
複製程式碼

相關文章