记一次 Nuxt.js 登录页性能优化(性能提升十倍加)

程序员成长指北

共 5290字,需浏览 11分钟

 · 2020-11-04

Nuxt.js 登录页性能优化

"color:rgb(255,255,255);">前言

最近有测试和 local 投诉,我们管理系统的登录页面经常加载很久,常常会有页面已经出来了,但是点击登录毫无反应,直到全部加载后才能登录。于是,他们提出让我们去优化。

这是一个好问题,登录页虽然不是移动端那种首页,但也是最先呈现给内部用户的。

"color:rgb(255,255,255);">定位耗时

遇到这种问题,首先需要找出耗时都花在了哪里,然后再去想具体办法去解决。

首先,打开登录页面控制面板,Disable Cache 之后查看一下每个资源的耗时。

a1a5a0ff9676677d765695b105b8efa3.webp

从图上可以明显看出来,有一个 2.2m 的文件足足耗时5s之久,文件的耗时主要在下载上面,看来主要的性能瓶颈就在这里了。

由于 JS 文件在腾讯云 CDN 上面配置了协商缓存(etag),所以在第二次加载的时候速度提升非常大,基本上不到 1s 就可以加载出来了。

7187b4c96f6dfc9a5f0e83ec46f48e46.webp

image

那么这个大文件是什么文件呢?

我去 Jenkins 上看一下构建记录,在 build 的时候看到这个文件就是基于第三方包打出来的 vendors 文件。

e7ff69fa652b156871576e4733be4e53.webp

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 这几个库,几乎占了一大半体积。

d970055ed45da75937b27e4c9bd227cb.webp

image

"color:rgb(255,255,255);">优化

生成多 HTML

既然知道 vendors 包里面都是一些第三方库了,那么是否可以只打出登录页依赖的第三方库,然后只去加载这个 chunk 文件呢?

我看了一下登录页逻辑很简单,不需要 lodash、moment,甚至连 iview 都不需要,完全可以自己去实现样式,这样就不必去加载体积这么大的 vendors chunk 了。

真是个好主意,可是问题来了,怎么才能不去加载 vendors 呢?

如果是在 webpack 里面,这个很容易,我们可以通过 html-webpack-plugin 来加载多个 HTML 文件,针对登录页生成一个 HTML 文件,让它只去加载自身依赖的 chunk 文件。

于是我去看了一下 Nuxt 源码,发现这里还是暴露了配置给我们去定义一个新的 HTML 模板的。

当然,到最后我也没去尝试这种方法,只是觉得应该可以实现。

bc076204871cb445aa180edb9fd8e18a.webp

image

从 HTML 模板中删除

Nuxt 会暴露给我们一个 app.html 模板文件,它会在服务端渲染出来数据,最后替换到这个文件里面。


<"color:#e06c75;">html {{ HTML_ATTRS }}>
  <"color:#e06c75;">head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  head>
  <"color:#e06c75;">body {{ BODY_ATTRS }}>
    {{ APP }}
  body>
html>

那么我们有没有可能在 Nuxt 替换这些占位符之前先去除掉不需要加载的 chunk 文件呢?其实也是可以的,只是需要修改到 Nuxt 的源码。

修改了源码之后,还需要用 patch-package 去打一个补丁,这样就可以做到修改 node_modules 里面的代码。

打开项目的 node_modules 文件夹,找到 @nuxt/vue-renderer/dist/vue-renderer.js,在 SSRRenderer 这个类里面的 render 方法中,我们可以看到如下代码:

c8ddbf1d546da2081aa75fc698bc9166.webpimage

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" />
                


              

              
                
                  

                  
                

              
            
          
        
        
        
      
    
  `
}

然后,在 /login 路由下面引入这个模块,传入必要的配置后直接输出,记得设置 Content-Typetext/html

"color:#5c6370;font-style:italic;">// login/index.ts
"color:#e6c07b;">module.exports = "color:#c678dd;">function(
  fastify: Fastify.FastifyInstance,
  opts: Fastify.RouteShorthandOptions,
  next: Function
) {
  fastify.get("color:#98c379;">'/login', "color:#c678dd;">async (request, reply) => {
    reply
      .code("color:#d19a66;">200)
      .header("color:#98c379;">'Content-Type', "color:#98c379;">'text/html; charset=utf-8')
      .send(login(Config))
  })
  next()
}

"color:#5c6370;font-style:italic;">// server/index.ts
fastify.register("color:#e6c07b;">require("color:#98c379;">'./routes/login'), { "color:#d19a66;">prefix: "color:#98c379;">'/' })

最后优化的效果也是非常明显的,不使用缓存的情况下耗时只有几百毫秒。

91a2e898662967e914a4976df8d7a8b5.webp

image

在开启了缓存之后,几乎是秒开,耗时只有短短 100ms,可以说性能得到了几十倍的提升。

a86eb5ff95f8d2dfe16d9374bc071b22.webp

image

"color:rgb(255,255,255);">总结

很多时候我们总会抱怨现在的工作都是重复劳动,找不到可以提升自己的地方。

但如果用心去找,还是能发现团队开发中的不少痛点的,自己在解决这些痛点的时候也能学习到很多新知识。

"padding-left:10px;font-size:18px;color:rgb(89,89,89);">❤️爱心三连击

1.看到这里了就点个在看支持下吧,你的「"color:rgb(255,0,0);">点赞,"color:rgb(255,0,0);">在看是我创作的动力。

2.关注公众号程序员成长指北,回复「1」加入Node进阶交流群!「在这里有好多 Node 开发者,会讨论 Node 知识,互相学习」!

3.也可添加微信【ikoala520】,一起成长。


"background-color:rgb(255,202,0);color:rgb(255,255,255);">“在看转发”是最大的支持

浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报