前不久從零開始寫了一個webpack多頁面打包boilerplate(webpack4-boilerplate),友善以後工作可以開箱即用,特此記錄下開發過程中的要點。
注意:本文不會詳細介紹webpack的基礎知識,如果完全不會,建議看下我之前寫過的基礎文章
多頁打包的原因
首先發出一個直擊靈魂的拷問:為什麼要多頁面打包?
習慣了React,Vue全家桶的同學,可能覺得寫代碼不就是:npm run dev npm run build一把梭嗎?
然而現實是骨感的,很多場景下,單頁應用的開發模式并不适用。比如公司經常開發一些活動頁:
https://www.demo.com/activity/activity1.html
https://www.demo.com/activity/activity2.html
https://www.demo.com/activity/activity3.html
上述三個頁面是完全不相幹的活動頁,頁面之間并沒有共享的資料。然而每個頁面都使用了React架構,并且三個頁面都使用了通用的彈框元件。在這種場景下,就需要使用webpack多頁面打包的方案了:
保留了傳統單頁應用的開發模式:使用Vue,React等前端架構(當然也可以使用jQuery),支援子產品化打包,你可以把每個頁面看成是一個單獨的單頁應用
獨立部署:每個頁面互相獨立,可以單獨部署,解耦項目的複雜性,你甚至可以在不同的頁面選擇不同的技術棧
是以,我們可以把多頁應用看成是乞丐版的前端微服務。
多頁面打包的原理
首先我們約定:
src/pages目錄下,每個檔案夾為單獨的一個頁面。每個頁面至少有兩個檔案配置:
app.js: 頁面的邏輯入口
index.html: 頁面的html打包模闆
src/pages
├── page1
│ ├── app.js
│ ├── index.html
│ ├── index.scss
└── page2
├── app.js
├── index.html
└── index.scss
前面我們說過:每個頁面可以看成是個獨立的單頁應用。
單頁應用怎麼打包的?單頁應用是通過配置webpack的的entry
module.exports = {
entry: './src/main.js', // 項目的入口檔案,webpack會從main.js開始,把所有依賴的js都加載打包
output: {
path: path.resolve(__dirname, './dist'), // 項目的打封包件路徑
filename: 'build.js' // 打包後的檔案名
}
};
是以,多頁應用隻需配置多個entry即可
module.exports = {
entry: {
'page1': './src/pages/page1/app.js', // 頁面1
'page2': './src/pages/page2/app.js', // 頁面2
},
output: {
path: path.resolve(__dirname, './dist'),
filename: 'js/[name]/[name]-bundle.js', // filename不能寫死,隻能通過[name]擷取bundle的名字
}
}
同時,因為多頁面的index.html模闆各不相同,是以需要配置多個HtmlWebpackPlugin。
注意:HtmlWebpackPlugin一定要配chunks,否則所有頁面的js都會被注入到目前html裡
module.exports = {
plugins: [
new HtmlWebpackPlugin(
{
template: './src/pages/page1/index.html',
chunks: ['page1'],
}
),
new HtmlWebpackPlugin(
{
template: './src/pages/page2/index.html',
chunks: ['page2'],
}
),
]
}
多頁面打包的原理就是:配置多個entry和多個HtmlWebpackPlugin
多頁打包的細節
代碼分割
把多個頁面共用的第三方庫(比如React,Fastclick)單獨打包出一個vendor.js
把多個頁面共用的邏輯代碼和共用的全局css(比如css-reset,icon字型圖示)單獨打包出common.js和common.css
把運作時代碼單獨提取出來manifest.js
把每個頁面自己的業務代碼打包出page1.js和page1.css
前3項是每個頁面都會引入的公共檔案,第4項才是每個頁面自己單獨的檔案。
實作方式也很簡單,配置optimization即可:
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
// 打包業務中公共代碼
common: {
name: "common",
chunks: "initial",
minSize: 1,
priority: 0,
minChunks: 2, // 同時引用了2次才打包
},
// 打包第三方庫的檔案
vendor: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
chunks: "initial",
priority: 10,
minChunks: 2, // 同時引用了2次才打包
}
}
},
runtimeChunk: { name: 'manifest' } // 運作時代碼
}
}
hash
最後打包出來的檔案,我們希望帶上hash值,這樣可以充分利用浏覽器緩存。webpack中有hash,chuckhash,contenthash:生産環境時,我們一般使用contenthash,而開發環境其實可以不指定hash。
// dev開發環境
module.exports = {
output: {
filename: 'js/[name]/[name]-bundle.js',
chunkFilename: 'js/[name]/[name]-bundle.js',
},
}
// prod生産環境
module.exports = {
output: {
filename: 'js/[name]/[name]-bundle.[contenthash:8].js',
chunkFilename: 'js/[name]/[name]-bundle.[contenthash:8].js',
},
}
mock和proxy
開發環境,通常需要mock資料,還需要代理api到伺服器。我們可以通過devServer配合mocker-api第三方庫實作。
const apiMocker = require('mocker-api');
// dev開發環境
module.exports = {
devServer: {
before(app) { // 本地mock資料
apiMocker(app, path.resolve(__dirname, '../mock/index.js'))
},
proxy: { // 代理接口
'/api': {
target: 'https://anata.me', // 後端聯調位址
changeOrigin: true,
secure: false,
},
}
},
}
拆分webpack配置
為了通用配置,把webpack的配置檔案分成3份。
build
├── webpack.base.js // 共用部分
├── webpack.dev.js // dev
└── webpack.prod.js // 生産
dev和prod配置的主要差別:
dev配置devServer,友善本地調試開發
prod打包壓縮檔案,單獨提取css (dev不提取是為了css熱更新),生成靜态資源清單manifest.json
關于為什麼要生成一份manifest.json,以及打包後的代碼如何部署,我将會在下一篇文章詳情介紹。
總結
webpack的學習始終是前端繞不過去的一道坎。通過這次從零搭建多頁面打包模闆,也算是鞏固了一下基礎知識,離webpack配置工程師又近了一步。
作者:深紅