背景
一个老
pc项目,基于vue-cli3,由于依赖了中element-ui,echarts,xlsx,monaco-editor,moment,jquery等常见比较大的库。导致项目打包完成的dist重达12M
使用webpack-bundle-analyzer分析图如下,大块头们我都标记了下


web项目build之后12M还是比较笨重的,作为一个有追求的前端攻城狮是很难接受的。于是开启了我的优化之路
正文
1、CDN抽离不经常更新的库
比如
element-ui,echarts,xlsx,moment以及vue的三大件都可以使用cdn缓存,以提高我们的速度以及缩小包大小
(1) 头部配置cdn对象
externals:外部扩展,详细了解查看看官网文档,这个操作同时会将chunk-vendors.js体积缩小,减少首屏白屏时间
// cdn配置
const cdn = {
externals: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
echarts: 'Echarts',
moment: 'moment',
xlsx: 'xlsx',
jquery: 'jquery',
},
// cdn的css
css: [
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css',
],
// cdn的js
js: [
'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
'https://cdn.bootcdn.net/ajax/libs/echarts/4.2.1/echarts.min.js',
"https://cdn.bootcss.com/moment.js/2.20.1/moment.min.js",
"https://cdn.bootcss.com/moment.js/2.20.1/locale/zh-cn.js",
'https://cdn.bootcdn.net/ajax/libs/xlsx/0.14.1/xlsx.min.js',
"http://libs.baidu.com/jquery/2.0.0/jquery.min.js"
]
}
//
在配置elementui的时候,要稍微注意点,我一开始是element-ui:ElementUi,结果报错,我仔细研究,发现https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js中有一句exports.ELEMENT = t(require("vue")) : e.ELEMENT = t(e.Vue),其中e传入的window,那么最后的全局变量名称就是ELEMENT
(2) public/index.html调整
css、js采用<link href= rel="preload" as="style">的方式对cdn缓存资源进行预加载,这样可以减少首屏白屏时间
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style">
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="preload" as="script">
<% } %>
<title><%= webpackConfig.name %></title>
</head>
<body>
<div id="app"></div>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<!-- built files will be auto injected -->
</body>
</html>
(3)vue.config.js中chainWebpack调整
这个地方主要是将cdn资源注入到html中
config.when(process.env.NODE_ENV !== 'development', //生产环境才做处理
config => {
config.plugin('html').tap(args => {
args[0].cdn = cdn
return args
})
}
)
(4)vue.config.js中configureWebpack调整
这个地方主要是将前面配置的cdn配置到webpack中
if (process.env.NODE_ENV !== 'development') config.externals = cdn.externals
这一套打下去,基本把我这个项目的大肥肉从build包里面拿出去了。

在实际操作过程中,还是要根据项目实际情况做一些取舍,优化的目标是较少http请求资源体积,如果过度使用
externals可能会导致http数量增多,而且导致体验感下降
2、productionSourceMap 设为 false
(1) 在vue.config.js调整
productionSourceMap: process.env.NODE_ENV !== 'production'
3、抽离公共代码
这个操作主要用来切割代码,缓存公共部分,在chainWebpack配置如下,
具体每个参数如何配置可以查看官方 SplitChunksPlugin
config.when(process.env.NODE_ENV !== 'development',
config => {
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 3,
priority: 5,
reuseExistingChunk: true
}
}
})
}
)
4、开启双端gzip
这个操作build包瘦身超级大绝杀,压缩后通常能帮我们减少响应 70% 左右的大小。但是不建议图片也进行gzip压缩,经测试效果不理想。
(1) 前端部分
首先安装依赖:cnpm install --save-dev compression-webpack-plugin
在vue.config.js configureWebpack 里面新增
//头部引入
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 生产环境相关配置
if (process.env.NODE_ENV !== 'development') {
// gzip压缩
const productionGzipExtensions = ['html', 'js', 'css']
config.plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|css|svg|woff|ttf|json|html)$/,
threshold: 10240,
minRatio: 0.8,
deleteOriginalAssets: false
})
)
}
(2) 服务端部分
以nginx为例,修改配置如下:
server {
gzip on;
gzip_static on;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
}
说明:
Nginx的动态压缩是对每个请求先压缩再输出,但是在我们优化过程中,我们已经通过webpack进行了压缩处理,故不需要再进行动态压缩,减少资源浪费。在ngx配置中
gzip_static on; 就开启优先加载gz结尾的文件
gzip on;开启gzip
大招一发,效果还是特别明显,文件都变得特别零碎了,而且很小个


5、图片压缩
gzip说过图片不建议使用
gzip压缩,但是可以通过image-webpack-loader插件可将大的图片进行压缩从而缩小打包体积,不过我这个项目中,图片很少,但是我有做过测试,这个压缩效果还是理想,优化配置如下:
在vue.config.js的chainWebpack中调整
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
.end()
6、完成版如下(去除了一些自己项目中的东西)
实例中的cdn都是公共的,优化自己可以根据公司情况选择
'use strict'
const path = require('path')
const webpack = require('webpack')
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
function resolve(dir) {
return path.join(__dirname, dir)
}
const port = '1024' // dev port
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const cdn = {
externals: {
vue: 'Vue',
vuex: 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'echarts': 'Echarts',
moment: 'moment',
xlsx: 'xlsx',
},
// cdn的css链接
css: [
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css',
],
// cdn的js链接
js: [
'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
'https://cdn.bootcdn.net/ajax/libs/echarts/4.2.1/echarts.min.js',
"https://cdn.bootcss.com/moment.js/2.20.1/moment.min.js",
"https://cdn.bootcss.com/moment.js/2.20.1/locale/zh-cn.js",
'https://cdn.bootcdn.net/ajax/libs/xlsx/0.14.1/xlsx.min.js',
]
}
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
publicPath: './',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: 'error',
productionSourceMap: process.env.NODE_ENV !== 'production',
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/api-dev/*': {
target: ``,
changeOrigin: true,
pathRewrite: {
'^/api-dev/': ''
},
logLevel: 'debug'
}
}
},
configureWebpack: config => {
config.externals = cdn.externals
return {
name: name,
resolve: {
alias: {
'@': resolve('src'),
'src': resolve('src')
}
},
plugins: [
new MonacoWebpackPlugin({
features: ['coreCommands']
}),
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
threshold: 10240, // 只有大小大于该值的资源会被处理 10240
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
deleteOriginalAssets: true // 删除原文件
})
]
}
},
chainWebpack(config) {
config.plugin('provide')
.use(webpack.ProvidePlugin, [{
$: 'jquery',
jquery: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}])
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
)
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'),
minChunks: 3,
priority: 5,
reuseExistingChunk: true
}
}
})
config.plugin('html').tap(args => {
args[0].cdn = cdn
return args
})
}
)
}
}
结语
在经历了上述5步骤之后,优化效果从12.1M => 3.4M。

