记一次 Nuxt.js 登录页性能优化(性能提升十倍加)
共 5290字,需浏览 11分钟
·
2020-11-04 10:14
Nuxt.js 登录页性能优化
"color:rgb(255,255,255);">前言
最近有测试和 local 投诉,我们管理系统的登录页面经常加载很久,常常会有页面已经出来了,但是点击登录毫无反应,直到全部加载后才能登录。于是,他们提出让我们去优化。
这是一个好问题,登录页虽然不是移动端那种首页,但也是最先呈现给内部用户的。
"color:rgb(255,255,255);">定位耗时
遇到这种问题,首先需要找出耗时都花在了哪里,然后再去想具体办法去解决。
首先,打开登录页面控制面板,Disable Cache 之后查看一下每个资源的耗时。
从图上可以明显看出来,有一个 2.2m 的文件足足耗时5s之久,文件的耗时主要在下载上面,看来主要的性能瓶颈就在这里了。
由于 JS 文件在腾讯云 CDN 上面配置了协商缓存(etag),所以在第二次加载的时候速度提升非常大,基本上不到 1s 就可以加载出来了。
image那么这个大文件是什么文件呢?
我去 Jenkins 上看一下构建记录,在 build 的时候看到这个文件就是基于第三方包打出来的 vendors 文件。
image"color:rgb(255,255,255);">webpack4 splitChunks
既然知道这个是 vendors 文件了,那就来分析一下 webpack 构建。
在 webpack4 里面出现了 splitChunk 来拆分 chunk 文件,webpack4 会有一个默认的 vendors chunk,它会把 node_modules 都给打成一个包,类似于:
optimization: {
"color:#d19a66;">splitChunks: {
"color:#d19a66;">chunks: "color:#98c379;">'initial',
"color:#d19a66;">cacheGroups: {
"color:#d19a66;">vendors: {****
test: "color:#98c379;">/[\\/]node_modules[\\/]/,
"color:#d19a66;">priority: "color:#d19a66;">-10
}
}
}
}
只不过,Nuxt 在这个基础上又拆分出了一个 commons ,配置规则如下:
optimization.splitChunks.cacheGroups.commons = {
"color:#d19a66;">test: "color:#98c379;">/node_modules[\\/](vue|vue-loader|vue-router|vuex|vue-meta|core-js|@babel\/runtime|axios|webpack|setimmediate|timers-browserify|process|regenerator-runtime|cookie|js-cookie|is-buffer|dotprop|nuxt\.js)[\\/]/,
"color:#d19a66;">chunks: "color:#98c379;">'all',
"color:#d19a66;">priority: "color:#d19a66;">10,
"color:#d19a66;">name: "color:#56b6c2;">true
}
priority 代表优先级,如果两个 cacheGroups 里面都引用了同一个库,那么就根据优先级来判断优先把这个库打进哪个 chunk 里面。
很明显 commons 的优先级要高于 vendors,所以会把 test 规则匹配到的第三方包优先拆分出来,这几个主要是 Nuxt 中依赖的一些库。
本地执行了一次 analyze 后,得到的构建图是这样的,可以看出来 vendors 明显远比其他的包都要大,尤其是 xlsx、iview、moment、lodash 这几个库,几乎占了一大半体积。
image"color:rgb(255,255,255);">优化
生成多 HTML
既然知道 vendors 包里面都是一些第三方库了,那么是否可以只打出登录页依赖的第三方库,然后只去加载这个 chunk 文件呢?
我看了一下登录页逻辑很简单,不需要 lodash、moment,甚至连 iview 都不需要,完全可以自己去实现样式,这样就不必去加载体积这么大的 vendors chunk 了。
真是个好主意,可是问题来了,怎么才能不去加载 vendors 呢?
如果是在 webpack 里面,这个很容易,我们可以通过 html-webpack-plugin
来加载多个 HTML 文件,针对登录页生成一个 HTML 文件,让它只去加载自身依赖的 chunk 文件。
于是我去看了一下 Nuxt 源码,发现这里还是暴露了配置给我们去定义一个新的 HTML 模板的。
当然,到最后我也没去尝试这种方法,只是觉得应该可以实现。
image从 HTML 模板中删除
Nuxt 会暴露给我们一个 app.html
模板文件,它会在服务端渲染出来数据,最后替换到这个文件里面。
<"color:#e06c75;">html {{ HTML_ATTRS }}>
<"color:#e06c75;">head {{ HEAD_ATTRS }}>
{{ HEAD }}
"color:#e06c75;">head>
<"color:#e06c75;">body {{ BODY_ATTRS }}>
{{ APP }}
"color:#e06c75;">body>
"color:#e06c75;">html>
那么我们有没有可能在 Nuxt 替换这些占位符之前先去除掉不需要加载的 chunk 文件呢?其实也是可以的,只是需要修改到 Nuxt 的源码。
修改了源码之后,还需要用 patch-package
去打一个补丁,这样就可以做到修改 node_modules
里面的代码。
打开项目的 node_modules 文件夹,找到 @nuxt/vue-renderer/dist/vue-renderer.js
,在 SSRRenderer
这个类里面的 render
方法中,我们可以看到如下代码:
m.script.text({ body: true })
这句代码拿到的就是最后页面上渲染出来的 script
标签,如果在这里匹配到 vendors 包,把它给排除掉,之后在页面上就不会加载这个 JS 文件了。
我这里的方案是这样的,首先把登录页不需要且体积很大的几个包(iview、moment、lodash)给单独打了一个 my-vendors
的包。
在 Nuxt 源码中用正则表达式去匹配这个文件名,然后手动 replace
掉(记得要把 link 标签里面预加载的也一起替换掉)
"color:#5c6370;font-style:italic;">// nuxt.config.js
config.optimization.splitChunks.cacheGroups.myVendors = {
"color:#d19a66;">test: "color:#98c379;">/node_modules[\\/](view-design|moment|moment-timezone|dayjs|crypto-js|simple-uploader\.js|vue2-google-maps|vuex-class|axios)[\\/]/,
"color:#5c6370;font-style:italic;">// cacheGroupKey here is `commons` as the key of the cacheGroup
automaticNamePrefix: "color:#98c379;">'my-vendors', "color:#5c6370;font-style:italic;">// 文件名以 my-vendors 为前缀
name: "color:#56b6c2;">true,
"color:#d19a66;">chunks: "color:#98c379;">'all',
"color:#d19a66;">priority: "color:#d19a66;">10
reuseExistingChunk: "color:#56b6c2;">true
}
"color:#5c6370;font-style:italic;">// vue-renderer.js
"color:#c678dd;">const scripts = APP.match("color:#98c379;">/(\
slot="title"
style="text-transform: capitalize; color: #595d65; font-size: 16px; display: flex; height: 25px;"
>
${
config.cdnServer.staticUrl
}/static/admin-website/logo.png" alt="logo" class="login-logo" />
Sign in with Google