如何使用webpack構建多頁面應用,這是一個我一直在想和解決的問題。網上也給出了很多的例子,很多想法。猛一看,覺得有那麼點兒意思,但仔細看也就那樣。
使用webpack這個構建工具,可以使我們少考慮很多的問題。
我們常見的單頁面應用只有一個頁面,它考慮問題,解決問題圍繞着中心化
去解決,因此很多麻煩都迎刃而解。如果你使用過vue.js,那麼想必你一定用過vue-router,vuex,它們就是典型的中心化管理模式,當然還有很多,這裏不一一列舉了。
而多頁面應用,我們不能再按照中心化
模式的路走了,因爲行不通,這也是很多人認爲多頁面應用不好做,或者乾脆認爲webapck只能做單頁面應用,而不能做多頁面應用的原因。
所以,我要說明的第一點兒是:不要用做單頁面應用的思維來做多頁面應用。
多頁面應用的特點兒
單頁面中的模塊兒共享和多頁面的模塊兒共享的區別
-
單頁面的模塊兒共享,其實是代碼塊兒在同一個頁面的不同位置的重複出現;而多頁面應用的代碼塊兒共享需要實現的不僅是同一個頁面的共享,還要做到跨頁面的共享。
所以,第一個要解決的問題是:不同頁面的代碼塊兒共享如何實現?
-
單頁面的路由管理,其實是根據用戶的觸發條件來實現不同的代碼塊的顯隱;而多頁面應用的路由管理則不然,它實現的是頁面的跳轉。
所以,第二個要解決的問題是:所頁面應用的導航該如何做?
單頁面的狀態管理,很受開發者喜好。單頁面是一個頁面,所以頁面中的數據狀態的管理操作起來還算得心應手,那麼多頁面應用的呢,顯然依靠它自身很難實現。
所以,第三個要解決的問題是:多頁面應用的狀態管理如何做?
注:這個問題問的其實有點兒傻,如果你做的是dom操作的多頁面兒應用,就不用做狀態管理了。如果你還是使用想vue.js這樣的庫,你就需要考慮要不要再用做多頁面的狀態管理了,因爲此法兒就是爲單頁面應用做的,多頁面兒行不通。
多頁面應用的探索
入口(entry):
webpack對入口不僅可以定義單個文件,也可以定義多個文件。
熟悉當頁面應用開發的對於下面的代碼應該不會陌生吧?
module.exports = {
entry: './src/index.js',
···
}
我第一次接觸真正的單頁面應用項目使用的就是angualrjs,使用的構建工具使webapck+gulp
,其中的webpack.config.js
中的看到的入口文件代碼就是它。
後來,接觸到的是數組形式,代碼如下:
module.exports = {
entry: ['./src/index.js', 'bootstrap']
···
}
這樣,將bootstrap和入口文件一起引用,就可以在任何一個代碼塊中使用boostrap。
再後來,接觸到的是對象形式,代碼如下:
module.exports = {
main: './src/index.js'
···
}
這樣做的目的是爲了給輸出的文件指定特定的名字。
再後來,就是做多頁面應用,就需要用到如下的代碼:
module.exports = {
entry: {
index: './src/index.js',
aboutUs: './src/aboutus.js',
contactUs: './src/contactus.js'
}
}
爲了引入第三方庫,我們可以像如下這樣做:
module.exports = {
entry: {
index: ['./src/index.js', 'loadsh'],
aboutUs: './src/aboutus.js',
contactUs: ['./src/contactus.js', 'lodash']
}
}
webpack3.x的探索
但爲了共享模塊代碼,我們需要像下面這這樣做:
const CommonsChunkPlugin = require('webpack').optimization.CommonsChunkPlugin
module.exports = {
entry: {
index: ['./src/index.js', './src/utils/load.js', 'loadsh'],
aboutUs: ['./src/aboutus.js', 'loadsh'],
contactUs: ['./src/contactus.js','./src/utils/load.js', 'lodash']
},
plugins: [
new CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
chunks: ["index", "aboutUs", "contactUs"]
})
]
}
這樣型就會形成如下所示的項目目錄結構:
├── src
│ ├── common // 公用的模塊
│ │ ├── a.js
│ │ ├── b.js
│ │ ├── c.js
│ │ ├── d.js
│ ├── uttils // 工具
│ │ ├── load.js // 工具代碼load.js
│ ├── index.js // 主模塊index.js (包含a.js, b.js, c.js, d.js)
│ ├── aboutUs.js // 主模塊aboutus.js (包含a.js, b.js)
│ ├── contactUs.js // 主模塊contactus.js (包含a.js, c.js)
├── webpack.config.js // css js 和圖片資源
├── package.json
├── yarn.lock
但是這個內置插件的侷限性比較大。正如上面所使用的那樣,它只會提取chunks
選項所匹配的模塊共有的代碼塊。就如同上面代碼表示的那樣,它只會提取pindex, aboutUs, contactUs
共有的代碼塊loadsh
,而不會提取index, contactUs
共有的代碼塊load.js
。
當然,一般的第三方庫,我們也不這樣使用,而是像下面這樣使用:
const CommonsChunkPlugin = require('webpack').optimization.CommonsChunkPlugin
module.exports = {
entry: {
index: ['./src/index.js', './src/utils/load.js'],
aboutUs: ['./src/aboutus.js'],
contactUs: ['./src/contactus.js','./src/utils/load.js'],
vendors: ['lodash']
},
externals: {
commonjs: "lodash",
root: "_"
},
plugins: [
new CommonsChunkPlugin({
name: "commons",
filename: "commons.js",
chunks: ["index", "aboutUs", "contactUs"]
})
]
}
對於web應用最終的目的是:匹配生成不同的html頁面。
這裏我們要使用的就是html-webpack-plugin
。
首先,需要安裝html-webpack-plugin
:
yarn add --dev html-webpack-plugin
然後引入插件,並配置如下:
...
const HtmlWebapckPlugin = require('html-webpack-plugin');
...
plugins: [
...
new HtmlWebapckPlugin({
filename: 'index.html',
chunks: ['vendors', 'commons', 'index']
}),
new HtmlWebapckPlugin({
filename: 'aboutUs.html',
chunks: ['vendors', 'commons', 'aboutUs']
}),
new HtmlWebapckPlugin({
filename: 'contactUs.html',
chunks: ['commons', 'contactUs']
})
],
...
這樣一個基於webpack3.x的多頁面框架就有了基本的樣子。
webpack4.x的探索
而使用webpack4.x則完全不同,它移除了內置的CommonsChunkPlugin
插件,引入了SplitChunksPlugin
插件,這個插件滿足了我們的需要,彌補了CommonsChunkPlugin
的不足。
如果你想要解決之前的不足,去提取index, contacUs
共有的模塊,操作起來會很簡單。正如上面的所列舉的那樣,我們有三個入口點index, aboutUs, contactUs
,SplitChunksPlugin
插件會首先獲取這三個入口點共有的代碼塊,然後建立一個文件,緊接着獲取每兩個入口點的共有代碼塊,然後將每個入口點獨有的代碼塊單獨形成一個文件。如果你使用了第三方庫,就像上面我們使用的loadsh
,它會將第三方入口代碼塊單獨打包爲一個文件。
配置文件webpack.config.js
需要增加如下的代碼:
···
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 20,
maxAsyncRequests: 20,
minSize: 40
}
}
···
因爲SplitChunksPlugin
可以提取任意的入口點之間的共同代碼,所以,我們就不需要使用vendors
入口節點了。那麼,爲匹配生成不同的頁面代碼可以修改成如下:
const HtmlWebapckPlugin = require('html-webpack-plugin')
···
plugins: [
new HtmlWebapckPlugin({
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebapckPlugin({
filename: 'aboutUs.html',
chunks: ['aboutUs']
}),
new HtmlWebapckPlugin({
filename: 'contactUs.html',
chunks: ['contactUs']
}),
]
···
可以發現結果越來越接近我們所想。但是這裏還是存在一個問題,第三方庫loadsh
因爲在入口點index, aboutUs
中被分別引入,但是構建的結果卻輸出了兩個第三方庫文件,這不是我們想要的。這個問題怎麼解決呢,因爲html-webpack-plugin
插件的chunks
選項,支持多入口節點,所以,我們可以再單獨創建一個第三方庫的入口節點vendors
。配置代碼修改如下:
...
entry: {
index: ['./src/index.js', './src/utils/load.js'],
aboutUs: ['./src/aboutUs.js'],
contactUs: ['./src/contactUs.js','./src/utils/load.js'],
vendors: ['loadsh']
},
...
plugins: [
new HtmlWebapckPlugin({
filename: 'index.html',
chunks: ['index', 'vendors']
}),
new HtmlWebapckPlugin({
filename: 'aboutUs.html',
chunks: ['aboutUs', 'vendors']
}),
new HtmlWebapckPlugin({
filename: 'contactUs.html',
chunks: ['contactUs']
}),
],
...
注意:如果不同的入口點兒之間有依賴關係,如上面的index
和vendors
之間,因爲index
依賴於vendors
,所以vendors
要置於index
之前。
這篇文章,說到這裏基本上已經結束了。當然,webpack多頁面應用的知識點還沒有講完,這些內容會放在後續的文章中詳解。