前端性能优化实战 从 30s 到 2s - zptime/blog GitHub Wiki

最近做的管理端项目,发现首屏加载速度贼慢,对于 5G 快网速时代,这真的是个大问题呀,所以进行了一系列的优化。

介绍了前端性能分析工具,如 Performance 面板、Lighthouse、PageSpeed Insights;也介绍了包分析工具,如 webpack chart、webpack bundle analyzer、webpack visualizer 等;进行了项目优化,如 Echarts 按需引入,Moment 压缩配置,ramda、ant design vue、ant design vue icons、Quasar 等的按需引入,gzip 压缩配置等等

项目介绍

管理端项目是用easywebpack-vue搭建起来的,采用的是服务端渲染,用到了quasarant design vue两个组件库

EasyWebpack 基本介绍:https://www.yuque.com/easy-team/easywebpack/home

Asset 渲染模式:https://www.yuque.com/easy-team/egg-vue/asset

Quasar:http://v0-16.quasarchs.com/

Ant Design Vue:https://antdv.com/docs/vue/introduce-cn/

性能分析工具

Performance 面板

Google 浏览器 Performance 面板就是一个很强大的工具,可以进行多方面的分析

Lighthouse

Lighthouse 是 google 一个开源的自动化工具,展示了白屏、首屏、可交互时间等性能指标和 SEO、PWA 等。

运行 Lighthouse 的方式有两种:一种是作为 Chrome 扩展程序运行;另一种作为命令行工具运行。

// 安装命令:
npm install -g lighthouse

// 审查某个网页:
lighthouse https://www.imooc.com/

// 帮助查看
lighthouse --help

 //导出HTML格式
lighthouse https://www.imooc.com/  --output html

PageSpeed Insights

查询网址:https://developers.google.com/speed/pagespeed/insights/

分析文档:https://developers.google.com/speed/docs/insights/about

Google Page Speed Insight(PSI)是一款旨在优化所有设备上的网页、提高网页加载速度的工具。

例如,淘宝首页分析:

效果

包分析工具介绍

webpack chart

网址:http://alexkuz.github.io/webpack-chart/

介绍:上传stat.json文件,即可生成整体的项目文件层次结构图。点击任何一个区域,进入子目录文件分析,这个工具只能提供一个整体的文件依赖结构,是一个类饼图结构。

webpack bundle analyzer

网址:http://webpack.github.io/analyse/

介绍:上传stat.json文件,即可生成整个项目文件个数(包括 vue 文件,js,css, image, chunk 等)、文件大小、文件依赖关系统计,可以很方便找出筛选出大的 js、image、css,然后进行针对性的优化,信息很全,对优化文件大小有很大帮助。

webpack bundle optimize helper

网址:https://webpack.jakoblind.no/optimize/

介绍:分析你的 bundle 包大小,并为你提供可操作的改进措施建议,以减少 bundle 体积大小。

webpack visualizer

网址:https://chrisbateman.github.io/webpack-visualizer/

介绍:生成一个饼状图,可视化 bundle 内容。以查看哪些模块正在占用空间,哪些可能是重复的

项目打包构建

构建包分析工具:analyzer(webpack-bundle-analyzer) 和 stats(stats-webpack-plugin) , 默认用 analyzer 插件。

easy 安装

如果提示“zsh: command not found: easy”,则运行如下命令,全局安装一下,就可以在命令行中使用 easywebpack or easy 命令, 比如 easy init, easy build, easy dev, easy print

$ sudo npm i easywebpack-cli  -g

Analyzer 分析

如下命令会对生产环境进行打包构建,移除开发辅助代码, 压缩 js/css/image 以及 hash。会自动打开两个地址,可以查看包的结构、大小、依赖关系等

$ easy build prod -s

(1)http://127.0.0.1:9997/

效果

(2)http://127.0.0.1:9999/

效果

Stats 分析

如下命令会对生产环境进行打包构建,而且会生成生成client_stats.json文件,就可以用上面介绍的分析工具进行分析了。

$ easy build prod -s stats

效果

使用 webpack analyse 分析如下:

效果

优化对比

优化前分析

如下就是优化前的分析,可以看出:

  1. vendor 文件占比情况:moment > ramda > runtime > vue-router > vuex
  2. [entry].js 文件占比情况:ant design vue > main.js > quasar > @ant design icons

webpack-bundle-analyzer 模块分析如下:

(1)http://127.0.0.1:9997/

效果

(2)http://127.0.0.1:9999/

效果

webpack bundle optimize helper 入口文件分析如下所示:

效果

未优化前,首页的 lighthouse 分析报告: 效果

优化后分析

如下就是优化前的分析,可以看出:

  1. vendor 文件占比情况: runtime > moment > vue-router > vuex > axios
  2. [entry].js 文件占比情况:main.js > ant design vue > @ant design icons > quasar

