如何提高Chrome Lighthouse Performance分数 (2)

问题

消除渲染阻塞资源.

资源阻塞了页面的第一次绘制。考虑内联交付关键的JS/CSS,并推迟所有非关键的JS/样式。

lighthouse提示:

Eliminate render-blocking resources Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles. Learn how to eliminate render-blocking resources. FCP LCP

Lighthouse 报告的"优化建议"部分会列出阻止对网页进行首次渲染的所有网址。我们的目标是通过内嵌关键资源、推迟非关键资源并移除任何未使用的资源来减少这些阻止呈现的网址的影响。

消除阻塞渲染的资源

哪些网址会被标记为阻止呈现的资源?

Lighthouse 会标记两种类型的阻塞渲染网址:脚本和样式表。

具有如下功能的 <script> 标记:

  • 在文档的 <head> 中。
  • 没有 defer 属性。
  • 没有 async 属性。

具有如下功能的 <link rel="stylesheet"> 标记:

  • 没有 disabled 属性。如果具有此属性,浏览器不会下载样式表。
  • 没有具体与用户设备匹配的 media 属性。media="all" 被视为阻塞渲染。

如何识别关键资源

为减少阻塞渲染的资源的影响,第一步是确定哪些资源至关重要,哪些资源不重要。使用 Chrome 开发者工具中的"Coverage"标签页来识别非关键 CSS 和 JS。 在加载或运行网页时,该标签页会向您显示使用的代码量,而非加载的代码量:

Chrome 开发者工具:"覆盖率"标签页:


看完Google的文章, 感觉无从下手吗?

下面进行实战:

Nodejs 中间件如何处理

在 Express 应用中实现拦截静态资源请求并删除特定的 <script> 标签(在 <head> 中、没有 deferasync 属性),我们可以使用自定义中间件来处理 HTML 文件并进行内容修改。

以下是一个示例代码,展示如何实现这一功能:

步骤一:设置 Express 应用

确保你已经安装了必要的依赖项:

sh 复制代码
npm install express

步骤二:创建 Express 应用

在你的 app.jsserver.js 文件中创建一个基本的 Express 应用,并添加处理静态资源的中间件:

js 复制代码
const express = require('express');
const path = require('path');
const fs = require('fs');

const app = express();
const port = 3000;

// 静态资源目录
const staticDir = path.join(__dirname, 'public');

// 自定义中间件拦截静态资源请求
app.use((req, res, next) => {
  const filePath = path.join(staticDir, req.path);

  // 仅处理 HTML 文件
  if (filePath.endsWith('.html')) {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) {
        return next(err);
      }

      // 删除 <head> 中没有 defer 和 async 属性的 <script> 标签
      const modifiedData = data.replace(
        /<head[^>]*>[\s\S]*?<\/head>/i,
        (headContent) => {
          return headContent.replace(
            /<script(?![^>]*\bdefer\b)(?![^>]*\basync\b)[^>]*><\/script>/gi,
            ''
          );
        }
      );

      res.send(modifiedData);
    });
  } else {
    next();
  }
});

// 静态资源处理
app.use(express.static(staticDir));

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

解释

  1. 自定义中间件

    • 使用 app.use 添加一个中间件,拦截所有请求。
    • 检查请求路径是否以 .html 结尾,仅处理 HTML 文件。
    • 使用 fs.readFile 读取文件内容,并使用正则表达式匹配 <head> 中的 <script> 标签,删除没有 deferasync 属性的 <script> 标签。
    • 返回修改后的内容。
  2. 静态资源处理

    • 使用 express.static 来处理静态资源文件。对于非 HTML 文件,直接交给 express.static 处理。

测试

在你的 public 目录中创建一个示例 HTML 文件 index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test Page</title>
  <script src="test.js"></script>
  <script src="defer-script.js" defer></script>
  <script src="async-script.js" async></script>
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>

启动你的 Express 应用:

sh 复制代码
node app.js

在浏览器中访问 http://localhost:3000/index.html,你应该会看到只有 <script> 标签没有 deferasync 属性的被删除,保留了有 deferasync 属性的 <script> 标签。

注意事项

  • 性能影响:上述方法在每次请求时都读取和修改文件内容,可能会对性能有影响。在生产环境中,可以考虑缓存修改后的文件内容。
  • 安全性:确保你只对可信任的文件类型进行内容修改,以避免潜在的安全风险。

这样,你就实现了在 Express 应用中拦截静态资源请求并删除特定的 <script> 标签的功能。


deferasync 属性的区别:

在 HTML 文档中,<script> 标签用于嵌入或引用脚本(通常是 JavaScript)。deferasync 属性控制脚本的加载和执行方式,优化页面加载性能。以下是这两个属性的详细解释:

defer 属性

  • 含义defer 属性表示脚本会被延迟执行,直到整个 HTML 文档完全解析和加载之后。
  • 加载顺序 :具有 defer 属性的脚本按它们在文档中出现的顺序加载和执行��
  • 位置 :通常放在 <head> 中。
  • 效果:脚本加载不会阻塞 HTML 文档的解析,脚本在文档解析完成后按顺序执行。

示例:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test Page</title>
  <script src="script1.js" defer></script>
  <script src="script2.js" defer></script>
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>

在这个例子中,script1.jsscript2.js 会在文档解析完毕后按顺序执行。

async 属性

  • 含义async 属性表示脚本会异步加载和执行,即加载完毕后立即执行,而不等待其他脚本。
  • 加载顺序 :具有 async 属性的脚本加载顺序是不确定的,取决于它们的加载速度。它们一旦加载完成就会立即执行。
  • 位置 :通常放在 <head> 中。
  • 效果:脚本加载不会阻塞 HTML 文档的解析,脚本加载完成后立即执行,不保证执行顺序。

示例:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Test Page</title>
  <script src="script1.js" async></script>
  <script src="script2.js" async></script>
</head>
<body>
  <h1>Hello World</h1>
</body>
</html>

在这个例子中,script1.jsscript2.js 会异步加载,一旦加载完成就会立即执行。执行顺序可能不按它们在文档中出现的顺序。

对比 deferasync

  • 加载时机
    • defer:脚本在文档解析完毕后按顺序执行。
    • async:脚本在加载完毕后立即执行,加载顺序和执行顺序不确定。
  • 使用场景
    • defer:适用于依赖于文档结构的脚本,并且需要保证执行顺序。
    • async:适用于独立的脚本,不依赖于其他脚本或文档结构,且无需保证执行顺序。

总结

  • deferasync 都用于优化脚本加载和页面性能。
  • defer 适合需要按照顺序执行且依赖文档结构的脚本。
  • async 适合独立的、不依赖其他脚本或文档结构的脚本。

理解这两个属性及其使用场景,可以帮助你更有效地管理脚本加载,提升网页性能和用户体验。

相关推荐
EnCi Zheng17 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen21 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技21 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人33 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实33 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha44 分钟前
三目运算符
linux·服务器·前端
晓晨的博客1 小时前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing1 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习