Lighthouse性能优化与实践

975 阅读6分钟

实战项目脚手架采用Umi

涉及到的知识点

  • Webpack优化
  • Lighthouse工具
  • Http相关知识
  • React性能优化
  • Umi配置相关

成果

先来看一下优化后的成果。

指标名称优化前优化后
Lighthouse Performance 评分6892
FCP(首次内容绘制)1.2s1.0s
LCP(最大内容绘制)4.8s1.2s
Speed Index(速度指数)3.0s2.1s
TBT(总阻塞时间)50ms0

优化前

image.png image.png

优化后

image.png image.png

测量工具及性能指标

Chrome devtools 内置的 Lighthouse,Lighthouse 是一种开源的自动化工具,用于提高 Web 应用程序的质量。 image.png 主要从5个方面分析页面, 性能、辅助功能、最佳实践、搜索引擎优化和 PWA。 image.png

Performance性能指标

First Contentful Paint (FCP:首次内容绘制)

FCP 测量在用户导航到页面后浏览器呈现第一段 DOM 内容所花费的时间。页面上的图像、非白色元素和 SVG 被视为 DOM 内容;不包括 iframe 内的任何内容。

Speed Index(速度指数)

Speed Index 衡量页面加载期间内容以视觉方式显示的速度。Lighthouse 首先捕获浏览器中页面加载的视频,并计算帧之间的视觉进度。

Largest Contentful Paint (LCP:最大内容绘制)

LCP 测量视口中最大的内容元素何时呈现到屏幕上。近似于页面的主要内容对用户可见的时间。

Total Blocking Time (TBT:总阻塞时间)

测量页面被阻止响应用户输入(例如鼠标点击、屏幕点击或键盘按下)的总时间。

Cumulative Layout Shift (CLS:累积布局偏移)

测量的是整个页面生命周期内发生的每一次意外布局转变的所有单个布局转变得分的总和。

Accessibility可访问性

Accessibility 辅助功能 : 无障碍设计,也称为网站可达性。是指所创建的网站对所有用户都可用/可访问,不管用户的生理/身体能力如何、不管用户是以何种方式访问网站。

Best Practices最佳实践

用于检测 Web 应用程序整体代码健康状况,包括是否包含文档类型、图片宽高比是否正确,网页安全性,如是否开启 HTTPS、网页存在的漏洞等。

SEO搜索优化

搜索引擎优化检测,如网页 title 是否符合搜索引擎的优化标准等。

需要优化的点

Performance

优化建议主要包括以下几点:

  • 预加载图像提升LCP
  • 减少未使用的 JS;
  • JS 压缩;
  • 消除呈现阻塞资源;
  • 合理使用图片的格式,webp 或者 avif 更快;
  • 使用适当大小的图像,避免字节浪费而减慢页面加载时间;

image.png 诊断出的网站存在的问题:

  • 有 46 个静态资源的缓存只有 1 小时;
  • 滚动事件没有添加标记{passive: true}),导致需要等待侦听器完成执行后再滚动页面;
  • 图片没有正确设置宽高;
  • 往返缓存问题;
  • 需要加载的资源太大,合计 4.8M;

image.png

Accessibility

缺失的属性:

  • 属性没有有效的值;
  • 表单元素没有关联的标签;
  • 禁用网页上的浏览器缩放,用于 元素或 [maximum-scale]属性小于 5;
  • 背景色和前景色没有足够的对比度;
  • html缺少lang属性

image.png

Best Practices

通用:

  • 注册卸载侦听器;
  • 缺少 JavaScript 的源映射,开发环境是有的,但是生产环境为了减少包大小,一般不会配置;

image.png

SEO

  • 缺失文档描述

优化

Performance

根据上面 Lighthouse 报告可以看出影响性能最大的因素,包括以下几点:

  • 体积太大,达 4.8mb;
  • 图片太大,图片格式也有影响;

代码体积优化

删除不需要的资源

检查项目中引入的 npm 资源,将没有使用到的删除。

去除console

// 去除生产console
extraBabelPlugins: [isProd ? 'transform-remove-console' : ''],

代码压缩

gzip压缩可以减少build之后包的体积,使用compression-webpack-plugin插件。 代码:

const CompressionPlugin = require('compression-webpack-plugin');