效果

效果

效果

优化实战

Echarts 按需引入

入口文件包大小减少 0.39MB,即 400KB

// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from "echarts/core";

// 引入各种图表,图表后缀都为 Chart
import { BarChart, LineChart, PieChart } from "echarts/charts";

// 引入提示框,标题,直角坐标系等组件,组件后缀都为 Component
import {
  TitleComponent,
  TooltipComponent,
  ToolboxComponent,
  GridComponent,
  LegendComponent,
  AxisPointerComponent,
  DatasetComponent,
} from "echarts/components";

// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { SVGRenderer } from "echarts/renderers";

// 注册必须的组件
echarts.use([
  BarChart,
  LineChart,
  PieChart,

  TitleComponent,
  TooltipComponent,
  ToolboxComponent,
  GridComponent,
  LegendComponent,
  AxisPointerComponent,
  DatasetComponent,

  SVGRenderer,
]);

export default echarts;

Moment 压缩配置

Moment 默认支持多种语言,但是本系统只用到了中文,故此可排除其他语言。使用如下配置后,vendor 减少了约 200kb

其实 Moment.js 本身就挺大的,有 232KB,可以换成 Day.js,只有 12KB。

dayjs 替换 momentJS 文档:https://github.com/ant-design/antd-dayjs-webpack-plugin

// 修改webpack.config.js文件
plugins: [
  // 排除i18n其他语言,仅加载中文 `moment/locale/zn-cn.js`
  new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
];

ramda 按需引入

按需引入后,vendor 减少了 92kb

// 安装依赖
npm install babel-plugin-ramda --save

// 配置babel.config.js文件
module.exports = {
  plugins: [
    // "ramda",
    // 或者
    ["ramda", { "useES" : true }]
  ],
};

ant design vue 按需引入

因为需要对组件库进行定制化改造,会直接修改源码,所以直接将组件库代码下载到本地项目中,相关的优化配置也会稍有不同。

使用 ant design vue 按需引入,入口文件减少了 0.65MB,即 666KB

// 安装依赖
npm install babel-plugin-import --save-dev

// 配置webpack.config.js文件
module.exports = {
  plugins: [
    [
      "import",
      // 默认js路径是[libraryName]/[moduleType]/[componentName]
      // 默认css路径是[libraryName]/[moduleType]/[componentName]/style
      {
          // 引入库名称,默认是ant-design-vue
        libraryName: "@ui/ant-design-vue",
        // 来源目录,默认是“es”
        libraryDirectory: "",
        // style:css-识别css文件;tree-识别less文件
        style: true,
        // // 自定义组件路径
        // customName: (name) => {
        //   return `ant-design-vue/${name.toLowerCase()}`
        // }
        // // 自定义组件库样式
        // style: (name) => {
        //  // name为组件所在的路径,按需加载样式配置提取对应组件名称的css即可
        //   return `@/ant-design-vue/${name}/style`
        // },
      },
    ],
  ],
};

// 配置后的实现
import { Button } from "@ui/ant-design-vue"
// 等价于
import "@ui/ant-design-vue/button/style/index.less"
import Button from "@ui/ant-design-vue/button/index.js"

ant design vue icons 按需引入

ant design vue icons 图标库也挺大的,实际项目中只用到了几个,大部分用的都是自己的图标,所以将图表库按需引入还是很有必要的。但是这个工作量稍微有点大,要对照处理,容易漏掉。

配置其实挺简单的,就是将 ant design vue icons 图标库的加载路径重新命名一下。去除所有图标,可以减少 0.47MB,即 480KB

(1)配置 webpack.config.js 文件

module.exports = {
  alias: {
    "@ant-design/icons/lib/dist$": resolve("./utils/icons.js"),
  },
};

(1)新增 icon.js 文件

export { default as CalendarOutline } from "@ant-design/icons/lib/outline/CalendarOutline";

export { default as SettingOutline } from "@ant-design/icons/lib/outline/SettingOutline";
export { default as GithubOutline } from "@ant-design/icons/lib/outline/GithubOutline";
export { default as CopyrightOutline } from "@ant-design/icons/lib/outline/CopyrightOutline";

Quasar 按需引入

Quasar 按需引入配置,入口文件减少了0.51MB,即522KB

Quasar 主要用于外层框架的构建,例如左侧菜单和头部导航等;内部的页面开发全部用的 ant design vue。后期可以慢慢将 Quasar 替换掉,减少一个前端框架。

// 配置babel.config.js文件
module.exports = {
  plugins: [
    [
      "transform-imports",
      {
        quasar: {
          transform: require("quasar/dist/babel-transforms/imports.js"),
          preventFullImport: true,
        },
      },
    ],
  ],
};

开启 gzip 压缩(直接减少 75%)

