对运行在浏览器中的前端应用来说,JS、CSS等各类静态资源加载的耗时,直接决定了页面的渲染速度,进而深刻影响着用户体验。所以这本书介绍的第一类优化方案,就是以优化静态资源加载耗时为目标的——资源优先级提示。
在介绍开始前,我们先看一下优化的预期效果:
通过资源优先级提示,仅需2行代码,就能实现上图中的优化效果,让JS文件的加载耗时从1.4秒减少到0.4秒,大幅减少951ms(-67%) ,代码实现非常简单方便,让我们一起学起来吧~
资源优先级提示是浏览器平台为控制资源加载时机而设计的一系列API,主要包括:
- 预取回 Prefetch
- 预加载 Preload
- 预连接 Preconnect
- DNS预取回 DNS-Prefetch
共4类,下面我们一起来详细了解其功能和细节。
1. 四类资源优先级提示
1. 预取回 Prefetch
预取回提示用于提示浏览器在CPU和网络带宽空闲时,预先下载指定URL的JS,图片等各类资源,存储到浏览器本地缓存中,从而减少该资源文件后续加载的耗时,从而优化用户体验。
具体使用方式是将link
标签的rel
属性设为prefetch
,并将href
属性设为目标资源URL,例如 <link rel="prefetch" href="https://github.com/JuniorTour/juniortour.js" />
。
该标签插入DOM后,将触发一次href
属性值对应URL的HTTP请求,并将响应保存到本地的prefetch cache
中,但是并不会进一步解析、运行该资源。
可以预取回的资源有很多:JS、CSS、各种格式的图片、音频、WASM文件、字体文件、甚至HTML文档本身都可实施 prefetch,预先缓存。
命中预取回缓存的请求,在开发者工具中的Network
标签中的Size
列,会有独特的(prefetch cache)
标记:
crossorigin
属性简介
crossorigin
属性是浏览器同源策略的相关API,用于对link
、script
和img
等元素指定以何种跨域资源共享模式
加载目标资源。
默认情况下,JS脚本、图片等部分静态资源不受同源策略的限制,可以从任何跨域域名加载第三方JS文件、图片文件。
这样的规则有明显的安全风险,例如:
- 第三方JS文件可以访问第一方网站的错误上下文,从而获取内部信息。
- 第三方资源的源服务器可以在HTTP请求过程中通过SSL握手验证、
cookies
等手段获取用户身份信息。
为了缓解这些安全风险,浏览器引入了可用于link
、script
和img
元素的crossorigin
属性,对于这些元素加载的资源指定3类跨域资源共享模式
,分别是:
- 没有
crossorigin
属性:无法获取 JS 的错误上下文,也不会在SSL握手阶段附带Cookies等用户身份相关的信息。 - 将
crossorigin
值设置为"anonymous"
:可以访问JavaScript的错误上下文,但在请求过程中的SSL握手阶段不会携带cookies或其他用户凭据。 - 将
crossorigin
值设置为"use-credentials"
:既可以访问JavaScript的错误上下文,也可以在请求过程中的SSL握手阶段携带Cookies或用户凭据。
此外,Chrome浏览器的HTTP缓存以及相应的Prefetch、Preconnect资源优先级提示效果也会受到crossorigin
属性的影响。
对于跨域资源,则其资源优先级提示也需要设置为跨域,即crossorigin="anonymous"
,例如:<link rel="prefetch" href="https://github.com/JuniorTour/juniortour.js" crossorigin="anonymous" />
资源是否跨域,可以依据浏览器自动附带的Sec-Fetch-Mode
请求头判断:
- 值为
no-cors
,表示当前资源加载的模式并非跨域资源共享模式。其对应的资源优先级提示不需要设置为跨域crossorigin="anonymous"
,就能命中Prefetch等专用缓存。 - 值为
cors
,表示当前资源加载的模式是跨域资源共享模式。其对应的资源优先级提示需要设置为跨域crossorigin="anonymous"
或"use-credentials"
,才能命中Prefetch等专用缓存。
2. 预加载 Preload
与预取回不同,预加载用于提高当前页面中资源加载的优先级,确保关键资源优先加载完成。
预加载最常见的用法是用于字体文件,减少因字体加载较慢导致的文字字体闪烁变化。例如:<link rel="preload" as="font" href="/main.woff" />
应用了preload
提示的资源,通常会以较高的优先级率先在网页中加载,例如下图中的nato-sans.woff2
请求,Priority
列的值为High
,加载顺序仅次于Document
本身,能让字体较早在页面中渲染生效。
as
属性是必填属性,是link
标签带有rel="preload"
属性时,确定不同类型资源优先级的依据。完整可选值请参考MDN:link attribute as
Preload 在线 DEMO:output.jsbin.com/cuxerej
3. 预连接 Preconnect
预连接提示用于提前与目标域名握手,完成DNS寻址,并建立TCP和TLS链接。
具体使用方式是将link
标签的rel
属性设为preconnect
,并将href
属性设为目标域名,例如 <link rel="preconnect" href="https://github.com" />
。
优化效果是通过提前完成DNS寻址、建立TCP链接和完成TLS握手,从而减少后续访问目标域名时的连接耗时,改善用户体验。
注意! 强烈建议只对重要域名进行Preconnect
优化,数量不要超过 6 个。
因为Preconnect
生效后,会与目标域名的保持至少10秒钟的网络连接,占用设备的网络和内存资源,甚至阻碍其他资源的加载。
4. DNS预取回 DNS-Prefetch
与上文的预取回Prefetch不同,DNS预取回用于对目标域名提前进行DNS寻址,取回并缓存域名对应的IP地址,而非像预取回Prefetch那样缓存文件资源。
具体使用方式是将link
标签的rel
属性设为dns-prefetch
,并将href
属性值设为目标域名,例如 <link rel="dns-prefetch" href="https://github.com" />
。
优化效果是通过提前解析出目标域名的IP地址,从而减少后续从目标域名加载资源的耗时,加快页面加载速度,改善用户体验。
通常来说,解析DNS的耗时往往有几十甚至几百毫秒,对资源加载耗时有直接影响。
DNS预取回的能力与预连接Preconnect有所重合,这是因为以往
dns-prefetch的浏览器兼容性
略好于preconnect
,往往两者一同使用。 但近年来,IE被废弃,用户大都已使用更新版本的现代浏览器,兼容性不再重要,单独使用preconnect
即可替代dns-prefetch
。
例如,我们的静态资源部署在域名为static.zhihu.com
的CDN上,那么添加如下2行HTML代码:
<link rel="preconnect" href="static.zhihu.com" />
<link rel="dns-prefetch" href="static.zhihu.com" />
就能观察到CDN上的JS、CSS等资源加载耗时显著减少,因为preconnect的生效使得资源加载时的DNS寻址、SSL握手等阶段得以提前进行,各个资源加载是其耗时就大幅减少,产生了显著的优化效果。
具体的优化效果可以参考这份数据:
优化前(无Preconnect && DNS-Prefetch) | 优化后(有Preconnect && DNS-Prefetch) | 差异 | |
---|---|---|---|
加载耗时 | 1400 ms | 451 ms | -949 ms (-67%) |
开发者工具计时截图 | 在DevTool - Network - 资源 - 计时(Timing)面板中可以看到,因为Preconnect && DNS-Prefetch优化生效,连接开始阶段的耗时从950ms降低到了1ms,使得资源的整体加载耗时大幅减少。 | ||
在线DEMO | jsbin.com/panorej/edi… | jsbin.com/haragis/edi… |
5. 四类资源优先级提示对比
类型 | 优化目标 | 示例 | 注意事项 |
---|---|---|---|
预取回 Prefetch | - 加载优先级较低的资源 - 后续页面浏览需要加载的资源 | <link rel="prefetch" href="/juniortour.js" /> | 1. Prefetch 预取回的资源并不会被立刻解析、运行:例如预取回JS文件时,JS文件内的代码逻辑并不会执行,只是文件保存到了浏览器缓存中。 这也是Prefetch与普通 link 标签( <link href="/static/main.3da2f.css" rel="stylesheet"> )的核心区别。 2. Prefetch 的触发时机不固定,会由浏览器相机决定,浏览器通常会在网络带宽、CPU运算都空闲时触发下载。 |
预加载 Preload | - 当前页面需要优先加载的静态资源 | <link rel="preload" as="font" href="/main.woff" /> | - 优化目标为当前页面所需资源,而非后续加载。 |
预连接 Preconnect | - 加载优先级较低的域名 - 后续页面浏览需要连接的域名 | <link rel="preconnect" href="<https://juniortour.net>" /> | - 用于跨域域名,同源域名不需要 - 控制只对关键域名应用,避免数量超过6个 |
DNS预取回 DNS-Prefetch | - 后续页面浏览需要连接的域名 | <link rel="dns-prefetch" href="<https://juniortour.net>" /> | (同预连接 Preconnect) |
注:在2022年初,Chrome 102 新增了
fetch-priority
属性,可用来更精细地控制资源加载的优先级,目前仍处于实验阶段,未来可能会更加完善,示例如下:
<img src="important.jpg" fetchpriority="high"> <img src="small-avatar.jpg" fetchpriority="low"> <script src="low-priority.js" fetchpriority="low"></script> // 只对 preload link 标签生效 <link href="main.css" rel="preload" as="image" fetchpriority="high">
2. 示例:快速增加资源优先级提示
笔者基于多年实践经验,制作了一套方便实用的资源优先级提示生成工具,目前已发布为开源NPM包:resource-hint-generator,支持根据构建产物,自动生成资源优先级提示代码。
下面我们以本书配套的示例项目:fe-optimization-demo 为例,演示如何接入该库,为我们的前端项目方便快捷地增加各类资源优先级提示。
《接入resource-hint-generator》完整示例代码 Merge Request 请参考:github.com/JuniorTour/…
1. 安装及配置resource-hint-generator
首先,我们需要安装NPM包,并添加运行命令及参数:
npm install resource-hint-generator --save-dev
并在项目根目录,新建配置文件resource-hint-generator-config.js
:
// resource-hint-generator-config.js
module.exports = {
distPath: `./dist`,
projectRootPath: __dirname,
resourceHintFileName: `resource-hint.js`,
includeFileTestFunc: (fileName) => {
return /(main.*js)|(main.*css)/g.test(fileName);
},
crossOriginValue: '',
publicPath: 'https://github.com/JuniorTour',
preconnectDomains: ['https://preconnect-example.com'],
};
主要参数说明:
distPath
:打包产物路径,也是生成的resource-hint.js输出路径;includeFileTestFunc
:指定一个函数,返回布尔值,表示遍历distPath
所找到的fileName
,是否会被作为prefetch的目标,即<link rel="prefetch">
的href
属性值;publicPath
:部署目标环境的CDN域名,用于和includeFileTestFunc
、includeFileNames
匹配到的文件名,拼接出<link rel="prefetch">
标签的完整href
属性值;preconnectDomains
:指定一个数组,数组中的每个字符串元素,都将产生2个href
属性值为当前字符串的<link rel="preconnect">
标签和<link rel="dns-prefetch">
标签;
2. 运行生成工具
我们的目标是在项目打包编译完成后,遍历产物文件,生成对应的资源优先级提示。因此我们需要在项目构建完成后,运行resource-hint-generator
。
例如,我们的前端项目通过调用 npm run build
运行打包构建,那只需要在这条命令中追加运行resource-hint-generator
的逻辑即可实现我们的目标。
具体做法是,在package.json
的scripts
中添加generate-resource-hint
命令,运行resource-hint-generator
,并将&& npm run generate-resource-hint
补充到原来的build
命令中,代码示例请参考:
// package.json
"scripts": {
"generate-resource-hint": "resource-hint-generator",
"build": "cross-env NODE_ENV=production webpack && npm run generate-resource-hint",
}
测试运行构建后,如果在打包产物文件夹(./dist
)中找到了生成的resource-hint.js
文件,并且其中包含我们配置的 prefetch
,preconnect
目标数据,就说明配置完成了。
resource-hint.js
内容示例
注:生产环境中下列代码会被压缩成1行,并去除空格。
/*
Add resource hint link element, eg: <link rel="prefetch" href="${targetPath}">
*/
try {
(function (prefetchFilesPath, preconnectDomains) {
const crossOriginAttrVal = '';
const CDN_HOST = 'https://github.com/JuniorTour';
if (!prefetchFilesPath || !prefetchFilesPath.length) {
return;
}
function addLinkTag(src, { rel, crossoriginVal }) {
const tag = document.createElement('link');
tag.rel = rel;
tag.href = src;
if (crossoriginVal !== undefined) {
tag.setAttribute('crossorigin', crossoriginVal);
}
const head = document.querySelector('head');
if (head && head.appendChild) {
head.appendChild(tag);
}
}
function addPrefetchDNSTag(src) {
addLinkTag(src, {
rel: 'dns-prefetch',
crossoriginVal: crossOriginAttrVal,
});
}
function addPreconnectTag(src) {
addLinkTag(src, {
rel: 'preconnect',
crossoriginVal: crossOriginAttrVal,
});
}
function addPrefetchTag(src) {
addLinkTag(src, { rel: 'prefetch', crossoriginVal: crossOriginAttrVal });
}
if (prefetchFilesPath && prefetchFilesPath.forEach) {
prefetchFilesPath.forEach((path) => {
if (path) {
addPrefetchTag(CDN_HOST + path);
}
});
}
if (preconnectDomains && preconnectDomains.forEach) {
preconnectDomains.forEach((domain) => {
if (domain) {
addPreconnectTag(domain);
addPrefetchDNSTag(domain);
}
});
}
})([
'/main.68eda6edda2178b7abfc.css',
'/main.a45a5ff47f82fd159b80.js',
],
[
'https://preconnect-example.com',
]
);
} catch (err) {
console.log(`[resource-hint.js] ERROR: err=${err}`);
}
上述代码就是我们运行resource-hint-generator
后生成的内容,自动包含了我们项目构建生成的main.${hash}.css, main.${hash}.js
和期望建立预连接的域名https://preconnect-example.com
。
3. 加载生成的resource-hint.js
推荐在登录页,活动页,官网首页等前端项目外页面提前加载运行resource-hint.js
,从而在项目正式加载渲染时,充分利用这些提前下载的Prefetch缓存和提前建立的Preconnect HTTP连接。
《接入resource-hint-generator》完整示例 请参考 Merge Request:github.com/JuniorTour/…
3. 验证,量化与评估
1. 上线前验证
优化上线前,在本地开发环境或设法直接到生产环境验证优化效果必不可少。
各类资源优先级提示是否生效,可以通过开发者工具中的网络 Network 面板判断。 我们主要使用优先级列(Priority),体积列(Size) 和 加载时间序标签页(Timing) 的内容判断。
类型 | 验证测试方式 | 生效依据 |
---|---|---|
预取回 Prefetch | 在页面中添加prefetch目标资源的link标签,检查Network面板。 例如,在生产环境中的页面上,打开浏览器控制台直接运行下列代码验证测试: function addLinkTag(src, { rel, crossoriginVal }) { const tag = document.createElement('link'); tag.rel = rel; tag.href = src; // enable crossorigin so that prefetch cache works for // Cross Origin Isolation status. if (crossoriginVal !== undefined) { tag.setAttribute('crossorigin', crossoriginVal); } const head = document.querySelector('head'); if (head && head.appendChild) { head.appendChild(tag); } } addLinkTag('<https://your-domain.com/static/main.h1712oidw.js>', { rel: 'preferch', }); | 优化目标资源加载后在Network面板中的Size列,值为独特的(prefetch cache)标记,且加载耗时极短,一般小于50毫秒: 不生效解决方案: - 检查请求资源是否为跨域资源,添加对应的crossorigin属性值 - 确认prefetch请求和优化目标资源请求的先后顺序,确保prefetch请求率先完成。 |
预加载 Preload | preload往往需要在较早的时机插入DOM,从而确保资源优先加载,如果在浏览器控制台执行脚本时机太晚,也可以考虑用Charles等代理工具拦截、替换响应体,插入preload标签 | 优化目标请求的Priority列的值为High,且加载时间较早,仅次于Document本身,例如: |
预连接 Preconnect 和DNS预取回 DNS-Prefetch | 也可通过浏览器控制台直接运行下列代码验证测试: addLinkTag('<https://your-domain.com>', { rel: 'preconnect', }); addLinkTag('<https://your-domain.com>', { rel: 'dns-prefetch', }); | 在浏览器开发者工具中的 Network - 资源 - 计时(Timing)面板中可以看到,如果Preconnect && DNS-Prefetch优化生效,目标优化资源连接开始阶段的耗时会大幅减少到接近1ms,例如: |
2. 建立量化监控指标
基于前文介绍的优化效果,我们可以通过对比2类监控指标在优化前后的变化来评估优化效果。
1. FCP
和 LCP
JS、CSS等各类静态资源更快的加载,更多的命中本地缓存,可以显著减少页面渲染耗时,预期也能改善我们在第1节介绍的web-vitals
首次内容绘制指标FCP
和最大内容绘制LCP
2项用户体验指标。
2. 缓存命中率指标
收集优化前后生产环境中用户资源请求是否命中缓存数据,也可以更直接地判断优化效果。
我们可以基于Performance API
的entry.duration
属性来实现缓存命中率指标,请看示例代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>上报缓存命中率指标示例</title>
<link
rel="prefetch"
href="https://static.zhihu.com/heifetz/6116.216a26f4.7e059bd26c25b9e701c1.css"
/>
</head>
<body>
<script>
// 上报 counter 计数类型 数据到 Grafana
async function report(name, labels, help = 'default help') {
await fetch('http://localhost:4001/counter-metric', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name,
help,
labels,
}),
});
}
// 检查资源加载是否命中缓存
function checkResourceCacheHit() {
// 获取页面加载性能信息
const perfEntries = performance.getEntriesByType('resource');
for (const entry of perfEntries) {
// 选项1:
// 使用 transferSize <=0 判断是否命中缓存,但是跨域域名需要配置响应头Timing-Allow-Origin
// let hitCache = entry.transferSize <= 0;
// 选项2:
// 判断资源的加载时间 duration是否小于50毫秒
// 50ms 来自于经验总结,可以根据实际情况调整
let hitCache = entry.duration < 50;
report(
'cacheHiteRate',
{
hitCache,
name: entry.name
},
'缓存命中率计数指标'
);
}
}
setTimeout(() => {
checkResourceCacheHit();
}, 3000);
</script>
</body>
</html>
注:如果担心用
entry.duration
小于50毫秒,判断不够准确,也可以考虑使用entry.transferSize
(传输体积,单位:字节 byte)小于0
来作为判断依据。但是,对于跨域域名,获取
transferSize
需要配置专门的响应头Timing-Allow-Origin: * ,有一定额外成本。
在上述示例中,我们将收集到的缓存命中率计数数据,通过第2节介绍的Node.js 数据收集应用
的HTTP接口/counter-metric
,上报到了Grafana,再加以格式化,我们就可以做出如下图所示的缓存命中率可视化图表:
点击展开:缓存命中率可视化图表 Grafana 配置JSON
{
"datasource": {
"uid": "grafanacloud-prom",
"type": "prometheus"
},
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineInterpolation": "linear",
"barAlignment": 0,
"lineWidth": 1,
"fillOpacity": 76,
"gradientMode": "hue",
"spanNulls": true,
"insertNulls": false,
"showPoints": "auto",
"pointSize": 5,
"stacking": {
"mode": "normal",
"group": "A"
},
"axisPlacement": "auto",
"axisLabel": "",
"axisColorMode": "text",
"axisBorderShow": false,
"scaleDistribution": {
"type": "linear"
},
"axisCenteredZero": false,
"hideFrom": {
"tooltip": false,
"viz": false,
"legend": false
},
"thresholdsStyle": {
"mode": "off"
}
},
"color": {
"mode": "palette-classic"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"min": 0,
"unit": "percentunit"
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "未命中"
},
"properties": [
{
"id": "color",
"value": {
"fixedColor": "dark-red",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "命中缓存 {__name__="cacheHiteRate", hitCache="true", instance="localhost:4001", job="integrations/nodejs"}"
},
"properties": [
{
"id": "displayName",
"value": "命中缓存"
}
]
},
{
"matcher": {
"id": "byName",
"options": "未命中 {__name__="cacheHiteRate", hitCache="false", instance="localhost:4001", job="integrations/nodejs"}"
},
"properties": [
{
"id": "displayName",
"value": "未命中缓存"
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 1
},
"id": 27,
"interval": "1m",
"options": {
"tooltip": {
"mode": "multi",
"sort": "none"
},
"legend": {
"showLegend": true,
"displayMode": "table",
"placement": "right",
"calcs": [
"min",
"max"
]
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "grafanacloud-prom"
},
"refId": "A",
"expr": "cacheHiteRate{hitCache="true"}",
"range": true,
"instant": false,
"editorMode": "builder",
"legendFormat": "__auto",
"useBackend": false,
"disableTextWrap": false,
"fullMetaSearch": false,
"includeNullMetadata": true,
"hide": true
},
{
"datasource": {
"type": "prometheus",
"uid": "grafanacloud-prom"
},
"refId": "B",
"expr": "cacheHiteRate{hitCache="false"}",
"range": true,
"instant": false,
"editorMode": "builder",
"legendFormat": "__auto",
"useBackend": false,
"disableTextWrap": false,
"fullMetaSearch": false,
"includeNullMetadata": true,
"hide": true
},
{
"refId": "C",
"datasource": {
"type": "__expr__",
"uid": "__expr__",
"name": "Expression"
},
"type": "math",
"hide": true,
"expression": "$A + $B"
},
{
"refId": "命中缓存",
"type": "math",
"hide": false,
"expression": "$A / $C"
},
{
"refId": "未命中",
"datasource": {
"type": "__expr__",
"uid": "__expr__",
"name": "Expression"
},
"type": "math",
"hide": false,
"expression": "$B / $C"
}
],
"title": "缓存命中率",
"type": "timeseries"
}
3. 评估优化效果
为了评估优化效果,首先,我们要记录优化前的状态,在优化上线前提前上线监控指标,并收集一段时间的指标数据。
同时建议在优化上线前持续观察7至15天,从而避免来自生产环境用户的指标数据受到工作日和节假日影响所产生的异常波动。
注:Grafana Cloud 的免费用户自带 14 天数据保存时长,超过这一时长的数据会被删除。
其次,建议在优化上线1至3个月后回归优化效果,确保优化稳定。
如果资源优先级提示这一优化生效,我们应该能观察到 首次内容绘制FCP
和最大内容绘制LCP
指标有明显的改善,例如下图:
观察FCP的评分百分比可视化图,在4月30日优化上线后,评分为优的用户占比从优化前的约50%,大幅提升到了优化后的90%。
再观察一段时间这一指标,如果评分优的占比都能稳定在90%,那我们就有理由判定资源优先级优化显著地加快了页面渲染,改善了用户体验!
同样的,我们也可以观察缓存命中率指标来判断优化效果,例如下图:
观察上述缓存命中率可视化图,在4月30日优化上线后,缓存命中率从优化前的约40%,大幅提升到了约70%,同样可以佐证我们的优化产生了显著的正向收益。
此外,上述指标的变化,也应该能从页面的加载耗时等直观表现上观察到显著的变化。我们也可以通过录制优化前后前端应用加载的视频,对比评估优化效果。
小结
这一节中,我们首先介绍了四类资源优先级提示的功能和细节:
- 预取回 Prefetch
- 预加载 Preload
- 预连接 Preconnect
- DNS预取回 DNS-Prefetch
其次,用resource-hint-generator作为示例,演示了快速生成资源优先级提示的代码逻辑。
最后,我们学习了使用Grafana可视化图表,验证和评估资源优先级优化效果具体方式。
《前端工程体验优化》全书现已发布到掘金小册,欢迎各位朋友交流学习
简介:不止性能优化,6000行源码+14类前端优化方案,打造前端体验优化理念,超越传统前端性能优化
- 你做的前端优化都错了-数据驱动、指标先行
- 前端优化数据量化必备神器-用户体验数据收集与可视化
- 光速入门Performance API
- 2行代码让JS加载耗时减少67%-资源优先级提示
- CDN最佳实践:让CDN流量节省10%
- CDN最佳实践:验证,量化与评估
- 超简单的代码模块懒加载:让JS加载体积减少13%
- 超简单的代码模块懒加载:懒加载常见问题解决方案
- 代码分割最佳实践:细粒度代码分割(Granular Code Split)
- 代码分割最佳实践:应用改造示例
- 前端渲染进化史:用SSR让首次内容绘制耗时(FCP)降低72%
- 前端渲染进化史:SSR进阶优化
- 图片加载体积减少20%-自适应选择最优图片格式
- GIF体积减少80%-GIF图片优化
- 万物皆可懒加载-3类通用资源懒加载实现方案
- 万物皆可懒加载-通用资源懒加载工具库
- 打包耗时减少43%-现代构建工具的魔力
- 重构CSS-用CSS In JS解决CSS的诸多痛点
- 打造技术改进文化-征求意见稿制度RFC
- 一错不再错-用自动化测试避免功能衰退