在http1的時代,比較常見的一種性能優化就是合併http的請求數量,一般咱們會把許多js代碼合併在一塊兒,可是若是一個js包體積特別大的話對於性能提高來講就有點矯枉過正了。而若是咱們對全部的代碼進行合理的拆分,將首屏和非首屏的代碼進行剝離,將業務代碼和基礎庫代碼進行拆分,在須要某段代碼的時候再加載它,下次若再須要用則從緩存中讀取,一來能夠更好地使用瀏覽器緩存,再者就是能夠提升首屏加載速度,很好提高用戶的體驗。javascript
這個其實很好理解,業務代碼一般更新迭代很頻繁,而基礎庫一般更新緩慢,這裏作拆分的話能夠充分利用瀏覽器緩存來加載基礎庫代碼。css
這個主要解決首屏請求大小的問題,咱們在訪問首屏的時候只須要加載首屏所需的邏輯,而不是加載全部路由的代碼。html
最近,採用vuetify改造了一個內部系統,一開始用了最經常使用的webpack配置,功能很快開發了,但是一打包,發現效果不是很明顯,打出不少大包前端
這裏咱們看下打包分佈,這裏使用的是 webpack-bundle-analyzer,能夠很清晰的看到 vue 和 vuetify等模塊都有出現 被重複打包的狀況。vue
這裏咱們先貼一下配置,一邊一下子分析時用:java
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
})
])
}
複製代碼
ventor入口這裏咱們發現並無篩選出全部引用的node_module下的模塊 ,好比axios ,因此致使打包到了app.js裏了,這裏咱們作下分離node
entry: {
vendor: ['vue', 'vue-router', 'vuetify', 'axios'],
app: './src/main.js'
},
複製代碼
那這裏又出現個問題了,我不可能手動去手動錄入模塊,這時咱們可能須要 自動化分離 ventor,這裏咱們須要引入 minChunks,在配置中咱們就能夠對全部mode_module下所引用的模塊進行打包 修改配置以下webpack
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下了。ios
這裏咱們能夠獲得一個經驗,就是在一個項目中能夠專門針對node_module下的模塊進行打包優化。可是這裏細心的你可能發現codemirror組件不也是node_module中的麼,但爲啥沒被打包進去反而重複打包到其餘單頁面了呢,其實這裏是由於在commonChunk中使用name屬性其實也就意味着只會沿着entry入口去找尋所依賴的包,因爲咱們的組件採用的是異步加載,故這裏就不會去打包了,咱們作個實驗驗證下,如今咱們去掉dbmanage和system頁面的路由懶加載改成直接引入web
// 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被打包進來了,那麼問題來了,這樣子好麼?
上面的問題答案是確定的,不能夠的,很明顯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="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
})
])
}
複製代碼
參考資料:
下面是咱們QQ音樂前端團隊公衆號,但願你們支持支持哈,咱們會努力寫出好的文章分享給你們