webpack 作為目前最流行的項目打包工具,被廣泛使用于項目的構建和開發過程中,其實說它是打包工具有點大材小用了,我個人認為它是一個集前端自動化、模塊化、組件化于一體的可拓展系統,你可以根據自己的需要來進行一系列的配置和安裝,最終實現你需要的功能并進行打包輸出。
而在 Vue 的項目中,webpack 同樣充當著舉足輕重的作用,比如打包壓縮、異步加載、模塊化管理等等。如果你了解 webpack 那么相信本文會讓你更了解其在 Vue 中的使用,如果你是一個 webpack 小白,那么也沒事,相信你會很容易的了解它在項目中的配置和功能。
## webpack 的使用
### 1\. 與 vue-cli 2.x 的差異
如果你使用過 vue-cli 2.x,那么你應該了解其構建出的目錄會包含相應的 webpack 配置文件,但是在 vue-cli 3.x 中你卻見不到一份關于 webpack 的配置文件,難道 3.x 拋棄了 webpack?其實不然,3.x 提供了一種開箱即用的模式,即你無需配置 webpack 就可以運行項目,并且它提供了一個 vue.config.js 文件來滿足開發者對其封裝的 webpack 默認配置的修改。如圖:

### 2\. vue.config.js 的配置
通過上方新老版本的對比,我們可以清晰的看出 vue.config.js 的配置項結構,如果你構建的項目中沒有該文件,那么你需要在根目錄手動創建它。下面我們就來介紹一下其常用配置項的功能和用途:
### a. baseurl
在第一節《Vue CLI 3 項目構建基礎》中我們通過 vue-cli 3.x 成功構建并在瀏覽器中打開 `http://localhost:8080/` 展示了項目首頁。如果現在你想要將項目地址加一個二級目錄,比如:`http://localhost:8080/vue/`,那么我們需要在 vue.config.js 里配置 baseurl 這一項:
```
// vue.config.js
module.exports = {
...
baseUrl: 'vue',
...
}
```
其改變的其實是 webpack 配置文件中 output 的 `publicPath` 項,這時候你重啟終端再次打開頁面的時候我們首頁的 url 就會變成帶二級目錄的形式。
### b. outputDir
如果你想將構建好的文件打包輸出到 output 文件夾下(默認是 dist 文件夾),你可以配置:
```
// vue.config.js
module.exports = {
...
outputDir: 'output',
...
}
```
然后運行命令 `yarn build` 進行打包輸出,你會發現項目跟目錄會創建 output 文件夾, 這其實改變了 webpack 配置中 output 下的 `path` 項,修改了文件的輸出路徑。
### c. productionSourceMap
該配置項用于設置是否為生產環境構建生成 source map,一般在生產環境下為了快速定位錯誤信息,我們都會開啟 source map:
```
// vue.config.js
module.exports = {
...
productionSourceMap: true,
...
}
```
該配置會修改 webpack 中 `devtool` 項的值為 `source-map`。
開啟 source map 后,我們打包輸出的文件中會包含 js 對應的 .map 文件,其用途可以參考:[JavaScript Source Map 詳解](http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html)
### d. chainWebpack
chainWebpack 配置項允許我們更細粒度的控制 webpack 的內部配置,其集成的是 [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) 這一插件,該插件可以讓我們能夠使用鏈式操作來修改配置,比如:
```
// 用于做相應的合并處理
const merge = require('webpack-merge');
module.exports = {
...
// config 參數為已經解析好的 webpack 配置
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.tap(options =>
merge(options, {
limit: 5120,
})
)
}
...
}
```
以上操作我們可以成功修改 webpack 中 module 項里配置 rules 規則為圖片下的 url-loader 值,將其 limit 限制改為 5M,修改后的 webpack 配置代碼如下:
```
{
...
module: {
rules: [
{
/* config.module.rule('images') */
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
/* config.module.rule('images').use('url-loader') */
{
loader: 'url-loader',
options: {
limit: 5120,
name: 'img/[name].[hash:8].[ext]'
}
}
]
}
]
}
...
}
```
這里需要注意的是我們使用了 webpack-merge 這一插件,該插件用于做 webpack 配置的合并處理,這樣 options 下面的其他值就不會被覆蓋或改變。
關于 webpack-chain 的使用可以參考其 github 官方地址:[https://github.com/mozilla-neutrino/webpack-chain](https://github.com/mozilla-neutrino/webpack-chain),它提供了操作類似 JavaScript Set 和 Map 的方式,以及一系列速記方法。

### e. configureWebpack
除了上述使用 chainWebpack 來改變 webpack 內部配置外,我們還可以使用 configureWebpack 來進行修改,兩者的不同點在于 chainWebpack 是鏈式修改,而 configureWebpack 更傾向于整體替換和修改。示例代碼如下:
```
// vue.config.js
module.exports = {
...
// config 參數為已經解析好的 webpack 配置
configureWebpack: config => {
// config.plugins = []; // 這樣會直接將 plugins 置空
// 使用 return 一個對象會通過 webpack-merge 進行合并,plugins 不會置空
return {
plugins: []
}
}
...
}
```
configureWebpack 可以直接是一個對象,也可以是一個函數,如果是對象它會直接使用 webpack-merge 對其進行合并處理,如果是函數,你可以直接使用其 config 參數來修改 webpack 中的配置,或者返回一個對象來進行 merge 處理。
你可以在項目目錄下運行 `vue inspect` 來查看你修改后的 webpack 完整配置,當然你也可以縮小審查范圍,比如:
```
# 只查看 plugins 的內容
vue inspect plugins
```
### f. devServer
vue.config.js 還提供了 devServer 項用于配置 webpack-dev-server 的行為,使得我們可以對本地服務器進行相應配置,我們在命令行中運行的 `yarn serve` 對應的命令 `vue-cli-service serve` 其實便是基于 webpack-dev-server 開啟的一個本地服務器,其常用配置參數如下:
```
// vue.config.js
module.exports = {
...
devServer: {
open: true, // 是否自動打開瀏覽器頁面
host: '0.0.0.0', // 指定使用一個 host。默認是 localhost
port: 8080, // 端口地址
https: false, // 使用https提供服務
proxy: null, // string | Object 代理設置
// 提供在服務器內部的其他中間件之前執行自定義中間件的能力
before: app => {
// `app` 是一個 express 實例
}
}
...
}
```
當然除了以上參數,其支持所有的 webpack-dev-server 中的選項,比如 `historyApiFallback` 用于重寫路由(會在后續的多頁應用配置中講解)、progress 將運行進度輸出到控制臺等,具體可參考:[devServer](https://www.webpackjs.com/configuration/dev-server/)
以上講解了 vue.config.js 中一些常用的配置項功能,具體的配置實現需要結合實際項目進行,完整的配置項可以查看:[vue.config.js](https://github.com/vuejs/vue-cli/blob/ce3e2d475d63895cbb40f62425bb6b3237469bcd/docs/zh/config/README.md)
### 3\. 默認插件簡介
通過對 vue.config.js 的了解,我們知道了 vue-cli 3.x 為我們默認封裝了項目運行的常用 webpack 配置,那么它給我們提供了哪些默認插件,每一個 plugin 又有著怎樣的用途呢?除了使用 `vue inspect plugins` 我們還可以通過運行 `vue ui` 進入可視化頁面查看,步驟如下:
* 打開可視化頁面,點擊對應項目進入管理頁面(如果沒有對應項目,需要導入或新建)
* 點擊側邊欄 Tasks 選項,再點擊二級欄 inspect 選項
* 點擊 Run task 按鈕執行審查命令
如圖所示:

最后我們從輸出的內容中找到 plugins 數組,其包含了如下插件(配置項已經省略,增加了定義插件的代碼):
```
// vue-loader是 webpack 的加載器,允許你以單文件組件的格式編寫 Vue 組件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// webpack 內置插件,用于創建在編譯時可以配置的全局常量
const { DefinePlugin } = require('webpack');
// 用于強制所有模塊的完整路徑必需與磁盤上實際路徑的確切大小寫相匹配
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
// 識別某些類型的 webpack 錯誤并整理,以提供開發人員更好的體驗。
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
// 將 CSS 提取到單獨的文件中,為每個包含 CSS 的 JS 文件創建一個 CSS 文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 用于在 webpack 構建期間優化、最小化 CSS文件
const OptimizeCssnanoPlugin = require('optimize-css-assets-webpack-plugin');
// webpack 內置插件,用于根據模塊的相對路徑生成 hash 作為模塊 id, 一般用于生產環境
const { HashedModuleIdsPlugin } = require('webpack');
// 用于根據模板或使用加載器生成 HTML 文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 用于在使用 html-webpack-plugin 生成的 html 中添加 <link rel ='preload'> 或 <link rel ='prefetch'>,有助于異步加載
const PreloadPlugin = require('preload-webpack-plugin');
// 用于將單個文件或整個目錄復制到構建目錄
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
/* config.plugin('vue-loader') */
new VueLoaderPlugin(),
/* config.plugin('define') */
new DefinePlugin(),
/* config.plugin('case-sensitive-paths') */
new CaseSensitivePathsPlugin(),
/* config.plugin('friendly-errors') */
new FriendlyErrorsWebpackPlugin(),
/* config.plugin('extract-css') */
new MiniCssExtractPlugin(),
/* config.plugin('optimize-css') */
new OptimizeCssnanoPlugin(),
/* config.plugin('hash-module-ids') */
new HashedModuleIdsPlugin(),
/* config.plugin('html') */
new HtmlWebpackPlugin(),
/* config.plugin('preload') */
new PreloadPlugin(),
/* config.plugin('copy') */
new CopyWebpackPlugin()
]
}
```
我們可以看到每個插件上方都添加了使用 chainWebpack 訪問的方式,同時我也添加了每個插件相應的用途注釋,需要注意的是要區分 webpack 內置插件和第三方插件的區別,如果是內置插件則無需安裝下載,而外部插件大家可以直接訪問:[https://www.npmjs.com/](https://www.npmjs.com/) 搜索對應的插件,了解其詳細的 api 設置。
## 結語
本文主要闡述了 vue-cli 3.x 下基于 vue.config.js 配置 webpack 的主要方法,同時也介紹了其默認的 webpack 插件與主要功能,相信大家在了解 webpack 的知識后能夠更加輕松的開展后續內容的學習,為接下來項目的構建和開發奠定基礎。
## 思考 & 作業
* 除了文章中介紹的配置項,`vue.config.js` 中還有哪些額外的配置?
* `webpack-merge` 的合并原理是怎樣的?
* 使用 `chainWebpack` 獲取到 webpack 中的某一插件后,如何修改其配置?
- 開篇:Vue CLI 3 項目構建基礎
- 構建基礎篇 1:你需要了解的包管理工具與配置項
- 構建基礎篇 2:webpack 在 CLI 3 中的應用
- 構建基礎篇 3:env 文件與環境設置
- 構建實戰篇 1:單頁應用的基本配置
- 構建實戰篇 2:使用 pages 構建多頁應用
- 構建實戰篇 3:多頁路由與模板解析
- 構建實戰篇 4:項目整合與優化
- 開發指南篇 1:從編碼技巧與規范開始
- 開發指南篇 2:學會編寫可復用性模塊
- 開發指南篇 3:合理劃分容器組件與展示組件
- 開發指南篇 4:數據驅動與拼圖游戲
- 開發指南篇 5:Vue API 盲點解析
- 開發拓展篇 1:擴充你的開發工具
- 開發拓展篇 2:將 UI 界面交給第三方庫
- 開發拓展篇 3:嘗試使用外部數據
- 總結篇:寫在最后