前端通过 webpack,grunt,或者 gulp 打包压缩,基本能压缩 50%以上,服务端开启 gzip 压缩可以在之前的基础上再压缩 50%以上,所以这个真的是一定要开启的呀。

经过 gzip 压缩,文件大小可以变为之前的 30%甚至更小。本次实践中,入口文件由 3.6MB 变为 1MB,减少了 72%,事件也由 30s 减为 2s 左右了

(1)查看客户端是否开启 gzip 压缩:请求头中有个 Accept-Encoding 来标识对压缩的支持。

效果

(2)查看服务端开启 gzip 压缩:在 http 响应头,我们可以看到 content-encoding:gzip

效果

(3)前端开启 gzip 压缩如下,要和服务端进行配合,服务端也要配置 gzip 才行:

// 配置webpack.config.js文件
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
  plugins: [
    new CompressionPlugin({
      // filename: '[path].gz[query]', // 目标文件名
      algorithm: "gzip", // 使用gzip压缩
      test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$/, // 压缩文件类型
      threshold: 102400, // 对超过100k的数据压缩
      minRatio: 0.8, // 最小压缩比达到0.8时才会被压缩
    }),
  ],
};

dll 配置

dll 配置官网:https://www.yuque.com/easy-team/easywebpack/dll

对于一些常用的,基本不咋修改的第三方文件,我们可以提前打包好,单独生成一个包。后面再打包的时候,直接用这个就行了,就提高了 webpack 的打包速度。

如果是一般的项目,需要自己进行 webpack dll 的配置,而 easywebpack 已经在底层集成好了,我们只需要如下简单配置一下就行。

dll  配置支持:默认提取的文件为  vendor.js.

// webpack.config.js
module.exports = {
 dll: ["vue", "vuex", "vue-router", "axios", "js-cookie", "moment"],
}

splitChunks 配置

要进行合理的代码分割,减少入口文件大小。默认配置只会生成 runtime,common,[entry].js 三个 js 文件。

module.exports = {
  optimization: {
    namedModules: true,
    namedChunks: true,
    runtimeChunk: {
      name: "runtime",
    },
    splitChunks: {
      name: false,
      chunks: "all", // all(全部), async(异步的模块),initial(同步的模块)
      minSize: 20000, // 表示文件大于1k的时候才对它进行打包
      minChunks: 2, // 当某个模块满足minChunks引用次数时,才会被打包。
      maxAsyncRequests: 5, // 在打包某个模块的时候,最多分成5个chunk,多余的会合到最后一个chunk中
      maxInitialRequests: Infinity,
      automaticNameDelimiter: "~", // 当vendors或者default中的filename不填时,打包出来的文件名就会带~
      cacheGroups: {
        default: false,
        vendors: {
          // 将从node_modules中引入的模块统一打包到common.js文件中
          name: "common",
          chunks: "all",
          minChunks: 2,
          test: /[\\/]node_modules[\\/]/,
        },
        styles: {
          // css统一打包到common.css文件中
          name: "common",
          chunks: "all",
          minChunks: 2,
          test: /\.(css|less|scss|stylus)$/,
          enforce: true,
          priority: 50,
        },
      },
    },
  },
};

DNS Prefetch

DNS Prefetch(DNS 预解析):根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短 DNS 解析时间,来提高网站的访问速度。

浏览器自动解析:浏览器引擎在解析 HTML 页面的时候,会自动获取当前页面所有的 a 标签 href 属性当中的域名,然后进行DNS Prefetch。这个解析过程是与用户浏览网页并行处理的。但是为了确保安全性,在 HTTPS 页面中没有开启 DNS Prefetch

<!-- 开启DNS Prefetch -->
<meta http-equiv="x-dns-prefetch-control" content="on" />

JS 的 async 和 defer 模式

JavaScript 引擎是独立于渲染引擎存在的,即会阻塞 HTML 解析,也会阻塞 CSS 解析,因此可以改变 JavaScript 的加载方式或者加载时机来进行优化:

  1. 尽量将 js 文件放在 body 底部
  2. 使用 defer 和 async 来避免不必要的阻塞

async模式:异步加载,不会阻塞浏览器的加载,加载成功后,会立即执行。使用多个 async 模式引入的多个文件执行顺序是不确定的。

defer 模式:异步加载,执行会被推迟。等整个文档解析完成后,会执行所有有 defer 加载的 js 文件代码。使用多个 defer 模式引入的多个文件从上往下顺序执行。

<!DOCTYPE html>
<html lang="en">
  <head>
    <script async src="m-es6-promise.min.js"></script>
    <script async src="m-polyfill-v5.3.js"></script>
  </head>
  <body>
    <script defer>
      /* 百度统计代码 */
      window._hmt = window._hmt || [];
      (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?{{hmtKey}}";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
      })();
    </script>
  </body>
</html>
⚠️ **GitHub.com Fallback** ⚠️