config.plugin('compression-webpack-plugin').use(CompressionPlugin, [
    {
      test: /\.(js|css|html)$/i, // 匹配
      threshold: 10240, // 超过10k的文件压缩
      deleteOriginalAssets: false, // 不删除源文件
    },
]);

打包的文件中出现了.gz结尾的压缩包,就是压缩成功了。 image.png

nginx配置开启gzip。

image.png

代码分包

通过 webpack-bundle-analyzer 插件分析包体积,将一些大的 npm 包和 runtimeChunk 独立分包,减小包体积。

未优化前包体积5.13M。 image.png 可以从下图看出,重复引用的地方很多,而且把json文件也打入到了包中,非常占空间。 image.png

提取公共文件,分包优化

chunks: isProd ? ['lodash', 'antd', 'echarts', 'vendors', 'umi'] : ['umi'],
config.merge({
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      minChunks: 2,
      cacheGroups: {
        echarts: {
          name: 'echarts',
          chunks: 'all',
          test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
          priority: 10,
          enforce: true,
        },
        lodash: {
          name: 'lodash',
          chunks: 'all',
          test: /[\\/]node_modules[\\/](lodash)[\\/]/,
          priority: 10,
          enforce: true,
        },
        antd: {
          name: 'antd',
          chunks: 'all',
          test: /(@antd|antd|@ant-design)/,
          priority: 10,
        },
        vendors: {
          chunks: 'all',
          name: 'vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -5,
        },
        umi: {
          chunks: 'all',
          name: 'umi',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
        },
      },
    },
  },
});

处理静态json文件

使用fetch请求静态文件。


export const usefetchStaticData = (url: RequestInfo | URL) => {
  const [data, setData] = useState([]);

  useLayoutEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(res => {
        setData(res);
      });
  }, []);

  return data;
};

// json文件放在public目录
// jsx 
usefetchStaticData('jsons/geoCity.json')

经过上面操作后代码体积缩小为1.2M。 image.png 从下图可以看出,基本上没有特别大的公共文件了。 image.png

懒加载组件

使用组件懒加载,只加载当前需要的模块。 使用react自带的组件懒加载方式:

import type { PropsWithChildren } from 'react';
import React, { lazy, Suspense } from 'react';
import type { IRouteComponentProps, Location, IRouteProps } from 'umi';
import styles from './style.scss';
import 'dayjs/locale/zh-cn';
const Header = lazy(() => import('@/components/Header'));
const Footer = lazy(() => import('@/components/Footer'));

export interface Props {
  routes: IRouteComponentProps;
  location: Location;
  route: IRouteProps;
}

const Layout: React.FC<Props & PropsWithChildren> = props => {
  return (
    <Suspense fallback={''}>
      <div className={styles.page}>
        <Header {...props} />
        <div className={styles.content}>{props.children}</div>
        <Footer />
      </div>
    </AuthProvider>
    </Suspense>
  );
};

export default Layout;

使用umi组件懒加载方式:

import { dynamic } from 'umi';
import Loading from './Loading';

export default dynamic({
  loader: async function () {
    const { default: Component } = await import('./index-static');
    return Component;
  },
  loading: Loading,
});

图片优化

图片压缩

压缩较大的图片,tinify.cn/

图片懒加载

对图片采用懒加载策略。

使用react-lazyload插件;

import LazyLoad from 'react-lazyload';

<LazyLoad>
  <img alt="banner" src={require(`@/assets/xxx.png`)} />
</LazyLoad>

图片尺寸

使用图片时,设置图片的合理尺寸。

图片格式设置

优先使用 webp 格式图片。

开启缓存

添加静态资源缓存,加速第二次访问加载速度。 image.png

Accessibility

html加入lang属性

image.png

属性没有有效的值、单元素没有关联的标签

这个是antd select组件本身的问题,不用管,这里我去掉了就不报问题了。

其他问题

  • 修改meta缩放
  • 修改报出问题的UI即可

Best Practices

开启source map,一般生产环境关闭。

SEO

  • 添加网站描述。
  • 图片加 alt 属性。

其他优化建议

  • 项目内有一些 json存储的静态数据,这部分文件上传至 CDN,改为 fetch 的方式按需引入。
  • 对于大的图片也采用CDN加载。
  • 一些比较大的库文件,例如echart等,也可以使用 CDN + externals 方式处理。

参考链接