詳解Vue打包優(yōu)化之code spliting
在http1的時代,比較常見的一種性能優(yōu)化就是合并http的請求數(shù)量,通常我們會把許多js代碼合并在一起,但是如果一個js包體積特別大的話對于性能提升來說就有點(diǎn)矯枉過正了。而如果我們對所有的代碼進(jìn)行合理的拆分,將首屏和非首屏的代碼進(jìn)行剝離,將業(yè)務(wù)代碼和基礎(chǔ)庫代碼進(jìn)行拆分,在需要某段代碼的時候再加載它,下次若再需要用則從緩存中讀取,一來可以更好地使用瀏覽器緩存,再者就是可以提高首屏加載速度,很好提升用戶的體驗(yàn)。
核心思想
業(yè)務(wù)代碼和基礎(chǔ)庫的分離
這個其實(shí)很好理解,業(yè)務(wù)代碼通常更新迭代很頻繁,而基礎(chǔ)庫通常更新緩慢,這里做拆分的話可以充分利用瀏覽器緩存來加載基礎(chǔ)庫代碼。
按需異步加載
這個主要解決首屏請求大小的問題,我們在訪問首屏的時候只需要加載首屏所需的邏輯,而不是加載所有路由的代碼。
實(shí)戰(zhàn)
最近,采用vuetify改造了一個內(nèi)部系統(tǒng),一開始用了最常用的webpack配置,功能很快開發(fā)了,可是一打包,發(fā)現(xiàn)效果不是很明顯,打出很多大包
這里我們看下打包分布,這里使用的是 webpack-bundle-analyzer,可以很清晰的看到 vue 和 vuetify等模塊都有出現(xiàn) 被重復(fù)打包的情況。
這里我們先貼一下配置,一邊一會兒分析時用:
const path = require('path') const webpack = require('webpack') const CleanWebpackPlugin = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const generateHtml = new HtmlWebpackPlugin({ title: '逍遙系統(tǒng)', template: './src/index.html', minify: { removeComments: true } }) module.exports = { entry: { vendor: ['vue', 'vue-router', 'vuetify'], app: './src/main.js' }, output: { path: path.resolve(__dirname, './dist'), filename: '[name].[hash].js', chunkFilename:'[id].[name].[chunkhash].js' }, resolve: { extensions: ['.js', '.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js', 'public': path.resolve(__dirname, './public') } }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { objectAssign: 'Object.assign' } }, { test: /\.css$/, loader: ['style-loader', 'css-loader'] }, { test: /\.styl$/, loader: ['style-loader', 'css-loader', 'stylus-loader'] } ] }, devServer: { historyApiFallback: true, noInfo: true }, performance: { hints: false }, devtool: '#eval-source-map', plugins: [ new BundleAnalyzerPlugin(), new CleanWebpackPlugin(['dist']), generateHtml, new webpack.optimize.CommonsChunkPlugin({ name: 'ventor' }), ] } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }
CommonChunkPlugin
ventor入口這里我們發(fā)現(xiàn)并沒有篩選出所有引用的node_module下的模塊 ,比如axios ,所以導(dǎo)致打包到了app.js里了,這里我們做下分離
entry: { vendor: ['vue', 'vue-router', 'vuetify', 'axios'], app: './src/main.js' },
那這里又出現(xiàn)個問題了,我不可能手動去手動錄入模塊,這時我們可能需要 自動化分離 ventor,這里我們需要引入 minChunks,在配置中我們就可以對所有mode_module下所引用的模塊進(jìn)行打包 修改配置如下
entry: { //vendor: ['vue', 'vue-router', 'vuetify', 'axios'], //刪除 app: './src/main.js' } new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: ({ resource }) => ( resource && resource.indexOf('node_modules') >= 0 && resource.match(/\.js$/) ) }),
經(jīng)過上面幾步的優(yōu)化,我們再看看文件分布,會發(fā)現(xiàn)node_module下的模塊都收歸到了vendor下了。
這里我們可以得到一個經(jīng)驗(yàn),就是在一個項(xiàng)目中可以專門針對node_module下的模塊進(jìn)行打包優(yōu)化。但是這里細(xì)心的你可能發(fā)現(xiàn)codemirror組件不也是node_module中的么,但為啥沒被打包進(jìn)去反而重復(fù)打包到其他單頁面了呢,其實(shí)這里是因?yàn)樵赾ommonChunk中使用name屬性其實(shí)也就意味著只會沿著entry入口去找尋所依賴的包,由于我們的組件采用的是異步加載,故這里就不會去打包了,我們做個實(shí)驗(yàn)驗(yàn)證下,現(xiàn)在我們?nèi)サ鬱bmanage和system頁面的路由懶加載改為直接引入
// const dbmanage = () => import(/* webpackChunkName: "dbmanage" */'../views/dbmanage.vue') // const system = () => import(/* webpackChunkName: "system" */'../views/system.vue') import dbmanage from '../views/dbmanage.vue' import system from '../views/system.vue'
這時我們重新打包可以發(fā)現(xiàn),codemirror被打包進(jìn)來了,那么問題來了,這樣子好么?
async
上面的問題答案是肯定的,不可以的,很明顯ventor是我們的入口代碼即首屏,我們完全沒有必要去加載這個codemirror組件,我們先把剛才的路由修改恢復(fù)回去,但是這時又有了新問題,我們的codemirror被同時打包進(jìn)了兩個單頁面,并且還有些自己封裝的components,例如MTable或是MDataTable等也出現(xiàn)了重復(fù)打包。并且codemirror特別大,同時加載到兩個單頁面也會造成很大的性能問題,簡單說就是,我們在訪問第一個單頁面加載了codemirror之后,在第二個頁面其實(shí)就不應(yīng)該再加載了。 要解決這個問題,這里我們可以使用 CommonsChunkPlugin 的 async 并在 minChunnks 里的count方法來判斷數(shù)量,只要是 重用次數(shù) 超過兩個包括兩個的異步加載模塊(即 import () 產(chǎn)生的chunk )我們都認(rèn)為是 可以 打成公共的 ,這里我們增加一項(xiàng)配置。
new webpack.optimize.CommonsChunkPlugin({ async: 'used-twice', minChunks: (module, count) => ( count >= 2 ), })
再次打包,我們發(fā)現(xiàn)所有服用的組件被重新打到了 0.used-twice-app.js中了,這樣各個單頁面大小也有所下降,平均小了近10k左右
可是,這里我們發(fā)現(xiàn)vuetify.js和vuetify.css實(shí)在太龐大了,導(dǎo)致我們的打包的代碼很大,這里,我們考慮把它提取出來,這里為了避免重復(fù)打包,需要使用external,并將vue以及vuetify的代碼采用cdn讀取的方式,首先修改index.html
//css引入 <link rel="stylesheet" type="text/css"> <link rel="external nofollow" rel="stylesheet"> //js引入 <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vuetify/dist/vuetify.js"></script> //去掉main.js中之前對vuetifycss的引入 //import 'vuetify/dist/vuetify.css'
再修改webpack配置,新增externals
externals: { 'vue':'Vue', "vuetify":"Vuetify" }
再重新打包,可以看到vue相關(guān)的代碼已經(jīng)沒有了,目前也只有used-twice-app.js比較大了,app.js縮小了近200kb。
但是新問題又來了,codemirror很大,而used-twice又是首屏需要的,這個打包在首屏肯定不是很好,這里我們要將system和dbmanage頁面的codemirror組件改為異步加載,單獨(dú)打包,修改如下:
// import MCode from "../component/MCode.vue"; //注釋掉 components: { MDialog, MCode: () => import(/* webpackChunkName: "MCode" */'../component/MCode.vue') },
重新打包下,可以看到 codemirror被抽離了,首屏代碼進(jìn)一步得到了減少,used-twice-app.js代碼縮小了近150k。
做了上面這么多的優(yōu)化之后,業(yè)務(wù)測的js基本都被拆到了50kb一下(忽略map文件),算是優(yōu)化成功了。
總結(jié)
可能會有朋友會問,單獨(dú)分拆vue和vuetify會導(dǎo)致請求數(shù)增加,這里我想補(bǔ)充下,我們的業(yè)務(wù)現(xiàn)在已經(jīng)切換成http2了,由于多路復(fù)用,并且加上瀏覽器緩存,我們分拆出的請求數(shù)其實(shí)也算是控制在合理的范疇內(nèi)。
這里最后貼一下優(yōu)化后的webpack配置,大家一起交流學(xué)習(xí)下哈。
const path = require('path') const webpack = require('webpack') const CleanWebpackPlugin = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const generateHtml = new HtmlWebpackPlugin({ title: '逍遙系統(tǒng)', template: './src/index.html', minify: { removeComments: true } }) module.exports = { entry: { app: './src/main.js' }, output: { path: path.resolve(__dirname, './dist'), filename: '[name].[hash].js', chunkFilename:'[id].[name].[chunkhash].js' }, resolve: { extensions: ['.js', '.vue'], alias: { 'vue$': 'vue/dist/vue.esm.js', 'public': path.resolve(__dirname, './public') } }, externals: { 'vue':'Vue', "vuetify":"Vuetify" }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { objectAssign: 'Object.assign' } }, { test: /\.css$/, loader: ['style-loader', 'css-loader'] }, { test: /\.styl$/, loader: ['style-loader', 'css-loader', 'stylus-loader'] } ] }, devServer: { historyApiFallback: true, noInfo: true }, performance: { hints: false }, devtool: '#eval-source-map', plugins: [ new CleanWebpackPlugin(['dist']), generateHtml ] } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' module.exports.plugins = (module.exports.plugins || []).concat([ new BundleAnalyzerPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'ventor', minChunks: ({ resource }) => ( resource && resource.indexOf('node_modules') >= 0 && resource.match(/\.js$/) ) }), new webpack.optimize.CommonsChunkPlugin({ async: 'used-twice', minChunks: (module, count) => ( count >= 2 ), }), new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue實(shí)現(xiàn)下拉加載其實(shí)沒那么復(fù)雜
這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)下拉加載的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08基于Vant UI框架實(shí)現(xiàn)時間段選擇器
這篇文章主要為大家詳細(xì)介紹了基于Vant UI框架實(shí)現(xiàn)時間段選擇器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12代替Vue?Cli的全新腳手架工具create?vue示例解析
這篇文章主要為大家介紹了代替Vue?Cli的全新腳手架工具create?vue示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10vue.js2.0 實(shí)現(xiàn)better-scroll的滾動效果實(shí)例詳解
better-scroll 是一個移動端滾動的解決方案,它是基于 iscroll 的重寫。better-scroll 也很強(qiáng)大,不僅可以做普通的滾動列表,還可以做輪播圖、picker 等等,下面通過本文給大家介紹vue.js2.0 實(shí)現(xiàn)better-scroll的滾動效果,感興趣的朋友一起看看吧2018-08-08解決vue項(xiàng)目Error:Cannot find module‘xxx’類報錯問題
當(dāng)npm運(yùn)行報錯Error:Cannot find module 'xxx'時,通常是因?yàn)閚ode_modules文件或依賴未正確安裝,解決步驟包括刪除node_modules和package-lock.json文件,重新運(yùn)行npm install,并根據(jù)需要安裝額外插件,若網(wǎng)絡(luò)問題導(dǎo)致安裝失敗2024-10-10Vue2.x如何解決Element組件el-tooltip滾動時錯位不消失的問題
這篇文章主要介紹了Vue2.x如何解決Element組件el-tooltip滾動時錯位不消失的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06