前言
前面我們寫了幾篇文章用來介紹webpack源碼,跟著官網(wǎng)結(jié)合demo把整個webpack配置擼了一遍:
- webpack源碼解析一
- webpack源碼解析二(html-webpack-plugin插件)
- webpack源碼解析三
- webpack源碼解析四
- webpack源碼解析五
- webpack源碼解析六(webpack-chain)
今天我們結(jié)合demo來看一下webpack的Optimization配置。
demo還是前面幾節(jié)中的: https://github.com/913453448/webpack-demo.git
optimization
webpack4.0版本后會根據(jù)當(dāng)前配置的mode對優(yōu)化操作,你也可以單獨配置或者是覆蓋默認的配置。
什么意思呢?比如在webpack4之前如果我們需要對打包好的資源進行壓縮,可能我們需要單獨用到uglifyjs-webpack-plugin插件,如果我們需要按規(guī)則拆分包可能需要用到CommonsChunkPlugin等等,由于這些操作在項目中很頻繁也很實用,所以webpack干脆就內(nèi)置到源碼變成一個“Optimization”配置選項。
minimize
boolean
是否利用默認的TerserPlugin插件或者自定義的插件去壓縮打包過后的資源文件。
生產(chǎn)環(huán)境默認是true
可以看一下webpack源碼,
webpack/lib/WebpackOptionsDefaulter.js:
...
const isProductionLikeMode = options => {
return options.mode === "production" || !options.mode;
};
...
this.set("optimization.minimize", "make", options =>
isProductionLikeMode(options)
);
...
可以看到,mode為“production”的時候默認是開啟的,我們可以這樣設(shè)置:
webpack.config.js
module.exports = {
//...
optimization: {
minimize: options.mode === "production"
}
};
minimizer
[TerserPlugin] and or [function (compiler)]
壓縮代碼使用的插件,默認是TerserPlugin ,你也可以使用該選項覆蓋默認的插件。
webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
}
}),
],
}
};
或者使用方法:
module.exports = {
optimization: {
minimizer: [
(compiler) => {
const TerserPlugin = require('terser-webpack-plugin');
new TerserPlugin({ /* your config */ }).apply(compiler);
}
],
}
};
源碼位置:
lib/WebpackOptionsDefaulter.js(默認插件)
...
this.set("optimization.minimizer", "make", options => [
{
apply: compiler => {
// Lazy load the Terser plugin
const TerserPlugin = require("terser-webpack-plugin");
new TerserPlugin().apply(compiler);
}
}
]);
...
lib/WebpackOptionsApply.js:
...
if (options.optimization.minimize) {
for (const minimizer of options.optimization.minimizer) {
if (typeof minimizer === "function") {
minimizer.call(compiler, compiler);
} else {
minimizer.apply(compiler);
}
}
}
...
ok!我們結(jié)合demo用一下這個選項,首先,我們把minimize選項設(shè)置成false,
webpack-chain.js:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("production")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
// .noParse(/babel-polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(false);
module.exports = config.toConfig();
然后我們執(zhí)行webpack:
? webpack-demo git:(master) ? npx webpack
Hash: bf5359b2e366b637ed00
Version: webpack 5.0.0-beta.7
Time: 1539ms
Built at: 2020-07-21 15:28:52
Asset Size
425.js 17.2 KiB [emitted]
63fe41824cb8236c0896a71b7df7f461.png 59.3 KiB [emitted]
app.0ebefcd962170615.bf5359b2e366b637.143.js 210 KiB [emitted] [immutable] [name: app]
Entrypoint app = app.0ebefcd962170615.bf5359b2e366b637.143.js
./index.js + 2 modules 222 KiB [built]
./demo-vue.vue + 5 modules 5.05 KiB [built]
./demo-vue.vue?vue&type=style&index=0&id=47a7e22a&lang=scss&scoped=true& 824 bytes [built]
../node_modules/style-loader/dist/cjs.js!../node_modules/css-loader/dist/cjs.js!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src??ruleSet[0].rules[0].use[2]!../node_modules/sass-loader/dist/cjs.js!../node_modules/vue-loader/lib??vue-loader-options!./demo-vue.vue?vue&type=style&index=0&id=47a7e22a&lang=scss&scoped=true& 810 bytes [built]
../pub1.png?external 80 bytes [built]
../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.64 KiB [built]
../node_modules/css-loader/dist/cjs.js!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/postcss-loader/src??ruleSet[0].rules[0].use[2]!../node_modules/sass-loader/dist/cjs.js!../node_modules/vue-loader/lib??vue-loader-options!./demo-vue.vue?vue&type=style&index=0&id=47a7e22a&lang=scss&scoped=true& 550 bytes [built]
../node_modules/css-loader/dist/runtime/api.js 2.46 KiB [built]
+ 8 hidden modules
? webpack-demo git:(master) ?
可以看到,在我們lib目錄下面生成了三個文件:
63fe41824cb8236c0896a71b7df7f461.png
425.js
app.0ebefcd962170615.bf5359b2e366b637.143.js
我們隨便看一個js文件:
lib/app.0ebefcd962170615.bf5359b2e366b637.143.js
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({});
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(__webpack_module_cache__[moduleId]) {
/******/ return __webpack_module_cache__[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
...
ok! 可以看到,代碼并沒有被壓縮,中間還有些注釋,這樣的代碼我們肯定是不能發(fā)布的,所以我們用一下webpack默認的壓縮,我們直接把minimize選擇設(shè)置成true(默認設(shè)置):
webpack-chain.js
...
config
.optimization
.minimize(true);
module.exports = config.toConfig();
然后我們執(zhí)行webpack編譯看結(jié)果:
./lib
├── 425.js
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.454b4ea21a1d7574.a4f7bd5606571443.143.js
└── app.454b4ea21a1d7574.a4f7bd5606571443.143.js.LICENSE
可以看到,lib下面出現(xiàn)了四個文件,
app.454b4ea21a1d7574.a4f7bd5606571443.143.js:
/*! For license information please see app.454b4ea21a1d7574.a4f7bd5606571443.143.js.LICENSE */
(()=>{"use strict";var t={},e={};function n(r){if(e[r])return e[r].exp...
可以看到,注釋webpack默認都幫我們移到了一個叫“app.454b4ea21a1d7574.a4f7bd5606571443.143.js.LICENSE”文件中,
app.454b4ea21a1d7574.a4f7bd5606571443.143.js.LICENSE:
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/
ok! 這就是vue的源碼中的注釋,webpack直接幫我們拎出來了,那如果我們不需要這些注釋我們該怎么做呢?
我們需要重新修改TerserPlugin 配置信息,
webpack-chain.js:
...
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false
}])
module.exports = config.toConfig();
??, 我們運行webpack:
./lib
├── 425.js
├── 63fe41824cb8236c0896a71b7df7f461.png
└── app.732205f4c110a904.a8ba809e02a92a83.143.js
0 directories, 3 files
可以看到,生產(chǎn)了三個文件,另外一個LICENSE文件不見了,我們打開“app.732205f4c110a904.a8ba809e02a92a83.143.js”看看,
app.732205f4c110a904.a8ba809e02a92a83.143.js:
(()=>{"use strict";var t,e={},n={};function r(t){if(n[t])return n[t].exports;var o=n[t...
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/
var o=Object.freeze({});function i(t){return null==t}function a(t){return null!=t}function s(t){return!0===t}function c(t){return"string"==typeof t||"numbe ...
可以看到,雖然注釋文件沒了,但是文件內(nèi)部的注釋并沒有去掉,我們修改一下配置文件,
webpack-chain.js:
...
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
module.exports = config.toConfig();
我們運行webpack看結(jié)果:
(()=>{"use strict";var t,e={},n={};function r(t){if(n[t])return...
運行的過程我就不演示了,可以看到,編譯過后的js文件代碼都在一行,并且進行了壓縮,去掉了注釋。
Terser-webpack-plugin更多的用法大家可以參考官網(wǎng):https://webpack.js.org/plugins/terser-webpack-plugin/
splitChunks
By default webpack v4+ provides new common chunks strategies out of the box for dynamically imported modules. See available options for configuring this behavior in the SplitChunksPlugin page.
在webpack4之前我們?nèi)绻枰远x拆分包規(guī)則的話用的是commons-chunk-plugin插件,webpack4以后內(nèi)置了SplitChunksPlugin 插件用于包規(guī)則處理。
在介紹SplitChunksPlugin之前我們先安裝一下webpack的一個包處理插件webpack-bundle-analyzer:
yarn add -D webpack-bundle-analyzer
然后我們修改一下配置文件webpack-chain.js:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("development")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
// .noParse(/babel-polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}]);
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
可看到,打包完畢后會幫我們自動打開一個包分析頁面,會把所有的chunk列出來,然后依賴關(guān)系也展示出來:
OK,在分析splitChunks配置之前我們先弄清楚幾個概念:
-
module:引入的模塊,也就是你用require跟import引入的代碼。 -
chunk:webpack根據(jù)入口文件結(jié)合配置信息對module的一個拆分,也就是說chunk是module的集合。 -
bundle:bundle是webpack對chunk進行編譯壓縮打包等處理過后的產(chǎn)物。
了解玩這些概念后,我們看一下webpack對splitChunks默認配置:
lib/WebpackOptionsDefaulter.js
...
this.set("optimization.splitChunks", {});
this.set("optimization.splitChunks.hidePathInfo", "make", options => {
return isProductionLikeMode(options);
});
this.set("optimization.splitChunks.chunks", "async");
this.set("optimization.splitChunks.minChunks", 1);
this.set("optimization.splitChunks.minSize", "make", options => {
return isProductionLikeMode(options) ? 30000 : 10000;
});
this.set("optimization.splitChunks.minRemainingSize", "make", options => {
return options.mode === "development" ? 0 : undefined;
});
this.set("optimization.splitChunks.maxAsyncRequests", "make", options => {
return isProductionLikeMode(options) ? 6 : Infinity;
});
this.set("optimization.splitChunks.automaticNameDelimiter", "-");
this.set("optimization.splitChunks.maxInitialRequests", "make", options => {
return isProductionLikeMode(options) ? 4 : Infinity;
});
this.set("optimization.splitChunks.cacheGroups", {});
this.set("optimization.splitChunks.cacheGroups.default", {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20
});
this.set("optimization.splitChunks.cacheGroups.defaultVendors", {
idHint: "vendors",
reuseExistingChunk: true,
test: NODE_MODULES_REGEXP,
priority: -10
});
...
我們先感受一下默認splitChunks對我們當(dāng)前項目的chunk處理,配置文件,
webpack-chain.js:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("development")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
// .noParse(/babel-polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后執(zhí)行webpack編譯:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js
├── demo-vue_vue.js
└── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
可以看到,多了一個“vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js”文件,那么這個文件是干什么的呢?
ok, 我們通過包分析器發(fā)現(xiàn)這個文件中包含的都是“demo-vue_vue.js”文件中的一些node_modules下面的一些依賴,而我們的“demo-vue_vue.js”文件又是入口文件“app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js”中的一個異步模塊,我們打開“app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js”文件稍微瞄一眼:
app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js
...
"demo-view":()=>Promise.all(/* import() */[__webpack_require__.e("vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d"), __webpack_require__.e("demo-vue_vue")]).then(__webpack_require__.bind(null, "./demo-vue.vue"))\n },\n render:(h)=>h("demo-view")\n});\n\n//# sourceURL=webpack:///./index.js?')}},
...
可以看到,我們的異步組件被當(dāng)成了異步模塊加載到了app.js入口文件中,也就是說如果需要加載“demo-view”組件的話,需要加載“vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js”跟“./demo-vue.vue”文件。
那么webpack是怎么把我們的“demo-vue.vue”異步組件又拆分出來了一個“vendors-xxx”文件的呢?
先提前透露一下哈,是以下webpack默認配置起的作用:
...
const NODE_MODULES_REGEXP = /[\\/]node_modules[\\/]/i;
...
//只對異步模塊做拆分處理,也就是我們的異步組件(demo-vue.vue)
this.set("optimization.splitChunks.chunks", "async");
//當(dāng)前異步組件(demo-vue.vue)中依賴的module的最小數(shù)量為1
this.set("optimization.splitChunks.minChunks", 1);
//分離出來的chunk需要滿足的最小尺寸(因為我們demo中的mode為“development”,所以為9.765625kb)
this.set("optimization.splitChunks.minSize", "make", options => {
return isProductionLikeMode(options) ? 30000 : 10000;
});
//需要從異步組件(demo-vue.vue)中的哪些模塊,默認是“NODE_MODULES_REGEXP”,也就是node_modules底下的模塊
this.set("optimization.splitChunks.cacheGroups.defaultVendors", {
idHint: "vendors",
reuseExistingChunk: true,
test: NODE_MODULES_REGEXP,
priority: -10
});
...
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js
├── demo-vue_vue.js
└── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
-
63fe41824cb8236c0896a71b7df7f461.png:src/demo-vue.vue中引入的圖片
pubImg: require("../pub1.png?external").default app.0da7d4017a7c8ff8.f56afc3595db81f3.app.js:入口文件生成
-
demo-vue_vue.js:入口文件中的異步模塊
... const app=new Vue({ el: "#app", components:{ "demo-view":()=>import("./demo-vue") }, render:(h)=>h("demo-view") }); ... vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js:webpack利用了默認的splitChunks配置從demo-vue_vue.js文件中分離出來的。
好啦! 可能有些小伙伴要暈了,沒關(guān)系,我們先提前感受一下,我們下面分析一下splitChunks每個屬性的含義。
chunks
function (chunk) string
指定需要從哪些模塊中進行拆包處理,
- async:表示只從異步加載得模塊(動態(tài)加載import())里面進行拆分
- initial:表示只從入口模塊進行拆分
- all:表示以上兩者都包括
async
webpack默認是“async”配置,前面我們也用到了,會把異步組件中node_module下的依賴都打到vendors中,我們用一下“initial”試試:
initial
webpack-chain.js
...
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: "initial"
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后執(zhí)行webpack看結(jié)果:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.7d5989fc2077821e.6b708c2c1d07233a.app.js
├── demo-vue_vue.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.6b708c2c1d07233a.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 4 files
先看一下我們的入口文件,src/index.js:
__webpack_public_path__ = "/";
import Vue from "vue";
import "demo-publicpath";
const root=document.createElement("div");
root.id="app";
document.body.appendChild(root)
const app=new Vue({
el: "#app",
components:{
"demo-view":()=>import("./demo-vue")
},
render:(h)=>h("demo-view")
});
按照我們的配置文件跟splitChunks的默認配置,webpack會幫我們把入口文件中node_modules下:
import Vue from "vue";
vue模塊單獨打包到vendors文件中,也就是最后生成的“vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.6b708c2c1d07233a.vendors-node_modules_vue_dist_vue_runtime_esm_js.js”文件。
all
我們繼續(xù)修改配置文件,把chunks改成“all”,
webpack-chain.js:
...
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: "all"
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后webpack編譯打包:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.58e33a3bd925d2c6.13725b5e05d55c03.app.js
├── demo-vue_vue.js
├── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.13725b5e05d55c03.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 5 files
可以看到,最后生成了5個文件,
- vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js:異步組件demo-vue.vue中node_modules下的依賴。
- vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.13725b5e05d55c03.vendors-node_modules_vue_dist_vue_runtime_esm_js.js:入口文件index.js中node_modules下的依賴。
function
你可以指定function并且按照自己規(guī)則選擇哪些chunk需要進行拆包處理,比如我們這里只讓入口app進行拆包處理,
webpack-chain.js:
...
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: (chunk)=>{
return chunk.name === "app";
}
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后打包處理就只會提取入口文件中的chunk:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.8cdd90e710751419.a59d0fe7901eaea9.app.js
├── demo-view.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.a59d0fe7901eaea9.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 4 files
如果我們只需要處理異步組件中的chunk,首先我們給我們的異步組件給定一個name,不然沒法做判斷,
src/index.js:
...
const app=new Vue({
el: "#app",
components:{
"demo-view":()=>import(/* webpackChunkName: "demo-vue" */ "./demo-vue")
},
render:(h)=>h("demo-view")
});
我們給了異步組件chunkname為“demo-view”,然后我們判斷當(dāng)chunk的name為“app”跟“demo-vue”的時候進行拆包處理,
webpack-chain.js:
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: (chunk)=>{
return chunk.name === "app" || chunk.name==="demo-vue";
}
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后執(zhí)行打包:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.f2a53484c6605e60.ddc69c26cfb6b94c.app.js
├── demo-vue.js
├── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.ddc69c26cfb6b94c.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 5 files
ok, 可以看到,同時對我們的入口“app”跟異步組件“demo-vue”進行了拆包處理。
cacheGroups
splitChunks會根據(jù)cacheGroups去進行拆包處理,splitChunks默認有兩個緩存組:vender和default,可以再來回顧一下splitChunks的默認配置:
this.set("optimization.splitChunks.cacheGroups.default", {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20
});
this.set("optimization.splitChunks.cacheGroups.defaultVendors", {
idHint: "vendors",
reuseExistingChunk: true,
test: NODE_MODULES_REGEXP,
priority: -10
});
ok,我們的vendors是一直在起作用,但是default貌似沒啥作用?
那是因為default中配置了“minChunks: 2”,也就是出現(xiàn)重復(fù)兩次的公共模塊才會被拆分。
ok,我們copy一份"src/index.js"叫"src/index2.js",然后在webpack中添加一個入口app2, 把chunks改成“all”
webpack-chain.js:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("development")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.entry("app2")
.add("./index2.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
.noParse(/polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: "all",
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
我們先看一下我們的入口文件index.js:
__webpack_public_path__ = "/";
import Vue from "vue";
import "demo-publicpath";
const root=document.createElement("div");
root.id="app";
document.body.appendChild(root)
const app=new Vue({
el: "#app",
components:{
"demo-view":()=>import(/* webpackChunkName: "demo-vue" */ "./demo-vue")
},
render:(h)=>h("demo-view")
});
index2.js文件內(nèi)容跟index.js一樣,可以發(fā)現(xiàn)兩個入口文件都依賴了“demo-publicpath”模塊:
import "demo-publicpath";
所以默認的cacheGroups中的default應(yīng)該是會起作用的,我們來試試,
我們直接webpack打包:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.f2a53484c6605e60.5634fdc906df69a3.app.js
├── app2.720bd2c9436db62f.5634fdc906df69a3.app2.js
├── demo-vue.js
├── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.5634fdc906df69a3.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 6 files
可以看到,只多了一個app2入口,但是app1跟app2中共用的“demo-publicpath.js”并沒有被單獨拆出來,這是為什么呢?
因為在webpack的默認配置中,splitChunks的最小size是10kb,我們的“demo-publicpath.js”模塊大小不夠。
好啦! 知道原因后,那我們就往“demo-publicpath.js”中多添加點內(nèi)容,我們直接去copy一份polyfill的源碼到src/assets目錄,然后在“demo-publicpath.js”中導(dǎo)入polyfill,
src/demo-publicpath.js:
import "./assets/polyfill";
export const say = () => {
document.body.append(document.createTextNode("hello webpack"))
}
然后再執(zhí)行webpack打包:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.7eb0f7d54d50246d.380c5a33877e21dc.app.js
├── app2.2746d1497b7d22f2.380c5a33877e21dc.app2.js
├── demo-publicpath_js.ca83113eaf484d9f.380c5a33877e21dc.demo-publicpath_js.js
├── demo-vue.js
├── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
└── vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.380c5a33877e21dc.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
0 directories, 7 files
??,可以看到,app跟app2中共有的“demo-publicpath.js”模塊被單獨打包成了“demo-publicpath_js.ca83113eaf484d9f.380c5a33877e21dc.demo-publicpath_js.js”文件。
好啦~ 說了這里有小伙伴要提出疑問了,為什么我們需要單獨copy一份polyfill源碼到src/assets呢?我們直接去導(dǎo)入node_modules下面的難道不行嗎?
我們試試看! 我們修改一下polyfill的導(dǎo)入形式,改成從node_modules導(dǎo)入:
src/demo-publicpath.js
import "babel-polyfill/dist/polyfill";
export const say = () => {
document.body.append(document.createTextNode("hello webpack"))
}
然后我們webpack編譯打包:
./lib
├── 63fe41824cb8236c0896a71b7df7f461.png
├── app.62234a61ae4b51ea.d1e25902f2ab5db9.app.js
├── app2.50e3c79cb0103ebc.d1e25902f2ab5db9.app2.js
├── demo-vue.js
├── vendors-node_modules_babel-polyfill_dist_polyfill_js-node_modules_vue_dist_vue_runtime_esm_js.c2493861c2404ba0.d1e25902f2ab5db9.vendors-node_modules_babel-polyfill_dist_polyfill_js-node_modules_vue_dist_vue_runtime_esm_js.js
└── vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
0 directories, 6 files
可以看到,default又不起作用了,那我們的polyfill到底去哪了呢? 沒錯! 到vendors里面去了,也就是生成的“vendors-node_modules_babel-polyfill_dist_polyfill_js-node_modules_vue_dist_vue_runtime_esm_js.c2493861c2404ba0.d1e25902f2ab5db9.vendors-node_modules_babel-polyfill_dist_polyfill_js-node_modules_vue_dist_vue_runtime_esm_js.js”文件,為什么呢?
因為在默認的配置中vendors的優(yōu)先級是高于default的,優(yōu)先級的配置是通過priority屬性:
this.set("optimization.splitChunks.cacheGroups.default", {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20
});
this.set("optimization.splitChunks.cacheGroups.defaultVendors", {
idHint: "vendors",
reuseExistingChunk: true,
test: NODE_MODULES_REGEXP,
priority: -10
});
所以為了讓default起作用,我們才會把polyfill復(fù)制了一份放到了src/assets讓“demo-publicpath.js”導(dǎo)入。
有沒有發(fā)現(xiàn)webpack默認拆分出來的chunk名字又長又丑呢?我們可以通過name屬性去修改默認的name:
webpack-chain.js
.splitChunks({
chunks: "all",
cacheGroups: {
default: {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20,
name: (module, chunks, cacheGroupKey)=>{
return "chunk-common";
}
}
}
效果就不演示了,小伙伴自己去運行。
ok,如果不想用默認cacheGroups的“default”跟“vendors”配置的話,我們只需要將它們置成false就可以了,
.splitChunks({
...
cacheGroups: {
default: false,
defaultVendors: false
}
})
maxInitialRequests
表示允許入口并行加載的最大請求數(shù),之所以有這個配置也是為了對拆分數(shù)量進行限制,不至于拆分出太多模塊導(dǎo)致請求數(shù)量過多而得不償失。
這里需要注意幾點:
- 入口文件本身算一個請求
- 如果入口里面有動態(tài)加載得模塊這個不算在內(nèi)
- 通過runtimeChunk拆分出的runtime不算在內(nèi)
- 只算js文件的請求,css不算在內(nèi)
- 如果同時又兩個模塊滿足cacheGroup的規(guī)則要進行拆分,但是maxInitialRequests的值只能允許再拆分一個模塊,那尺寸更大的模塊會被拆分出來
看一下webpack默認配置:
...
this.set("optimization.splitChunks.maxInitialRequests", "make", options => {
//生產(chǎn)環(huán)境是4個,測試環(huán)境不限制
return isProductionLikeMode(options) ? 4 : Infinity;
});
...
demo當(dāng)前配置:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("development")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.entry("app2")
.add("./index2.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
.noParse(/polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
chunks: "all",
cacheGroups: {
default: {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20,
name: (module, chunks, cacheGroupKey)=>{
return "commons";
}
}
}
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后webpack打包后會生成:
63fe41824cb8236c0896a71b7df7f461.png
app.0287e18f3a59d8de.032bbe1fbfafc5e2.app.js
app2.58808f5a4e707707.032bbe1fbfafc5e2.app2.js
commons.cfe1450bd9c9d2ff.032bbe1fbfafc5e2.commons.js
demo-vue.js
vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.032bbe1fbfafc5e2.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
入口app的requests有:
- app.0287e18f3a59d8de.032bbe1fbfafc5e2.app.js
- commons.cfe1450bd9c9d2ff.032bbe1fbfafc5e2.commons.js
- vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.032bbe1fbfafc5e2.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
app2跟app一樣,maxInitialRequests默認是4,所以可以正常分離出“commons.cfe1450bd9c9d2ff.032bbe1fbfafc5e2.commons.js”,如果我們將maxInitialRequests設(shè)置“2”試試,
webpack-chain.js:
.splitChunks({
chunks: "all",
maxInitialRequests: 2,
cacheGroups: {
default: {
idHint: "",
reuseExistingChunk: true,
minChunks: 2,
priority: -20,
name: (module, chunks, cacheGroupKey)=>{
return "commons";
}
}
}
})
webpack編譯看結(jié)果:
63fe41824cb8236c0896a71b7df7f461.png
app.0644d4709680673b.2d87a8324473e235.app.js
app2.0c27f9cb5e923e85.2d87a8324473e235.app2.js
demo-vue.js
vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.2d87a8324473e235.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
可以看到,“commons”并沒有被分離出來。
maxAsyncRequests
maxAsyncRequests跟maxInitialRequests差不多,maxAsyncRequests主要是用來限制異步模塊內(nèi)部的并行最大請求數(shù)的。
在我們demo中指的就是“demo-vue.vue”異步組件,目前demo-vue.vue的requests有:
- demo-vue.js
- vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js
有小伙伴要疑問了“難道異步組件不需要依賴vue嗎?”,也就是“vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.2d87a8324473e235.vendors-node_modules_vue_dist_vue_runtime_esm_js.js”文件,因為在入口文件中也有依賴vue,所以webpack認為vue是必須存在的,因此不算在里面。
ok, 如果我們把maxAsyncRequests改成“1”看一下還會分離“vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js”文件嗎?
63fe41824cb8236c0896a71b7df7f461.png
app.425e43abcaba52da.cd98b0378c0ec2df.app.js
app2.ca0b00010af3e31c.cd98b0378c0ec2df.app2.js
demo-vue.js
vendors-node_modules_vue_dist_vue_runtime_esm_js.1c5043955daac720.cd98b0378c0ec2df.vendors-node_modules_vue_dist_vue_runtime_esm_js.js
可以看到,最后“vendors-node_modules_css-loader_dist_runtime_api_js-node_modules_style-loader_dist_runtime_in-e18f0d.js”文件沒有生成,此時demo-vue.vue(異步組件)的requests有:
- demo-vue.js
實戰(zhàn)
ok!說了那么多概念性的東西,我們結(jié)合demo來點平時項目中會用到的配置,比如我們demo這里,目前有兩個入口“app”跟“app1”,分包規(guī)則如下:
- node_modules底下的依賴跟polyfill都放入一個叫“chunk-vendors”的包中
- 引用次數(shù)超過兩次的放入到“chunk-common”包中。
ok, 了解完需求后我們直接修改一下配置文件:
webpack-chain.js:
const Config = require('webpack-chain');
const config = new Config();
const path = require("path");
config
.mode("development")
.context(path.resolve(__dirname, "./src"))
.entry("app")
.add("./index.js")
.end()
.entry("app2")
.add("./index2.js")
.end()
.output
.path(path.join(process.cwd(), "lib"))
.pathinfo(false)
.filename("[name].[contenthash:16].[fullhash:16].[id].js")
.chunkFilename("[id].js")
.end()
.set("experiments",{})
.module
.noParse(/polyfill/)
.rule("vue")
.test(/\.vue$/)
.use("vue-loader")
.loader("vue-loader")
.end()
.end()
.rule("sass")
.test( /\.(sass|scss)$/)
.use("style-loader")
.loader("style-loader")
.end()
.use("css-loader")
.loader("css-loader")
.end()
.use("postcss-loader")
.loader("postcss-loader")
.options( {
config: {
path: path.resolve(__dirname, "./postcss.config.js")
}
})
.end()
.use("sass-loader")
.loader("sass-loader")
.end()
.end()
.rule("png")
.test(/\.png$/)
.oneOf("png-loader")
.rule("url-loader")
.resourceQuery(/inline/)
.use("url-loader")
.loader("url-loader")
.options({
limit: 1024 * 1024 * 10
})
.end()
.end()
.rule("file-loader")
.resourceQuery(/external/)
.use("file-loader")
.loader("file-loader")
.end()
.end()
.end()
.end()
.end()
.resolve
.alias
.set("DemoVue", path.resolve(__dirname, "./src/demo-vue.vue"))
.end()
.extensions
.add(".wasm").add(".mjs").add(".js").add(".json").add(".vue")
.end()
.modules
.add(path.resolve(__dirname, "src")).add("node_modules")
.end()
.unsafeCache(/demo-publicpath/)
.end()
.plugin("vue-loader-plugin")
.use(require("vue-loader-plugin"),[])
.end()
.devServer
.before((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
req.query.name="hello "+req.query.name;
next();
});
})
.after((app, server, compiler)=>{
app.get("/login",(req,res,next)=>{
res.json({msg: req.query.name});
});
})
.clientLogLevel("info")
.allowedHosts
.add("localhost")
.end()
.contentBase(path.join(process.cwd(), "lib"))
.filename(/app\.js/)
.headers({
'X-Custom-Foo': 'bar'
})
.historyApiFallback(true)
.host("0.0.0.0")
.port("8090")
.hot(true)
.set("liveReload", true)
.open(true)
.useLocalIp(true)
.overlay(true)
.end()
.performance
.hints("warning")
.end();
config
.optimization
.minimize(true)
.minimizer("terser")
.use(require("terser-webpack-plugin"),[{
extractComments: false,
terserOptions:{
output: {
comments: false
}
}
}])
.end()
.splitChunks({
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'all'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'all',
reuseExistingChunk: true
}
}
})
config.plugin("webpack-bundle-analyzer").use(require("webpack-bundle-analyzer").BundleAnalyzerPlugin,[]);
module.exports = config.toConfig();
然后我們webpack打包編譯:
63fe41824cb8236c0896a71b7df7f461.png
app.55ad00d0136c7d7d.306645240be8f953.app.js
app2.efd24a48f5ab103e.306645240be8f953.app2.js
chunk-common.3baec624a3539123.306645240be8f953.chunk-common.js
chunk-vendors.js
demo-vue.js
- chunk-vendors.js: 包含了node_modules底下的模塊(vue、style-loader等等。)
- chunk-common.3baec624a3539123.306645240be8f953.chunk-common.js: 包含了assets/polyfill.js
ok, 我們直接用bundle-analyzer工具查看一下:
chunk-vendors.js
chunk-common.3baec624a3539123.306645240be8f953.chunk-common.js:
總結(jié)
我們花了很多章節(jié)來介紹webpack,不得不說webpack內(nèi)容是真的多,對webpack作者佩服的五體投地,開源不易?。。?/p>
好啦!我們的webpack差不多就告一段落了,后面可能會對vue、react腳手架對webpack的配置做分析,敬請期待?。?/p>