[TOC]
# 概念總結
<span style="font-family: 楷體; font-size: 20px; color: #007fff; font-weight: bold;">entry points(入口)</span>
指示 webpack 應該使用哪個模塊,來作為構建其內部依賴圖的開始。
進入入口起點后,webpack 會找出有哪些模塊和庫是入口起點(直接和間接)依賴的。
<span style="font-family: 楷體; font-size: 20px;color: #007fff; font-weight: bold;">output(輸出)</span>
output 屬性告訴 webpack 在哪里輸出它所創建的 bundles,以及如何命名這些文件,默認值為 ./dist
<span style="font-family: 楷體; font-size: 20px;color: #007fff; font-weight: bold;">loader </span>
loader 讓 webpack 能夠去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
loader 可以將所有類型的文件轉換為 webpack 能夠處理的有效模塊,然后你就可以利用 webpack 的打包能力,對它們進行處理。
test 屬性:用于標識出應該被對應的 loader 進行轉換的某個或某些文件
use 屬性:表示進行轉換時,應該使用哪個 loader
<span style="font-family: 楷體; font-size: 20px;color: #007fff; font-weight: bold;">plugins</span>
插件的范圍包括,從打包優化和壓縮,一直到重新定義環境中的變量。
想要使用一個插件,你只需要 require() 它,然后把它添加到 plugins 數組中。
多數插件可以通過選項(option)自定義。
你也可以在一個配置文件中因為不同目的而多次使用同一個插件,這時需要通過使用 new 操作符來創建它的一個實例。
<span style="font-family: 楷體; font-size: 20px;color: #007fff; font-weight: bold;">module、chunk、bundle</span>
- module:webpack 支持 commonJS、ES6 等模塊化規范,簡單來說就是你通過 import 語句引入的代碼就可以視為一個個的 module。
- chunk: chunk 是 webpack 根據功能拆分出來的,包含三種情況:
1、你的項目入口(entry)
2、通過 import() 動態引入的代碼
3、通過 splitChunks 拆分出來的代碼
chunk 包含著 module,可能是一對多也可能是一對一。
- bundle:bundle 是 webpack 打包之后的各個文件,一般就是和 chunk 是一對一的關系(也可能是多個 chunk 對應一個 bundle),bundle 就是對 chunk 進行編譯壓縮打包等處理之后的產出。
# Webpack 生命周期(流程)
[https://juejin.im/post/5beb8875e51d455e5c4dd83f#heading-14](https://juejin.im/post/5beb8875e51d455e5c4dd83f#heading-14)
這里只是大概梳理一下,具體分析可閱讀參考鏈接
1、初始化參數:從配置文件和 `Shell` 語句中讀取與合并參數,得出最終的參數;
2、開始編譯:用上一步得到的參數初始化 `Compiler` 對象,加載所有配置的插件,執行對象的 `run` 方法開始執行編譯;
3、確定入口:根據配置中的 entry 找出所有的入口文件
4、編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;
5、完成模塊編譯:在經過第 4 步使用 Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內容以及它們之間的依賴關系;
6、輸出資源:根據入口和模塊之間的依賴關系,組裝成一個個包含多個模塊的 `Chunk`,再把每個 `Chunk` 轉換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內容的最后機會;
7、輸出完成:在確定好輸出內容后,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。 在以上過程中,`webpack` 會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件后會執行特定的邏輯,并且插件可以調用 `webpack` 提供的 API 改變 `webpack` 的運行結果。
# Entry 與 Output 配置
## 基礎
先來看最基礎的寫法:
```js
const config = {
entry: './path/to/my/entry/file.js'
};
// 是下面的簡寫
const config = {
entry: {
main: './path/to/my/entry/file.js'
}
};
```
那么其表示什么意思呢?
入口起點(entry point) 指示 webpack 應該使用哪個模塊,來作為構建其內部 **依賴圖** 的開始。進入入口起點后,webpack 會找出有哪些模塊和庫是入口起點(直接和間接)依賴的。
每個依賴項隨即被處理,最后輸出到稱之為 bundles 的文件中。可以把依賴圖理解為一個表示模塊間依賴關系的數據結構,有了這個數據結構才可以做一些代碼分割,tree shaking 之類的操作。
## 配置多個入口
注意多個入口并不是指多頁面,即如下的配置并不會生成多個 html 文件,那么這表示什么意思呢?這告訴 webpack 需要 2 個獨立分離的依賴圖。
```js
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js', // 使用占位符確保生成的文件名不同
path: __dirname + '/dist'
}
}
// 寫入到硬盤:./dist/app.js, ./dist/search.js
```
## output 中的常用配置
- filename:用于輸出文件的文件名
- path:目標輸出目錄的絕對路徑
- publicPath:如果配置了給選項,所有的 src 都會增加 publicPath 前綴(把靜態資源放在 CDN 上才使用這個配置)
假設有如下的配置:
```js
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub: './src/index.js'
},
output: {
publicPath: 'http://cdn.com.cn',
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
```
那么我們的 dist 目錄結構會是這樣的:
```js
|-- dist
|-- index.html
|-- main.js
|-- sub.js
```
生成的 html 文件會是這樣的:所以說其 src 都會加上 publicPath 前綴
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>html 模版</title>
</head>
<body>
<div id='root'></div>
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script><script type="text/javascript" src="http://cdn.com.cn/sub.js"></script></body>
</html>
```
## 多頁面配置
多頁面簡單來說就是打包生成的 html 文件不止一個,呃...至少記住你要生成幾個 html 文件 entry 就要有幾個,然后對應的 HtmlWebpackPlugin 也要 new 幾次。可以參考這篇文章:[https://blog.csdn.net/nongweiyilady/article/details/79255746](https://blog.csdn.net/nongweiyilady/article/details/79255746)
# 常用 loader
## file-loader
官網:[https://www.webpackjs.com/loaders/file-loader/](https://www.webpackjs.com/loaders/file-loader/)
安裝:`npm install --save-dev file-loader`
用途:可用于加載 .png .jpg .gif 等格式的圖片(似乎可以被 url-loader 替代?)
默認情況下,生成的文件的文件名就是文件內容的 MD5 哈希值并會保留所引用資源的原始擴展名
```js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
}
}
```
## url-loader
官網:[https://www.webpackjs.com/loaders/url-loader/](https://www.webpackjs.com/loaders/url-loader/)
安裝:`npm install --save-dev url-loader`
用途:功能類似于 file-loader,但是在文件大小(單位 byte)低于指定的限制時,可以返回一個 DataURL。(即 base64 格式,圖片被合并到 JS 代碼中,這樣可以節省 HTTP 請求,但是對大文件不應該這么處理,因為這么做會影響對 JS 文件大小有較大的影響)
PS:如果對一個 3KB 的圖片做 base64 處理,那么其合并到的 JS 文件增加的體積幾乎是略多于 3KB 的
下圖就是 base64 的形式:

一般配置:
```js
module.exports = {
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]', // 占位符 [name]:文件原本的名字 [ext]:擴展名
outputPath: 'images/', // 生成到 dist 目錄下 images 文件夾下
limit: 2048 // 2048 = 2KB
}
}
}
]
}
}
```
## 處理樣式文件(以 sass 為例)
`npm install css-loader style-loader -D`
- css-loader:解釋 @import 和 url() ,會 import / require() 后再解析(resolve)它們
- style-loader:將 CSS 解析為 \<style> 標簽后添加到 DOM 中
`npm install sass-loader node-sass –save-dev`
- sass-loader:讀取 SASS/SCSS 文件并將其編譯為 CSS 文件
配置 webpack 時需要注意 loader 的順序:從 use 數組中最后一個 loader 開始
```js
module.exports = {
...
module: {
rules: [
{
test: /\.scss$/,
use: [{
loader: 'style-loader' // 將 JS 字符串生成 style 節點
}, {
loader: 'css-loader' // 將 CSS 轉化成 CommonJS 模塊
}, {
loader: 'sass-loader' // 將 sass 編譯成 css
}]
}
]
}
}
```
一般也會加上 postcss-loader (如自動為 CSS3 屬性添加廠商前綴,還有其他功能)
官網:[https://www.webpackjs.com/loaders/postcss-loader/](https://www.webpackjs.com/loaders/postcss-loader/)
`npm i postcss-loader -D`
`npm i autoprefixer -D`
可以像下面這樣配置,也可以將 postcss.config.js 抽離出來
```js
{
test: /\.scss$/,
use: [
'style-loader', // 將 JS 字符串生成為 style 節點
'css-loader', // 將 css 轉換成 CommonJS 模塊
'postcss-loader' // 添加其他 css 需求,如處理廠商前綴
'sass-loader', // 將 Sass 編譯成 css
]
}
```
## 處理字體樣式
基本上就是復制到輸出目錄,做個哈希處理
```js
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
```
# 常用 plugin
## HtmlWebpackPlugin
官網:[https://www.webpackjs.com/plugins/html-webpack-plugin/](https://www.webpackjs.com/plugins/html-webpack-plugin/)
安裝:`npm install --save-dev html-webpack-plugin`
用途: 打包時并沒有把 index.html 放入 dist 目錄,使用這個插件可以解決這個問題,其還幫助我們自動把生成的 script 文件添加到 index.html 中(即使每次打包生成的文件名不同,其也可以正確地引入)
我們使用腳手架構建的 vue 或 react 項目一般都有一個 html 模板:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>html 模版</title>
</head>
<body>
<div id='root'></div>
</body>
</html>
```
HtmlWebpackPlugin 就可以幫助我們整合這個模板到輸出目錄的 index.html 中:
```js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
...
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html' // template 配置接收一個模板文件
}), new CleanWebpackPlugin(['dist'])],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
```
最后生成的 index.html 中就會多一行如
```html
<script src="bundle.js"></script>
```
## CleanWebpackPlugin
上面也有寫到這個插件,這個插件在每次重新構建前幫我們清理 /dist 文件夾
安裝:`npm install clean-webpack-plugin --save-dev`
像上面那樣 require 引入然后 new CleanWebpackPlugin(['dist']) 即可
## DLLPlugin
[https://www.webpackjs.com/plugins/dll-plugin/](https://www.webpackjs.com/plugins/dll-plugin/)
該插件主要是針對第三方模塊,減少對這些模塊的重復的不必要的分析;這個插件主要用于提升打包速度而不是減小打包生成的文件的體積。
其原理就是把網頁依賴的基礎模塊抽離出來打包到 dll 文件中,當需要導入的模塊存在于某個 dll 中時,這個模塊不再被打包,而是去 dll 中獲取。
>TODO:配置
## workbox-webpack-plugin(PWA 插件)
簡言之:在你第一次訪問一個網站的時候,如果成功,做一個緩存,當服務器掛了之后,你依然能夠訪問這個網頁 ,這是 PWA 的特性之一。這個只需要在線上環境,才需要做 PWA 的處理,以防不測。
` cnpm i workbox-webpack-plugin -D
`
```js
const WorkboxPlugin = require('workbox-webpack-plugin') // 引入 PWA 插件
const prodConfig = {
plugins: [
// 配置 PWA
new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
]
}
在入口文件加上
// 判斷該瀏覽器支不支持 serviceWorker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(registration => {
console.log('service-worker registed')
})
.catch(error => {
console.log('service-worker registed error')
})
})
}
```
# 其他常用配置項及概念介紹
## SourceMap
官網:[https://www.webpackjs.com/configuration/devtool/](https://www.webpackjs.com/configuration/devtool/)
sourceMap 通過 devtool 選項來配置,sourceMap 的作用就是建立一個源代碼和打包生成的代碼的映射關系。
假設打包生成的 dist 目錄下的 main.js 文件 96 行出錯了,如果有 sourceMap,那么它就可以幫我們找到是源代碼 src 目錄下 index.js 中的第一行(我們寫代碼的位置)出錯了。
常用配置:
- 開發環境(development):cheap-module-eval-source-map
- 線上環境(production):一般不建立 source-map,如果出錯了需要排查再使用
另外,關于其可選值的一些理解:
- inline:即 source-map 文件以 data-url 的形式放在打包生成的文件中
- cheap:簡化提示信息(減少某些不必要的映射關系如第三方模塊),可以減少打包時間
- module:與第三方模塊的錯誤映射以及一些 loader 出錯相關
- eval:錯誤會在打包生成的代碼中以 eval 的形式提示
```js
module.exports = {
mode: 'development',
// development devtool: 'cheap-module-eval-source-map',
// production devtool: 'cheap-module-source-map' or 'none',
+ devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/index.js'
},
module: {
...
},
plugins: [
...
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
```
## WebpackDevServer
官網:[https://www.webpackjs.com/configuration/dev-server/#devserver-proxy](https://www.webpackjs.com/configuration/dev-server/#devserver-proxy)
通過 devServer 選項來進行配置,只用于開發環境,幫助我們快速進行開發。隨便說幾個功能就比如可以做代理服務,修改代碼自動刷新(重新打包后自動刷新)等。
```js
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/index.js'
},
+ devServer: {
+ contentBase: './dist', // 服務器根路徑:當前目錄 dist 文件下
+ open: true, // 啟動 webpack-dev-server 時幫我們自動啟動瀏覽器并訪問服務
+ port: 8080 // 為什么需要以 web 服務器的形式啟動? 如果以文件形式打開 URL 是 file 而不是 http 協議,不能發 AJAX 請求
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
```
## 開啟 Hot Module Replace(HMR,模塊熱替換)
官網:[https://www.webpackjs.com/guides/hot-module-replacement/](https://www.webpackjs.com/guides/hot-module-replacement/)
如果只進行上述 WebPackDevserver 的配置你會發現你的操作會被重置,這種完全刷新對于 vuex 這類狀態管理工具是不友好的(Vue Loader,React Hot Loader 都內置了一些這種 module.hot 的判斷來幫我們實現模塊熱替換)。
該功能允許在運行時更新各種模塊,而無需進行完全刷新,注意以下幾個要點:
- 方便我們修改 CSS 直接查看效果,不影響 JS
- 需要注意一些 JS 操作,比如添加節點,我們更改代碼后是刪除之前的節點再重新執行添加節點的操作還是直接重新執行添加節點的操作?(前者是1個節點后者是2個,看官網提供的例子)
- HMR 不適用于生產環境,應該只在開發環境使用
- 打包生成的文件是在內存中的,因此我們看不到生成的文件
我們只需要更新 webpack-dev-server 的配置,以及使用 HMR 的插件
- hot:開啟 HMR
- hotOnly:即使 HMR 不生效,也不讓瀏覽器自動刷新
另外,HMR 插件是 webpack 自帶的,所以 require(‘webpack’) 即可,new webpack.HotModuleReplacementPlugin()
```js
const webpack = require('webpack');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/index.js'
},
devServer: {
contentBase: './dist',
open: true,
port: 8080,
+ hot: true,
+ hotOnly: true
},
module: {
rules: [{
...
}]
},
plugins: [
+ new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
```
### HMR 的實現原理
來源:[https://zhuanlan.zhihu.com/p/30669007](https://zhuanlan.zhihu.com/p/30669007)

根據這張流程圖來分析:
1. 第一步,在 webpack 的 watch 模式下,文件系統中某一個文件發生修改,webpack 監聽到文件變化,根據配置文件對模塊重新編譯打包,并將打包后的代碼通過簡單的 JavaScript 對象保存在內存中。
2. 第二步是 webpack-dev-server 和 webpack 之間的接口交互,而在這一步,主要是 dev-server 的中間件 webpack-dev-middleware 和 webpack 之間的交互,webpack-dev-middleware 調用 webpack 暴露的 API 對代碼變化進行監控,并且告訴 webpack,將代碼打包到內存中。
3. 第三步是 webpack-dev-server 對文件變化的一個監控,這一步不同于第一步,并不是監控代碼變化重新打包。當我們在配置文件中配置了 devServer.watchContentBase 為 true 的時候,Server 會監聽這些配置文件夾中靜態文件的變化,變化后會通知瀏覽器端對應用進行 live reload。注意,這兒是瀏覽器刷新,和 HMR 是兩個概念。
4. 第四步也是 webpack-dev-server 代碼的工作,該步驟主要是通過 sockjs(webpack-dev-server 的依賴)在瀏覽器端和服務端之間建立一個 websocket 長連接,將 webpack 編譯打包的各個階段的狀態信息告知瀏覽器端,同時也包括第三步中 Server 監聽靜態文件變化的信息。瀏覽器端根據這些 socket 消息進行不同的操作。當然服務端傳遞的最主要信息還是新模塊的 hash 值,后面的步驟根據這一 hash 值來進行模塊熱替換。
5. webpack-dev-server/client 端并不能夠請求更新的代碼,也不會執行熱更模塊操作,而把這些工作又交回給了 webpack,webpack/hot/dev-server 的工作就是根據 webpack-dev-server/client 傳給它的信息以及 dev-server 的配置決定是刷新瀏覽器呢還是進行模塊熱更新。當然如果僅僅是刷新瀏覽器,也就沒有后面那些步驟了。
6. HotModuleReplacement.runtime 是客戶端 HMR 的中樞,它接收到上一步傳遞給他的新模塊的 hash 值,它通過 JsonpMainTemplate.runtime 向 server 端發送 Ajax 請求,服務端返回一個 json,該 json 包含了所有要更新的模塊的 hash 值,獲取到更新列表后,該模塊再次通過 jsonp 請求,獲取到最新的模塊代碼。這就是上圖中 7、8、9 步驟。
7. 而第 10 步是決定 HMR 成功與否的關鍵步驟,在該步驟中,HotModulePlugin 將會對新舊模塊進行對比,決定是否更新模塊,在決定更新模塊后,檢查模塊之間的依賴關系,更新模塊的同時更新模塊間的依賴引用。
8. 最后一步,當 HMR 失敗后,回退到 live reload 操作,也就是進行瀏覽器刷新來獲取最新打包代碼。
## tree shaking
官方文檔:[https://www.webpackjs.com/guides/tree-shaking/](https://www.webpackjs.com/guides/tree-shaking/)
- 開發環境默認不開啟 tree shaking
- 線上環境默認開啟,也就是說你只要配置 mode 即可
tree shaking 是一個術語,通常用于描述移除 JavaScript 上下文中的未引用代碼(dead-code)。
它依賴于 ES2015 模塊系統中的靜態結構特性,例如 import 和 export。這個術語和概念實際上是興起于 ES2015 模塊打包工具 rollup。
只支持 import 和 export 的模塊化方法。
簡單來說就是把代碼中不需要引入的模塊去掉,或者說只引入模塊的一部分。
比如我們定義一個 math.js 文件:
```js
// math.js
export const add = (a, b) => {
console.log( a + b );
}
export const minus = (a, b) => {
console.log( a - b );
}
```
然后 index.js 只引入 add 方法:
```js
// index.js
// Tree Shaking 只支持 ES Module
import { add } from './math.js';
add(1, 2);
```
如果沒有開啟 tree shaking,你會發現 minus 方法也被添加到打包生成的文件中,如果使用了 tree shaking,那么 minus 方法就屬于未被引用的代碼,丟棄。
> 你可以將應用程序想象成一棵樹。綠色表示實際用到的源碼和 library,是樹上活的樹葉。灰色表示無用的代碼,是秋天樹上枯萎的樹葉。為了除去死去的樹葉,你必須搖動這棵樹,使它們落下。
package.json 中可以配置 sideEffects ,值為 false 為一個數組,如果為數組,數組中的模塊表示不需要進行 tree shaking 的
副作用的定義是,在導入時會執行特殊行為的代碼,而不是僅僅暴露一個 export 或多個 export。舉例說明,例如 polyfill,它影響全局作用域,并且通常不提供 export。
注意,任何導入的文件都會受到 tree shaking 的影響。這意味著,如果在項目中使用類似 css-loader 并導入 CSS 文件,則需要將其添加到 side effect 列表中,以免在生產模式中無意中將它刪除:
```js
// package.json
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js",
"*.css"
]
}
```
## Code Spliting(代碼分離)
官網:[https://webpack.js.org/configuration/optimization/](https://webpack.js.org/configuration/optimization/)
用途:將代碼分離到不同的 bundle 中,然后便可以按需加載或并行加載這些文件。
為什么要把整個應用分為一個個的 bundle ?如果全部打包為一個 bundle 將會有以下后果:
- 首屏加載時間長
- 影響緩存性能:如果修改了整個代碼中的任何一部分,那么用戶下次訪問時將重新加載全部的文件;如果分為 bundle,我們修改了一部分的 bundle,那么用戶下次訪問只需要重新加載這一個 bundle,其他的 bundle 可以直接從緩存中讀取
有兩種方式來實現:看你的引入模塊的方式
1.如果是靜態加載(import 語句),只需要配置最外層的 optimization 選項
```js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
+ optimization: {
+ splitChunks: {
+ chunks: 'all'
+ }
+ }
};
```
2.如果是動態加載(使用 import() 語法),無需上述配置,webpack 會解析該語法幫我們做代碼分離
其區別大概是這樣?
```js
import _ from 'lodash' // 同步引入模塊的方式
var element = document.createElement('div')
element.innerHTML = _.join(['Hello'], 'world')
document.body.appendChild(element)
// do something...
// 異步方式
function getComponent () {
// 下面的注釋稱為 magicname
return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['Hello'], 'world')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
```
> 可能解釋的有錯,可以去看官網
## SplitChunksPlugin
我們可以看到上面的 optimization 其實就是使用了該插件,它有如下的默認配置項:
```js
splitChunks: {
chunks: "async", // 做代碼分割時只對異步代碼生效 async(默認) / all / initial
minSize: 30000, // 引入的模塊大于 30000 字節才做代碼分割 30KB
minChunks: 1, // 當一個模塊至少被引用了多少次才做代碼分割
maxAsyncRequests: 5, // 最多同時加載的請求數,一般不動
maxInitialRequests: 3, // 不動
automaticNameDelimiter: '~', // 打包生成的文件的連接符 vendors~main.js
name: true, // 不動
cacheGroups: { // 分割出的代碼的文件名,緩存組:符合同等要求的第三方庫會打包到一起
vendors: { // 配置組
test: /[\\/]node_modules[\\/]/, // 如果在node_modules 中
priority: -10, // 優先級
// filename: ‘vendors.js’ 則放到 vendors.js 文件中
},
default: { // 如果上述組不匹配
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 防止模塊的重復引入
}
}
}
```
## 懶加載
官網:[https://www.webpackjs.com/guides/lazy-loading/](https://www.webpackjs.com/guides/lazy-loading/)
懶加載或者按需加載,是一種很好的優化網頁或應用的方式。這種方式實際上是先把你的代碼在一些邏輯斷點處分離開,然后在一些代碼塊中完成某些操作后,立即引用或即將引用另外一些新的代碼塊。這樣加快了應用的初始加載速度,減輕了它的總體體積,因為某些代碼塊可能永遠不會被加載。
許多框架和類庫對于如何用它們自己的方式來實現(懶加載)都有自己的建議。這里有一些例子:
* React:[Code Splitting and Lazy Loading](https://reacttraining.com/react-router/web/guides/code-splitting)
* Vue:[Lazy Load in Vue using Webpack's code splitting](https://alexjoverm.github.io/2017/07/16/Lazy-load-in-Vue-using-Webpack-s-code-splitting/)
* AngularJS:[AngularJS + Webpack = lazyLoad](https://medium.com/@var_bin/angularjs-webpack-lazyload-bb7977f390dd)by[@var\_bincom](https://twitter.com/var_bincom)
## resolve 配置項
resolve 配置項一般用于配置 **別名** 和 **默認引入文件**
例如,一些位于 src/ 文件夾下的常用模塊:
```js
alias: {
'@': path.join(__dirname, 'src'),
Utilities: path.resolve(__dirname, 'src/utilities/'),
Templates: path.resolve(__dirname, 'src/templates/')
}
```
現在,替換「在導入時使用相對路徑」這種方式,就像這樣:
```js
import Utility from '../../utilities/utility';
```
你可以這樣使用別名:
```js
import Utility from 'Utilities/utility';
```
再看下面這段代碼:
```js
resolve: {
extensions: ['.js', '.jsx']
}
```
這段配置表示:當我們引入一個 js 模塊時,先找 js 文件,再找 jsx 文件,這樣我們就可以寫成
`import Child from ‘./child` 而不是 `import Child from ‘./child.jsx’`
但是注意如果 extensions 過多,反而會影響文件的查找速度。
一般資源類的文件要求寫后綴(.css .png),邏輯類(.vue .jsx .js)可以不寫(自己配)
```js
resolve: {
extensions: ['.js', '.jsx'],
mainFiles: ['index', 'child']
}
```
mainFiles 配置表示:當你引入一個目錄時,默認找名字為 index 的文件,如果找不到就找名字為 child 的文件。
## babel 配置
官網:[https://www.babeljs.cn/docs/](https://www.babeljs.cn/docs/)
官方提供的 webpack 配置說明:[https://www.babeljs.cn/setup#installation](https://www.babeljs.cn/setup#installation)
>TODO: babel 配置
## library 打包
library 主要是你在寫一個庫才要注意這些問題,你寫的庫可能被別人用 import、require 等方式引入,要注意很多問題,還要保證文件體積盡可能小。
```js
const path = require('path')
// 作為一個 library / 庫 別人會怎么引入呢?
// 1.import 2.require 3.AMD ... 如何配置?
module.exports = {
mode: 'production',
entry: './src/index.js',
externals: 'lodash', // 考慮這么一種情況:用戶引入我們的庫又引入了 lodash, 該配置項會忽略(不打包)lodash,但會要求用戶先引入 lodash
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library', // 支持 <script src="library.js"></script> 方式引入
libraryTarget: 'umd' // 不管是哪種模塊化方式引入,都可正確配置 還有 this,window,global 可選值
}
}
```
如果我們想發布庫到 npm 倉庫,需要先注冊 npm 賬號,然后改一下入口文件(package.json),然后 npm publish,然后別人就可以 npm isntall 了
比如我們的打包結果是 dist 目錄下的 library.js
那么 package.json 中配置如下一行即可:
`"main": "./dist/library.js"`
## TypeScript 的打包配置
使用 typeScript 最大的好處就是規范我們的代碼,報錯信息更詳細(類型檢查等)
官網:[https://www.webpackjs.com/guides/typescript/#%E5%9F%BA%E7%A1%80%E5%AE%89%E8%A3%85](https://www.webpackjs.com/guides/typescript/#%E5%9F%BA%E7%A1%80%E5%AE%89%E8%A3%85)
1.npm install typescript ts-loader 以支持 typescript 打包
2.需要在根路徑下創建 tsconfig.json
```js
// tsconfig.js
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"allowJs": true
}
}
```
3.配置 loader
```js
module: {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
```
4.使用第三方庫并使用 TypeScript 做類型檢驗時需要先安裝例如 @types/lodash
## 開發環境(Development)與線上環境(Production)
其大致有如下區別:
1. sourceMap:開發環境比較詳細,線上環境可以不需要
2. 代碼壓縮:開發環境一般不進行代碼壓縮,線上環境 webpack 自動?幫我們進行代碼壓縮
3. 線上環境不需要 devServer 配置
4. 線上環境不需要熱重啟
5. 開發環境打包生成的文件是存儲在內存中的(便于熱重啟),線上環境直接以文件形式存儲
對于重復的配置代碼,可以提取到 webpack.common.js 中,`npm install webpack-merge -D`,然后可以這么做合并:
```js
+ const merge = require('webpack-merge');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+ devtool: 'inline-source-map',
+ devServer: {
+ contentBase: './dist'
+ }
+ });
```
# 自己寫一個 loader 和 plugin
## loader
如果需要都代碼做一些統一的替換或者包裝之類的,可以利用 webpack 的 loader API:[https://www.webpackjs.com/api/loaders/#%E5%90%8C%E6%AD%A5-loader](https://www.webpackjs.com/api/loaders/#%E5%90%8C%E6%AD%A5-loader)
loader 相當于一個函數,對你的源代碼進行處理后再返回,webpack 規定了與配置對應的 API,比如參數如何獲取之類的;
我們寫一個 replaceloader.js 文件如下:需要先引入 loader-tuils `npm install loader-utils -D`
```js
const loaderUtils = require('loader-utils')
// source 為 compiler 傳遞給 Loader 的一個文件的原內容
module.exports = function (source) {
// 獲取到用戶給當前 Loader 傳入的 options
const options = loaderUtils.getOptions(this);
return source.replace('lee', 'world')
}
```
這個 loader 的作用很簡單,就是代碼中的 'lee' 字符串替換成 'world',其參數為資源文件
然后 webpack 配置如下:
```js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
resolveLoader: {
modules: ['node_modules', './loaders']
},
module: {
rules: [{
test: /\.js/,
use: [
{
loader: 'replaceLoader',
}
]
}]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
```
打包后 index.js 中的代碼就會被這個 loader 處理
## plugin
plugin 比 loader 麻煩些,plugin 相當于一個個的鉤子,在打包過程中的某個時刻起作用,webpack 就提供了一系列鉤子供我們使用:[https://www.webpackjs.com/api/plugins/](https://www.webpackjs.com/api/plugins/)
真要寫的話得又得研究一堆 API 了......
假設我們寫一個 copyright-webpack-plugin.js 文件如下:
```js
class CopyrightWebpackPlugin {
apply(compiler) {
compiler.hooks.compile.tap('CopyrightWebpackPlugin', (compilation) => {
console.log('compiler');
})
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => {
debugger;
compilation.assets['copyright.txt']= {
source: function() {
return 'copyright by dell lee'
},
size: function() {
return 21;
}
};
cb();
})
}
}
module.exports = CopyrightWebpackPlugin;
```
我們的配置文件這么寫就可以使用我們寫的插件了:
```js
const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new CopyRightWebpackPlugin()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
```
# 其他
## 提升 Webpack 打包速度
① 升級 webpack、node 、npm、yarn 的版本
② 在盡可能少的模塊上應用 Loader,使用 include / exclude 配置,一般忽略 node_modules 下的 js 文件
③ Plugin 盡可能精簡并確保可靠,首先要注意開發環境和線上環境的一些區別:如代碼是否需要壓縮,SourceMap 是否需要準確,devServer 是否啟用等;另外最好使用官方網站使用的插件,插件會影響打包速度
## 打包分析工具
推薦[https://github.com/webpack-contrib/webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)
## 搭建 vue 和 react 的 webpack 配置
vue:[https://juejin.im/post/5cc55c336fb9a032086dd701#heading-21](https://juejin.im/post/5cc55c336fb9a032086dd701#heading-21)
react:[https://juejin.im/post/5cfe4b13f265da1bb13f26a8#heading-2](https://juejin.im/post/5cfe4b13f265da1bb13f26a8#heading-2)
react-webpack4:[https://github.com/Z6T/react-webpack4-cook](https://github.com/Z6T/react-webpack4-cook)
再去看看 vue 和 react 的腳手架的 webpack 的配置...我 TM 都學了點啥......

## 參考資料
[https://juejin.im/post/5cfe4b13f265da1bb13f26a8#heading-20](https://juejin.im/post/5cfe4b13f265da1bb13f26a8#heading-20)
[https://segmentfault.com/a/1190000015088834](https://segmentfault.com/a/1190000015088834)
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs