這篇文章主要介紹了詳解Vue打包優化之code spliting,現在分享給大家,有需要的朋友參考一下
在http1的時代,比較常見的一種效能優化就是合併http的請求數量,通常我們會把許多js程式碼合併在一起,但是如果一個js包體積特別大的話對於效能提升來說就有點矯枉過正了。而如果我們對所有的程式碼進行合理的拆分,將首屏和非首屏的程式碼進行剝離,將業務代碼和基礎庫代碼進行拆分,在需要某段代碼的時候再加載它,下次若再需要用則從快取中讀取,一來可以更好地使用瀏覽器緩存,再者就是可以提高首屏加載速度,很好提升用戶的體驗。
核心思想
業務程式碼和基礎庫的分離
#這個其實很好理解,業務程式碼通常更新迭代很頻繁,而基礎庫通常更新緩慢,這裡做拆分的話可以充分利用瀏覽器快取來載入基礎庫程式碼。
按需非同步載入
這個主要解決首屏請求大小的問題,我們在訪問首屏的時候只需要加載首屏所需的邏輯,而不是載入所有路由的程式碼。
實戰
最近,採用vuetify改造了一個內部系統,一開始用了最常用的webpack配置,功能很快開發了,可是一打包,發現效果不是很明顯,打出很多大包
這裡我們看下打包分佈,這裡使用的是webpack-bundle-analyzer,可以很清晰的看到vue 和vuetify等模組都有出現被重複打包的情況。
這裡我們先貼一下配置,一邊一會兒分析時用:
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: '逍遥系统', 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入口在這裡我們發現並沒有篩選出所有引用的node_module下的模組,例如axios ,所以導致打包到了app.js裡了,這裡我們做下分離
entry: { vendor: ['vue', 'vue-router', 'vuetify', 'axios'], app: './src/main.js' },
那這裡又出現個問題了,我不可能手動去手動錄入模組,這時我們可能需要自動化分離ventor,這裡我們需要引入minChunks,在配置中我們就可以對所有mode_module下所引用的模組進行打包修改配置如下
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$/) ) }),
經過上面幾步的優化,我們再看看檔案分佈,會發現node_module下的模組都收歸到了vendor下了。
這裡我們可以得到一個經驗,就是在一個專案中可以專門針對node_module下的模組進行打包最佳化。但這裡細心的你可能發現codemirror元件不也是node_module中的麼,但為啥沒被打包進去反而重複打包到其他單頁了呢,其實這裡是因為在commonChunk中使用name屬性其實也就意味著只會沿著entry入口去找尋所依賴的包,由於我們的元件採用的是異步加載,故這裡就不會去打包了,我們做個實驗驗證下,現在我們去掉dbmanage和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'
這時我們重新打包可以發現,codemirror被打包進來了,那麼問題來了,這樣子好麼?
async
上面的問題答案是肯定的,不可以的,很明顯ventor是我們的入口程式碼即首屏,我們完全沒有必要去加載這個codemirror組件,我們先把剛才的路由修改恢復回去,但是這時又有了新問題,我們的codemirror被同時打包進了兩個單頁面,並且還有些自己封裝的components,例如MTable或是MDataTable等也出現了重複打包。而且codemirror特別大,同時加載到兩個單頁面也會造成很大的性能問題,簡單說就是,我們在訪問第一個單頁加載了codemirror之後,在第二個頁面其實就不應該再加載了。 要解決這個問題,這裡我們可以使用CommonsChunkPlugin 的async 並在minChunnks 裡的count方法來判斷數量,只要是重用次數超過兩個包括兩個的異步加載模組(即import () 產生的chunk )我們都認為是可以打成公共的,這裡我們增加一項配置。
new webpack.optimize.CommonsChunkPlugin({ async: 'used-twice', minChunks: (module, count) => ( count >= 2 ), })
再次打包,我们发现所有服用的组件被重新打到了 0.used-twice-app.js中了,这样各个单页面大小也有所下降,平均小了近10k左右
可是,这里我们发现vuetify.js和vuetify.css实在太庞大了,导致我们的打包的代码很大,这里,我们考虑把它提取出来,这里为了避免重复打包,需要使用external,并将vue以及vuetify的代码采用cdn读取的方式,首先修改index.html
//css引入 <link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css"> <link href="https://unpkg.com/vuetify/dist/vuetify.min.css" 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相关的代码已经没有了,目前也只有used-twice-app.js比较大了,app.js缩小了近200kb。
但是新问题又来了,codemirror很大,而used-twice又是首屏需要的,这个打包在首屏肯定不是很好,这里我们要将system和dbmanage页面的codemirror组件改为异步加载,单独打包,修改如下:
// import MCode from "../component/MCode.vue"; //注释掉 components: { MDialog, MCode: () => import(/* webpackChunkName: "MCode" */'../component/MCode.vue') },
重新打包下,可以看到 codemirror被抽离了,首屏代码进一步得到了减少,used-twice-app.js代码缩小了近150k。
做了上面这么多的优化之后,业务测的js基本都被拆到了50kb一下(忽略map文件),算是优化成功了。
总结
可能会有朋友会问,单独分拆vue和vuetify会导致请求数增加,这里我想补充下,我们的业务现在已经切换成http2了,由于多路复用,并且加上浏览器缓存,我们分拆出的请求数其实也算是控制在合理的范畴内。
这里最后贴一下优化后的webpack配置,大家一起交流学习下哈。
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: '逍遥系统', 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 }) ]) }
相关推荐:
如何修改Vue.js中scoped模式下的子组件内部标签样式
Vue中computed与methods的区别详解_vue.js
以上是Vue打包優化之code spliting_vue.js的詳細內容。更多資訊請關注PHP中文網其他相關文章!