本文大約二千字,看完本文大概需要二十分鐘,動手嘗試需要一小時
前段時間做專案,技術棧是vue+webpack,主要就是官網首頁加後臺管理系統 根據當時情況,分析出三種方案
- 一個專案程式碼裡面嵌兩個spa應用(官網和後臺系統)
- 分開兩套專案原始碼
- 一套專案原始碼裡面就一個spa應用
思考:
- 直接否定了一套專案原始碼裡一個spa應用(ui樣式會相互覆蓋,如果沒有程式碼規範後期比較難維護)
- 兩套原始碼的話,後臺可能開兩個埠,然後需要用nginx反向代理可能比較麻煩,而且前端開發也比較麻煩麻煩,畢竟需要維護兩個git倉庫,兩套git上線流程,可能會損耗很多時間。
- 對自己的技術(盲目)自信,也想嚐嚐鮮,分析出需求也不算很複雜。選了第一種方案,就是多個單頁面應用在一套原始碼裡面
上一張多頁面的結構圖
下載vue spa模板
npm install vue-cli -g
vue init webpack multiple-vue-amazing
複製程式碼
改造多頁面應用
npm install glob --save-dev
複製程式碼
修改src資料夾下面的目錄結構
在util.js裡面加入
/* 這裡是新增的部分 ---------------------------- 開始 */
// glob是webpack安裝時依賴的一個第三方模組,還模組允許你使用 *等符號, 例如lib/*.js就是獲取lib資料夾下的所有js字尾名的檔案
var glob = require('glob')
// 頁面模板
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 取得相應的頁面路徑,因為之前的配置,所以是src資料夾下的pages資料夾
var PAGE_PATH = path.resolve(__dirname, '../src/pages')
// 用於做相應的merge處理
var merge = require('webpack-merge')
//多入口配置
// 通過glob模組讀取pages資料夾下的所有對應資料夾下的js字尾檔案,如果該檔案存在
// 那麼就作為入口處理
exports.entries = function () {
var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
var map = {}
entryFiles.forEach((filePath) => {
var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
map[filename] = filePath
})
return map
}
//多頁面輸出配置
// 與上面的多頁面入口配置相同,讀取pages資料夾下的對應的html字尾檔案,然後放入陣列中
exports.htmlPlugin = function () {
let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
let arr = []
entryHtml.forEach((filePath) => {
let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
let conf = {
// 模板來源
template: filePath,
// 檔名稱
filename: filename + '.html',
// 頁面模板需要加對應的js指令碼,如果不加這行則每個頁面都會引入所有的js指令碼
chunks: ['manifest', 'vendor', filename],
inject: true
}
if (process.env.NODE_ENV === 'production') {
conf = merge(conf, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
})
}
arr.push(new HtmlWebpackPlugin(conf))
})
return arr
}
/* 這裡是新增的部分 ---------------------------- 結束 */
複製程式碼
webpack.base.conf.js 檔案
/* 修改部分 ---------------- 開始 */
entry: utils.entries(),
/* 修改部分 ---------------- 結束 */
複製程式碼
webpack.dev.conf.js 檔案
/* 註釋這個區域的檔案 ------------- 開始 */
// new HtmlWebpackPlugin({
// filename: 'index.html',
// template: 'index.html',
// inject: true
// }),
/* 註釋這個區域的檔案 ------------- 結束 */
new FriendlyErrorsPlugin()
/* 新增 .concat(utils.htmlPlugin()) ------------------ */
].concat(utils.htmlPlugin())
複製程式碼
webpack.prod.conf.js 檔案
/* 註釋這個區域的內容 ---------------------- 開始 */
// new HtmlWebpackPlugin({
// filename: config.build.index,
// template: 'index.html',
// inject: true,
// minify: {
// removeComments: true,
// collapseWhitespace: true,
// removeAttributeQuotes: true
// // more options:
// // https://github.com/kangax/html-minifier#options-quick-reference
// },
// // necessary to consistently work with multiple chunks via CommonsChunkPlugin
// chunksSortMode: 'dependency'
// }),
/* 註釋這個區域的內容 ---------------------- 結束 */
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
/* 該位置新增 .concat(utils.htmlPlugin()) ------------------- */
].concat(utils.htmlPlugin())
複製程式碼
引入第三方ui庫
npm install element-ui bootstrap-vue --save
複製程式碼
分別在不同的頁面引入不同的ui index.js
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
複製程式碼
admin.js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
複製程式碼
上面多頁面的配置是參考網上的,而且網上的思路大都很相似,核心就是改多個entry,配置完成了之後,開發的時候也是發現不了問題的,然後大概就開發了一個月,開發完之後對官網進行效能分析時發現,webpack打包的vendor.js網路載入時間特別長,導致首屏的白屏時間非常長,最終通過-webpack-bundle-analyzer分析得到了結論
npm run build --report
複製程式碼
你會發現vendor.js包含了index.html和admin.html的共同部分,所以這個vendor包註定會很大很冗餘
解決思路
既然是vendor過大引起載入速度慢,那就分離這個vendor就好了。我是這樣想的,把各個頁面中都使用到的第三方程式碼提取至vendor.js中,然後各個頁面中用到的第三方程式碼再打包成各自的vendor-x.js,例如現有頁面index.html、admin.html,則最終會打包出vendor.js、vendor-index.js、vendor-admin.js。
webpack.prod.conf.js 檔案
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-admin',
chunks: ['vendor'],
minChunks: function (module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0 &&
module.resource.indexOf('element-ui') != -1
)
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-index',
chunks: ['vendor'],
minChunks: function (module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0 &&
module.resource.indexOf('bootstrap-vue') != -1
)
}
}),
複製程式碼
再次分析,一切都很ok,vendor.js被分離成了vendor.js、vendor-index、vendor-admin.js
本來以為解決了CommonsChunkPlugin的分離vendor.js的問題,就可以了,然後打包出來發現index.html和admin.html都少了一個引入(各自對應的那個vendor-xx.js)解決方案
這個問題其實就是HtmlWebpackPlugin的問題 把原來的 chunksSortMode: 'dependency'改成自定義函式的配置,如下
util.js檔案
chunksSortMode: function (chunk1, chunk2) {
var order1 = chunks.indexOf(chunk1.names[0])
var order2 = chunks.indexOf(chunk2.names[0])
return order1 - order2
},
複製程式碼
抽離多個頁面之間公共的模組common-api (2018/4/23)更新
其實多頁面之間抽離公共的檔案的場景,中大型專案會用的比較多,最初是看到下面評論說需要抽離common-api的時候會多打包出來common-api.css,並且有同學私信我遇到一些關於commonChunk的問題,我做一個更新,並提供思路
需求:專案中一些公共的js甚至是css,可複用到每一個page裡邊,例如admin.js引用common-api.js,index.js也引用了common-api.js。現在抽離多個頁面之間公共的模組common-api
新增common目錄
新建一個common/index.js 直接引用一個本地檔案js(我這裡用jquery來代替公共js)
引用公共檔案
在admin.js index.js裡引用
import $ from '../../common'
console.log($('body'))
複製程式碼
打包明顯能發現jquery被打包了兩次,資源浪費了。
解決方案
- 因為是多頁面,所以必須新增一個公共的入口common-api
- commonChunkPlugin 抽取common-api(有坑,下面會講)
- 修改htmlWebpackPlugin的chunks順序
坑就是必須指定chunks 不然會報錯webpack ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk
一開始有位同學問我這個錯誤怎麼解決,一開始我也不知道,後來查閱了一些資料,發現只要指定了chunks就可以解決這個錯誤,貼一段github的issues,感興趣的可以深入瞭解
重新指定util.js 裡面的htmlPlugin的順序
let chunks = filename === 'admin' ?
['manifest', 'vendor', 'vendor-admin', 'common-api', filename] :
['manifest', 'vendor', 'vendor-index', 'common-api', filename]
複製程式碼
最後看看結果,抽離出來了common-api,jq只被載入了一次, 並且並沒有多打包出來common-api.css,html裡面script的順序也是對的
最終實現- 每個頁面載入各自的chunk
- 每個頁面有不同的引數
- 每個頁面能共享公共chunk
- 瀏覽器快取,效能更好
- 如果還嫌慢的話,開啟gzip
- 每個頁面抽離公共函式common-api(更新於2018/4/23)
感想
大功告成了,雖然配置看起來很簡單,不過我當時開發的時候,思考了很久,所以假如你CommonsChunkPlugin和HtmlWebpackPlugin不熟悉或者只會用別人第三方的配置表,估計會踩大坑,比如說,CommonsChunkPlugin不指定chunks,預設是什麼?minChunks大多數人只會寫一個數值,然而自定義一個函式的寫法其實才是最強大的,根據我個人的經驗chunks結合minChunks自定義函式的寫法,能解決幾乎所有CommonsChunkPlugin靈異的事件。
webpack4
雖然本篇文章是基於webpack3來講的,但是webpack4多頁面配置和優化打包的思想其實也是一樣的,放心大膽的使用吧,有坑就解決它。
原始碼
本文原始碼喜歡點個贊