问题
消除渲染阻塞资源.
资源阻塞了页面的第一次绘制。考虑内联交付关键的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>
中、没有 defer
和 async
属性),我们可以使用自定义中间件来处理 HTML 文件并进行内容修改。
以下是一个示例代码,展示如何实现这一功能:
步骤一:设置 Express 应用
确保你已经安装了必要的依赖项:
sh
npm install express
步骤二:创建 Express 应用
在你的 app.js
或 server.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}`);
});
解释
-
自定义中间件:
- 使用
app.use
添加一个中间件,拦截所有请求。 - 检查请求路径是否以
.html
结尾,仅处理 HTML 文件。 - 使用
fs.readFile
读取文件内容,并使用正则表达式匹配<head>
中的<script>
标签,删除没有defer
和async
属性的<script>
标签。 - 返回修改后的内容。
- 使用
-
静态资源处理:
- 使用
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>
标签没有 defer
和 async
属性的被删除,保留了有 defer
和 async
属性的 <script>
标签。
注意事项
- 性能影响:上述方法在每次请求时都读取和修改文件内容,可能会对性能有影响。在生产环境中,可以考虑缓存修改后的文件内容。
- 安全性:确保你只对可信任的文件类型进行内容修改,以避免潜在的安全风险。
这样,你就实现了在 Express 应用中拦截静态资源请求并删除特定的 <script>
标签的功能。
defer
和 async
属性的区别:
在 HTML 文档中,<script>
标签用于嵌入或引用脚本(通常是 JavaScript)。defer
和 async
属性控制脚本的加载和执行方式,优化页面加载性能。以下是这两个属性的详细解释:
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.js
和 script2.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.js
和 script2.js
会异步加载,一旦加载完成就会立即执行。执行顺序可能不按它们在文档中出现的顺序。
对比 defer
和 async
- 加载时机 :
defer
:脚本在文档解析完毕后按顺序执行。async
:脚本在加载完毕后立即执行,加载顺序和执行顺序不确定。
- 使用场景 :
defer
:适用于依赖于文档结构的脚本,并且需要保证执行顺序。async
:适用于独立的脚本,不依赖于其他脚本或文档结构,且无需保证执行顺序。
总结
defer
和async
都用于优化脚本加载和页面性能。defer
适合需要按照顺序执行且依赖文档结构的脚本。async
适合独立的、不依赖其他脚本或文档结构的脚本。
理解这两个属性及其使用场景,可以帮助你更有效地管理脚本加载,提升网页性能和用户体验。