Webpack 入門
已發表: 2022-03-10在 JavaScript 中引入模塊化的早期,沒有原生支持在瀏覽器中運行模塊。 使用 CommonJS 藍圖在 Node.js 中實現了對模塊化編程的支持,並被那些使用 JavaScript 構建服務器端應用程序的人採用。
它還具有開發大型 Web 應用程序的前景,因為開發人員可以通過以更模塊化的模式編寫代碼來避免命名空間衝突並構建更易於維護的代碼庫。 但是仍然存在一個挑戰:模塊不能在通常執行 JavaScript 的 Web 瀏覽器中使用。
為了解決這個問題,我們編寫了諸如 webpack、Parcel、Rollup 和 Google 的 Closure Compiler 之類的模塊捆綁器來創建優化的代碼包,供最終用戶的瀏覽器下載和執行。
“捆綁”你的代碼是什麼意思?
捆綁代碼是指將多個模塊組合和優化為一個或多個可用於生產的捆綁包。 這裡提到的捆綁可以更好地理解為整個捆綁過程的最終產品。
在本文中,我們將重點關注 webpack,這是一個由 Tobias Koppers 編寫的工具,隨著時間的推移,它已經發展成為 JavaScript 工具鏈中的主要工具,經常用於大大小小的項目中。
注意:要從本文中受益,最好熟悉 JavaScript 模塊。 你還需要在本地機器上安裝Node ,這樣你就可以在本地安裝和使用 webpack。
什麼是 webpack?
webpack 是一個用於 JavaScript 應用程序的高度可擴展和可配置的靜態模塊打包器。 憑藉其可擴展性,您可以插入外部加載器和插件來實現您的最終目標。
如下圖所示,webpack 從根入口點遍歷您的應用程序,構建由直接或間接作用於根文件的依賴項組成的依賴關係圖,並生成組合模塊的優化包。
要了解 webpack 是如何工作的,我們需要了解它使用的一些術語(查看 webpack Glossary。這個術語在本文中經常使用,並且在 webpack 的文檔中也經常引用。
- 塊
塊是指從模塊中提取的代碼。 此代碼將存儲在一個塊文件中。 當使用 webpack 執行代碼拆分時,通常會使用塊。 - 模塊
模塊是應用程序的分解部分,您可以導入它們以執行特定任務或功能。 Webpack 支持使用 ES6、CommonJS 和 AMD 語法創建的模塊。 - 資產
資產一詞通常在 webpack 和其他捆綁器中經常使用。 它指的是在構建過程中捆綁的靜態文件。 這些文件可以是從圖像到字體甚至視頻文件的任何內容。 隨著您進一步閱讀本文,您將看到我們如何使用加載器來處理不同的資產類型。
推薦閱讀: Webpack - 詳細介紹
一旦我們了解了 webpack 是什麼以及它使用了什麼術語,讓我們看看它們如何應用於為演示項目組合配置文件。
注意:你還需要安裝webpack-cli
才能在你的機器上使用 webpack。 如果未安裝,終端將提示您安裝它。
webpack 配置文件
除了在終端中使用 webpack-cli,您還可以通過配置文件在項目中使用 webpack。 但是使用最新版本的 webpack,我們可以在我們的項目中使用它而無需配置文件。 我們可以使用webpack
作為package.json
文件中命令之一的值——不帶任何標誌。 這樣,webpack 將假定您項目的入口點文件位於src
目錄中。 它會將入口文件捆綁並輸出到dist
目錄。
下面的示例package.json
文件就是一個示例。 在這裡,我們使用 webpack 打包應用程序,無需配置文件:
{ "name" : "Smashing Magazine", "main": "index.js", "scripts": { "build" : "webpack" }, "dependencies" : { "webpack": "^5.24.1" } }
當運行上面文件中的 build 命令時,webpack 會將src/index.js
目錄中的文件打包並輸出到dist
目錄中的main.js
文件中。 然而,webpack 比這靈活得多。 我們可以通過編輯帶有-- config
標誌的配置文件來更改入口點、調整輸出點並優化許多其他默認行為。
一個示例是上面package.json
文件中修改後的構建命令:
"build" : "webpack --config webpack.config.js"
上面,我們添加了--config
標誌並將webpack.config.js
指定為具有新 webpack 配置的文件。
webpack.config.js
文件還不存在。 所以我們需要在我們的應用程序目錄中創建它,並將下面的代碼粘貼到文件中。
# webpack.config.js const path = require("path") module.exports = { entry : "./src/entry", output : { path: path.resolve(__dirname, "dist"), filename: "output.js" } }
上面的文件仍然配置 webpack 來打包你的 JavaScript 文件,但是現在我們可以定義一個自定義的入口和輸出文件路徑,而不是 webpack 使用的默認路徑。
關於 webpack 配置文件的一些注意事項:
- webpack 配置文件是一個 JavaScript 文件,編寫為 JavaScript CommonJS 模塊。
- webpack 配置文件導出具有多個屬性的對象。 這些屬性中的每一個都用作捆綁代碼時配置 webpack 的選項。 一個例子是
mode
選項:-
mode
在配置中,此選項用於在捆綁期間設置NODE_ENV
值。 它可以具有production
或development
價值。 如果未指定,它將默認為none
。 同樣重要的是要注意 webpack 根據mode
值以不同的方式捆綁您的資產。 例如,webpack 在開發模式下自動緩存你的包,以優化和減少包時間。 請參閱 webpack 文檔的模式部分,以查看在每種模式中自動應用的選項的更改日誌。
-
webpack 概念
通過 CLI 或通過配置文件配置 webpack 時,有四個主要概念用作選項。 本文的下一部分將重點介紹這些概念,並在構建演示 Web 應用程序的配置時應用它們。
請注意,下面解釋的概念與其他模塊捆綁器有一些相似之處。 例如,當使用帶有配置文件的 Rollup 時,您可以定義一個輸入字段來指定依賴關係圖的入口點,一個輸出對象配置生成的塊的放置方式和位置,還可以定義一個插件對像用於添加外部插件。
入口
配置文件中的entry字段包含 webpack 開始構建依賴圖的文件的路徑。 從這個入口文件,webpack 將繼續處理直接或間接依賴於入口點的其他模塊。
您的配置的入口點可以是具有單個文件值的 Single Entry 類型,類似於以下示例:
# webpack.configuration.js module.exports = { mode: "development", entry : "./src/entry" }
入口點也可以是多主入口類型,具有包含多個入口文件路徑的數組,類似於以下示例:
# webpack.configuration.js const webpack = require("webpack") module.exports = { mode: "development", entry: [ './src/entry', './src/entry2' ], }
輸出
顧名思義,配置的輸出字段是創建的包所在的位置。 當您有多個模塊時,此字段會派上用場。 您可以指定自己的文件名,而不是使用 webpack 生成的名稱。
# webpack.configuration.js const webpack = require("webpack"); const path = require("path"); module.exports = { mode: "development", entry: './src/entry', output: { filename: "webpack-output.js", path: path.resolve(__dirname, "dist"), } }
裝載機
默認情況下,webpack 只理解應用程序中的 JavaScript 文件。 但是,webpack 將作為模塊導入的每個文件都視為依賴項,並將其添加到依賴關係圖中。 為了處理靜態資源,例如圖像、CSS 文件、JSON 文件,甚至存儲在 CSV 中的數據,webpack 使用加載器將這些文件“加載”到包中。
加載器足夠靈活,可以用於很多事情,從轉譯 ES 代碼到處理應用程序的樣式,甚至使用 ESLint 對代碼進行 linting。
在您的應用程序中使用加載程序有三種方法。 其中之一是通過內聯方法直接將其導入文件中。 例如,為了最小化圖像大小,我們可以直接在文件中使用image-loader
加載器,如下所示:
// main.js import ImageLoader from 'image-loader'
使用加載器的另一個首選選項是通過您的 webpack 配置文件。 這樣,您可以使用加載器執行更多操作,例如指定要應用加載器的文件類型。 為此,我們創建一個rules
數組並在一個對像中指定加載器,每個加載器都有一個測試字段,其中的正則表達式匹配我們想要應用加載器的資產。
例如,在前面的示例中直接導入image-loader
,我們可以在 webpack 配置文件中使用它,並使用文檔中最基本的選項。 這將如下所示:
# webpack.config.js const webpack = require("webpack") const path = require("path") const merge = require("webpack-merge") module.exports = { mode: "development", entry: './src/entry', output: { filename: "webpack-output.js", path: path.resolve(__dirname, "dist"), }, module: { rules: [ { test: /\.(jpe?g|png|gif|svg)$/i, use: [ 'img-loader' ] } ] } }
仔細查看包含上述image-loader
的對像中的test
字段。 我們可以發現匹配所有圖像文件的正則表達式: jp(e)g
、 png
、 gif
和svg
格式。
使用 Loaders 的最後一種方法是通過帶有--module-bind
標誌的 CLI。
awesome-webpack 自述文件包含一個詳盡的加載器列表,您可以將它們與 webpack 一起使用,每個加載器都分為它們執行的操作類別。 以下只是一些您可能會在您的應用程序中找到方便的加載器:
- Responsive-loader在添加圖像以適應您的響應式網站或應用程序時,您會發現此加載器非常有用。 它從單個圖像創建多個不同尺寸的圖像,並返回一個與圖像匹配的
srcset
,以便在適當的顯示屏幕尺寸下使用。 - 通天塔裝載機
這用於將 JavaScript 代碼從現代 ECMA 語法轉換為 ES5。 - GraphQL 加載器
如果你是 GraphQL 愛好者,你會發現這個加載器非常有用,因為它加載了包含 GraphQL 模式、查詢和突變的.graphql
文件——以及啟用驗證的選項。
插件
插件的使用允許 webpack 編譯器對捆綁模塊產生的塊執行任務。 儘管 webpack 不是任務運行器,但通過插件,我們可以執行一些自定義操作,這些操作在捆綁代碼時加載器無法執行。
webpack 插件的一個例子是 webpack 內置的ProgressPlugin 。 它提供了一種自定義編譯過程中在控制台中打印的進度的方法。
# webpack.config.js const webpack = require("webpack") const path = require("path") const merge = require("webpack-merge") const config = { mode: "development", entry: './src/entry', output: { filename: "webpack-output.js", path: path.resolve(__dirname, "dist"), }, module: { rules: [ { test: /\.(jpe?g|png|gif|svg)$/i, use: [ 'img-loader' ] } ] }, plugins: [ new webpack.ProgressPlugin({ handler: (percentage, message ) => { console.info(percentage, message); }, }) ] } module.exports = config
通過上面配置的 Progress 插件,我們提供了一個處理函數,它會在編譯過程中將編譯百分比和消息打印到控制台。
下面是一些來自 awesome-webpack 自述文件的插件,您可以在 webpack 應用程序中找到它們。
- 離線插件
該插件首先利用服務工作者或可用的 AppCache 為 webpack 託管項目提供離線體驗。 - Purgecss-webpack-plugin
這個插件在嘗試優化你的 webpack 項目時會派上用場,因為它會在編譯期間刪除應用程序中未使用的 CSS。
至此,我們已經為一個相對較小的應用程序設置了第一個 webpack 配置。 讓我們進一步考慮如何在我們的應用程序中使用 webpack 做某些事情。
處理多個環境
在您的應用程序中,您可能需要為開發或生產環境配置不同的 webpack。 例如,您可能不希望 webpack 每次對生產環境中的持續集成管道進行新部署時都輸出輕微的警告日誌。
正如 webpack 和社區所推薦的,有幾種方法可以實現這一點。 一種方法是將配置文件轉換為導出返回對象的函數。 這樣,webpack 編譯器會將當前環境作為第一個參數傳遞給函數,其他選項作為第二個參數。
如果您希望根據當前環境執行一些不同的操作,這種處理 webpack 環境的方法會派上用場。 但是,對於具有更複雜配置的大型應用程序,您最終可能會得到一個包含大量條件語句的配置。
下面的代碼片段顯示瞭如何使用functions
方法在同一文件中處理production
和development
環境的示例。
// webpack.config.js module.exports = function (env, args) { return { mode : env.production ? 'production' : 'development', entry: './src/entry', output: { filename: "webpack-output.js", path: path.resolve(__dirname, "dist"), }, plugins: [ env.development && ( new webpack.ProgressPlugin({ handler: (percentage, message ) => { console.info(percentage, message); }, }) ) ] } }
通過上面代碼片段中的導出函數,您將看到傳遞給函數的env
參數如何與三元運算符一起使用來切換值。 它首先用於設置 webpack 模式,然後它也用於僅在開發模式下啟用 ProgressPlugin。
處理生產和開發環境的另一種更優雅的方法是為這兩個環境創建不同的配置文件。 完成此操作後,我們可以在打包應用程序時將它們與package.json
腳本中的不同命令一起使用。 看看下面的片段:
{ "name" : "smashing-magazine", "main" : "index.js" "scripts" : { "bundle:dev" : "webpack --config webpack.dev.config.js", "bundle:prod" : "webpack --config webpack.prod.config.js" }, "dependencies" : { "webpack": "^5.24.1" } }
在上面的package.json
中,我們有兩個腳本命令,每個命令都使用不同的配置文件來處理捆綁應用程序資產時的特定環境。 現在,您可以在開發模式下使用npm run bundle:dev
捆綁您的應用程序,或者在創建生產就緒包時使用npm run bundle:prod
捆綁您的應用程序。
使用第二種方法,您可以避免從函數返回配置對象時引入的條件語句。 但是,現在您還必須維護多個配置文件。
拆分配置文件
此時,我們的 webpack 配置文件有 38 行代碼(LOC)。 這對於具有單個加載器和單個插件的演示應用程序來說非常好。
但是對於一個更大的應用程序,我們的 webpack 配置文件肯定會更長,有幾個加載器和插件,每個都有它們的自定義選項。 為了保持配置文件的干淨和可讀性,我們可以將配置拆分為多個文件中的較小對象,然後使用 webpack-merge 包將配置對象合併到一個基本文件中。
要將其應用於我們的 webpack 項目,我們可以將單個配置文件拆分為三個較小的文件:一個用於加載程序,一個用於插件,最後一個文件作為基礎配置文件,我們將其他兩個文件放在一起。
創建一個webpack.plugin.config.js
文件並將下面的代碼粘貼到其中以使用帶有附加選項的插件。
// webpack.plugin.config.js const webpack = require('webpack') const plugin = [ new webpack.ProgressPlugin({ handler: (percentage, message ) => { console.info(percentage, message); }, }) ] module.exports = plugin
上面,我們有一個從webpack.configuration.js
文件中提取的插件。
接下來,使用以下代碼為 webpack 加載器創建一個webpack.loader.config.js
文件。
// webpack.loader.config.js const loader = { module: { rules: [ { test: /\.(jpe?g|png|gif|svg)$/i, use: [ 'img-loader' ] } ] } }
在上面的代碼塊中,我們將 webpack img-loader
移到了一個單獨的文件中。
最後,創建一個webpack.base.config.js
文件,其中 webpack 應用程序的基本輸入和輸出配置將與上面創建的兩個文件一起保存。
// webpack.base.config.js const path = require("path") const merge = require("webpack-merge") const plugins = require('./webpack.plugin.config') const loaders = require('./webpack.loader.config') const config = merge(loaders, plugins, { mode: "development", entry: './src/entry', output: { filename: "webpack-output.js", path: path.resolve(__dirname, "dist"), } }); module.exports = config
看一眼上面的 webpack 文件,您可以觀察到它與原始webpack.config.js
文件相比有多緊湊。 現在配置的三個主要部分已被分解為較小的文件,可以單獨使用。
優化大型構建
當您在一段時間內繼續處理您的應用程序時,您的應用程序肯定會在功能和大小方面變得更大。 發生這種情況時,將創建新文件,修改或重構舊文件,並安裝新的外部包——所有這些都會導致 webpack 發出的包大小增加。
默認情況下,如果您的配置模式設置為production
,webpack 會自動嘗試為您優化包。 例如,默認情況下 webpack 應用的一種技術(從 webpack 4+ 開始)來優化和減小包大小是 Tree-Shaking。 本質上,它是一種用於刪除未使用代碼的優化技術。 在捆綁期間的簡單級別,導入和導出語句用於在將未使用的模塊從發出的捆綁包中刪除之前檢測它們。
您還可以通過將具有某些字段的optimization
對象添加到配置文件中來手動優化您的應用程序包。 webpack 文檔的優化部分包含完整的字段列表,您可以在optimization
對像中使用這些字段來優化您的應用程序。 讓我們考慮 20 個記錄字段中的一個。
-
minimize
這個布爾字段用於指示 webpack 最小化包大小。 默認情況下,webpack 將嘗試使用 TerserPlugin 來實現這一點,TerserPlugin 是 webpack 附帶的代碼壓縮包。
縮小適用於通過從代碼中刪除不必要的數據來最小化您的代碼,這反過來又會減少處理後產生的代碼大小。
我們還可以通過在optimization
對像中添加minimizer
器數組字段來使用其他首選的縮小器。 一個例子是下面使用 Uglifyjs-webpack-plugin。
// webpack.config.js const Uglify = require("uglifyjs-webpack-plugin") module.exports = { optimization { minimize : true, minimizer : [ new Uglify({ cache : true, test: /\.js(\?.*)?$/i, }) ] } }
上面, uglifyjs-webpack-plugin
被用作一個縮小器,有兩個非常重要的選項。 首先,啟用cache
意味著 Uglify 只會在現有文件是新更改時縮小它們,並且test
選項指定我們要縮小的具體文件類型。
注意: uglifyjs-webpack-plugin 提供了一個完整的列表,列出了在使用它來縮小代碼時可用的選項。
一點優化演示
讓我們手動嘗試通過在更大的項目中應用一些字段來優化演示應用程序以查看差異。 雖然我們不會深入優化應用程序,但我們會看到在development
模式下運行 webpack 與在production
模式下運行時包大小的差異。
對於這個演示,我們將使用一個使用 Electron 構建的桌面應用程序,它的 UI 也使用 React.js——所有這些都與 webpack 捆綁在一起。 Electron 和 React.js 聽起來像是一個非常重的組合,可能會生成更大的包。
注意:如果您是第一次學習Electron ,本文將深入了解Electron是什麼以及如何使用它來構建跨平台桌面應用程序。
要在本地試用演示,請從 GitHub 存儲庫克隆應用程序並使用以下命令安裝依賴項。
# clone repository git clone https://github.com/vickywane/webpack-react-demo.git # change directory cd demo-electron-react-webpack # install dependencies npm install
桌面應用程序相當簡單,只有一個頁面使用 styled-components 設置樣式。 當使用yarn start
命令啟動桌面應用程序時,單個頁面會顯示從 CDN 獲取的圖像列表,如下所示。
讓我們先創建這個應用程序的開發包,無需任何手動優化來分析最終包的大小。
從項目目錄中的終端運行yarn build:dev
將創建開發包。 此外,它還會將以下統計信息打印到您的終端:
該命令將向我們顯示整個編譯和發出的包的統計信息。
請注意mainRenderer.js
塊的大小為 1.11 Mebibyte(約 1.16 MB)。 mainRenderer
是 Electron 應用程序的入口點。
接下來,讓我們在webpack.base.config.js
文件中添加 uglifyjs-webpack-plugin 作為已安裝的插件,以進行代碼壓縮。
// webpack.base.config.js const Uglifyjs = require("uglifyjs-webpack-plugin") module.exports = { plugins : [ new Uglifyjs({ cache : true }) ] }
最後,讓我們在production
模式下將應用程序與 webpack 捆綁在一起。 從終端運行yarn build:prod
命令會將以下數據輸出到終端。
這次記下mainRenderer
塊。 它已經下降到驚人的 182 千字節(大約 186 KB),這是之前發出的mainRenderer
塊大小的 80% 以上!
讓我們使用 webpack-bundler-analyzer 進一步可視化發出的包。 使用yarn add webpack-bundle-analyzer
命令安裝插件並修改webpack.base.config.js
文件以包含添加插件的以下代碼。
// webpack.base.config.js const Uglifyjs = require("uglifyjs-webpack-plugin"); const BundleAnalyzerPlugin = require("webpack-bundle-analyzer"); .BundleAnalyzerPlugin; const config = { plugins: [ new Uglifyjs({ cache : true }), new BundleAnalyzerPlugin(), ] }; module.exports = config;
從終端運行yarn build:prod
以重新捆綁應用程序。 默認情況下,webpack-bundle-analyzer 將啟動一個 HTTP 服務器,在瀏覽器中提供可視化的 bundle 概覽。
從上圖中,我們可以看到發出的包和包內文件大小的可視化表示。 在視覺上,我們可以觀察到在node_modules
文件夾中,最大的文件是react-dom.production.min.js
,其次是stylis.min.js
。
使用分析器可視化的文件大小,我們將更好地了解哪些已安裝的軟件包貢獻了包的主要部分。 然後我們可以尋找優化它的方法或用更輕的包替換它。
注意: webpack-analyzer-plugin文檔列出了其他可用於顯示從發出的包創建的分析的方法。
webpack 社區
webpack 的優勢之一是其背後有龐大的開發者社區,這對於第一次嘗試 webpack 的開發者來說非常有用。 就像這篇文章一樣,有幾篇文章、指南和資源以及在使用 webpack 時作為很好的指南的文檔。
例如,來自 webpack 博客的構建性能指南包含有關優化 webpack 構建的技巧,而 Slack 的案例研究(雖然有點舊)解釋瞭如何在 Slack 優化 webpack。
一些社區資源解釋了 webpack 文檔的部分內容,為您提供了示例演示項目,以展示如何使用 webpack 的功能。 一個例子是一篇關於 Webpack 5 Module Federation 的文章,它解釋瞭如何在 React 應用程序中使用 webpack 的新 Module Federation 功能。
概括
經過七年的存在,webpack 已經真正證明了自己是大量項目使用的 JavaScript 工具鏈的重要組成部分。 本文僅簡要介紹了利用 webpack 的靈活和可擴展性可以實現的目標。
下次你需要為你的應用程序選擇一個模塊打包器時,希望你能更好地理解 Webpack 的一些核心概念,它解決的問題,以及設置你的配置文件的步驟。
關於 SmashingMag 的進一步閱讀:
- Webpack - 詳細介紹
- 使用 Webpack 和 Workbox 構建 PWA
- 使用 Webpack 為現代 React 項目設置 TypeScript
- 如何利用機器:與任務運行者一起提高工作效率