來(lái)源:https://www.sitepoint.com/webpack-beginner-guide/
現(xiàn)在闻葵,我們被迫使用許多輔助工具來(lái)方便藐握、加速和優(yōu)化我們的web開(kāi)發(fā)工作流程瞪浸。不過(guò),這些工具通常會(huì)給堆棧增加額外的復(fù)雜性悬蔽。因此棒仍,我們需要利用額外的時(shí)間和精力來(lái)正確地學(xué)習(xí)讯柔、理解和使用這些工具抡蛙。webpack也是如此。
第一次使用webpack時(shí)魂迄,可能很難理解它是如何工作的以及應(yīng)該如何使用它粗截。盡管它有很好的文檔,但對(duì)于新手來(lái)說(shuō)捣炬,它可能會(huì)讓人望而生畏熊昌,而且它有一個(gè)陡峭的學(xué)習(xí)曲線。然而湿酸,webpack是值得學(xué)習(xí)的婿屹,從長(zhǎng)遠(yuǎn)來(lái)看,它可以節(jié)省大量的時(shí)間和精力推溃。在本教程中昂利,我將介紹所有的核心概念,以幫助您入門铁坎。
注意:在本教程中蜂奸,我使用的是webpack 5.9.0。
一硬萍、Webpack是什么?
作為其核心扩所,webpack是一個(gè)靜態(tài)模塊捆綁器。在一個(gè)特定的項(xiàng)目中朴乖,webpack將所有的文件和資源都視為模塊祖屏。在底層,它依賴于一個(gè)依賴圖寒砖。依賴關(guān)系圖描述了模塊如何使用文件之間的引用(require和import語(yǔ)句)相互關(guān)聯(lián)赐劣。通過(guò)這種方式,webpack靜態(tài)遍歷所有模塊來(lái)構(gòu)建圖形哩都,并使用它生成一個(gè)bundle(或多個(gè)bundle)——一個(gè)JavaScript文件魁兼,包含所有模塊的代碼,并按正確的順序組合在一起。" static "的意思是咐汞,當(dāng)webpack構(gòu)建它的依賴關(guān)系圖時(shí)盖呼,它不會(huì)執(zhí)行源代碼,而是將模塊及其依賴關(guān)系縫合到一個(gè)包中化撕。然后可以將其包含在HTML文件中几晤。
現(xiàn)在,為了擴(kuò)展上面的概述植阴,讓我們探討一下webpack使用的主要概念蟹瘾。
二、Webpack主要概念
Webpack有一些主要的概念掠手,在深入了解它的實(shí)際實(shí)現(xiàn)之前憾朴,我們需要清楚地理解它們。讓我們逐一檢查它們:
Entry:入口點(diǎn)是webpack用來(lái)開(kāi)始構(gòu)建其內(nèi)部依賴圖的模塊喷鸽。從那里众雷,它確定入口點(diǎn)(直接和間接地)依賴哪些其他模塊和庫(kù),并將它們包含在圖中做祝,直到?jīng)]有任何依賴關(guān)系砾省。默認(rèn)情況下,entry屬性被設(shè)置為./src/index.js混槐,但我們可以在webpack配置文件中指定不同的模塊(甚至多個(gè)模塊)编兄。
Output: Output屬性指示webpack在哪里發(fā)出包以及文件的名稱。這個(gè)屬性的默認(rèn)值是主包的./dist/main.js声登,其他生成的文件的./dist -例如圖像翻诉。當(dāng)然,我們可以根據(jù)需要在配置中指定不同的值捌刮。
Loaders:默認(rèn)情況下碰煌,webpack只理解JavaScript和JSON文件。為了處理其他類型的文件并將它們轉(zhuǎn)換成有效的模塊绅作,webpack使用了加載器芦圾。加載器轉(zhuǎn)換非javascript模塊的源代碼,允許我們?cè)趯⑦@些文件添加到依賴關(guān)系圖之前對(duì)它們進(jìn)行預(yù)處理俄认。例如个少,加載器可以將CoffeeScript語(yǔ)言的文件轉(zhuǎn)換為JavaScript,或者將內(nèi)聯(lián)圖像轉(zhuǎn)換為數(shù)據(jù)url眯杏。有了加載器夜焦,我們甚至可以直接從JavaScript模塊中導(dǎo)入CSS文件。
Plugins:插件用于加載器不能完成的任何其他任務(wù)岂贩。它們?yōu)槲覀兲峁┝岁P(guān)于資產(chǎn)管理茫经、bundle最小化和優(yōu)化等廣泛的解決方案。
Mode:通常,當(dāng)我們開(kāi)發(fā)應(yīng)用程序時(shí)卸伞,我們使用兩種類型的源代碼——一種用于開(kāi)發(fā)構(gòu)建抹镊,另一種用于生產(chǎn)構(gòu)建。Webpack允許我們通過(guò)將mode參數(shù)更改為development荤傲、production或none來(lái)設(shè)置我們想要生成哪一個(gè)垮耳。這允許webpack使用對(duì)應(yīng)于每個(gè)環(huán)境的內(nèi)置優(yōu)化。默認(rèn)值為production遂黍。none模式意味著不使用默認(rèn)的優(yōu)化選項(xiàng)终佛。要了解更多關(guān)于webpack在開(kāi)發(fā)和生產(chǎn)模式中使用的選項(xiàng),請(qǐng)?jiān)L問(wèn)模式配置頁(yè)面雾家。
三查蓉、Webpack是如何工作的
在本節(jié)中,我們將研究webpack是如何工作的榜贴。即使是一個(gè)簡(jiǎn)單的項(xiàng)目也包含HTML、CSS和JavaScript文件妹田。此外唬党,它還可以包含字體、圖像等資產(chǎn)鬼佣。所以驶拱,一個(gè)典型的webpack工作流應(yīng)該包括用適當(dāng)?shù)腃SS和JS鏈接建立一個(gè)index.html文件,以及必要的資源晶衷。此外蓝纲,如果您有許多相互依賴的CSS和JS模塊,則需要對(duì)它們進(jìn)行優(yōu)化晌纫,并將它們適當(dāng)?shù)亟M合到一個(gè)單元中税迷,以便投入生產(chǎn)。
要做到這一點(diǎn)锹漱,webpack依賴于配置箭养。從版本4及以上開(kāi)始,webpack提供了合理的默認(rèn)值哥牍,因此不需要?jiǎng)?chuàng)建配置文件毕泌。然而,對(duì)于任何重要的項(xiàng)目嗅辣,您都需要提供一個(gè)特殊的webpack.config.js文件撼泛,該文件描述了應(yīng)該如何轉(zhuǎn)換文件和資產(chǎn),以及應(yīng)該生成什么樣的輸出澡谭。這個(gè)文件可能很快就會(huì)變成一個(gè)整體愿题,這使得理解webpack是如何工作的變得很困難,除非你知道它工作背后的主要概念。
基于所提供的配置抠忘,webpack從入口點(diǎn)開(kāi)始撩炊,并在構(gòu)建依賴關(guān)系圖時(shí)解析它遇到的每個(gè)模塊。如果模塊包含依賴項(xiàng)崎脉,則會(huì)針對(duì)每個(gè)依賴項(xiàng)遞歸地執(zhí)行進(jìn)程拧咳,直到遍歷完成。然后webpack將所有的項(xiàng)目模塊打包成少量的包——通常只有一個(gè)——由瀏覽器加載囚灼。
四骆膝、Webpack 5有什么新內(nèi)容
webpack 5于2020年10月發(fā)布。這篇文章很長(zhǎng)灶体,探討了webpack的所有變化阅签。不可能提到所有的更改,對(duì)于像這樣的初學(xué)者指南來(lái)說(shuō)也沒(méi)有必要蝎抽。相反政钟,我將試著列出一個(gè)小列表,包含一些一般的重點(diǎn):
(1)持久性緩存提高了構(gòu)建性能樟结。開(kāi)發(fā)人員現(xiàn)在可以啟用基于文件系統(tǒng)的緩存养交,這將加快開(kāi)發(fā)構(gòu)建。
(2)長(zhǎng)期緩存也得到了改進(jìn)瓢宦。在webpack 5中碎连,對(duì)代碼所做的改變不會(huì)影響最小化的bundle版本(注釋、變量名)驮履,這不會(huì)導(dǎo)致緩存失效鱼辙。此外,還添加了新的算法玫镐,以確定的方式將短數(shù)字id分配給模塊和塊倒戏,并將短名稱分配給導(dǎo)出。在webpack 5中恐似,它們?cè)谏a(chǎn)模式下默認(rèn)是啟用的峭梳。
(3)改進(jìn)了包的大小,這要?dú)w功于更好的搖樹(shù)和代碼生成蹂喻。多虧了新的嵌套搖樹(shù)功能葱椭,webpack現(xiàn)在能夠跟蹤對(duì)導(dǎo)出的嵌套屬性的訪問(wèn)。CommonJs搖樹(shù)讓我們可以消除未使用的CommonJs導(dǎo)出口四。
(4)最小支持Node.js版本從6增加到10.13.0 (LTS)孵运。
(5)代碼庫(kù)被清理干凈。所有webpack 4中標(biāo)記為棄用的項(xiàng)目都將被移除蔓彩。
(6)自動(dòng)的Node.js膩?zhàn)幽_本被移除治笨。以前的webpack版本包含了原生Node.js庫(kù)的膩?zhàn)幽_本驳概,比如crypto。在許多情況下旷赖,它們是不必要的顺又,并且會(huì)極大地增加包的大小。這就是為什么webpack 5會(huì)自動(dòng)停止填充這些核心模塊等孵,而專注于前端兼容模塊稚照。
(7)作為開(kāi)發(fā)的一個(gè)改進(jìn),webpack 5允許我們傳遞目標(biāo)列表俯萌,也支持target的版本果录。它提供了公共路徑的自動(dòng)確定。此外咐熙,它還提供了自動(dòng)的弱恒、唯一的命名,這可以防止多個(gè)webpack運(yùn)行時(shí)使用相同的全局變量進(jìn)行塊加載時(shí)發(fā)生沖突棋恼。
(8)webpack-dev-server命令現(xiàn)在是webpack serve返弹。
(9)引入了資產(chǎn)模塊,它取代了文件加載器爪飘、原始加載器和url加載器的使用义起。
五、開(kāi)始
注意:你可以在GitHub repo中找到我們項(xiàng)目的文件(https://github.com/sitepoint-editors/learn_webpack)悦施。
現(xiàn)在我們有了堅(jiān)實(shí)的理論基礎(chǔ),讓我們?cè)趯?shí)踐中實(shí)現(xiàn)它去团。
首先抡诞,我們將創(chuàng)建一個(gè)新目錄并切換到它。然后我們將初始化一個(gè)新項(xiàng)目:
mkdir learn-webpack
cd learn-webpack
npm init -y
接下來(lái)土陪,我們需要在本地安裝webpack和webpack CLI(命令行界面):
npm install webpack webpack-cli --save-dev
現(xiàn)在昼汗,生成的package.json的內(nèi)容應(yīng)該類似于以下內(nèi)容:
{
? "name": "learn-webpack",
? "version": "1.0.0",
? "description": "",
? "main": "index.js",
? "scripts": {
? ? "test": "echo \"Error: no test specified\" && exit 1"
? },
? "keywords": [],
? "author": "",
? "license": "ISC",
? "devDependencies": {
? ? "webpack": "^5.9.0",
? ? "webpack-cli": "^4.2.0"
? }
}
除了作為一個(gè)包管理器,npm還可以作為一個(gè)簡(jiǎn)單的任務(wù)運(yùn)行器鬼雀。我們可以創(chuàng)建webpack任務(wù)顷窒,方法是在package.json文件的scripts部分中加上任務(wù)的名稱和指令。現(xiàn)在讓我們?cè)囋囘@個(gè)源哩。打開(kāi)package.json鞋吉,將scripts對(duì)象更改為如下內(nèi)容:
"scripts": {
? "test": "echo \"Error: no test specified\" && exit 1",
? "dev": "webpack --mode development",
? "build": "webpack --mode production"
},
在scripts屬性中,npm允許我們引用本地安裝的Node.js包的名稱励烦。我們使用這個(gè)和——mode標(biāo)志來(lái)定義開(kāi)發(fā)和構(gòu)建任務(wù)谓着,它們將分別在開(kāi)發(fā)(npm run dev)和生產(chǎn)(npm run build)模式下運(yùn)行webpack。
在測(cè)試我們剛剛創(chuàng)建的任務(wù)之前坛掠,讓我們創(chuàng)建一個(gè)src目錄赊锚,并在其中放入一個(gè)index.js文件治筒,這樣它就包含了console.log("Hello, Webpack!");現(xiàn)在我們已經(jīng)可以運(yùn)行dev任務(wù)來(lái)啟動(dòng)webpack在開(kāi)發(fā)模式:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.js 874 bytes [emitted] (name: main)
./src/index.js 31 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 122 ms
正如我之前提到的,webpack將默認(rèn)入口點(diǎn)設(shè)置為./src/index.js舷蒲,將默認(rèn)輸出設(shè)置為./dist/main.js耸袜。因此,當(dāng)我們運(yùn)行dev任務(wù)時(shí)牲平,webpack所做的就是從index.js文件中獲取源代碼堤框,并將最終代碼打包到main.js文件中。
太棒了!它像預(yù)期的那樣工作欠拾。但是為了驗(yàn)證我們得到了正確的輸出胰锌,我們需要在瀏覽器中顯示結(jié)果。為了做到這一點(diǎn)藐窄,讓我們?cè)赿ist目錄下創(chuàng)建一個(gè)index.html文件:
<!doctype html>
<html>
? <head>
? ? <title>Getting Started With Webpack</title>
? </head>
? <body>
? ? <script src="main.js"></script>
? </body>
</html>
現(xiàn)在资昧,如果我們?cè)跒g覽器中打開(kāi)這個(gè)文件,我們應(yīng)該會(huì)看到Hello, Webpack!消息荆忍。
到目前為止格带,一切順利。但在某些情況下刹枉,手動(dòng)編寫(xiě)index.html文件可能會(huì)有問(wèn)題叽唱。例如,如果我們改變?nèi)肟邳c(diǎn)的名稱微宝,生成的bundle將被重命名棺亭,但是我們的index.html文件仍然會(huì)引用舊的名稱。因此蟋软,每次重命名一個(gè)入口點(diǎn)或添加一個(gè)新入口點(diǎn)時(shí)镶摘,我們都需要手動(dòng)更新HTML文件。幸運(yùn)的是岳守,我們可以用html-webpack-plugin輕松地解決這個(gè)問(wèn)題凄敢。讓我們現(xiàn)在安裝它:
npm install html-webpack-plugin@next --save-dev
注意:注意我輸入的是html-webpack-plugin@next而不是html-webpack-plugin。在撰寫(xiě)本文時(shí)湿痢,前者是webpack 5的合適版本涝缝,而后者是webpack 4的合適版本。這在將來(lái)可能會(huì)改變譬重,所以對(duì)于實(shí)際版本拒逮,請(qǐng)檢查html-webpack-plugin repo。
現(xiàn)在臀规,為了激活這個(gè)插件消恍,我們需要在根目錄下創(chuàng)建一個(gè)webpack.config.js文件,包含以下內(nèi)容:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');
module.exports = {
? plugins: [
? ? new HtmlWebpackPlugin({
? ? ? title: "Webpack Output",
? ? }),
? ],
};
如你所見(jiàn)以现,要激活webpack插件狠怨,我們需要把它包含在文件中约啊,然后把它添加到plugins數(shù)組中。如果需要佣赖,我們也將選項(xiàng)傳遞給插件恰矩。請(qǐng)參閱html-webpack-plugin repo了解所有可用的選項(xiàng)以及編寫(xiě)和使用自己模板的能力。
現(xiàn)在運(yùn)行webpack看看會(huì)發(fā)生什么:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.js 874 bytes [compared for emit] (name: main)
asset index.html 234 bytes [emitted]
./src/index.js 31 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 151 ms
讓我們打開(kāi)index.html憎蛤。如我們所見(jiàn)外傅,插件會(huì)自動(dòng)為我們創(chuàng)建一個(gè)更新后的index.html文件,該文件使用了配置文件中的title選項(xiàng):
<!DOCTYPE html>
<html>
? <head>
? ? <meta charset="utf-8">
? ? <title>Webpack Output</title>
? ? <meta name="viewport" content="width=device-width, initial-scale=1">
? ? <script defer src="main.js"></script>
? </head>
? <body>
? </body>
</html>
現(xiàn)在讓我們展開(kāi)項(xiàng)目俩檬,并為輸入和輸出屬性指定自定義名稱萎胰。在webpack.config.js中,我們?cè)趐lugins屬性前添加以下內(nèi)容:
entry: {
? main: path.resolve(__dirname, './src/app.js'),
},
output: {
? filename: '[name].bundle.js',
? path: path.resolve(__dirname, 'deploy')
},
在這里棚辽,我們將輸入文件更改為app.js技竟,并將輸出文件夾更改為要部署的文件夾。我們還稍微調(diào)整了生成的bundle文件的名稱∏辏現(xiàn)在它將以條目的名稱(“main”)開(kāi)始榔组,后面是單詞“bundle”和.js文件擴(kuò)展名。
現(xiàn)在联逻,我們要?jiǎng)?chuàng)建一個(gè)src/component.js文件:
export default (text = "Hello, Webpack!") => {
? const element = document.createElement("h1");
? element.innerHTML = text;
? return element;
};
接下來(lái)搓扯,我們將index.js重命名為app.js,以反映我們的改變包归,并將其內(nèi)容替換為以下內(nèi)容:
import component from './component';
document.body.appendChild(component());
現(xiàn)在锨推,讓我們?cè)俅芜\(yùn)行webpack:
$ npm run dev
> learn-webpack@1.0.0 dev C:\WEBDEV\learn-webpack
> webpack --mode development
[webpack-cli] Compilation finished
asset main.bundle.js 4.67 KiB [emitted] (name: main)
asset index.html 241 bytes [emitted]
runtime modules 668 bytes 3 modules
cacheable modules 230 bytes
? ./src/app.js 79 bytes [built] [code generated]
? ./src/component.js 151 bytes [built] [code generated]
webpack 5.9.0 compiled successfully in 194 ms
讓我們檢查并澄清webpack輸出的信息。在“編譯完成”消息之后公壤,您可以看到在deploy目錄中生成的文件(main.bundle.js和index.html)换可。在它們下面,你可以看到源文件:入口模塊(app.js)及其依賴項(xiàng)(component.js)境钟。
現(xiàn)在锦担,在deploy文件夾中俭识,我們有了新生成的包文件main.bundle.js。如果我們?cè)跒g覽器中打開(kāi)index.html文件,我們會(huì)看到Hello, Webpack!界面顯示蹬屹。
同樣扮碧,如果我們檢查index.html的源代碼,我們會(huì)看到腳本標(biāo)記中的src屬性的值被更新為main.bundle.js堤瘤。
現(xiàn)在玫芦,我們可以刪除webpack最初生成的dist文件夾,因?yàn)槲覀儾辉傩枰恕?/p>
六本辐、將現(xiàn)代JavaScript編譯為ES5
在本節(jié)中桥帆,我們將了解如何將ES6編譯成兼容es5的代碼医增,這些代碼可以在所有瀏覽器中運(yùn)行。讓我們先運(yùn)行以下命令:
npm run dev -- --devtool inline-source-map
在這里老虫,我運(yùn)行webpack并將devtool選項(xiàng)設(shè)置為inline-source-map叶骨,以便使代碼更具可讀性。這樣我就可以更清楚地演示從ES6到ES5的代碼轉(zhuǎn)換過(guò)程祈匙。
接下來(lái)忽刽,讓我們打開(kāi)main.bundle.js:
/***/ "./src/component.js":
/*!**************************!*\
? !*** ./src/component.js ***!
? \**************************/
/*! namespace exports */
/*! export default [provided] [no usage info] [missing usage info prevents renaming] */
/*! other exports [not provided] [no usage info] */
/*! runtime requirements: __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */? "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((text = "Hello, Webpack!") => {
? const element = document.createElement("h1");
? element.innerHTML = text;
? return element;
});
/***/ })
如你所見(jiàn),默認(rèn)情況下夺欲,component.js模塊中的現(xiàn)代ES6特性(箭頭函數(shù)和const聲明)不會(huì)轉(zhuǎn)換為兼容es5的代碼跪帝。為了讓我們的代碼能在舊的瀏覽器上運(yùn)行,我們必須添加Babel加載器:
npm install babel-loader @babel/core @babel/preset-env --save-dev
然后些阅,在webpack.config.js中伞剑,在output屬性后添加module:
module: {
? rules: [
? ? {
? ? ? test: /\.js$/,
? ? ? exclude: /node_modules/,
? ? ? use: {
? ? ? ? loader: 'babel-loader',
? ? ? ? options: {
? ? ? ? ? presets: ['@babel/preset-env']
? ? ? ? }
? ? ? }
? ? },
? ]
},
當(dāng)我們?yōu)閣ebpack加載器定義規(guī)則時(shí),通常需要定義三個(gè)主要的屬性:
test扑眉,它描述了應(yīng)該轉(zhuǎn)換什么樣的文件纸泄。
exclude,它定義了不應(yīng)該被加載器處理的文件(如果有這些文件的話)腰素。
use聘裁,它告訴哪個(gè)加載器應(yīng)該針對(duì)匹配的模塊使用。在這里弓千,我們還可以設(shè)置加載器選項(xiàng)衡便,就像我們剛剛完成的預(yù)置選項(xiàng)一樣。
重新執(zhí)行如下命令:
npm run dev -- --devtool inline-source-map
這一次洋访,main.bundle.js中的代碼被編譯:
/***/ "./src/component.js":
/*!**************************!*\
? !*** ./src/component.js ***!
? \**************************//*! namespace exports *//*! export default [provided] [no usage info] [missing usage info prevents renaming] *//*! other exports [not provided] [no usage info] *//*! runtime requirements: __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* *//***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) =>{__webpack_require__.r(__webpack_exports__);/* harmony export */__webpack_require__.d(__webpack_exports__,{/* harmony export */"default":()=>__WEBPACK_DEFAULT_EXPORT__/* harmony export */});/* harmony default export */const__WEBPACK_DEFAULT_EXPORT__=(function(){vartext=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Hello, Webpack!";varelement=document.createElement("h1");element.innerHTML=text;returnelement;});/***/})
完美的×蜕拢現(xiàn)在我們可以使用現(xiàn)代的JS特性了,webpack會(huì)轉(zhuǎn)換我們的代碼姻政,這樣它就可以被舊的瀏覽器執(zhí)行了呆抑。
https://www.freecodecamp.org/news/an-intro-to-webpack-what-it-is-and-how-to-use-it-8304ecdc3c60/
https://flaviocopes.com/webpack/