前言
webpack 作為前端領域的模組化打包工具,相信大家都不陌生。現在很火的 react 和 vue 的一些腳手架都是基於 webpack 開發定製的,因此,瞭解並會配置 webpack 還是很有必要的(文章基於 webpack4.x 版本來講解)。
PS:文章內容可能有點長,大家提前做好心理準備。
1.webpack 是什麼
官方定義:
webpack 是一個現代 JavaScript 應用程式的靜態模組打包器。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖,其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。
個人理解:
webpack 作為一個模組化打包工具,根據入口檔案(任何型別檔案,不一定是 js 檔案)遞迴處理模組中引入的 js/css/scss/image 等檔案,將其轉換打包為瀏覽器可以識別的基礎檔案(js/css/image 檔案等)。
![webpack](https://i.iter01.com/images/6459391796c743369bd3899c8756ae3f9f8fc4877c82a70e46720b5ab3399c7c.png)
與 grunt/gulp 等區別:
1.grunt 與 gulp 屬於自動化流程工具,通過配置檔案指明對哪些檔案執行編譯、組合、壓縮等具體任務,由工具自動完成這些任務。
2.webpack 作為模組化打包工具,把專案作為一個整體,通過入口檔案,遞迴找到所有依賴檔案,通過 loader 和 plugin 針對檔案進行處理,最後打包生成不同的 bundle 檔案。
2.webpack 基本配置
當你想使用 webpack 打包專案時,需要在專案目錄下新建 webpack.config.js,webpack 預設會讀取 webpack.config.js 作為配置檔案,進而執行打包構建流程。
先來看一下 webpack 的基本配置項,留個印象先。
webpack.config.js
const path = require('path');
module.exports = {
mode: 'production/development/none', // 打包模式,使用對應模式的內建優化
entry: './src/index.js', // 入口,支援單入口、多入口
output: { // 輸出相關配置
filename: 'xx.js', // 輸出檔案的檔名
path: path.resolve(__dirname, 'dist') // 輸出檔案的絕對路徑,預設為dist
},
module: { // 針對不同型別檔案的轉換
rules: [
{
test: /\.xx$/, // 針對某型別檔案處理,使用正則匹配檔案
use: [
{
loader: 'xx-loader', // 使用xx-loader進行轉換
options: {} // xx-loader的配置
}
]
}
]
},
plugins: [ // 外掛,完成特定任務,如壓縮/拆分
new xxPlugin({ options });
]
};
複製程式碼
webpack 有五個核心概念:入口(entry)、輸出(output)、模式(mode)、loader、外掛(plugins)。
2.1.入口(entry)
入口指示 webpack 應該使用哪個模組,來作為構建其內部依賴圖的開始。預設值為./src
。
2.1.1.單入口
單入口是指 webpack 打包只有一個入口,單入口支援單檔案和多檔案打包。
通常像 vue/react spa 應用都屬於單入口形式,以
src/index.js
作為入口檔案。
(1)單檔案打包
不指定入口檔案的 entryChunkName 時,預設為 main。
// webpack.config.js
module.exports = {
entry: "./src/index.js"
};
複製程式碼
上面的單入口語法,是下面的簡寫:
module.exports = {
entry: {
main: "./src/index.js"
}
};
複製程式碼
main 表示 entryChunkName 為 main,打包後生成的檔案 filename 為 main。
webpack 打包後,dist 資料夾生成 main.js 檔案。
![webpack打包單檔案](https://i.iter01.com/images/45a3eb2f7f936789220748a12fec486210fed9065e38dbf6446636be8d1ae076.png)
也可以將 entryChunkName 修改為其他值,打包出的 filename 也會對應改變。
(2)多檔案打包
多檔案打包入口以陣列形式表示,表示將多個檔案一起注入到 bundle 檔案中。
module.exports = {
entry: ["./src/index.js", "./src/main.js"]
};
複製程式碼
2.1.2.多入口
多入口是指 webpack 打包有多個入口模組,多入口 entry 一般採用物件語法表示,應用場景:
(1)分離應用程式 app 和第三方入口(vendor)
module.exports = {
entry: {
app: "./src/index.js",
vendor: "./src/vendor.js"
}
};
複製程式碼
webpack 打包後,生成應用程式 app.js 和 vendor.js。
![分離應用程式和第三方](https://i.iter01.com/images/ec19d1ab986f9a68227b2c90f9367634fdc2cfd1e94ec6ef5657b3afebc1b9c1.png)
(2)多頁面打包,一般指多個 html 文件形式,每個文件只使用一個入口。
module.exports = {
entry: {
app: "./src/app.js",
home: "./src/home.js",
main: "./src/main.js"
}
};
複製程式碼
webpack 打包後,dist 資料夾下生成 app.js、home.js、main.js 三個檔案。
![多入口打包](https://i.iter01.com/images/5d1b4d1975c558c6e6be81aaebf3ddd347903ffd81401496782fffc503a43f05.png)
2.2.輸出(output)
output 選項可以控制 webpack 如何輸出打包檔案,output 屬性包含 2 個屬性:
- filename:輸出檔案的檔名
- path:輸出目錄的絕對路徑(注意是絕對路徑)
即使存在多個入口起點,webpack 只有一個輸出配置,不對 output 進行配置時,預設輸出到./dist 資料夾。
2.2.1.單入口輸出
單入口打包常用配置如下:
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js"
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist") // __dirname表示js檔案執行的絕對路徑,使用path.resolve生成dist資料夾的絕對路徑
}
};
複製程式碼
webpack 打包後,dist 資料夾下生成 bundle.js 檔案
![單入口輸出](https://i.iter01.com/images/3e02594a672b1afa4ce7400034683f807042a8a2b4adc1417995f4fe07c9cb49.png)
2.2.2.多入口輸出
當存在多入口時,應該使用佔位符來確保每個檔案具有唯一的名稱,否則 webpack 打包會報錯。
![webpack打包報錯](https://i.iter01.com/images/f9c4ce7d349ebb541a3b7f1412a63ee7634922b49eda2f32a56dcc973a745270.png)
佔位符 name 與 entry 物件中的 key 一一對應。
正確的寫法如下:
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].js", // 使用佔位符來表示
path: path.resolve(__dirname, "dist")
}
};
複製程式碼
webpack 打包後,在 dist 資料夾下生成了 app.js、home.js、main.js 檔案。
![多入口輸出](https://i.iter01.com/images/4f59710a72c20ec0f7af5c23ba18959457149b6c71aa0b1392f96cec128fff5d.png)
2.2.3.hash、chunkhash、contenthash 揭祕
在揭祕 hash、chunkhash、contenthash 之前,我們先看下 webpack 打包輸出資訊。
![webpack打包資訊](https://i.iter01.com/images/7764697a1514eb0f3d1d1260ecdedeba14e5c3b3391e043c97d4cf1262e3f732.png)
Hash:與整個專案構建相關,當專案中不存在檔案內容變更時,hash 值不變,當存在檔案修改時,會生成新的一個 hash 值。
Version:webpack 版本
Time:構建時間
Build at:開始構建時間
Asset:輸出檔案
Size:輸出檔案大小
Chunks:chunk id
ChunkNames:對應 entryChunkName
Entrypoint:入口與輸出檔案的對應關係
如果使用佔位符來表示檔案,當檔案內容變更時,仍然生成同樣的檔案,無法解決瀏覽器快取檔案問題。藉助於 hash、chunkhash、contenthash 可以有效解決問題。
(1)hash
整個專案構建生成的一個 md5 值,專案檔案內容不變,hash 值不變。
使用 hash 關聯輸出檔名稱
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].[hash].js",
path: path.resolve(__dirname, "dist")
}
};
複製程式碼
filename: "[name].[hash:7].js"表示去 hash 值的前 7 位
webpack 打包,看到新生成檔案帶上了 hash 值
![hash](https://i.iter01.com/images/05e0bcf08010d436254c9bee901ee1186746e9f96b8c100f790eae12dfced5f3.png)
當我們修改 app.js 檔案內容後,重新打包,發現可以生成了新的 hash 值,所有檔案的名稱都發生了變更。
![新hash](https://i.iter01.com/images/63cec344c5627cf1e53165617196b13abaabadd5166c5df97513729406835440.png)
問題:當我修改了專案中的任何一個檔案時,導致未修改檔案快取都將失效。
(2)chunkhash
webpack 構建時,根據不同的入口檔案,構建對應的 chunk,生成對應的 hash 值,每個 chunk 的 hash 值都是不同的。
使用 chunkhash 關聯檔名
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
}
};
複製程式碼
使用 webpack 打包後,dist 目錄下,每個 bundle 檔案都帶有不同的 chunkash 值。
![chunkhash](https://i.iter01.com/images/05a82272bb190bc643f4359f5a609468df13113e208b6adf0b0f5f9959c0f4f6.png)
修改 app.js 內容,重新打包,只有 app 檔名稱發生了變更。
![新chunkash](https://i.iter01.com/images/eed9273246e8cdc115abb6f5efa0ae74c3cc2b63aa0d937a12646f09160e72b0.png)
使用 chunkhash 可以有效解決 hash 快取失效問題,但是當在 js 檔案裡面引入 css 檔案時,將 js、css 分別打包,若 js 件內容變化時,css 檔名稱也會變更。
app.js 中引入了 css 檔案
import "./css/style.css";
console.log("app");
複製程式碼
webpack 配置
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, "css-loader"]
}
]
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css檔案
filename: "css/style.[chunkhash].css"
})
]
};
複製程式碼
打包,dist 資料夾下生成了 css 與 js 檔案,chunkhash 一致。
![chunkhash-css-js](https://i.iter01.com/images/d46302a30e4c7305344d98cc178d00509d0bd0b6a42589437921442a3f58e43e.png)
當我們修改 app.js 檔案內容後,重新打包,發現 css 檔名也變更了,css 檔案快取將失效,這顯然不是我們想要的結果。
![chunk-css-js1](https://i.iter01.com/images/a5f9dfc5fd1b4818116457c4dceceb2108decca1d4704c344b8ffa11bc77356c.png)
問題:js 引入 css 等其他檔案時,js 檔案變更,css 等檔名也會變更,快取失效。
(3)contenthash
contenthash 表示由檔案內容產生的 hash 值,內容不同產生的 contenthash 值也不一樣。藉助於 contenthash 可以解決上述問題,只要 css 檔案不變,快取一直有效。
修改 webpack 配置,css filename 使用 contenthash 表示。
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, "css-loader"]
}
]
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css檔案
filename: "css/style.[contenthash].css"
})
]
};
複製程式碼
打包後,dist 目錄下,生成了 css、js 檔案,app 檔案包含 chunkhash 值,css 檔案包含 contenthash 值。
![contenthash](https://i.iter01.com/images/049af779a729b2e3c9e1e7f4180c1d01760335321a1781090bb945b1a8b9248b.png)
修改 app.js 檔案內容,重新打包,app 檔案重新命名了,css 檔案沒變,快取有效。
![contenthash1](https://i.iter01.com/images/4c50ab3296865dc0684ce027e4b20cfa9c5309538183885dcb7746938496e058.png)
專案中 css 等非 js 檔案抽離最好使用 contenthash。
2.3.模式(mode)
webpack 提供了 mode 配置選項,用來選擇使用響應的內建優化,不配置 mode 選項時,預設使用 production 模式。
mode 選項有 3 個可選值:production(生產模式、預設)、development(開發模式)、none。
2.3.1.production 模式
production 模式下,會自動開啟 Tree Shaking(去除無用程式碼)和檔案壓縮(UglifyJs)。
在 fun.js 中定義了 2 個函式
export function f1() {
console.log("f1");
}
export function f2() {
console.log("f2");
}
複製程式碼
在 app.js 中只引入了 f1
import { f1 } from "./fun";
f1();
複製程式碼
production 模式打包,檢視打包後的檔案,只引入了 f1,並且程式碼進行了壓縮。
![production模式打包](https://i.iter01.com/images/784a2c8733f46e7872d17cf2b029f46fa7e9d175c9cbe8a20b24bef6c30c856c.png)
2.3.2.development 模式
development 模式下,webpack 會啟用 NamedChunksPlugin 和 NamedModulesPlugin 外掛。
同樣的程式碼,development 模式下打包,將 f1 和 f2 都一起打包了,而且程式碼並沒有進行壓縮。
![development模式下打包](https://i.iter01.com/images/38ce61922ab7c8949bb51b151bf739993fb6d687be6e2399d3ca44228144d706.png)
2.3.3.none 模式
none 模式下,webpack 不會使用任何內建優化,這種模式很少使用。
2.4.loader
loader 用於對模組的原始碼進行轉換。loader 可以實現檔案內容的轉換,比如將 es6 語法轉換為 es5 語法,將 scss 轉換為 css 檔案,將 image 轉換為 base 64 位編碼。一般 loader 配置在 module 的 rules 選項中。
常用的 loader 有:
- 處理 js/jsx/ts
babel-loader:將程式碼轉換為 ES5 程式碼
ts-loader:將 ts 程式碼轉換為 js 程式碼 - 處理樣式
style-loader:將模組的匯出作為樣式新增到 DOM style 中
css-loader:解析 css 檔案後,使用 import 載入,並且返回 CSS 程式碼
less-loader:載入和轉譯 less 檔案
sass-loader:載入和轉譯 sass/scss 檔案 - 檔案轉換
file-loader:將檔案傳送到輸出資料夾,返回相對 url,一般用於處理圖片、字型
url-loader:和 file-loader 功能一樣,但如果檔案小於限制,返回 data URL,常用於圖片 base 64 轉換
下面就以 scss 轉換的例子,描述如何使用 loader
app.js 中引入了 main.scss 檔案
// app.js
import "./css/main.scss";
複製程式碼
webpack 配置如下
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
entry: {
app: "./src/app.js"
},
module: { // 針對專案中不同型別模組的處理
rules: [ // 匹配請求的規則陣列
{
test: /\.scss$/, // 檢測scss檔案結尾的檔案
exclude: /node_modules/, // 排除查詢範圍
include: [path.resolve(__dirname, "src/css")], // 限定查詢範圍
use: [miniCssExtractPlugin.loader, "css-loader", "sass-loader"] // loader鏈式呼叫,從最右邊向左處理
}
]
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css檔案
filename: "css/style.[contenthash].css"
})
]
};
複製程式碼
其中,sass-loader 用於將 scss 檔案編譯成 css 檔案,css-loader 用於解釋 import(),miniCssExtractPlugin 用於將 css 抽離到單獨的檔案中。
關於 loader 有幾點說明:
1.loader 支援鏈式呼叫,一組鏈式 loader 按照相反的順序執行,loader 鏈中的前一個 loader 返回值給下一個 loader,最後一個 loader 輸出檔案。
上面例子中,loader 執行順序:sass-loader => css-loader => miniCssExtractPlugin.loader。
2.loader 可以使用 options 物件進行配置,像下面這樣:
module: {
//
rules: [
{
test: /\.scss$/, // 檢測scss檔案結尾的檔案
exclude: /node_modules/, // 排除查詢範圍
include: [path.resolve(__dirname, "src/css")], // 限定查詢範圍
use: [
miniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true
}
},
"sass-loader"
] // loader鏈式呼叫,從最右邊向左處理
}
]
},
複製程式碼
2.5.plugins(外掛)
外掛是 webpack 的支柱功能,旨在解決 loader 無法實現的其他事。外掛可以攜帶引數,配置外掛需要向 plugins 陣列中傳入 new 例項。
常用的外掛有:
clean-webpack-plugin:清空 dist 資料夾
html-webpack-plugin:生成 html 檔案
mini-css-extract-plugin:抽離 css 檔案
optimize-css-assets-webpack-plugin:優化和壓縮 css
css-split-webpack-plugin:針對 css 大檔案進行拆分
webpack-bundle-analyzer:webpack 打包結果分析
webpack.DllPlugin:建立 dll 檔案和 manifest 檔案
webpack.DllReferencePlugin:把只有 dll 的 bundle 引用到需要的預編譯的依賴。
SplitChunksPlugin:拆分程式碼塊,在 optimization.splitChunks 中配置。
下面以 html-webpack-plugin 為例說明 plugins 的用法,這裡只列出 plugins 部分的配置
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css檔案
filename: "css/style.[contenthash].css"
}),
new HtmlWebpackPlugin({ // 生成外掛例項
filename: "index.html", // 生成模板的名稱
minify: {
collapseWhitespace: true, // 去除空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
removeEmptyElements: true, // 移除空元素
removeScriptTypeAttributes: true, // 移除script type屬性
removeStyleLinkTypeAttributes: true // 移除link type屬性
}
})
]
複製程式碼
打包後,生成了 index.html 檔案
![html-webpack-plugin1](https://i.iter01.com/images/14ec0aca8819602feb6a44f235b31ebf4fcdc30129ba9d2fa168679a6bd34f59.png)
開啟 index.html,看到 css 和 js 檔案被引入了
![html-webpack-plugin2](https://i.iter01.com/images/c92dc781ccbd5714d4de6c9e8e8f7d85e06936879620757195b018e98a5505f5.png)
接下來描述 webpack 其他常用的一些配置 resolve、devServer、devtool。
2.6.resolve(解析)
resolve 選項設定模組如何被解析。
2.6.1.alias
建立 import 或 require 的別名,確保模組引入變得簡單。
下面的例子針對 css、util 資料夾 設定了 alias 別名,引入資料夾下面的檔案可以直接使用相對地址。
resolve: {
alias: {
css: path.resolve(__dirname, "src/css"),
util: path.resolve(__dirname, "src/util")
}
},
複製程式碼
app.js 檔案中引入 util 資料夾下的 common.js 檔案,就會引入 src/util/common.js 檔案。
import fun1 from "util/common.js";
複製程式碼
2.6.2.extensions
自動解析引入模組的擴充套件,按照從左到右的順序解析。
resolve: {
extensions: [".js", ".json"]
}
複製程式碼
在 app.js 中引入 common.js 可以不攜帶字尾,由 webpack 自動解析。
import fun1 from "util/common";
複製程式碼
2.6.3.mainFiles
解析目錄時要使用的檔名,預設
mainFiles: ["index"]
複製程式碼
也可以指定多個 mainFiles,會依次從左到右解析
mainFiles: ["index", "main"]
複製程式碼
比如需要從 util 資料夾下引入 index.js 檔案,import 只需要匯入到 util 資料夾,webpack 會自動從 util 資料夾下引入 index.js 檔案。
import index from "util";
複製程式碼
extentsion 和 mainFiles 屬性雖然會方便開發者簡寫,但是會增加 webpack 額外的解析時間。
2.7.devServer
devServer 主要用於 development 模式配置本地開發伺服器,需要安裝 webpack-dev-server。
npm i webpack-dev-server -g
複製程式碼
devServer 常用的配置項如下:
devServer: {
contentBase: path.resolve(__dirname, "dist"), // 告訴伺服器從哪裡提供內容
host: "localhost", // 制定一個host,預設localhost
port: 3000, // 請求埠號,預設8080
compress: true, // 啟用gzip壓縮
https: true, // 開啟http服務
hot: true, // 啟用模組熱替換
open: true, // 自動開啟預設瀏覽器
index: "index.html", // 頁面入口html檔案,預設index.html
headers: {
// 所有響應中新增首部內容
"X-Custom-Foo": "bar"
},
proxy: {
"/api": "http://localhost:3000"
}
}
複製程式碼
讀取配置檔案,啟動開發伺服器。
webpack-dev-server --config webpack.dev.js
複製程式碼
2.8.devtool
source map 一個儲存原始碼與編譯程式碼對應位置的對映資訊檔案,它是專門給偵錯程式準備的,它主要用於 debug。
webpack 通過配置 devtool 屬性來選擇一種 source map 來增強除錯過程。
以下是官方對於 devtool 的各種 source map 的比較:
![devtool](https://i.iter01.com/images/d56b98f9cd856adf9b482bd85d4c005cc3323a9cf5d71f79a7d3b6513bea06f8.png)
development 模式下 devtool 設定為 cheap-module-eval-source-map,production 模式下 devtool 設定為 souce-map。
3.webpack 實踐
接下來將通過一個完整的例子實現 react 專案的完整 webpack 配置。 先全域性安裝 webpack 和 webpack-dev-server。
npm i webpack webpack-dev-server -g
複製程式碼
3.1.配置執行檔案
新建一個目錄,結構如下:
![目錄完整結構](https://i.iter01.com/images/092f4821938e3b760b66823e48725126053dd943ac649239ce22d4086b569453.png)
其中 public 資料夾下包含 index.html 入口 html 檔案,src 資料夾下包含 index.js 入口 js 檔案,css 資料夾、font 資料夾、image 資料夾。
webpack 配置如下:
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
}
};
複製程式碼
在目錄下使用npm init
新建 package.json 檔案,設定 dev 和 build 的 script,
分別用於開發模式和生產模式。
![package.json](https://i.iter01.com/images/6b7da02d8686e6125d57a62b8dbc732d3561a37c5b943a32d5a1997fbb89a70a.png)
3.1.處理 jsx、es6
在 react 專案中,我們使用 jsx 和 es6 語法,為了相容低版本瀏覽器,需要通過 babel 轉換。
先安裝 babel 相關依賴包
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/preset-react @babel/polyfill @babel/runtime -D
複製程式碼
babel-loader:處理 ES6 語法,將其編譯為瀏覽器可以執行的 js 語法
@babel/core-babel:babel 核心模組
@babel/preset-env:轉換 es6 語法,支援最新的 javaScript 語法
@babel/preset-react:轉換 jsx 語法
@babel/plugin-transform-runtime: 避免 polyfill 汙染全域性變數,減小打包體積
@babel/polyfill: ES6 內建方法和函式轉化墊片
將 index.js 作為入口檔案,引入 App.jsx 元件
//index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
console.log(App);
ReactDOM.render(<App />, document.getElementById("root"));
//App.jsx
import React, { Component } from "react";
class App extends Component {
render() {
return <h2>This is a react app.</h2>;
}
}
export default App;
複製程式碼
webpack 配置如下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // clean-webpack-plugin用來清空dist資料夾
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [new CleanWebpackPlugin()]
};
複製程式碼
clean-webpack-plugin:清空 dist 資料夾
新建.babelrc 檔案
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
複製程式碼
執行npm run build
,打包成功,在 dist 資料夾下生成了 app.js 檔案
![jsx轉換](https://i.iter01.com/images/ec50b85699b70e3ba08f5626ae49c5d52544187d18aa3aff44e5d1a9b1fa7e8b.png)
3.2.配置 html 模板
配置 html 模板表示配置 index.html 檔案相關配置,將打包後的檔案引入到 index.html 檔案,通過 html-webpack-plugin 外掛實現。
先安裝 html-webpack-plugin 外掛
npm i html-webpack-plugin -D
複製程式碼
webpack 配置如下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 引入html-webpack-plugin外掛
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html", // 模板檔名
template: path.resolve(__dirname, "public/index.html"), // 模板檔案源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製程式碼
執行 npm run build,打包成功,在 dist 資料夾下生成了 index.html 和 app.js 打包檔案
![模板配置](https://i.iter01.com/images/e019214ea090e56aa29aca1e90c73f7ea7599c484a1ef4102f8abe48d31bbb99.png)
開啟 index.html,引入了 app.js 打包檔案
![webpack4入門](https://i.iter01.com/images/1f9b4ef0a2aec42942252cc8bdc7e947378e065a9b2787abc12199fb7be85c6e.png)
3.3.編譯 css、scss
在 index.js 中引入 main.scss 檔案
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
import "./css/main.scss";
ReactDOM.render(<App />, document.getElementById("root"));
複製程式碼
當在 js 檔案中引入 css/scss 檔案時,需要經過 loader 轉換,才能引入到 index.html 檔案中。
安裝相關依賴包
npm i css-loader sass-loader node-sass mini-css-extract-plugin optimize-css-assets-webpack-plugin css-split-webpack-plugin -D
複製程式碼
sass-loader:將 scss/sass 檔案編譯為 css
css-loader:解析 import/require 匯入的 css 檔案
mini-css-extract-plugin:將 js 中引入的 css 檔案抽離成單獨的 css 檔案
optimize-css-assets-webpack-plugin:優化和壓縮 css 檔案
css-split-webpack-plugin:css 檔案拆分
webpack 配置如下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CSSSplitWebpackPlugin = require("css-split-webpack-plugin").default;
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.(sa|sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name].[hash:7].css",
chunkFilename: "[id].css"
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"), // //引入cssnano配置壓縮選項
cssProcessorPluginOptions: {
preset: [
"default",
{
discardComments: {
// 移除註釋
removeAll: true
},
normalizeUnicode: false
}
]
},
canPrint: true
}),
new CSSSplitWebpackPlugin({
size: 4000, // 超過4kb的css檔案進行拆分
filename: "[name]-[part].[ext]"
}),
new HtmlWebpackPlugin({
filename: "index.html", // 模板檔名
template: path.resolve(__dirname, "public/index.html"), // 模板檔案源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製程式碼
執行npm run build
,在 dist 資料夾下生成了 css 資料夾和編譯的 css 檔案
![編譯css、scss檔案](https://i.iter01.com/images/e4278f4b708c48503368edf5722a60317a4dbe28ab62c19250ea515a8f19482c.png)
開啟 index.html,css 檔案被引入到 index.html 中。
![編譯css、scss到index.html](https://i.iter01.com/images/6ebc12c8c1cb05d696889c6f418f6572b3f0bf27baf46e325aec6dc8c8b8f6f1.png)
3.4.處理圖片、字型檔案
在 index.js 中引入圖片,在 main.scss 檔案中引入字型庫
// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
import "./css/main.scss";
import image from "./image/image1.png";
const newImage = new Image();
newImage.src = image;
newImage.style.cssText = "width: 100px; height: 100px;";
document.body.append(newImage);
ReactDOM.render(<App />, document.getElementById("root"));
複製程式碼
在 main.scss 檔案中引入字型庫
// main.scss
@font-face {
font-family: 'MyFont';
src: url('../font/icomoon.eot') format('eot'),
+ url('../font/icomoon.woff') format('woff');
font-weight: 600;
font-style: normal;
}
body {
background-color: blue;
}
複製程式碼
當在 js 檔案中引入圖片或字型檔案時,需要通過 url-loader 和 file-loader 來處理。
file-loader:解析 import/require 匯入的檔案,將其輸出到生產目錄,併產生一個 url 地址。
url-loader:不超過限定 limit 時,轉換為 base64 url。
安裝 file-loader 和 url-loader
npm i url-loader file-loader -D
複製程式碼
webpack module 部分配置如下:
...
module: {
...
rules: [
{
test: /\.(png|jpg|jpeg|gif|svg)/,
use: [
{
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
limit: 204800 // 小於200kb,進行base64轉碼
}
}
]
},
{
test: /\.(eot|woff2?|ttf)/,
use: [
{
loader: "url-loader",
options: {
name: "[name]-[hash:5].min.[ext]",
limit: 5000,
outputPath: "fonts/"
}
}
]
}
]
...
}
...
複製程式碼
執行npm run build
打包,在 dist 目錄下生成了 image 資料夾和 font 資料夾
![圖片字型轉換](https://i.iter01.com/images/965767a2cc44b0df5ebd8ef92361b645bf80ed1343ca469a45c4be91e3b820f6.png)
開啟 index.html,圖片成功引入
![圖片字型轉換](https://i.iter01.com/images/a6151b3c4fb9c2f936d7aaccb2f42e56d28f43f7d2d434ef7f0f34859be2a8df.png)
3.5.配置 devServer
webpack-dev-server 就是在本地為搭建了一個小型的靜態檔案伺服器,有實時重載入的功能,為將打包生成的資源提供了 web 服務,適用於本地開發模式。
devServer: {
contentBase: path.join(__dirname, "../dist"), // 資源目錄
host: 'localhost', // 預設localhost
port: 3000, // 預設8080
hot: true, // 支援熱更新
inline: true
}
複製程式碼
執行npm run dev
,web 服務起在 localhost:3000
![devServer配置](https://i.iter01.com/images/f77c88e241fa965dea8995146a027a5a320d6baf5d928b0136d47c71a08cc481.png)
3.6.提取公共程式碼
當我們在程式碼裡引入了第三方庫和公共程式碼時,可以使用 splitChunks 提取公共程式碼,避免載入的包太大。
webpack 配置如下:
...
optimization: {
splitChunks: {
// 提取公共程式碼
chunks: "all", // async(動態載入模組),initital(入口模組),all(全部模組入口和動態的)
minSize: 3000, // 抽取出來的檔案壓縮前最小大小
maxSize: 0, // 抽取出來的檔案壓縮前的最大大小
minChunks: 1, // 被引用次數,預設為1
maxAsyncRequests: 5, // 最大的按需(非同步)載入次數,預設為 5;
maxInitialRequests: 3, // 最大的初始化載入次數,預設為 3;
automaticNameDelimiter: "~", // 抽取出來的檔案的自動生成名字的分割符,預設為 ~;
name: "vendor/vendor", // 抽取出的檔名,預設為true,表示自動生成檔名
cacheGroups: {
// 快取組
common: {
// 將node_modules模組被不同的chunk引入超過1次的抽取為common
test: /[\\/]node_modules[\\/]/,
name: "common",
chunks: "initial",
priority: 2,
minChunks: 2
},
default: {
reuseExistingChunk: true, // 避免被重複打包分割
filename: "common.js", // 其他公共函式打包成common.js
priority: -20
}
}
}
},
...
複製程式碼
執行npm run build
,在 dist 資料夾下生成了 vendor.js 包
![提取公共程式碼](https://i.iter01.com/images/1f4503b232dfdf8188ac3406e37a2564900f2bb6a311cdeea39066177e254b6f.png)
開啟 index.html,vendor.js 成功引入
![提取公共程式碼index.html](https://i.iter01.com/images/df2c403fdf364a5473e20af427fafed7744fea1f60f35307c69ac18a161b448e.png)
3.7.分離 webpack 配置檔案
由於開發環境和生產環境下的 webpack 配置存在公共配置,因此最好將公共配置抽離成 webpack.common.js,然後針對開發環境和生產環境分別配置,通過 webpack-merge merge 配置,即可滿足開發環境和生產環境不同的配置。
先安裝 webpack-merge,用來 merge webpack 配置項
npm i webpack-merge -D
複製程式碼
在目錄下新建 tools 資料夾,存放 webpack 相關配置
![建立tools資料夾](https://i.iter01.com/images/7804e1c1db42fefd215ebd5725ed9fe9e03bfbee57c043f8bd81e653c41bcc1e.png)
新建 pathConfig.js 檔案,返回 entry js、output 目錄及 index.html 模板目錄的絕對地址
// pathConfig.js
const path = require("path");
const fs = require("fs");
const appDirectory = fs.realpathSync(process.cwd()); // 獲取當前根目錄
const resolvePath = (relativePath) => path.resolve(appDirectory, relativePath);
module.exports = {
appHtml: resolvePath("public/index.html"), // 模板html
appBuild: resolvePath("dist"), // 打包目錄
appIndexJs: resolvePath("src/index.js") // 入口js檔案
};
複製程式碼
webpack 公共配置,引入 pathConfig.js 檔案
// webpack.common.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CSSSplitWebpackPlugin = require("css-split-webpack-plugin").default;
const { appIndexJs, appBuild, appHtml } = require("./pathConfig");
module.exports = {
entry: {
app: appIndexJs
},
output: {
filename: "[name].[hash:7].js",
path: appBuild
},
module: {
rules: [
{
test: /\.js[x]?$/, // jsx、js處理
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.(sa|sc|c)ss$/, // scss、css處理
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
},
{
test: /\.(png|jpg|jpeg|gif|svg)/, // 圖片處理
use: [
{
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
limit: 204800 // 小於200kb採用base64轉碼
}
}
]
},
{
test: /\.(eot|woff2?|ttf)/, // 字型處理
use: [
{
loader: "url-loader",
options: {
name: "[name]-[hash:5].min.[ext]",
limit: 5000, // 5kb限制
outputPath: "fonts/"
}
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
optimization: {
splitChunks: {
// 提取公共程式碼
chunks: "all", // async(動態載入模組),initital(入口模組),all(全部模組入口和動態的)
minSize: 3000, // 抽取出來的檔案壓縮前最小大小
maxSize: 0, // 抽取出來的檔案壓縮前的最大大小
minChunks: 1, // 被引用次數,預設為1
maxAsyncRequests: 5, // 最大的按需(非同步)載入次數,預設為 5;
maxInitialRequests: 3, // 最大的初始化載入次數,預設為 3;
automaticNameDelimiter: "~", // 抽取出來的檔案的自動生成名字的分割符,預設為 ~;
name: "vendor/vendor", // 抽取出的檔名,預設為true,表示自動生成檔名
cacheGroups: {
// 快取組
common: {
// 將node_modules模組被不同的chunk引入超過1次的抽取為common
test: /[\\/]node_modules[\\/]/,
name: "common",
chunks: "initial",
priority: 2,
minChunks: 2
},
default: {
reuseExistingChunk: true, // 避免被重複打包分割
filename: "common.js", // 其他公共函式打包成common.js
priority: -20
}
}
}
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name].[hash:7].css",
chunkFilename: "[id].css"
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"), // //引入cssnano配置壓縮選項
cssProcessorPluginOptions: {
preset: [
"default",
{
discardComments: {
// 移除註釋
removeAll: true
},
normalizeUnicode: false
}
]
},
canPrint: true
}),
new CSSSplitWebpackPlugin({
size: 4000, // 超過4kb進行拆分
filename: "[name]-[part].[ext]"
}),
new HtmlWebpackPlugin({
filename: "index.html", // 模板檔名
template: appHtml, // 模板檔案源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製程式碼
開發環境 webpack 配置
// webpack.dev.config.js
const path = require("path");
const merge = require("webpack-merge");
const baseConfig = require("./webpack.config");
module.exports = merge(baseConfig, {
mode: "development",
devtool: "cheap-module-eval-source-map",
devServer: {
contentBase: path.join(__dirname, "../dist"),
port: 3000,
historyApiFallback: true,
hot: true,
inline: true
}
});
複製程式碼
生產環境配置,啟用 webpack-bundle-analyzer 進行打包分析,啟用 compression-webpack-plugin 生成 gzip 壓縮。
// webpack.prod.config.js
const merge = require("webpack-merge");
const baseConfig = require("./webpack.com.config");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const CompressionWebpackPlugin = require("compression-webpack-plugin");
module.exports = merge(baseConfig, {
mode: "production",
devtool: "source-map",
plugins: [
new CompressionWebpackPlugin({
filename: "[path].gz[query]", // path-原資源路徑,query-原查詢字串
algorithm: "gzip", // 壓縮演算法
threshold: 0, // 檔案壓縮閾值
minRatio: 0.8 // 最小壓縮比例
}),
new BundleAnalyzerPlugin()
]
});
複製程式碼
修改 package.json 中 script 中 dev 和 build,--config 表示讀取後面的檔案作為配置檔案。
"scripts": {
"dev": "webpack-dev-server --config ./tools/webpack.dev.config.js",
"build": "webpack --config ./tools/webpack.prod.config.js"
},
複製程式碼
執行npm run build
,專案打包成功
![打包成功](https://i.iter01.com/images/d972ea603367378eeee0e7b7200366f88139591a78442a0d83b65502b2084190.png)
執行npm run dev
,啟動開發者模式,執行在 localhost:3000。
![開發成功](https://i.iter01.com/images/3d7f1de2feedd8df1cb69f7efc109f58faaa816bb6d09bed395781967d462159.png)
到此為止,我們的案例就完成了。
程式碼地址:案例連結
結語
看完這篇文章,相信大家對於 webpack 已經有了一個初步的瞭解,學習 webpack 最好的方式還是多動手實踐,覺得不錯的小夥伴可以點個贊(碼字不易,灰常感謝)。
相關連結
webpack 官方連結:www.webpackjs.com/