Webpack 2 入門
Webpack 2 一旦文檔完成,就將結束 Beta 測試期。不過這並不意味著你現在不能開始使用第 2 版,前提是你知道怎麼配置它。(LCTT 譯注:Webpack 2.1 已經發布。)
Webpack 是什麼
簡單來說,Webpack 是一個 JavaScript 模塊打包器。然而,自從它發布以來,它發展成為了你所有的前端代碼的管理工具(或許是有意的,或許是社區的意願)。

老式的任務運行器的方式:你的標記、樣式和 JavaScript 是分離的。你必須分別管理它們每一個,並且你需要確保每一樣都達到產品級
任務運行器task runner,例如 Gulp,可以處理許多不同的預處理器preprocesser和轉換器transpiler,但是在所有的情景下,它都需要一個輸入源並將其壓縮到一個編譯好的輸出文件中。然而,它是在每個部分的基礎上這樣做的,而沒有考慮到整個係統。這就造成了開發者的負擔:找到任務運行器所不能處理的地方,並找到適當的方式將所有這些模塊在生產環境中聯合在一起。
Webpack 試圖通過提出一個大膽的想法來減輕開發者的負擔:如果有一部分開發過程可以自動處理依賴關係會怎樣?如果我們可以簡單地寫代碼,讓構建過程最終隻根據需求管理自己會怎樣?

