我們在小冊介紹中提到,webpack 是一個 JS 代碼模塊化的打包工具,藉由它強大的擴展能力,隨著社區的發展,逐漸成為一個功能完善的構建工具。相信開始學習這個小冊的同學們多多少少都能夠理解為什么前端開發中會使用到 webpack,我們不再詳細介紹 webpack 的使用背景,直奔本小節的主題。
## 安裝和使用
我們使用 npm 或者 yarn 來安裝 webpack,可以作為一個全局的命令來使用:
```
npm install webpack webpack-cli -g
# 或者
yarn global add webpack webpack-cli
# 然后就可以全局執行命令了
webpack --help
```
[webpack-cli](https://github.com/webpack/webpack-cli) 是使用 webpack 的命令行工具,在 4.x 版本之后不再作為 webpack 的依賴了,我們使用時需要單獨安裝這個工具。
在項目中,我們更多地會把 webpack 作為項目的開發依賴來安裝使用,這樣可以指定項目中使用的 webpack 版本,更加方便多人協同開發:
> 確保你的項目中有 package.json 文件,如果沒有可以使用 `npm init` 來創建。
```
npm install webpack -D
# 或者
yarn add webpack -D
```
這樣 webpack 會出現在 package.json 中,我們再添加一個 npm scripts:
```
"scripts": {
"build": "webpack --mode production"
},
"devDependencies": {
"webpack": "^4.1.1",
"webpack-cli": "^2.0.12",
}
```
然后我們創建一個 `./src/index.js` 文件,可以寫任意的 JS 代碼。創建好了之后執行 `npm run build` 或者 `yarn build` 命令,你就會發現新增了一個 `dist` 目錄,里邊存放的是 webpack 構建好的 `main.js` 文件。
因為是作為項目依賴進行安裝,所以不會有全局的命令,npm/yarn 會幫助我們在當前項目依賴中尋找對應的命令執行,如果是全局安裝的 webpack,直接執行 `webpack --mode production` 就可以。
webpack 4.x 的版本可以零配置就開始進行構建,但是筆者覺得這個功能還不全面,缺少很多實際項目需要的功能,所以基本你還是需要一個配置文件,后邊會詳細講解。
我們先來了解 webpack 中的一些基本概念。
## webpack 的基本概念
webpack 本質上是一個打包工具,它會根據代碼的內容解析模塊依賴,幫助我們把多個模塊的代碼打包。借用 webpack 官網的圖片:

如上圖,webpack 會把我們項目中使用到的多個代碼模塊(可以是不同文件類型),打包構建成項目運行僅需要的幾個靜態文件。webpack 有著十分豐富的配置項,提供了十分強大的擴展能力,可以在打包構建的過程中做很多事情。我們先來看一下 webpack 中的幾個基本概念。
### 入口
如上圖所示,在多個代碼模塊中會有一個起始的 `.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](https://webpack.js.org/plugins/uglifyjs-webpack-plugin/),常用的還有定義環境變量的 [DefinePlugin](https://webpack.js.org/plugins/define-plugin/),生成 CSS 文件的 [ExtractTextWebpackPlugin](https://webpack.js.org/plugins/extract-text-webpack-plugin/) 等。在這里提到這些 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](https://github.com/facebookincubator/create-react-app)
create-react-app 的 webpack 配置在這個項目下:[react-scripts](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/README.md)。
* [angular/devkit/build-webpack](https://github.com/angular/devkit/blob/master/packages/angular_devkit/build_webpack/README.md)
通常 angular 的項目開發和生產的構建任務都是使用 angular-cli 來運行的,但 angular-cli 只是命令的使用接口,基礎功能是由 [angular/devkit](https://github.com/angular/devkit) 來實現的,webpack 的構建相關只是其中一部分,詳細的配置可以參考 [webpack-configs](https://github.com/angular/devkit/tree/master/packages/angular_devkit/build_webpack/src/angular-cli-files/models/webpack-configs) 。
* [vue-cli](https://github.com/vuejs/vue-cli/)
vue-cli 使用 webpack 模板生成的項目文件中,webpack 相關配置存放在 build 目錄下。
這些工具都提供了極其完整的配置來幫助開發者快捷開始一個項目,我們可以學習了解它們所提供的 webpack 配置,有些情況下,還會嘗試修改這些配置以滿足特殊的需求。
所以你也會發現,這些極其流行的前端類庫或者框架都提供了基于 webpack 的工具,webpack 基本成為前端項目構建工具的標配。
> 這三個工具中,只有 angular-cli 使用了 4.x 版本的 webpack,其他的都還是用的 3.x 版本,學習的時候要留意一下版本區別。
## 小結
webpack 的安裝和使用和大多數使用 Node.js 開發的命令行工具一樣,使用 npm 安裝后執行命令即可,webpack 4.x 版本的零配置特性也讓上手變得更加簡單。
前面我們已經介紹了 webpack 的幾個重要的概念:入口,loader,plugin,輸出,并且展示了一個簡單的 webpack 配置例子,最后提供了前端社區三大框架基于 webpack 的腳手架工具的鏈接,也許這些工具提供的配置會比較難懂,后續的小節會幫助你逐漸去深入,慢慢地,你會對 webpack 配置越來越得心應手。
## 例子
本小節提及的一些簡單的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。