Vue SPA打包优化
这是一篇对以往工作的一个整理小结,网上相关的都可以查到,主要方便自己日后查阅。
我们在进行项目打包的时候,如果文件大小过大,会抛出如下警告:
asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). This can impact web performance. Assets: js/chunk-vendors.1e3142a4.js (500 KiB)
通常首次请求中包含 chunk-vendor.***.js
这类的文件,而该文件往往是很大的。这也是 vue spa 下首屏加载慢的原因之一。
使用 bundle-analyzer-plugin 进行可视化分析
安装插件yarn add -D webpack-bundle-analyzer
我使用的是 vue-cli3,所以在 vue.config.js
中配置:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
chainWebpack: config => {
config.plugin('webpack-bundle-analyzer')
.use(new BundleAnalyzerPlugin({
openAnalyzer: false // 设置为 false,则不自动在浏览器中打开结果
}))
}
};
其他选项可自行去项目网址查阅。
配置好插件后,无论在开发环境 yarn serve
还是生产环境 yarn build
打包,都会自动运行该分析工具。两种模式下,分析的 chunk 大小是不同的,开发模式下比较大,因为没有经过优化。个人倾向于分析生产环境的打包结果。在浏览器中打开 8888
端口就可以看到分析打包文件的结果图:
通过分析图,可以直观看出来打包的代码,哪些地方是还可以优化的。
关闭 productionSourceMap
在 vue.config.js
文件中配置
module.exports = {
productionSourceMap: false
}
若不设置 productionSourceMap: false
,则打包时会生成一堆 map 文件,map 文件好比对源码进行标记跟踪,当项目运行报错时,会输出报错信息和错误代码的位置。建议生产环境关闭,可以有效减少整体文件大小,同时避免源码暴露风险。
关闭 Prefetch
prefetch 在 vue-cli3 中是默认开启的,它会在浏览器页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。当我们访问首屏时,会看到除了默认请求 app.****.js
文件,还会加载若干个 chunk,其中有一些是暂时还用不到的其他页面的路由文件。
遵循官方的方法,配置 vue.config.js
,下面两种二选一:
module.exports = {
chainWebpack: config => {
// 移除 prefetch 插件
config.plugins.delete('prefetch')
// 修改它的选项:
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || []
options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/)
return options
})
}
}
再次测试进行对比,预加载文件少了很多:
其实这些文件本身都不大,小则几 KB,但请求数多了会对服务器造成一定的压力。如果你想有更多的灵活性的话,关闭prefetch 后也可以手动通过 webpack 的内联注释进行预加载。
import(/* webpackPrefetch: true */ './someAsyncComponent.vue')
webpack 的运行时会在父级区块被加载之后注入 prefetch 链接。
路由懒加载
路由懒加载是最常用的方法,它将不同路由对应的组件代码分割成不同的文件,打包后对应的代码块会从最后生成的app.****.js
文件中剥离出来,只有相应的路由被访问时才进行请求加载。
路由懒加载的语法如下:
export default {
path: '/projects',
name: 'route-projects',
component: () => import('@/views/Projects') // 路由懒加载
}
按需加载组件
很多前端 UI 框架库都给我们提供了按需加载组件这一方法,与全部导入相比,按需加载只会导入我们用的组件。前者虽然在开发时比较方便,但当我们实际部署时,为了减小打包后文件的大小,就不得不考虑这一问题。
一般都要先安装插件 yarn add -D babel-plugin-component
,后面的配置根据不同框架略有不同。这里不展开。
我用的是 bootstrap-vue,它提供以插件和组件两种方式导入,插件稍微方便点,会将一组相关的所有组件一并导入,组件则更为精确。测试了一下,将插件导入换成组件导入后,共减少了 100 多 KB。
CSS拆分
vue-cli3 默认开启插件 ExtractTextPlugin
,它会将每个模块的 css 抽取出来独立成单个文件,通常来说自定义的 css 文件一般不会太大,但数量上会比较多,过多的文件会增加请求数,给服务器造成压力。我们可以在 vue.config.js
中关闭它:
module.exports = {
// ......
css: {
extract: false
}
};
关闭后打包就不会生成 .css 文件,这些 css 代码会被合并到 js 文件中。这样带来的一个问题就是,有的 js 文件本身就已经很大了,合并后会更大,可能会影响首屏加载速度。拆不拆分,看个人取舍。
服务端开启Gzip
安装插件 yarn add -D compression-webpack-plugin
首先在 vue 项目的 vue.config.js
文件中配置:
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
configureWebpack: () => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new CompressionPlugin({
test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png|\.svg|\.ttf/, // 压缩的文件类型
threshold: 10240, // 阈值,超过大小就压缩,单位:B
deleteOriginalAssets: false // 是否删除原文件
})
]
}
}
}
};
为了避免有请求不支持 gzip 的情况,这里设置 deleteOriginalAssets: false
来保留原文件,原文件同 .gz 文件最后一并上传到服务器。使用 yarn build
打包后,在 dist 文件夹下就可以看到大小超过阈值的文件都会对应有一个压缩的 .gz版本:
后端 nginx 进行如下配置:
http {
# 开启gzip服务
gzip on;
gzip_disable "msie6";
gzip_static on;
gzip_proxied any;
gzip_http_version 1.1;
}
$gzip_ratio
:压缩率。
gzip
:gzip开关,默认 off。
gzip_buffers
:设置缓冲区的数量和大小,用于存储压缩结果。默认值是 32 4K 或 16 8K。缓冲区的大小默认等于内存页的大小,具体是 4K 还是 8K 取决于平台。
gzip_comp_level
:设置压缩等级。范围从 1 到 9,数字越大,压缩效果越好,但压缩时间会更长,且更加消耗 cpu。默认为 1。
gzip_disable
:后面跟正则表达式,若请求头中的 “User-Agent” 匹配,则会取消 gzip 压缩。常见的写法有:gzip_disable “msie6”,这里的 “msie6” 对应正则表达式 “MSIE [4-6]\.”,它用于在 IE5.5 和 IE6 SP1 中禁止 gzip 压缩。相比较而言,直接写 “msie6” 匹配速度更快。
gzip_http_version
:设置压缩可以响应的最低 HTTP 版本。默认是 HTTP 1.1。
gzip_min_length
: 设置允许压缩的页面最小字节数(从响应头中的 Content-Length 中获取)。默认是 20,建议大于 1K。
gzip_proxied
:nginx 作为反向代理时启用,开启或者关闭后端服务器返回的结果,匹配的前提是后端服务器必须要返回包含 “Via” 的 header 头。gzip_proxy 支持如下参数:
- off:对所有的代理请求取消压缩;
- expired:若 header 头中包含 “Expired” 信息,则启用;
- no-cache:若 header 头中包含 “Cache-Control:no-cache” 信息,则启用;
- no-store:若 header 头中包含 “Cache-Control:no-store” 信息,则启用;
- private:若 header 头中包含 “Cache-Control:private” 信息,则启用;
- no_last_modified:若 header 头中不包含 “Last-Modified” 头信息,则启用;
- no_etag:若 header 头中不包含 “ETag” 头信息,则启用;
- auth:若 header 头中中包含 “Authorization” 头信息,则启用;
- any:对任何代理请求均启用压缩。
gzip_types
:gzip 支持的类型。
gzip_vary
:如果设置了 gzip、gzip_static 或 gunzip 指令,则启用响应头 “Vary: Accept-Encoding”。默认 off。
gzip_static
:开启后,nginx 将不会动态地对每个请求先压缩再传输。考虑到项目不大,而且前端打包时已经进行过压缩,我们完全可以将打包好 .gz 上传到服务器上,这样 nginx 会直接传输 .gz 文件,减少了很多服务器资源的浪费。具体用法可参考 Module ngx_http_gzip_static_module
当我们请求的数据的响应头出现 content-encoding:gzip
时,就表明 gzip 压缩设置成功:
跟以前没设置 gzip 相比,传输效率明显提升了很多:
第一张是以前部署的服务,下面一个则是最新部署的,尽管原始文件更大,但速度反而更快。
在本篇的种种方法中,该方法的效果是最明显的。
此外,还有一些其他方法,比如使用 CDN 等等,这里不做展开。