Webpack 的方式:如果 Webpack 了解依賴關係,它會僅捆綁我們在生產環境中實際需要的部分
如果你過去幾年一直參與 web 社區,你已經知道解決問題的首選方法:使用 JavaScript 來構建。而且 Webpack 嚐試通過 JavaScript 傳遞依賴關係使得構建過程更加容易。不過這個設計真正的亮點不是簡化代碼管理部分,而是管理層由 100% 有效的 JavaScript 實現(具有 Nodejs 特性)。Webpack 能夠讓你編寫有效的 JavaScript,更好更全麵地了解係統。
換句話來說:你不需要為 Webpack 寫代碼。你隻需要寫項目代碼。而且 Webpack 就會持續工作(當然需要一些配置)。
簡而言之,如果你曾經遇到過以下任何一種情況:
- 載入有問題的依賴項
- 意外引入一些你不需要在生產中用上的 CSS 樣式表和 JS 庫,使項目膨脹
- 意外的兩次載入(或三次)庫
- 遇到作用域的問題 —— CSS 和 JavaScript 都會有
- 尋找一個讓你在 JavaScript 中使用 Node/Bower 模塊的構建係統,要麼就得依靠一個令人發狂的後端配置才能正確地使用這些模塊
- 需要優化資產asset交付,但你擔心會弄壞一些東西
等等……
那麼你可以從 Webpack 中收益了。它通過讓 JavaScript 輕鬆處理你的依賴關係和加載順序,而不是通過開發者的大腦。最好的部分是,Webpack 甚至可以純粹在服務器端運行,這意味著你還可以使用 Webpack 構建漸進增強式網站。
第一步
我們將在本教程中使用 Yarn(運行命令 brew install yarn) 代替 npm,不過這完全取決於你的喜好,它們做同樣的事情。在我們的項目文件夾中,我們將在終端窗口中運行以下代碼,將 Webpack 2 添加到我們的全局軟件包以及本地項目中:
yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.9yarn add --dev webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.9
我們接著會通過項目根目錄的一個 webpack.config.js 文件來聲明 webpack 的配置:
'use strict';const webpack = require('webpack');module.exports = {context: __dirname + '/src',entry: {app: './app.js',},output: {path: __dirname + '/dist',filename: '[name].bundle.js',},};
注意:此處 __dirname 是指你的項目根目錄
記住,Webpack “知道”你的項目發生了什麼。它通過閱讀你的代碼來實現(別擔心,它簽署了保密協議 :D )。Webpack 基本上執行以下操作:
- 從
context文件夾開始…… - ……它查找
entry下的文件名…… - ……並讀取其內容。每一個
import(ES6)或require()(Nodejs)的依賴會在它解析代碼的時候找到,它會在最終構建的時候打包這些依賴項。然後,它會搜索那些依賴項以及那些依賴項所依賴的依賴項,直到它到達“樹”的最底端 —— 隻打包它所需要的,沒有其它東西。 - Webpack 從
context文件夾打包所有東西到output.path文件夾,使用output.filename命名模板來為其命名(其中[name]被替換成來自entry的對象的鍵)。
所以如果我們的 src/app.js 文件看起來像這樣(假設我們事先運行了 yarn add --dev moment):
'use strict';import moment from 'moment';var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');console.log( rightNow );// "October 23rd 2016, 9:30:24 pm"
我們應該運行:
webpack -p
注意:p 標誌表示“生產”模式,這會壓縮輸出文件。
它會輸出一個 dist/app.bundle.js,並將當前日期和時間打印到控製台。要注意 Webpack 會自動識別 上麵的 'moment' 指代的是什麼(比如說,雖然如果你有一個 moment.js 文件在你的目錄,默認情況下 Webpack 會優先考慮你的 moment Node 模塊)。
使用多個文件
你可以通過僅僅修改 entry 對象來指定任意數量的入口entry/輸出點output。
打包多個文件
'use strict';const webpack = require("webpack");module.exports = {context: __dirname + "/src",entry: {app: ["./home.js", "./events.js", "./vendor.js"],},output: {path: __dirname + "/dist",filename: "[name].bundle.js",},};
所有文件都會按照數組的順序一起被打包成一個 dist/app.bundle.js 文件。
輸出多個文件
const webpack = require("webpack");module.exports = {context: __dirname + "/src",entry: {home: "./home.js",events: "./events.js",contact: "./contact.js",},output: {path: __dirname + "/dist",filename: "[name].bundle.js",},};
或者,你可以選擇打包成多個 JS 文件以便於分割應用的某些模塊。這將被打包成 3 個文件:dist/home.bundle.js,dist/events.bundle.js 和 dist/contact.bundle.js。
高級打包自動化
如果你將你的應用分割成多個 output 輸出項(如果你的應用的一部分有大量你不需要預加載的 JS,這會很有用),你可能會重用這些文件的代碼,因為它將分別解析每個依賴關係。幸運的是,Webpack 有一個內置的 CommonsChunk 插件來處理這個:
module.exports = {// …plugins: [new webpack.optimize.CommonsChunkPlugin({name: "commons",filename: "commons.bundle.js",minChunks: 2,}),],// …};
現在,在你的 output 文件中,如果你有任何模塊被加載 2 次以上(通過 minChunks 設置),它會把那個模塊打包到 common.js 文件中,然後你可以將其緩存在客戶端。這將生成一個額外的請求頭,但是你防止了客戶端多次下載同一個庫。因此,在很多情景下,這會大大提升速度。
開發
Webpack 實際上有自己的開發服務器,所以無論你是開發一個靜態網站還是隻是你的網站前端原型,它都是無可挑剔的。要運行那個服務器,隻需要添加一個 devServer 對象到 webpack.config.js:
module.exports = {context: __dirname + "/src",entry: {app: "./app.js",},output: {filename: "[name].bundle.js",path: __dirname + "/dist/assets",publicPath: "/assets", // New},devServer: {contentBase: __dirname + "/src", // New},};
現在創建一個包含以下代碼的 src/index.html 文件:
<script src="/assets/app.bundle.js"></script>
……在你的終端中運行:
webpack-dev-server
你的服務器現在運行在 localhost:8080。注意 script 標簽裏麵的 /assets 是怎麼匹配到output.publicPath 的 —— 你可以隨意更改它的名稱(如果你需要一個 CDN 的時候這會很有用)。
Webpack 會熱加載所有 JavaScript 更改,而不需要刷新你的瀏覽器。但是,所有 webpack.config.js文件裏麵的更改都需要重新啟動服務器才能生效。
全局訪問方法
需要在全局空間使用你的函數?在 webpack.config.js 裏麵簡單地設置 output.library:
module.exports = {output: {library: 'myClassName',}};
……這會將你打包好的文件附加到一個 window.myClassName 實例。因此,使用該命名空間,你可以調用入口文件的可用方法(可以在該文檔中閱讀有關此設置的更多信息)。
加載器
到目前為止,我們所做的一切隻涉及 JavaScript。從一開始就使用 JavaScript 是重要的,因為它是 Webpack 唯一支持的語言。事實上我們可以處理幾乎所有文件類型,隻要我們將其轉換成 JavaScript。我們用加載器loader來實現這個功能。
加載器可以是 Sass 這樣的預處理器,或者是 Babel 這樣的轉譯器。在 NPM 上,它們通常被命名為 *-loader,例如 sass-loader 和 babel-loader。
Babel 和 ES6
如果我們想在項目中通過 Babel 來使用 ES6,我們首先需要在本地安裝合適的加載器:
yarn add --dev babel-loader babel-core babel-preset-es2015
然後將它添加到 webpack.config.js,讓 Webpack 知道在哪裏使用它。
module.exports = {// …module: {rules: [{test: /\.js$/,use: [{loader: "babel-loader",options: { presets: ["es2015"] }}],},// Loaders for other file types can go here],},// …};
Webpack 1 的用戶注意:加載器的核心概念沒有任何改變,但是語法改進了。直到官方文檔完成之前,這可能不是確切的首選語法。
/\.js$/ 這個正則表達式查找所有以 .js 結尾的待通過 Babel 加載的文件。Webpack 依靠正則檢查給予你完全的控製權 —— 它不限製你的文件擴展名或者假定你的代碼必須以某種方式組織。例如:也許你的 /my_legacy_code/ 文件夾下的內容不是用 ES6 寫的,所以你可以修改上述的 test 為/^((?!my_legacy_folder).)\.js$/,這將會排除那個特定的文件夾,不過會用 Babel 處理其餘的文件。
CSS 和 Style 加載器
如果我們隻想為我們的應用所需加載 CSS,我們也可以這樣做。假設我們有一個 index.js 文件,我們將從那裏引入:
import styles from './assets/stylesheets/application.css';
我們會得到以下錯誤:你可能需要一個合適的加載器來處理這種類型的文件。記住,Webpack 隻能識別 JavaScript,所以我們必須安裝合適的加載器:
yarn add --dev css-loader style-loader
然後添加一條規則到 webpack.config.js:
module.exports = {// …module: {rules: [{test: /\.css$/,use: ["style-loader", "css-loader"],},// …],},};
加載器以數組的逆序處理。這意味著 css-loader 會比 style-loader 先執行。
你可能會注意到,即使在生產版本中,這實際上是將你的 CSS 和 JavaScript 打包在一起,style-loader 手動將你的樣式寫到 <head>。乍一看,它可能看起來有點怪異,但你仔細想想就會發現這就慢慢開始變得更加有意義了。你已經節省了一個頭部請求 —— 節省了一些連接上的時間。如果你用 JavaScript 來加載你的 DOM,無論如何,這從本質上消除了 FOUC。
你還會注意到一個開箱即用的特性 —— Webpack 已經通過將這些文件打包在一起以自動解決你所有的@import 查詢(而不是依靠 CSS 默認的 import 方式,這會導致無謂的頭部請求以及資源加載緩慢)。
從你的 JS 加載 CSS 是非常驚人的,因為你現在可以用一種新的強大的方式將你的 CSS 模塊化。比如說你要隻通過 button.js 來加載 button.css,這將意味著如果 button.js 從來沒有真正使用過的話,它的 CSS 就不會膨脹我們的生產版本。如果你堅持麵向組件的 CSS 實踐,如 SMACSS 或 BEM,你會看到更緊密地結合你的 CSS 和你的標記和 JavaScript 的價值。
CSS 和 Node 模塊
我們可以使用 Webpack 來利用 Node.js 使用 ~ 前綴導入 Node 模塊的優勢。如果我們運行 yarn add normalize.css,我們可以使用:
@import "~normalize.css";
……並且充分利用 NPM 來管理我們的第三方樣式 —— 版本控製、沒有任何副本和粘貼的部分。此外,讓 Webpack 為我們打包 CSS 比起使用 CSS 的默認導入方式有明顯的優勢 —— 節省無謂的頭部請求和加載時間。
更新:這一節和下麵一節已經更新為準確的用法,不再使用 CSS 模塊簡單地導入 Node 的模塊。感謝Albert Fernández 的幫助!
CSS 模塊
你可能聽說過 CSS 模塊,它把 CSS 變成了 SS,消除了 CSS 的層疊性Cascading。通常它的最適用場景是隻有當你使用 JavaScript 構建 DOM 的時候,但實質上,它神奇地將你的 CSS 類放置到加載它的 JavaScript 文件裏(在這裏了解更多)。如果你打算使用它,CSS 模塊已經與 css-loader 封裝在一起(yarn add --dev css-loader):
module.exports = {// …module: {rules: [{test: /\.css$/,use: ["style-loader",{ loader: "css-loader", options: { modules: true } }],},// …],},};
注意:對於 css-loader,我們現在使用擴展對象語法expanded object syntax來給它傳遞一個選項。你可以使用一個更為精簡的字符串來取代默認選項,正如我們仍然使用了 style-loader。
值得注意的是,當允許導入 CSS 模塊的時候(例如:@import 'normalize.css';),你完全可以刪除掉 ~。但是,當你 @import 你自己的 CSS 的時候,你可能會遇到構建錯誤。如果你遇到“無法找到 ____”的錯誤,嚐試添加一個 resolve 對象到 webpack.config.js,讓 Webpack 更好地理解你的模塊加載順序。
const path = require("path");module.exports = {//…resolve: {modules: [path.resolve(__dirname, "src"), "node_modules"]},};
我們首先指定源目錄,然後指定 node_modules。這樣,Webpack 會更好地處理解析,按照既定的順序(分別用你的源目錄和 Node 模塊的目錄替換 "src" 和 "node_modules"),首先查找我們的源目錄,然後再查找已安裝的 Node 模塊。
Sass
需要使用 Sass?沒問題。安裝:
yarn add --dev sass-loader node-sass
並添加新的規則:
module.exports = {// …module: {rules: [{test: /\.(sass|scss)$/,use: ["style-loader","css-loader","sass-loader",]}// …],},};
然後當你的 Javascript 對一個 .scss 或 .sass 文件調用 import 方法的時候,Webpack 會處理的。
CSS 獨立打包
或許你在處理漸進增強的問題;或許你因為其它原因需要一個單獨的 CSS 文件。我們可以通過在我們的配置中用 extract-text-webpack-plugin 替換 style-loader 而輕易地做到這一點,這不需要更改任何代碼。以我們的 app.js 文件為例:
import styles from './assets/stylesheets/application.css';
讓我們安裝這個插件到本地(我們需要 2016 年 10 月的測試版本):
yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4
並且添加到 webpack.config.js:
const ExtractTextPlugin = require("extract-text-webpack-plugin");module.exports = {// …module: {rules: [{test: /\.css$/,use: [ExtractTextPlugin.extract("css"),{ loader: "css-loader", options: { modules: true } },],},// …]},plugins: [new ExtractTextPlugin({filename: "[name].bundle.css",allChunks: true,}),],};
現在當運行 webpack -p 的時候,你的 output 目錄還會有一個 app.bundle.css 文件。隻需要像往常一樣簡單地在你的 HTML 中向該文件添加一個 <link> 標簽即可。
HTML
正如你可能已經猜到,Webpack 還有一個 [html-loader][6] 插件。但是,當我們用 JavaScript 加載 HTML 時,我們針對不同的場景分成了不同的方法,我無法想出一個單一的例子來為你計劃下一步做什麼。通常,你需要加載 HTML 以便於在更大的係統(如 React、Angular、Vue 或 Ember)中使用 JavaScript 風格的標記,如 JSX、Mustache 或 Handlebars。或者你可以使用類似 Pug (以前叫 Jade)或 Haml 這樣的 HTML 預處理器,抑或你可以直接把同樣的 HTML 從你的源代碼目錄推送到你的構建目錄。你怎麼做都行。
教程到此為止了:你可以用 Webpack 加載標記,但是進展到這一步的時候,關於你的架構,你將做出自己的決定,我和 Webpack 都無法左右你。不過參考以上的例子以及搜索 NPM 上適用的加載器應該足夠你發展下去了。
從模塊的角度思考
為了充分使用 Webpack,你必須從模塊的角度來思考:細粒度的、可複用的、用於高效處理每一件事的獨立的處理程序。這意味著采取這樣的方式:
└── js/└── application.js // 300KB of spaghetti code
將其轉變成這樣:
└── js/├── components/│ ├── button.js│ ├── calendar.js│ ├── comment.js│ ├── modal.js│ ├── tab.js│ ├── timer.js│ ├── video.js│ └── wysiwyg.js│└── application.js // ~ 1KB of code; imports from ./components/
結果呈現了整潔的、可複用的代碼。每一個獨立的組件通過 import 來引入自身的依賴,並 export 它想要暴露給其它模塊的部分。結合 Babel 和 ES6,你可以利用 JavaScript 類 來實現更強大的模塊化,而不用考慮它的工作原理。
原文發布時間為:2017-12-23
本文來自雲棲社區合作夥伴“Linux中國”
最後更新:2017-06-06 07:38:00