Nuxt.js 登录页性能优化
前言
近期有测试和 local 投诉,咱们管理系统的登录页面经常加载很久,常常会有页面已然出来了,然则点击登录毫无反应,直到所有加载后才可登录。于是,她们提出让咱们去优化。
这是一个好问题,登录页虽然不是移动端那种首页,但亦是最先呈现给内部用户的。
定位耗时
遇到这种问题,首要需要找出耗时都花在了哪里,而后再去想详细办法去处理。
首要,打开登录页面掌控面板,Disable Cache 之后查看一下每一个资源的耗时。
从图上能够显著看出来,有一个 2.2m 的文件足足耗时5s之久,文件的耗时重点在下载上面,看来重点的性能瓶颈就在这儿了。
因为 JS 文件在腾讯云 CDN 上面配置了协商缓存(etag),因此在第二次加载的时候速度提高非常大,基本上不到 1s 就能够加载出来了。
image
那样这个大文件是什么文件呢?
我去 Jenkins 上看一下构建记录,在 build 的时候看到这个文件便是基于第三方包打出来的 vendors 文件。
imagewebpack4 splitChunks
既然晓得这个是 vendors 文件了,那就来分析一下 webpack 构建。
在 webpack4 里面显现了 splitChunk 来拆分 chunk 文件,webpack4 会有一个默认的 vendors chunk,它会把 node_modules 都给打成一个包,类似于:
optimization: { splitChunks
: { chunks: initial
, cacheGroups
: { vendors
: {**** test: /[\\/]node_modules[\\/]/
, priority: -10
}
}
}
}
只不外,Nuxt 在这个基本上又拆分出了一个 commons ,配置规则如下:
optimization.splitChunks.cacheGroups.commons = { test: /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)[\\/]/
, chunks: all
, priority: 10
, name: true
}
priority 表率优先级,倘若两个 cacheGroups 里面都引用了同一个库,那样就按照优先级来判断优先把这个库打进哪个 chunk 里面。
很显著 commons 的优先级要高于 vendors,因此会把 test 规则匹配到的第三方包优先拆分出来,这几个重点是 Nuxt 中依赖的有些库。
本地执行了一次 analyze 后,得到的构建图是这般的,能够看出来 vendors 显著远比其他的包都要大,尤其是 xlsx、iview、moment、lodash 这几个库,几乎占了一大半体积。
image
优化
生成多 HTML
既然晓得 vendors 包里面都是有些第三方库了,那样是不是能够只打出登录页依赖的第三方库,而后只去加载这个 chunk 文件呢?
我看了一下登录页规律很简单,不需要 lodash、moment,乃至连 iview 都不需要,完全能够自己去实现样式,这般就不必去加载体积这么大的 vendors chunk 了。
真是个好主意,可是问题来了,怎么才可不去加载 vendors 呢?
倘若是在 webpack 里面,这个很容易,咱们能够经过 html-webpack-plugin 来加载多个 HTML 文件,针对登录页生成一个 HTML 文件,让它只去加载自己依赖的 chunk 文件。
于是我去看了一下 Nuxt 源码,发掘这儿还是暴露了配置给咱们去定义一个新的 HTML 模板的。
当然,到最后我亦没去尝试这种办法,只是觉得应该能够实现。
image
从 HTML 模板中删除
Nuxt 会暴露给咱们一个 app.html 模板文件,它会在服务端渲染出来数据,最后替换到这个文件里面。 <html {{ HTML_ATTRS }}> <head {{ HEAD_ATTRS }}>
{{ HEAD }} </head> <body {{ BODY_ATTRS }}>
{{ APP }} </body></html>
那样咱们有无可能在 Nuxt 替换这些占位符之前先去除掉不需要加载的 chunk 文件呢?其实亦是能够的,只是需要修改到 Nuxt 的源码。
修改了源码之后,还需要用 patch-package 去打一个补丁,这般就能够做到修改 node_modules 里面的代码。
打开项目的 node_modules 文件夹,找到 @nuxt/vue-renderer/dist/vue-renderer.js,在 SSRRenderer 这个类里面的 render 办法中,咱们能够看到如下代码: imagem.script.text({ body: true }) 这句代码拿到的便是最后页面上渲染出来的 script 标签,倘若在这儿匹配到 vendors 包,把它给排除掉,之后在页面上就不会加载这个 JS 文件了。
我这儿的方法是这般的,首要把登录页不需要且体积很大的几个包(iview、moment、lodash)给单独打了一个 my-vendors 的包。
在 Nuxt 源码中用正则表达式去匹配这个文件名,而后手动 replace 掉(记得要把 link 标签里面预加载的亦一块替换掉) // nuxt.config.js
config.optimization.splitChunks.cacheGroups.myVendors = { test: /node_modules[\\/](view-design|moment|moment-timezone|dayjs|crypto-js|simple-uploader\.js|vue2-google-maps|vuex-class|axios)[\\/]/
, // cacheGroupKey here is `commons` as the key of the cacheGroup automaticNamePrefix: my-vendors, // 文件名以 my-vendors 为前缀 name: true
, chunks: all
, priority: 10reuseExistingChunk:true
} // vue-renderer.jsconst scripts = APP.match(/(\<script[\s\S]*?>[\s\S]*?\<\/script\>)/g
) || [] const script = scripts.find(s =>s.indexOf(my-vendors) > -1
);
APP = APP.replace(script, ) const links = HEAD.match(/(\<link rel="preload" [\s\S]*?>)/g
) || [] const link = links.find(s =>s.indexOf(my-vendors) > -1
); HEAD = HEAD.replace(link || ""
, )
最后的效果的确是不会加载这个文件了,然则点击事件失效了,对比前后两次加载的文件,差别仅有这个 my-vendors.js,不清楚为何点击事件失效,因此最后为了赶时间亦就没运用这个办法。
服务端直出
除了上面两种方式之外,还有一种比较简单的方式。
因为 Nuxt 本身就会起步一个服务,官方亦支持咱们运用 express\koa 等等来实现服务端的路由,因此咱们能够把登录页面直接用纯服务端渲染,去掉所有不必要的第三方库和文件。
触及到照片之类的,我事先把她们上传到了 CDN 上面,而后按照环境变量去加载区别的 CDN 位置。 // login/template.tsexport default
(config) => { return
`
AirPay Admin
<link data-n-head="true" rel="preconnect" href="
${
config.cdnServer.staticUrl }
" crossorigin="true"> <link data-n-head="true" rel="preconnect" href="
${
config.apiServer.baseUrl }
" crossorigin="true">
article, aside, blockquote, body, button, dd, details, div, dl, dt, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, input, legend, li, menu, nav, ol, p, section, td, textarea, th, ul {
margin: 0;
padding: 0;
}
img {
border-style: none;
}
slot="title"
style="text-transform: capitalize; color: #595d65; font-size: 16px; display: flex; height: 25px;"
> <img src="
${
config.cdnServer.staticUrl }
/static/admin-website/logo.png" alt="logo" class="login-logo" />
Sign in with Google
particlesJS(particles, {
"particles": {},
"interactivity": {}
}
}, function() {
console.log(callback - particles.js config loaded);
});
function login() {} `
}
而后,在 /login 路由下面引入这个模块,传入必要的配置后直接输出,记得设置 Content-Type 为 text/html。 // login/index.tsmodule.exports = function(
fastify: Fastify.FastifyInstance,
opts: Fastify.RouteShorthandOptions,
next: Function
)
{ fastify.get(/login, async
(request, reply) => {
reply .code(200
) .header(Content-Type, text/html; charset=utf-8
)
.send(login(Config))
})
next()
} // server/index.tsfastify.register(require(./routes/login), { prefix: /
})
最后优化的效果亦是非常显著的,不运用缓存的状况下耗时仅有几百毫秒。
image
在开启了缓存之后,几乎是秒开,耗时仅有短短 100ms,能够说性能得到了几十倍的提高。
image
总结
非常多时候咱们总会抱怨此刻的工作都是重复劳动,找不到能够提高自己的地区。
但倘若用心去找,还是能发掘团队研发中的不少痛点的,自己在处理这些痛点的时候亦能学习到很多新知识。
❤️爱心三连击
1.看到这儿了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。
2.关注公众号程序员成长指北,回复「1」加入Node进阶交流群!「在这儿有好多 Node 研发者,会讨论 Node 知识,互相学习」!
3.亦可添加微X【ikoala520
】,一块成长。
“在看转发”是最大的支持
|