在構建代碼并部署到生產環境之前,我們需要一個本地環境,用于運行我們開發的代碼。這個環境相當于提供了一個簡單的服務器,用于訪問 webpack 構建好的靜態文件,我們日常開發時可以使用它來調試前端代碼。
之前在第 2 小節的**啟動靜態服務**部分,我們已經簡單介紹過 webpack-dev-server 的使用了。webpack-dev-server 是 webpack 官方提供的一個工具,可以基于當前的 webpack 構建配置快速啟動一個靜態服務。當 mode 為 development 時,會具備 hot reload 的功能,即當源碼文件變化時,會即時更新當前頁面,以便你看到最新的效果。
## webpack-dev-server 的基礎使用
webpack-dev-server 是一個 npm package,安裝后在已經有 webpack 配置文件的項目目錄下直接啟動就可以:
```
npm install webpack-dev-server -g
webpack-dev-server --mode development
```
webpack-dev-server 本質上也是調用 webpack,4.x 版本的也要指定 mode,其實 webpack-dev-server 應該直接把 development 作為默認值,有興趣的同學可以查看這個 issue:[Default mode to development?](https://github.com/webpack/webpack-dev-server/issues/1327)。
建議把 webpack-dev-server 作為開發依賴安裝,然后使用 npm scripts 來啟動,如:
```
npm install webpack-dev-server --save-dev
```
package 中的 scripts 配置:
```
{
// ...
"scripts": {
"start": "webpack-dev-server --mode development"
}
}
```
```
npm run start
```
webpack-dev-server 默認使用 8080 端口,如果你使用了 html-webpack-plugin 來構建 HTML 文件,并且有一個 index.html 的構建結果,那么直接訪問 http://localhost:8080/ 就可以看到 index.html 頁面了。如果沒有 HTML 文件的話,那么 webpack-dev-server 會生成一個展示靜態資源列表的頁面。

## webpack-dev-server 的配置
在 webpack 的配置中,可以通過 `devServer` 字段來配置 webpack-dev-server,如端口設置、啟動 gzip 壓縮等,這里簡單講解幾個常用的配置。
`public` 字段用于指定靜態服務的域名,默認是 http://localhost:8080/ ,當你使用 Nginx 來做反向代理時,應該就需要使用該配置來指定 Nginx 配置使用的服務域名。
`port` 字段用于指定靜態服務的端口,如上,默認是 8080,通常情況下都不需要改動。
`publicPath` 字段用于指定構建好的靜態文件在瀏覽器中用什么路徑去訪問,默認是 `/`,例如,對于一個構建好的文件 `bundle.js`,完整的訪問路徑是 `http://localhost:8080/bundle.js`,如果你配置了 `publicPath: 'assets/'`,那么上述 `bundle.js` 的完整訪問路徑就是 `http://localhost:8080/assets/bundle.js`。可以使用整個 URL 來作為 `publicPath` 的值,如 `publicPath: 'http://localhost:8080/assets/'`。**如果你使用了 HMR,那么要設置 `publicPath` 就必須使用完整的 URL**。
> 建議將 `devServer.publicPath` 和 `output.publicPath` 的值保持一致。
`proxy` 用于配置 webpack-dev-server 將特定 URL 的請求代理到另外一臺服務器上。當你有單獨的后端開發服務器用于請求 API 時,這個配置相當有用。例如:
```
proxy: {
'/api': {
target: "http://localhost:3000", // 將 URL 中帶有 /api 的請求代理到本地的 3000 端口的服務上
pathRewrite: { '^/api': '' }, // 把 URL 中 path 部分的 `api` 移除掉
},
}
```
webpack-dev-server 的 proxy 功能是使用 [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) 來實現的,如果需要更詳細的 proxy 配置,可以參考官方文檔 [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware#example)。
`contentBase` 用于配置提供額外靜態文件內容的目錄,之前提到的 `publicPath` 是配置構建好的結果以什么樣的路徑去訪問,而 `contentBase` 是配置額外的靜態文件內容的訪問路徑,即那些不經過 webpack 構建,但是需要在 webpack-dev-server 中提供訪問的靜態資源(如部分圖片等)。推薦使用絕對路徑:
```
// 使用當前目錄下的 public
contentBase: path.join(__dirname, "public")
// 也可以使用數組提供多個路徑
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "assets")]
```
> `publicPath` 的優先級高于 `contentBase`。
`before` 和 `after` 配置用于在 webpack-dev-server 定義額外的中間件,如
```
before(app){
app.get('/some/path', function(req, res) { // 當訪問 /some/path 路徑時,返回自定義的 json 數據
res.json({ custom: 'response' })
})
}
```
`before` 在 webpack-dev-server 靜態資源中間件處理之前,可以用于攔截部分請求返回特定內容,或者實現簡單的數據 mock。
`after` 在 webpack-dev-server 靜態資源中間件處理之后,比較少用到,可以用于打印日志或者做一些額外處理。
webpack-dev-server 的配置項比較多,這里只列舉了一些日常比較有用的,更多的請參考官方文檔 [webpack-dev-server](https://doc.webpack-china.org/configuration/dev-server/)。
## webpack-dev-middleware
如果你熟悉使用 Node.js 來開發 Web 服務,使用過 [Express](https://expressjs.com/) 或者 [Koa](http://koajs.com/),那么對中間件的概念應該會有所了解。
簡而言之,中間件就是在 Express 之類的 Web 框架中實現各種各樣功能(如靜態文件訪問)的這一部分函數。多個中間件可以一起協同構建起一個完整的 Web 服務器。
不熟悉 Express 中間件概念的同學可以參考 Express 的官方文檔 [使用中間件](http://www.expressjs.com.cn/guide/using-middleware.html)。
[webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) 就是在 Express 中提供 webpack-dev-server 靜態服務能力的一個中間件,我們可以很輕松地將其集成到現有的 Express 代碼中去,就像添加一個 Express 中間件那么簡單。
首先安裝 webpack-dev-middleware 依賴:
```
npm install webpack-dev-middleware --save-dev
```
接著創建一個 Node.js 服務的腳本文件,如 app.js:
```
const webpack = require('webpack')
const middleware = require('webpack-dev-middleware')
const webpackOptions = require('./webpack.config.js') // webpack 配置文件的路徑
// 本地的開發環境默認就是使用 development mode
webpackOptions.mode = 'development'
const compiler = webpack(webpackOptions)
const express = require('express')
const app = express()
app.use(middleware(compiler, {
// webpack-dev-middleware 的配置選項
}))
// 其他 Web 服務中間件
// app.use(...)
app.listen(3000, () => console.log('Example app listening on port 3000!'))
```
然后用 Node.js 運行該文件即可:
```
node app.js # 使用剛才創建的 app.js 文件
```
使用 webpack-dev-server 的好處是相對簡單,直接安裝依賴后執行命令即可,而使用 webpack-dev-middleware 的好處是可以在既有的 Express 代碼基礎上快速添加 webpack-dev-server 的功能,同時利用 Express 來根據需要添加更多的功能,如 mock 服務、代理 API 請求等。
其實 webpack-dev-server 也是基于 Express 開發的,前面提及的 webpack-dev-server 中 `before` 或 `after` 的配置字段,也可以用于編寫特定的中間件來根據需要添加額外的功能。
## 實現一個簡單的 mock 服務
在前端的日常開發工作中,我們本地需要的不僅僅是提供靜態內容訪問的服務,還需要模擬后端 API 數據來做一些應用測試工作,這個時候我們需要一個 mock 數據的服務,而 webpack-dev-server 的 `before` 或 `proxy` 配置,又或者是 webpack-dev-middleware 結合 Express,都可以幫助我們來實現簡單的 mock 服務。
這一部分內容涉及比較多的 Node.js 代碼實現,這里不做過于詳細的例子解釋,只提供一些實現的思路。
我們最主要的需求是當瀏覽器請求某一個特定的路徑時(如 /some/path ),可以訪問我們想要的數據內容。
我們先基于 Express app 實現一個簡單 mock 功能的方法:
```
module.export = function mock(app) {
app.get('/some/path', (req, res) => {
res.json({ data: '' })
})
// ... 其他的請求 mock
// 如果 mock 代碼過多,可以將其拆分成多個代碼文件,然后 require 進來
}
```
然后應用到配置中的 `before` 字段:
```
const mock = require('./mock')
// ...
before(app) {
mock(app) // 調用 mock 函數
}
```
這樣的 `mock` 函數照樣可以應用到 Express 中去,提供與 webpack-dev-middleware 同樣的功能。
由于 `app.get('', (req, res) => { ... })` 的 callback 可以拿到 `req` 請求對象,其實可以根據請求參數來改變返回的結果,即通過參數來模擬多種場景的返回數據來協助測試多種場景下的代碼應用。
當你單獨實現或者使用一個 mock 服務時,你可以通過 proxy 來配置部分路徑代理到對應的 mock 服務上去,從而把 mock 服務集成到當前的開發服務中去,相對來說也很簡單。
當你和后端開發進行聯調時,亦可使用 proxy 代理到對應聯調使用的機器上,從而可以使用本地前端代碼的開發環境來進行聯調。當然了,連線上環境的異常都可以這樣來嘗試定位問題。
## 小結
本小節介紹了 webpack-dev-server 的基礎使用及其更多的一些配置選項,如何使用 webpack-dev-middleware 來將 webpack 的開發環境集成到現有的 Node 服務中去,以及如何在 webpack-dev-server 和 webpack-dev-middleware 的基礎上實現簡單的 mock 服務。
## 例子
本小節提及的一些簡單的 Demo 可以在 [webpack-examples](https://github.com/teabyii/webpack-examples) 找到。