• 看懂 Lighthouse 中 Performance 核心指标


    简介

    Lighthouse 是谷歌开源的一款 Web 前端性能测试工具,它将针对此页面运行一连串的测试,然后生成一个有关页面性能的报告。本文中仅对 Performance 部分的指标进行介绍。

    Performance 指标

    Performance 项的总和得分由6个指标的性能按一定比例综合计算得到。下面是Lighthouse 性能分析的分值报告图例:

    为了方便了解各项指标的分值占比、各项指标数据与得分的关系,可使用 Lighthouse Scoring Calculator 进行查看:

    First Contentful Paint

    首次内容渲染,简称 FCP。测量在用户导航到您的页面后浏览器呈现第一段 DOM 内容所需的时间。1.8 秒内达到快速级别。

    如何提升

    确保文本在 webfont 加载期间保持可见

    确保文本在 webfont 加载期间保持可见。当网页使用自定义字体时,字体文件通常都是较大文件,需要一段时间才能加载完成,某些浏览器会在字体加载之前隐藏文本,从而导致不可见文本闪烁(FOIT)。

    避免 FOIT 最简单方法是临时显示系统字体,font-display: swap 告诉浏览器使用自定义字体的文本应立即使用系统字体显示,自定义字体准备就绪后再替换系统字体(遗憾的是 swap 会导致重排)。

    @font-face {font-family: 'Pacifico'; font-style: normal;font-weight: 400; src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2'); font-display: swap;
     } 
    
    • 1
    • 2

    消除阻塞渲染的资源

    消除阻塞渲染的资源。报告中会列出阻止当前页面首次绘制的所有 URL。通过内联关键资源、推迟非关键资源和删除任何未使用的内容来减少这些阻止渲染的 URL 的影响。

    哪些资源会阻塞渲染?

    以下情况的脚本和样式表将会阻塞渲染:

    • 中没有 deferasync 属性的 内联的方式加载,比如 webpack 打包产物中的运行时资源(runtimeChunk),其中包含了 webpack 进行模块解析、加载、模块清单等代码。runtimeChunk 每次构建都会发生变化,单独提取出来可以避免非变更 chunk 的 contenthash 变更,从而更好的利用浏览器缓存。但这些代码通常只有几 KB 甚至更小,为此增加网络资源请求十分浪费,通过类似 InlineChunkHtmlPlugin 插件内联到 html 中是更好的选择。
    • 非关键脚本移至 之前HTML 解析过程中如遇到同步的 JS 会等待 JS 下载并执行完之后才继续解析,如果将 JS 资源放在 中可能造成页面持续白屏一段时间。所以当需要兼容一些旧浏览器时将所有的JS脚本都放在 之前是最好的选择。这样可以保证非脚本的其他一切元素能够以最快的速度得到加载和解析。
    • 异步加载其他非关键脚本deferasync 都可以实现异步加载 JS 资源,async 是无序异步加载,defer 则是有序顺序来异步加载。
    
     
    
    • 1
    • 2

    async :并行下载后并直接运行,下载的过程不会阻塞 DOM 解析,但执行会,执行的时间与 DOMContentLoaded 不相关,可能领先也可能在其后。多个 async JS 无法保证按引入的顺序执行,所以当 JS 资源完全独立时可选择此方式加载。

    defer :并行下载,但下载完成后不会立即执行,它们在 DOM 解析完成之后、DOMContentLoaded 之前按照引入顺序依次执行,因此不会阻塞 DOM 解析。

    DOMContentLoaded :当初始的 HTML ****文档被完全加载和解析完成之后,DOMContentLoaded ****事件会被触发,而不必等待样式表,图片等资源完成加载。

    Load :当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。

    • 代码拆分、懒加载主流的打包工具(如 webpack、Rollup)都可以通过动态导入来拆分包。动态导入的模块不会包含在初始包中,而是会进行懒加载,或在指定的时机加载。
    import moduleA from "library";
    form.addEventListener("submit", e => {e.preventDefault();someFunction();
    });
    
    
    // 使用 import()动态导入
    form.addEventListener("submit", e => {e.preventDefault();import('library.moduleA').then(module => module.default).then(someFunction()).catch(handleError());
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    实际项目中懒加载所有第三方模块并不是很常见,通常使用 SplitChunksPlugin 等工具将第三方依赖被拆分并打包成一个单独的 vendors chunk 是更好的选择,因为它们不会经常更新,使用 chunkhash 可以保证每次构建 vendor chunk 的文件名不会变更,从而更好的进行持久化缓存。

    使用 import()React.lazy() 在路由或组件级别进行模块拆分则是一种更简单的方式。

    import * as React from "react";
    import { Routes, Route } from "react-router-dom";
    
    const About = React.lazy(() => import("./pages/About"));
    export default function App() {return (}>} />...}>}/>);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还有一些专门进行懒加载的工具库可以使用,如 Loadable components

    // Component Splitting
    import loadable from '@loadable/component';
    const Home = loadable(() => import('./Home'));
    // Library Splitting
    const Moment = loadable.lib(() => import('moment')); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    如何消除阻塞渲染的样式?
    • 削减 CSS对于浏览器来说,CSS 文件中的空格、缩进或注释等字符都是不必要的,删除这些字符能减少 CSS 资源大小。optimize-css-assets-webpack-plugingulp-clean-cssrollup-plugin-css-porter 都是常用削减 CSS 的工具。
    • 内联关键样式将重要样式进行内联后,就不再需要通过往返请求来获取关键 CSS。您内联了大量 CSS,则会延迟 HTML 文档其余部分的传输,为了最大限度地减少首次渲染的往返次数,目标是将首屏内容保持在 14 KB(压缩)以下,将这些类放在