【W(wǎng)ebpack】1362- 通過插圖來理解webpack

相信每個(gè)前端開發(fā)者都聽說過webpack。作為前端開發(fā)最重要的構(gòu)建工具,它極大地提高了我們的開發(fā)效率。
雖然網(wǎng)上有很多關(guān)于Webpack的教程,但由于Webpack本身的復(fù)雜性,很多初學(xué)者在閱讀這些教程后仍然無法理解Webpack。所以本文試著寫一個(gè)更容易理解的教程,并制作一些動(dòng)畫。幫助更容易地理解它。
webpack做了什么?
Webpack的核心功能是將不同的JavaScript模塊合并在一起。假設(shè)我們有兩個(gè)JavaScript文件:add.js :
exports.default = function (a, b) {
return a + b
}
index.js :
var add = require('./add.js').default
console.log(add(1, 2))
它們使用CommonJS標(biāo)準(zhǔn)模塊語法。如果我們直接使用nodejs來執(zhí)行這段代碼,它可以正常工作:
$ node index.js3
但是,如果它們被直接引用到HTML文件中,瀏覽器將無法正確執(zhí)行以下代碼:
<script src="./add.js"></script>
<script src="./index.js"></script>

這主要是因?yàn)镃ommonJS標(biāo)準(zhǔn)不是Web API的一部分。瀏覽器無法理解exports對(duì)象和require函數(shù),因此無法正確執(zhí)行上述代碼。為了解決這個(gè)問題,我們可以使用Webpack來打包代碼。Webpack需要做的是將上述代碼轉(zhuǎn)換成瀏覽器可以理解的新版本,并且不更改原始的執(zhí)行邏輯。理解一個(gè)特性的最好方法是自己實(shí)現(xiàn)它。讓我們看看相關(guān)功能是如何實(shí)現(xiàn)的。
實(shí)現(xiàn)Exports
如果我們想加載一個(gè)模塊,我們只需要兩個(gè)步驟:
讀取文件的內(nèi)容 然后將字符串轉(zhuǎn)換為可執(zhí)行代碼 在讀取文件內(nèi)容時(shí),我們可以使用類似 fs.readfileSync()的API。然后我們可以使用eval函數(shù)將文件中的字符串作為代碼執(zhí)行。
所以我們可以把a(bǔ)dd.js轉(zhuǎn)換為:
var exports = {}
eval('exports.default = function(a,b) {return a + b}')
瀏覽器可以理解這些代碼:

我們可以用動(dòng)畫來表示這個(gè)過程:



然而,上面的實(shí)現(xiàn)有一個(gè)輕微的缺點(diǎn),即如果在模塊中聲明了一個(gè)變量,那么它在eval之后將成為一個(gè)全局變量,從而污染全局命名空間。

為了解決這個(gè)問題,我們用立即調(diào)用的函數(shù)表達(dá)式來封裝作用域:
var exports = {}
(function (exports, code) {
eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')
這就是Webpack處理導(dǎo)出的方式。
實(shí)現(xiàn)Require
那么如何編寫require函數(shù)呢?require函數(shù)需要做的事情非常簡(jiǎn)單,即取出exports中的內(nèi)容。我們可以簡(jiǎn)化這個(gè)問題,假設(shè)我們只需要在add中加載內(nèi)容。require函數(shù)可以這樣寫:
function require(file) {
var exports = {};
(function (exports, code) {
eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')
return exports
}
如果我們?yōu)榇a轉(zhuǎn)換過程設(shè)置動(dòng)畫,它應(yīng)該如下所示:

如果需要加載多個(gè)模塊,那么我們應(yīng)該將所有模塊的文件名和代碼字符串組織到一個(gè)鍵值表中,然后我們可以根據(jù)參數(shù)加載不同的模塊:
let moduleList = {
"index.js": `
var add = require('add.js').default
console.log(add(1 , 2))
`,
"add.js": `exports.default = function(a,b){return a + b}`,
}
function require(file) {
var exports = {};
(function (exports, code) {
eval(code);
})(exports, moduleList[file]);
return exports;
}
require("index.js");
最后,為了避免引入變量moduleList,我們可以將上述代碼編寫為立即調(diào)用的函數(shù)表達(dá)式:
(function (list) {
function require(file) {
var exports = {};
(function (exports, code) {
eval(code);
})(exports, list[file]);
return exports;
}
require("index.js");
})({
"index.js": `
var add = require('add.js').default
console.log(add(1 , 2))
`,
"add.js": `exports.default = function(a,b){return a + b}`,
});
這里我們只是簡(jiǎn)單的實(shí)現(xiàn)require函數(shù)。
總過程
除了解決exports 和 require問題,Webpack還將做很多事情。在打包過程中,Webpack還處理模塊之間的依賴關(guān)系。它生成的依賴關(guān)系圖如下所示:
{
"./src/index.js": {
"deps": { "./add.js": "./src/add.js" },
"code": "....."
},
"./src/add.js": {
"deps": {},
"code": "......"
}
}
此外,Webpack需要將ES6語法轉(zhuǎn)換為ES5語法。綜上所述,Webpack打包的過程大致如下:
分析模塊之間的依賴關(guān)系 將ES6轉(zhuǎn)換為ES5 替代exports 和 require 我們可以用動(dòng)畫來表示這個(gè)過程:

?譯自:https://betterprogramming.pub/struggling-with-webpack-understand-it-through-illustrations-c8009e6ad4d5
?
