文章主要介紹了 webpack 的幾個重要的概念:入口、loader、plugin 和輸出,並且展示了一個簡單的 webpack 配置例子,最後提供了前端社群三大框架基於 webpack 的腳手架工具的連結。
學習目錄
- 前端構建的基本知識
- webpack 的基本使用
- 使用 webpack 定製前端開發環境
- 使用 webpack 優化前端資源的載入
- 開發 webpack 的 loader 和 plugin
- 瞭解 webpack 內部基本的工作流程
- 結合實際專案使用 webpack 的經驗
目標能力:
- 瞭解前端構建,瞭解構建需求的原因和由來
- 原有的 webpack 構建出問題了,我可以迅速定位解決問題
- 專案構建中需要 xxx,我可以簡單調整 webpack 的配置來搞定
- 專案構建流程中需要 xxx,我可以寫個 webpack plugin 來實現
- 新專案中,我可以自由根據需要使用 webpack 來規範前端構建
環境
- 建議使用 Node 8.x 以上版本
- 所有配置和 demo 基於 webpack 4.1.1 版本,理論上 4.x 都支援,特殊說明的除外
什麼是WebPack,為什麼要使用它?
為什要使用WebPack
現今的很多網頁其實可以看做是功能豐富的應用,它們擁有著複雜的JavaScript程式碼和一大堆依賴包。為了簡化開發的複雜度,前端社群湧現出了很多好的實踐方法
- 模組化,讓我們可以把複雜的程式細化為小的檔案;
- 類似於TypeScript這種在JavaScript基礎上擴充的開發語言:使我們能夠實現目前版本的JavaScript不能直接使用的特性,並且之後還能轉換為JavaScript檔案使瀏覽器可以識別;
- Scss,less等CSS前處理器
- ...
這些改進確實大大的提高了我們的開發效率,但是利用它們開發的檔案往往需要進行額外的處理才能讓瀏覽器識別,而手動處理又是非常繁瑣的,這就為WebPack類的工具的出現提供了需求。
什麼是Webpack
WebPack可以看做是模組打包機:它做的事情是,分析你的專案結構,找到JavaScript模組以及其它的一些瀏覽器不能直接執行的擴充語言(Scss,TypeScript等),並將其轉換和打包為合適的格式供瀏覽器使用。
基本概念
入口
如上圖所示,在多個程式碼模組中會有一個起始的 .js
檔案,這個便是 webpack 構建的入口。webpack 會讀取這個檔案,並從它開始解析依賴,然後進行打包。如圖,一開始我們使用 webpack 構建時,預設的入口檔案就是 ./src/index.js
。
我們常見的專案中,如果是單頁面應用,那麼可能入口只有一個;如果是多個頁面的專案,那麼經常是一個頁面會對應一個構建入口。
入口可以使用 entry
欄位來進行配置,webpack 支援配置多個入口來進行構建:
module.exports = {
entry: './src/index.js'
}
// 上述配置等同於
module.exports = {
entry: {
main: './src/index.js'
}
}
// 或者配置多個入口
module.exports = {
entry: {
foo: './src/page-foo.js',
bar: './src/page-bar.js',
// ...
}
}
// 使用陣列來對多個檔案進行打包
module.exports = {
entry: {
main: [
'./src/foo.js',
'./src/bar.js'
]
}
}
複製程式碼
最後的例子,可以理解為多個檔案作為一個入口,webpack 會解析兩個檔案的依賴後進行打包。
loader
webpack 中提供一種處理多種檔案格式的機制,便是使用 loader。我們可以把 loader 理解為是一個轉換器,負責把某種檔案格式的內容轉換成 webpack 可以支援打包的模組。
舉個例子,在沒有新增額外外掛的情況下,webpack 會預設把所有依賴打包成 js 檔案,如果入口檔案依賴一個 .hbs
的模板檔案以及一個 .css
的樣式檔案,那麼我們需要 handlebars-loader
來處理 .hbs
檔案,需要 css-loader
來處理 .css
檔案(這裡其實還需要 style-loader
,後續詳解),最終把不同格式的檔案都解析成 js 程式碼,以便打包後在瀏覽器中執行。
當我們需要使用不同的 loader 來解析處理不同型別的檔案時,我們可以在 module.rules
欄位下來配置相關的規則,例如使用 Babel 來處理 .js
檔案:
module: {
// ...
rules: [
{
test: /\.jsx?/, // 匹配檔案路徑的正規表示式,通常我們都是匹配檔案型別字尾
include: [
path.resolve(__dirname, 'src') // 指定哪些路徑下的檔案需要經過 loader 處理
],
use: 'babel-loader', // 指定使用的 loader
},
],
}
複製程式碼
loader 是 webpack 中比較複雜的一塊內容,它支撐著 webpack 來處理檔案的多樣性。後續我們還會介紹如何更好地使用 loader 以及如何開發 loader。
plugin
在 webpack 的構建流程中,plugin 用於處理更多其他的一些構建任務。可以這麼理解,模組程式碼轉換的工作由 loader 來處理,除此之外的其他任何工作都可以交由 plugin 來完成。通過新增我們需要的 plugin,可以滿足更多構建中特殊的需求。例如,要使用壓縮 JS 程式碼的 uglifyjs-webpack-plugin
外掛,只需在配置中通過 plugins
欄位新增新的 plugin 即可:
const UglifyPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins: [
new UglifyPlugin()
],
}
複製程式碼
除了壓縮 JS 程式碼的 uglifyjs-webpack-plugin
,常用的還有定義環境變數的 DefinePlugin
,生成 CSS 檔案的 ExtractTextWebpackPlugin
等。在這裡提到這些 plugin,只是希望讀者們能夠對 plugin 的作用有個大概的印象,後續的小節會詳細介紹如何使用這些 plugin。
plugin 理論上可以干涉 webpack 整個構建流程,可以在流程的每一個步驟中定製自己的構建需求。第 15 小節我們會介紹如何開發 plugin,讓讀者們在必要時,也可以在 webpack 的基礎上開發 plugin 來應對一些專案的特殊構建需求。
輸出
webpack 的輸出即指 webpack 最終構建出來的靜態檔案,可以看看上面 webpack 官方圖片右側的那些檔案。當然,構建結果的檔名、路徑等都是可以配置的,使用 output
欄位:
module.exports = {
// ...
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
}
// 或者多個入口生成不同檔案
module.exports = {
entry: {
foo: './src/foo.js',
bar: './src/bar.js',
},
output: {
filename: '[name].js',
path: __dirname + '/dist',
},
}
// 路徑中使用 hash,每次構建時會有一個不同 hash 值,避免釋出新版本時線上使用瀏覽器快取
module.exports = {
// ...
output: {
filename: '[name].js',
path: __dirname + '/dist/[hash]',
},
}
複製程式碼
我們一開始直接使用 webpack 構建時,預設建立的輸出內容就是 ./dist/main.js
。
一個簡單的 webpack 配置
我們把上述涉及的幾部分配置內容合到一起,就可以建立一個簡單的 webpack 配置了,webpack 執行時預設讀取專案下的 webpack.config.js
檔案作為配置。
所以我們在專案中建立一個 webpack.config.js
檔案:
const path = require('path')
const UglifyPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.jsx?/,
include: [
path.resolve(__dirname, 'src')
],
use: 'babel-loader',
},
],
},
// 程式碼模組路徑解析的配置
resolve: {
modules: [
"node_modules",
path.resolve(__dirname, 'src')
],
extensions: [".wasm", ".mjs", ".js", ".json", ".jsx"],
},
plugins: [
new UglifyPlugin(),
// 使用 uglifyjs-webpack-plugin 來壓縮 JS 程式碼
// 如果你留意了我們一開始直接使用 webpack 構建的結果,你會發現預設已經使用了 JS 程式碼壓縮的外掛
// 這其實也是我們命令中的 --mode production 的效果,後續的小節會介紹 webpack 的 mode 引數
],
}
複製程式碼
webpack 的配置其實是一個 Node.js 的指令碼,這個指令碼對外暴露一個配置物件,webpack 通過這個物件來讀取相關的一些配置。因為是 Node.js 指令碼,所以可玩性非常高,你可以使用任何的 Node.js 模組,如上述用到的 path
模組,當然第三方的模組也可以。
建立了 webpack.config.js
後再執行 webpack 命令,webpack 就會使用這個配置檔案的配置了。
有的時候我們開始一個新的前端專案,並不需要從零開始配置 webpack,而可以使用一些工具來幫助快速生成 webpack 配置。
腳手架中的 webpack 配置
現今,大多數前端框架都提供了簡單的工具來協助快速生成專案基礎檔案,一般都會包含專案使用的 webpack 的配置,如:
-
create-react-app 的 webpack 配置在這個專案下:react-scripts。
-
通常 angular 的專案開發和生產的構建任務都是使用 angular-cli 來執行的,但 angular-cli 只是命令的使用介面,基礎功能是由 angular/devkit 來實現的,webpack 的構建相關只是其中一部分,詳細的配置可以參考 webpack-configs 。
-
vue-cli 使用 webpack 模板生成的專案檔案中,webpack 相關配置存放在 build 目錄下。
這些工具都提供了極其完整的配置來幫助開發者快捷開始一個專案,我們可以學習瞭解它們所提供的 webpack 配置,有些情況下,還會嘗試修改這些配置以滿足特殊的需求。
所以你也會發現,這些極其流行的前端類庫或者框架都提供了基於 webpack 的工具,webpack 基本成為前端專案構建工具的標配。
注意: 這三個工具中,只有 angular-cli 使用了 4.x 版本的 webpack,其他的都還是用的 3.x 版本,學習的時候要留意一下版本區別。