引言
對於一般的專案,特別是移動端專案,我們所熟知的都是webpack+vue,webpack+react等打包成一個單頁應用,包括我在github上所能找到的很多都是將我們所有的模組打包成一個大大的js包(甚至css也打包進去)然後在html中引入。
眾所周知,單頁應用依賴模組很大時會有很大的效能問題,當我們的web應用越來越大時,我們不可能會將所有的功能都放在同一個網頁上,這時一般會做的是按功能劃分模組,分成多個單頁應用,這樣做會有利於後期功能放入擴充套件調整。
要構建多頁應用,我們應該要考慮以下的問題:
- 單頁應用應該會有公共的程式碼部分,我們要將這些公用的部分抽離出來,以免重複引用載入。
- 隨著業務的發展,後面可能會不斷加入新的單頁應用,但是在加入新應用時都不
能改動構建相關的程式碼。
先看看一般單頁應用的配置:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './main.js'// Chunk app 的 JS 執行入口檔案
},
output: {
filename: '[name]_[chunkhash:8].js',// 給輸出的檔名稱加上 hash 值
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目錄下的檔案,node_modules 目錄下的檔案都是採用的 ES5 語法,沒必要再通過 Babel 去轉換
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,// 增加對 CSS 檔案的支援
// 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'] // 壓縮 CSS 程式碼
}),
},
]
},
plugins: [
// 使用WebPlugin,一個 WebPlugin 對應一個 HTML 檔案
new WebPlugin({
template: './template.html', // HTML 模版檔案所在的檔案路徑
filename: 'index.html' // 輸出的 HTML 的檔名稱
}),
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 檔名稱加上 hash 值
}),
new DefinePlugin({
// 定義 NODE_ENV 環境變數為 production 去除 react 程式碼中的開發時才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 壓縮輸出的 JS 程式碼
new UglifyJsPlugin({
// 最緊湊的輸出
beautify: false,
// 刪除所有的註釋
comments: false,
//忽略無關緊要的...
}),
],
};
複製程式碼
這裡最主要的是WebPlugin
這個外掛,能夠讓我們實現自動化輸出html檔案並引入打包後的模組,雖然這樣已經對我們打包應用提供了很大的便利,但對於多個頁面的管理依然是個麻煩的問題。如果我們要增加一個頁面我們需要new一個webPlugin例項並在entry入口增加配置項。
解決方案
這裡用AutoWebPlugin來構建我們的多頁應用,專案原始碼的目錄結構如下:
從目錄結構中可以看出以下幾點要求:
- 所有單頁應用的程式碼都需要放到一個 目錄下,例如都放在 pages 目錄下;
- 一個單頁應用對應一個單獨的資料夾,例如最後生成的 index.html 相關的程式碼都在 index 目錄下, login.html 同理:
- 每個單頁應用的目錄下都有一個 index.js 檔案作為入口執行檔案。AutoWebP lugin 強制性地規定了專案部分的目錄結構,從實戰經驗來看,這是一種優雅的目錄規範,合理地拆分了程式碼,又能讓新人快速看懂專案的結構,方便日後維護。
webpack配置檔案如下:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { AutoWebPlugin } = require('web-webpack-plugin');
// 使用AutoWebPlugin,自動尋找 pages 目錄下的所有目錄,把每一個目錄看成一個單頁應用
const autoWebPlugin = new AutoWebPlugin('pages', {
template: './template.html', // HTML 模版檔案所在的檔案路徑
postEntrys: ['./common.css'],// 所有頁面都依賴這份通用的 CSS 樣式檔案
// 提取出所有頁面公共的程式碼
commonsChunk: {
name: 'common',// 提取出公共程式碼 Chunk 的名稱
},
});
module.exports = {
// AutoWebPlugin 會找為尋找到的所有單頁應用,生成對應的入口配置,
// autoWebPlugin.entry 方法可以獲取到生成入口配置
entry: autoWebPlugin.entry({
// 這裡可以加入你額外需要的 Chunk 入口
}),
output: {
filename: '[name]_[chunkhash:8].js',// 給輸出的檔名稱加上 hash 值
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目錄下的檔案,node_modules 目錄下的檔案都是採用的 ES5 語法,沒必要再通過 Babel 去轉換
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,// 增加對 CSS 檔案的支援
// 提取出 Chunk 中的 CSS 程式碼到單獨的檔案中
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'] // 壓縮 CSS 程式碼
}),
},
]
},
plugins: [
autoWebPlugin,
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 給輸出的 CSS 檔名稱加上 hash 值
}),
new DefinePlugin({
// 定義 NODE_ENV 環境變數為 production 去除 react 程式碼中的開發時才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 壓縮輸出的 JS 程式碼
new UglifyJsPlugin({
// 最緊湊的輸出
beautify: false,
// 刪除所有的註釋
comments: false,
//忽略無關緊要的...
}),
],
};
複製程式碼
AutoWebPlugin 會找出 pages 目錄下的兩個資料夾 index 和 login ,將這兩個資料夾看作兩個單頁應用,並且分別為每個單頁應用生成一個 Chunk 配置和 WebPlugin 配置 。每個單頁應用的 Chunk 名稱等同於資料夾的名稱,也就是說 autoWebPlugin.entry()方法返回的內容其實是:
{ "index:["./pages/index/index.js", "./common.css"],
"login": ["./pages/login/index.js", "./common.css"]
}複製程式碼
template.html 模板檔案如下:
<html>
<head>
<meta charset="UTF-8">
<!--該頁面所依賴的其它剩下的 CSS 注入的地方-->
<!--STYLE-->
<!--已忽略無關程式碼-->
</head>
<body>
<div id="app"></div>
<!--該頁面所依賴的其它剩下的 JavaScript 注入的地方-->
<!--SCRIPT-->
<!--已忽略無關程式碼-->
</body>
</html>
複製程式碼
注意到在模板檔案中出現了兩個重要的新關鍵字<!--STYLE-->
和<!--SCRIPT-->
,
它們是什麼意思呢?
由於該模板檔案被當作專案中所有單頁應用的模板,因為需要被注入當前頁面的 Chunk 名稱是不固定的,每個單頁應用都會有自己的名稱。<!--STYLE-->
和<!--SCRIPT-->
的作用在於保證該頁面所依賴的資源都會被注入生成的 HTML 模板裡。
web-webpack-plugin 能分析出每個頁面依賴哪些資源,例如對於 login.html 來說,
- 所有頁面都依賴的公共 css 程式碼 common. css;
- 所有頁面都依賴的公共 JavaScript 程式碼 common. js;
- 只有這個頁面依賴的 css 程式碼 login.css;
- 只有這個頁面依賴的 JavaScript 程式碼 login .css 。
<!--STYLE-->
和<!--SCRIPT-->
所在的位置。- 將 css 型別的檔案注入<!--STYLE-->所在的位置,如果<!--STYLE-->不存在,就注入 HTML HEAD 標籤的最後 。
- 將 JavaScript 型別的檔案注入<!--SCRIPT-->所在的位置,如果<<!--SCRIPT-->不存在,就注入 HTML BODY 標籤的最後 。
輸出 HTML 檔案的名稱,在目錄下放這個頁面相關的程式碼即可,無須改動構建程式碼。
在npm run build
後打包的程式碼如下:
login.html:
<html>
<head>
<meta charset="UTF-8">
<!--該頁面所依賴的其它剩下的 CSS 注入的地方-->
<link rel="stylesheet" href="common_7cc98ad0.css">
<link rel="stylesheet" href="login_e31e214b.css">
<!--已忽略無關程式碼-->
</head>
<body>
<div id="app"></div>
<!--該頁面所依賴的其它剩下的 JavaScript 注入的地方-->
<script src="common_bdac69cb.js"></script>
<script src="login_0a3feca9.js"></script>
<!--已忽略無關程式碼-->
</body>
</html>
複製程式碼
參考閱讀:
- https://www.npmjs.com/package/web-webpack-plugin
- 深入淺出webpack