原文地址:從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;
}
};
複製程式碼