转载:https://juejin.cn/post/7359077652445806642

# 需求背景

  1. 从第三方采购的 vue2 + ElementUI 实现的云管平台,乙方说 2011 年左右就开始有这个项目了(那时候有 Vue 了吗,思考.jpg)。十几年的项目,我何德何能可以担此责任。里面的代码经过多人多年迭代可以用惨不忍睹来形容,吐槽归吐槽,混口饭吃,多烂的代码都得啃下去。
  2. 有一天领导找到我,问我怎么回事,打开页面需要十几秒时间也太慢了,后台管理系统不要求首屏加载时间都没有这么慢,这个对外的系统超过 1 秒打开时间,都会流失很多客户,不优化好年终自己看着办吧。
  3. 什么?影响年终?好的领导,我马上抽时间解决(🐂🐴)。

# 如何看优化是否做到位?

# 1. Lighthouse 谷歌插件,从首页打开速度分析页面的性能,并给出指标和打分。

  • 如何使用 Lighthouse?
  • F12 打开控制台 - Lighthouse
  • 如下图所示选择,然后点击 Anlyze page load 就可以了
  • 这里只关注性能所以只勾选了 Performance 指标,其他可访问性、SEO 有需求一同检测的自行勾选上。 image.png

# 2. webpack-bundle-analyzer 插件,分析代码打包大小的工具。

  • 如何使用 webpack-bundle-analyzer?
  • npm install --save-dev webpack-bundle-analyzer 安装依赖
//webpack.config.js 配置文件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;  
  
module.exports = {  
  //... 其他配置 ...  
  plugins: [  
    new BundleAnalyzerPlugin()  
  ]  
};
  • 这样在打包的时候会生成一个静态网站查看各个模块占用的存储空间大小 image.png

# 优化成果(有图有真相) 🔥

LightHouse性能指标解释:
FCP:衡量的是打开网页后,浏览器渲染第一段 DOM 内容所用的时间
LCP:用于测量视口中最大的内容元素何时渲染到屏幕上。这粗略地估算出网页主要内容何时对用户可见。
因为要优化的页面没有像官网那样有轮播图占据大量显示位置的元素,所以我主要关注FCP,FCP解决LCP也会相应变快

# 1. 优化前首页加载速度(FCP 15.8s)

image.png

# 2. 优化前体积(打包后大小 80.5M)

makefileStat: 源代码阶段
Parsed: 经过webpack打包后的大小
Gzipped: 经过Gzip压缩过后的大小,实际浏览器接收的大小,需要服务器开启Gzip压缩
这里的大小主要关注chunk-vendors.js和app.js。其他都跟首页加载关系不大。下面的首屏代码大小是这两个js代码大小之和。
chunk-vendors是引用的第三方库如element-ui、echarts、vue等等打包后的代码。app.js是项目的代码。
总大小首屏代码大小
Stat76.5M21.1M
Parsed80.5M24.2M
Gzipped13.5M5.9M
  • 这里只放 Gzipped 的大小,全部都放图太多了。

image.png

# 3. 优化后体积(三倍左右的代码体积减少,打包后大小减少 64M!)

总大小首屏代码大小
Stat30.2M15.2M
Parsed16.6M6.2M
Gzipped4.3M1.9M

image.png

# 4. 优化后首页加载速度(FCP 快了 13.6 秒!)

image.png

  • 看到这有人说,虽然是快了 13.6 秒,但是还是要 2.2s,还是不能秒开,你的年终还是不保啊。
  • 上面 Lighthouse 是不会用到缓存去检测性能的,为了有效他每次检测都相当于首次全部加载,打包后的 css、js 静态文件都是可缓存的。用户第二次打开时,是可以做到秒开。(年终有啦 -.-) image.png

# 怎么优化 ✏️

