前言:
我最近需要整理一下 webpack 這個前端構(gòu)建工具的相關(guān)知識壁熄,希望對前端工程化的和模塊化有更多的理解草丧,我以前對 webpack打包機制感到非常的困惑烛亦,也沒有深入的理解煤禽, 都是淺嘗輒止檬果, 最近看到了相關(guān)的文章介紹选脊,并對
webpack
對js
打包有了深入的理解;
這篇文章會幫助你理解如下的問題:
1.webpack
當個文件如何進行打包?
2.webpack
多個文件如何進行代碼切割?
3.webpack2
如何做到 tree shaking?
4.webpack3
如何做到 scope hoisting(提升)?
1.webpack
當個文件如何進行打包?
首先現(xiàn)在作為主流的前端模塊化工具,在 webpack 剛剛才開始流行起來的時候角寸,我們經(jīng)常看到 webpack 將所有處理文件全部打包成一個
bundle
文件沮峡,現(xiàn)在我們看看下面的例子
// src/single/index.js
let index2 = require("./index2");
let util = require("./util");
console.log(index2);
console.log(util);
// src/single/index2.js
let util = require("./util");
console.log(util);
module.exports = "index2 js";
// src/single/util.js
module.exports = "hello liyao";
// 通過 webpack.config.js中的配置
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: {
index: [path.resolve(__dirname, "../src/single/index.js")]
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].[chunkhash:8].js"
}
};
那么我們將其使用 npm run build
來進行打包,我們將會得到下面的一段代碼塊
// dist/index.xxxx.js
(function(modules) {
// 已經(jīng)加載過的模塊
var installedModules = {};
// 模塊加載函數(shù)
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
return __webpack_require__((__webpack_require__.s = 3));
})([
/* 0 */
function(module, exports, __webpack_require__) {
var util = __webpack_require__(1);
console.log(util);
module.exports = "index 2";
},
/* 1 */
function(module, exports) {
module.exports = "Hello World";
},
/* 2 */
function(module, exports, __webpack_require__) {
var index2 = __webpack_require__(0);
index2 = __webpack_require__(0);
var util = __webpack_require__(1);
console.log(index2);
console.log(util);
},
/* 3 */
function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(2);
}
]);
我們從上面知道 webpack 將所有的模塊都包含在一個函數(shù)中,并傳入默認的參數(shù)颁虐,這里有三個文件再加上一個入口模塊一共有四個模塊另绩,將他們放在一個數(shù)組中笋籽,命名為
modules
车海, 并通過數(shù)組的下標來作為moduleId
侍芝;
將
modules
傳入一個自執(zhí)行的函數(shù)中, 自執(zhí)行的函數(shù)中包含一個installModules
已經(jīng)加載過的模塊和一個模塊加載函數(shù)留量, 最后加載入口模塊并返回楼熄;
__webpack_raquire__ 模塊加載可岂, 先判斷
installModules
中是否被加載過了缕粹,加載過了會直接就放回 exports 數(shù)據(jù)平斩,沒有加載過模塊就通過 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 執(zhí)行模塊并且將 module.exports 給放回;
根據(jù)上面的解釋, 我們需要注意的是:
每個模塊 webpack 只會加載一次欺税, 所以重復(fù)加載的模塊只會執(zhí)行一次晚凿,加載過的模塊會放到 installModules, 下次需要該模塊會從其中直接獲取歼秽。
模塊的 id 直接通過數(shù)組下標去一一對應(yīng)的燥筷,這樣保證簡單唯一滥比,通過其他的方式比如文件名或者是文件路徑的方式就比較麻煩, 因為文件名可能會出現(xiàn)重名濒持,不唯一柑营,文件路徑則會增大文件體積官套,并且將路徑暴露給前端奶赔, 不夠安全站刑;
modules[moduleId].call(module.exports, module, module.exports, __module_require__), 保證了模塊加載時绞旅, this 的指向 module.exports 并且傳入默認的參數(shù)
webpack 多個文件如何進行代碼切割?
webpack 單文件打包的方式應(yīng)付一些簡單場景就足夠了堕汞, 但是我們在開發(fā)一些復(fù)雜的應(yīng)用時囤捻,如果沒有對代碼進行切割视哑,將第三方庫 或 框架 和業(yè)務(wù)代碼全部打包在一起的話誊涯,就會導(dǎo)致用戶訪問頁面的速度很慢跪呈, 不能有效的利用緩存耗绿;那么網(wǎng)站的體驗就會很差误阻;
那么 webpack 多個文件入口如何進行代碼切割呢究反?那么我們先來看下面一個例子:
// src/multiple/page1.js
const utilA = require("./js/utilA");
// 模塊B 被引用了兩次:<1>
const utilB = require("./js/utilB");
console.log(utilA);
console.log(utilB);
// src/multiple/page2.js
const utilB = require("./js/utilB"); // <2>;
console.log(utilB);
// 異步加載文件精耐, 類似于 import()
const utilC = () => {
return require.ensure(["./js/utilC"], function(require) {
console.log(require("./js/utilC"));
});
};
utilC();
// src/multiple/js/utilA.js 可類比于公共庫;
module.exports = "util A";
// src/multiple/js/utilB.js
module.exports = "util B";
// src/multiple/js/utilC.js
module.exports = "util C";
那么我們使用定義了兩個入口 pageA 和 pageB 和第三個庫 util惊完, 那么我們希望做到:
兩個入口都是用到 utilB, 我們希望把它抽離成單個文件专执,并且當用戶訪問 pageA 和 pageB 是時候都可 j 加載 utilB 這個公共的模塊 e 而不是存在各自的入口文件中本股。
pageB 中 utilC 不是頁面拄显,一開始加載時候需 k 考慮的內(nèi)容躬审,假如 utilC 很大遭殉,我們不希望頁面加載時就直接加載 utilC博助,而是當用戶達到某個條件(如: 點擊按鈕) 才去異步加載 utilC富岳, 這時候我們需將 utilC 抽離成單獨的文件窖式, 當用戶需要的時候我們再去加載這個文件
那么我們的 webpack 的配置該如何的去配置呢淮逻?
// config/webpack.config.multiple.js 打包
const webpack = require("webpack");
const path = require("path");
module.exports = {
entry: {
pageA: [path.resolve(__dirname, "../src/multiple/pageA.js")],
pageB: path.resolve(__dirname, "../src/multiple/pageB.js")
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].[chunkhash:8].js"
},
plugins: [
new webpack.optimize.CommonChunkPlugin({
name: "vendor",
minChunks: 2
}),
new webpack.optimize.CommonChunkPlugin({
name: "manifest",
chunks: ["vendor"]
})
]
};
單個配置多個 entry 是不夠的 -> 這樣只會生成兩個 bundle 文件弦蹂,將 pageA 和 pageB 所需要的內(nèi)容全部收入,跟單個入口文件并沒有區(qū)別翅溺,要做到代碼的切割咙崎,我們需要使用 webpackn 內(nèi)置的插件
CommonsChunkPlugin
。首先 webpack 執(zhí)行存在的一部分運行時代碼伊滋,即一部分初始化工作,就像之前單文件中的 __webpack_require__, 這個部分需要加載于所有文件之前昼浦,相當于初始化工作,少了這部分初始化代碼使兔,后面加載過來的代碼就無法識別并工作了火诸。
// 這個代碼的含義是置蜀,在這些入口文件中,找到那些 ***引用兩次的模塊*** (如:utilB)秋秤, 那么 utilB 會抽離成一個叫 vendor 文件。此時那部分初始化工作的代碼會被抽離到 vendor 文件中灼卢。
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: 2
});
// 這段代碼的含義是在 vendor 文件中幫我把初始化代碼抽離到 mainfest 文件中,此時 vendor 文件中就只剩下 utilB 這個模塊了涩咖,那么我們?yōu)槭裁葱枰@么做呢檩互?你可能會有這樣的問題
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
chunks: ["vendor"]
// minChunks: Infinity 可寫可不寫
});
我們?yōu)槭裁葱枰獙?webpack 的初始化模塊的代碼抽離到 manifest 文件中呢咨演?
因為這樣可以給 vendor 生成穩(wěn)定的 hash 值闸昨。每次修改業(yè)務(wù)代碼(pageA), 這段初始化代碼會發(fā)生變化。那么如果將這段初始化代碼放在 vendor 文件中的話饵较。每次都會生成新的
vendor.xxxx.js溉跃, 這樣不利于持久化緩存,好像對這個概念不是很理解告抄。沒關(guān)系撰茎,我們后面會講到這個問題, 另外 webpack 默認會抽離異步加載代碼。這個不需要做額外的配置打洼,pageB 中異步加載的 utilC 文件會直接抽離為 chunk.xxxx.js 文件
那么這個時候我們的頁面加載順序會是:
manifest.xxx.js; // 初始化代碼
vendor.xxx.js; // pageA 和 pageB 共同用到的模塊,抽離
pageX.xxx.js; // 業(yè)務(wù)代碼
// 當pageB 需要加載 utilC 的使用就會異步加載 utilC
那么我們執(zhí)行 webpack 打包, 我們看到打包之后的內(nèi)容退敦,我們來看看 manifest 文件如何做到初始化的工作钝域?
// dist/mainfest.xxx.js
(function(modules) {
// 在 window 對象中掛載了一個 webpack 的打包函數(shù)战虏, 拿到 chunkIds手趣, 和 modules
// <函數(shù)1>
window["webpackJsonp"] = function webpackJsonpCallback(
chunkIds,
moreModules
) {
var moduleId,
chunkId,
i = 0,
callbacks = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (installedChunks[chunkId])
callbacks.push.apply(callbacks, installedChunks[chunkId]);
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
while (callbacks.length) callbacks.shift().call(null, __webpack_require__);
if (moreModules[0]) {
installedModules[0] = 0;
return __webpack_require__(0);
}
};
var installedModules = {};
var installedChunks = {
4: 0
};
// <函數(shù)2>
function __webpack_require__(moduleId) {
// 和單文件一致
}
// requireEnsure
__webpack_require__.e = function requireEnsure(chunkId, callback) {
if (installedChunks[chunkId] === 0)
return callback.call(null, __webpack_require__);
if (installedChunks[chunkId] !== undefined) {
installedChunks[chunkId].push(callback);
} else {
installedChunks[chunkId] = [callback];
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.charset = "utf-8";
script.async = true;
script.src =
__webpack_require__.p +
"" +
chunkId +
"." +
({ "0": "pageA", "1": "pageB", "3": "vendor" }[chunkId] || chunkId) +
"." +
{ "0": "e72ce7d4", "1": "69f6bbe3", "2": "9adbbaa0", "3": "53fa02a7" }[
chunkId
] +
".js";
head.appendChild(script);
}
};
})([]);
與單文件內(nèi)容一致, 定義了一個自執(zhí)行的函數(shù),因為它不包含任何模塊,所以傳入的一個空數(shù)組,除了定義了一個 __webpack_require__ 加載的模塊棍现, 還另外定義了兩個函數(shù)用來進行加載模塊朴肺。
首先講解代碼前需要的兩個概念: module 和 Chunk
- Chunk 代表生成后的
js 文件
,一個chunkId
對應(yīng)一個打包好的 js 文件(共 5 個)肋乍,從這段代碼可以看出 manifest 的 chunkId 為 4帝雇,并且從代碼中還可以看到 0-3 分別對應(yīng)的 pageA, pageB,和異步加載的 utilC豁辉, vendor 公共模塊现使,那么這個就是我們?yōu)槭裁床荒軐⑦@段代碼放在 vendor 的原因欺抗,因為文件的 hash 值會變,內(nèi)容變了,vendor 生成的 hash 值也就變了 - module 對應(yīng)的
模塊
父晶,可以簡單的理解為打包前每個 js 文件對應(yīng)的一個模塊, 也就是之前 __webpack_require__ 加載模塊蓖乘, 同樣使用數(shù)組下標作為 moduleId 且是唯一不重復(fù)的
那么為什么要區(qū)分 Chunk 和 module 呢览徒?
首先使用 installedChunks 來保存每一個 chunkId 是否被加載過如果被加載過浪规,則說該 chunk 中所包含的模塊已經(jīng)被放到 modules 中,注意的是 modules 而不是 installedModuls, 我們來看一下 vendor chunk 打包出來的內(nèi)容:
// vendor.xxxx.js
webpackJsonp([3, 4], {
3: function(module, exports) {
module.exports = "util B";
}
});
在執(zhí)行完 manifest 后就會執(zhí)行 vendor 文件,結(jié)合上面的 webpack.Jsonp 的定義,我們可以知道 [3,4] 代表 chunkId,當加載到 vendor 文件后番舆,installedChunks[3] 和 installedChunks[4] 將會被變?yōu)?0圣蝎, 這表明 chunk3 和 chunk4 被加載過了等太。
webpackJsonpCallback
一共存在兩個參數(shù), chunkIds 一般會包含 chunk 文件依賴的 chunkId 以及自身 chunkId,moreModules 代表的 chunk 文件帶來新的模塊
佃迄;
var moduleId,
chunkId,
i = 0,
callbacks = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (installedChunks[chunkId])
callbacks.push.apply(callbacks, installedChunks[chunkId]);
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
while (callbacks.length) callbacks.shift().call(null, __webpack_require__);
if (moreModules[0]) {
installedModules[0] = 0;
return __webpack_require__(0);
}
簡簡單單來說 webpacJsonpCallback 做了哪些事情
首先判斷 chunkIds 在 installedChunks 里沒有回調(diào)函數(shù)未執(zhí)行完, 有的話則放到 callbacks 中猿涨, 并且等一下統(tǒng)一執(zhí)行澡绩,j 將 chunkIds 在 installedChunks 中全部表為 0, 然后將 moreModules 合并到 modules 中俺附。
這里面只有 mouules[0] 不是固定的肥卡,其他 modules 下標都是唯一的,在打包的時候 webpack 已經(jīng)為他們統(tǒng)一編號事镣,而 0 則為入口文件即 pageA 和 pageB 各有一個 modules[0]步鉴。
將 callbacks 執(zhí)行完畢并清空, 保證了該模塊加載開始前所有前置依賴內(nèi)容都會加載完畢蛮浑。最后判斷 morMModules[0]唠叛。有值說明該文件為入口文件,則開始執(zhí)行入口文件的模塊
那么上面的解釋沮稚,好像 pageA 這種同步加載 manifest艺沼, vendor 以及 pageA 文件來說,每次加載的時候 callbacks 都是為空的蕴掏,因為他們在 installedChunks 中的值要么為 uundefined(未加載)障般, 要么為 0 (已被加載), installedChunks[chunkId] 的值永遠為 false盛杰, 所以在這種情況下 callbacks 里面不會出現(xiàn)函數(shù)挽荡,如果僅僅是考慮這樣的場景,上面的 webpacJsonCallback 完全可以改為下面這樣
var moduleId,
chunkId,
i = 0,
callbacks = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (moreModules[0]) {
installedModules[0] = 0;
return __webpack_require__(0);
}
我們來看看異步加載 js 文件的時候(比如 pageB 中異步加載了 utilC 文件)即供, 那么這樣就沒有這么簡單了定拟,我們來看看 webpack 是如何異步加載腳本的:
// 異步加載函數(shù)掛載在 __webpack_require__.e 上
__webpack_require__.e = function requireEnsure(chunkId, callback) {
var installedChunkData = installedChunks[chunkId];
if (installedChunkData === 0) {
return new Promise(function(resolve) {
resolve();
});
}
// a Promise means "currently loading".
if (installedChunkData) {
return installedChunkData[2];
}
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
// start chunk loading
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.charset = "utf-8";
script.async = true;
script.timeout = 120000;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src =
__webpack_require__.p +
"" +
chunkId +
"." +
{ "0": "15fddb8c", "1": "9b6201b1", "2": "292dfd7b", "3": "167999ad" }[
chunkId
] +
".js";
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
chunk[1](new Error("Loading chunk " + chunkId + " failed."));
}
// 將該 chunk 變?yōu)槲醇虞d
installedChunks[chunkId] = undefined;
}
}
head.appendChild(script);
return promise;
};
我們看到上面的異步加載,可以看出 webpack 是基于 promise 的逗嫡, 所以需要引入 promise-polyfill青自,當腳本請求超時或者是加載失敗株依,會將 installedChunks[chunkId] 清空, 當下次重新請求該 chunk 文件會重新加載延窜,提高頁面的容錯性
大致分為三種情況(已經(jīng)加載過恋腕,正在加載中以及從未加載過)
已經(jīng)加載過該 Chunk 文件, 那就不用重新加載該 Chunk 了逆瑞, 直接執(zhí)行回調(diào)函數(shù)即可荠藤,可以理解假如頁面中有兩種操作需要加載
異步腳本,但是兩個腳本都依賴于公共模塊获高, 那么第二次加載的時候發(fā)現(xiàn)之前第一次操作已經(jīng)加載過了該 Chunk哈肖, 則不用獲取異步腳本了, 以為該公共模塊已經(jīng)被執(zhí)行過了谋减。從未被加載過牡彻,則動態(tài)的去插入 script 腳本去請求 js 文件扫沼, 這就為什么取名 webpackJsonpCallback, 因為跟 jsonp 的思想很類型出爹, 所以這種異步腳本在做腳本錯誤監(jiān)控時會經(jīng)常出現(xiàn)
script error
正在加載中代表該 Chunk 文件已經(jīng)在加載中,比如說點擊按鈕觸發(fā)異步腳本缎除,用戶點擊太快了严就,連點兩次就可能會出現(xiàn)這種情況,此時將回調(diào)函數(shù)放入 installedChunks器罐。
我們通過 utiC 生成的 Chunk:
webpackJsonp(
[0],
[
,
/* 0 */ /* 1 */
/***/ function(module, exports) {
module.exports = "utils C";
/***/
}
]
);
//那么需要異步加載這個 Chunk:
/***/ 6:
/***/ (function(module, exports, __webpack_require__) {
const utilB = __webpack_require__(0);
console.log(utilB);
// 異步加載文件梢为,類似于 import()
const utilC = () => __webpack_require__.e/* require.ensure */(0).then((function (require) {
console.log(__webpack_require__(1))
}).bind(null, __webpack_require__)).catch(__webpack_require__.oe);
utilC();
/***/ })
當 pageB 進行某些操作需要加載 utilC 時,就會執(zhí)行 __webpack_require__.e(0).then(xxx), 代表需要加載的模塊 chunkId(utilC)轰坊, 異步加載 utilC 并將 callback 添加到 installedChunks 中铸董, 然后當 utilC 的 Chunk 文件加載完畢后,chunkids 包含 0肴沫, 發(fā) iinstalledChunks[2] 是一個數(shù)組粟害, 里面還有之前未執(zhí)行的 callback 函數(shù),那么這樣颤芬,那我們將自己帶來的模塊先放到 modules 中悲幅,然后在統(tǒng)一執(zhí)行之前未完成的 callbacks 中的函數(shù),這里指的是存放于 installedChunks[2] 中回調(diào)函數(shù)(可能存在多個)站蝠,
這也說明這里先后順序
:
// 先將 moreModules 合并到 modules, 再去執(zhí)行 callbacks, 不然之前未執(zhí)行的 callback 依賴于新來的模塊汰具,你不放進 module 我豈不是得不到想要的模塊
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
while (callbacks.length) callbacks.shift().call(null, __webpack_require__);
在 webpack2 或者之后的版本中:
在 version2 中 moduleId[0] 不在為入口函數(shù)做保留,所以說明 moduleId[0] 不在是入口打包函數(shù)菱魔,取而代之的是
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {}
那么傳入的第三個參數(shù) executeModules, 這個數(shù)組留荔, 如果參數(shù)存在則說明它是入口函數(shù)模塊, 然后在去執(zhí)行
那么 webpack 的 tree shaking
(官方解釋)
這里簡單解釋下澜倦, tree shaking 在打包過程中將沒有用的代碼進行清除(dead code)聚蝶, 一般 dead code 具有一下特征:
- 代碼不會被執(zhí)行拔疚, 不可到達
- 代碼執(zhí)行結(jié)果不會被用到
- 代碼只會影響死變量(只寫不讀)
首先,模塊引入要基于 ES6 模塊機制既荚,不在使用 commonjs 規(guī)范稚失,因為 es6 模塊的依賴關(guān)系是確定的,和運行時的狀態(tài)無關(guān)恰聘,可以進行可靠的靜態(tài)分析句各,然后清除沒有用的代碼, 而 commonjs 的依賴關(guān)系是要到運行時才能確定下來晴叨,其次需要開始 js 壓縮凿宾, 使用 UglifysPlugin 這個插件對代碼進行壓縮,我們看下面的例子:
// webpack.config.js
const webpack = require("webpack");
const path = require("path");
module.exports = {
entry: {
pageA: path.resolve(__dirname, "../src/es6/pageA.js")
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].[chunkhash:8].js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
};
那么我們引入沒有用的變量兼蕊,函數(shù)會被清除初厚,未執(zhí)行的代碼會被清除,但是類的方法不會被清除孙技, 因為 webpack 不會區(qū)分是定義在 classC 的 prototype 還是其他 Array 的 prototype 的产禾,比如將 classC 寫成這樣,
webpack 無法保證 prototype 掛載的對象是 classC牵啦, 這種代碼亚情,靜態(tài)分析是完成不了的,就算可以哈雏,不能保證完全正確楞件,所以 webpack 干脆不處理方法,不對類進行 tree shaking
const classC = function() {};
var a = "class" + "C";
var b;
if (a === "Array") {
b = a;
} else {
b = "classC";
}
b.prototype.saySomething = function() {
console.log("class C");
};
export default classC;
那么 webpack3 是如何做到 scope hoisting (作用域提升)
scope hoisting, 顧名思義就是將模塊的作用域提升裳瘪,在 webpack 中不能將所有的模塊直接放在同一個作用下土浸, 有一下幾個原因:
- 按需加載的模塊
- 使用 commonjs 規(guī)范的模塊
- 被多個 entry 共享的模塊
在 webpack3 中, 這些情況生成的模塊不會進行作用域提升彭羹,下面?zhèn)€例子:
我們看這個例子比較典型黄伊, utilA 被 pageA 和 pageB 共享,utilB 被 pageB 單獨加載皆怕, utilC 被 pageB 異步加載毅舆。那么 webpack3 生效,則需要 plugins 中添加 ModuleConcatenationPlugin愈腾。
// src/hoist/utilA.js
export const utilA = "util A";
export function funcA() {
console.log("func A");
}
// src/hoist/utilB.js
export const utilB = "util B";
export function funcB() {
console.log("func B");
}
// src/hoist/utilC.js
export const utilC = "util C";
// src/hoist/pageA.js
import { utilA, funcA } from "./utilA";
console.log(utilA);
funcA();
// src/hoist/pageB.js
import { utilA } from "./utilA";
import { utilB, funcB } from "./utilB";
funcB();
import("./utilC").then(function(utilC) {
console.log(utilC);
});
我們來看看這個配置如下:
const webpack = require("webpack");
const path = require("path");
module.exports = {
entry: {
pageA: path.resolve(__dirname, "../src/hoist/pageA.js"),
pageB: path.resolve(__dirname, "../src/hoist/pageB.js")
},
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].[chunkhash:8].js"
},
plugins: [
// 需要使用這個插件
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: 2
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
})
]
};
那么在 webpack4 中有那些新的東西呢憋活?
- 配置默認初始化一個些配置, 比如: entry 默認是 ./src
- 開發(fā)模式和發(fā)布模式虱黄, 插件默認內(nèi)置
- CommonsChunk 配置簡化
- 使用 ES6 語法悦即,比如: Map, Set, includes
- 新增 WebAssembly 構(gòu)建支持
- 如果要使用 webpack cli 命令辜梳, 需要單獨安裝 webpack-cli
默認配置:
在webpack4 中不再要求強制指定 entry 和 output 路徑粱甫, 在 webpack4 會默認使用 ./src (entry), ./dist (output);
構(gòu)建mode:
webpack4 配置, 必p配置 mode 屬性作瞄,k可選值有 development茶宵, production, none宗挥,
development 默認開啟插件(無需配置):
NamedModulesPlugin > optimization.namedModules
development 模式乌庶, 使用 eval 構(gòu)建 module, 用來提升構(gòu)建速度
webpack.DefinePlugin 插件 process.env.NODE_ENV 的值不需要再定義契耿, 默認是 development
production 默認開啟插件(無需配置):
NoEmitOnErrorsPlugin > optimization.noEmitOnErrors
ModuleCOncatenationPlugin > optimization.concatenateModules
webpack.DefinePlugin 插件 process.env.NODE_ENV 的值不需要再定義瞒大, 默認是 production
公共代碼提取:
webpack3 的 commonsChunk hash 問題不是很優(yōu)雅搪桂, 使用復(fù)雜透敌, webpack4 中直接將 CommonsChunkPlugin
插件直接改為
optimization.splitChunks
和 optmization.runTimeChunk
兩個配置
-
webapck3:
plugins:[ new webpack.optimize.CommonsChunkPlugin({ names: 'common'}), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', chunks:['common']}) ]
webpack4
optimization: {
splitChunks: {
chunks: 'all',
name: 'common',
},
runtimeChunk: {
name: 'runtime',
}
}
壓縮
壓縮插件更新到 uglifyjs-webpack-plugin 1.0 版本,支持多進程壓縮踢械,緩存以及 es6 語法酗电, 無需單獨安裝轉(zhuǎn)換器, 當
mode='production'
默認開始時壓縮裸燎, 無需配置顾瞻, 可以通過, ``optimization.minimize 和 optimization.minimizer``` 自定義配置德绿, 測試發(fā)現(xiàn),第二次打包時間是第一次打包的一半左右
. optimization.minimize 是否啟用壓縮
. optimizatioon.minimizer 制定壓縮庫退渗, 默認 uglifyjs-webpack-plugin 1.0
optimization: {
minimize:true
}
配置優(yōu)化
webpack4 提供了 sideEffects 的配置移稳, 引入的第三方插件在 package.json 里面配置。sideEffects: fasle, 后会油, 據(jù)說是可以大幅度的減少打包出的體積个粱; 目前初步了解 sideEffects 的信息: sideEffects: false, 標示該模塊無副作用,當你需要導(dǎo)入翻翩,但不需要導(dǎo)出任何東西時都许,但需要導(dǎo)入時
【未完待續(xù).......】