# 1. 静态文件缓存 (js,css 等),图片和 SVG 进行压缩或者替换。

  • 这一点在优化官网的时候很有用!大多数官网代码都十分精简,首屏加载慢大多都是因为轮播图没压缩,文件太大请求慢导致的,用压缩工具压缩一下,或者让 UI 换个不失真占用空间小的即可解决问题。
  • 在用 webpack-bundle-analyzer 查看包大小的时候,发现一个 SVG 竟然有 1.5M!你敢信?而且 SVG 不会经过打包有大小变化,就是即使经过 Webpack 打包,Gzip 压缩,他也会占用 1.5M 的的大小,优化前也才 5.9M,所以 1.5M 占比很大需要优化。
    • 为什么会有 1.5M 的 SVG?
    • 图片可以转 SVG,但是只是粗暴的将图片的 base64 编码塞到 SVG 里面,体积增大 33%
    • 图片未经压缩,原图比较大(甩锅设计,哈哈哈)
  • 最后改为使用静态图片引用,而且不是首页需要用到的 SVG,所以首页代码大大减少
  • 从下图可以看到支付宝相关的 SVG 都比较大,最后都被我用阿里的 iconfont 用更小的 svg 替换了,减少了 2.5M 左右的大小! image.png

# 2. 删掉无用路由、引用的库(实际未使用),然后启用树摇

  • 删掉无用路由很有用!加上树摇,可以去掉很多代码。正常来说公司自研的项目,每一个路由都是必要。但是这是第三方经过多人多年迭代的项目,很多路由都是没用的。
  • 还有一些库,在 main.js 文件里注册了 Vue 的全局组件,但是搜索整个项目根本没有用到,而且这个库还挺大的。又可以减少一些代码。
  • 这里有个奇葩的点是,webpack@4.46.0 的版本必须要指定 mode: 'production' 才会启用树摇!否则打包大小基本和源代码大小一样,参考上面优化前源代码 76.5M,打包后 80.5M 代码还多了一点!(也有可能树摇开启成功了,只是启用了 production 其他优化减少了体积,有没有大佬指导一下!)

# 3. 除了首页组件以外,其他组件改为异步组件,异步加载。同一个路由的组件打包到一个 js 上。减少首屏加载时请求数太多。

import Home from '@views/Home.vue'
const router = [
    // 首页不要异步,才用导入的方式打包到 app.js,优先加载
    {
        path: '/home',
        component: Home
    },
    // 其他组件异步加载,多个小组件可以打包到一起,减少请求数,代码分离要恰到好处
    {
        path: '/xx',
        component: () => import(/* webpackChunkName: "xx" */ '@/views/xx.vue')
    }
],

# 4. 异步加载首页不需要用到的 js 和 css 文件。

  • 项目的 index.html 总有一些奇奇怪怪的 js 和 css 引入如下所示,会阻塞页面的解析,我们在前端首页解析完后(DOMContentLoaded 事件)加载它们。
<link
  rel="stylesheet"
  href="./luckysheet/plugins/css/pluginsCss.css" />
<link
  rel="stylesheet"
  href="./luckysheet/plugins/plugins.css" />
<link
  rel="stylesheet"
  href="./luckysheet/css/luckysheet.css" />
<link
  rel="stylesheet"
  href="./luckysheet/assets/iconfont/iconfont.css" />
<script src="./luckysheet/plugins/js/plugin.js"></script>
<script src="./luckysheet/luckysheet.umd.js"></script>
  • 比如这样
<script>
      document.addEventListener('DOMContentLoaded', () => {
        ;['./luckysheet/plugins/js/plugin.js', './luckysheet/luckysheet.umd.js'].forEach((item) => {
          const script = document.createElement('script')
          script.defer = true
          script.src = item
          document.body.appendChild(script)
        })
        ;[
          './luckysheet/plugins/css/pluginsCss.css',
          './luckysheet/plugins/plugins.css',
          './luckysheet/css/luckysheet.css',
          './luckysheet/assets/iconfont/iconfont.css'
        ].forEach((item) => {
          const link = document.createElement('link')
          link.rel = 'stylesheet'
          link.type = 'text/css'
          link.href = item
          document.head.appendChild(link)
        })
      })
    </script>

# 5. 此外还有服务端开启 http2、开启 Gzip 压缩,笔者优化之前已经开启所以没有对比,就不再赘述,实际上提升也非常大。

# 6. 最后效果

  • 大小、请求数、加载时间都大大减少
  • 注意优化后的时间 chunk-vendors.js 和 app.js 加载一共耗时 1.59s,不是 1.11+1.59,chunk-vendors 的下载解析会阻塞 app.js 的下载解析,所以一共 1.59s。
  • 前:

image.png

  • 后:

image.png

# 引流