预加载
Preload 是一个声明性的获取请求,它会告诉浏览器尽快请求资源。通过在 HTML 文档头部添加带有 rel=“preload” 的 标签来预加载关键资源:
<link rel="preload" as="style" href="css/style.css" />预加载最常见的用法是用于字体文件,减少因字体加载较慢导致的文字字体闪烁变化。例如:<link rel="preload" as="font" href="/main.woff" />
应用了preload提示的资源,通常会以较高的优先级率先在网页中加载,例如下图中的nato-sans.woff2请求,Priority列的值为High,加载顺序仅次于Document本身。
具体实现代码
在微前端 中可以预加载微应用中的资源来进行优化
isSupportPrefetch() {
const link = document.createElement("link");
const relList = link?.relList;
return relList && relList.supports && relList.supports("prefetch");
}
// 预请求资源,注意只是请求资源,但是不会解析和执行
prefetchStatic(href, as) {
// prefetch 浏览器支持检测
if (!this.isSupportPrefetch()) {
return;
}
const $link = document.createElement("link");
$link.rel = "prefetch";
$link.as = as;
$link.href = href;
document.head.appendChild($link);
}
// 新增 prefetch 处理,浏览器会在空闲时自动请求相应的资源
prefetchMicroAppStatic() {
const prefetchMicroApps = this.microApps?.filter(
(microapp) => microapp.prefetch
);
prefetchMicroApps?.forEach((microApp) => {
microApp.script && this.prefetchStatic(microApp.script, "script");
microApp.style && this.prefetchStatic(microApp.style, "style");
});
}如果浏览器自身不兼容 Prefetch 的能力或者资源需要通过 Ajax 请求进行手动隔离执行时,我们也可以在浏览器空闲的时候通过 JS 进行资源的 Ajax 预请求处理。
预渲染
使用浏览器自带的 Prefetch 命中的缓存能力是浏览器自身的控制能力,手动实现 Prefetch 的缓存能力则完全可以由开发者自行决定,可以是 SessionStorage 缓存,也可以是 LocalStorage 缓存(属于黑科技),当然最常见的是当前应用执行期间的临时缓存能力。
这个依赖的就是requestIdleCallback
实现
async fetchScript() {
try {
const res = await window.fetch(this.app.script);
return await res.text();
} catch (err) {
console.error(err);
}
}
// 预渲染
rerender() {
// 当前主线程中存在多个并行执行的 requestIdleCallback 时,浏览器会根据空闲时间来决定要在当前 Frame 还是下一个 Frame 执行
requestIdleCallback(async () => {
// 预请求资源
this.scriptText = await this.fetchScript();
// 预渲染处理
this.idlePrerender();
});
}
idlePrerender() {
// 预渲染
requestIdleCallback((dealline) => {
console.log("deadline: ", dealline.timeRemaining());
// 这里只有在浏览器非常空闲时才可以进行操作
if (dealline.timeRemaining() > 40) {
// TODO: active 中还可以根据 Performance 性能面板进行再分析,如果内部的某些操作比较耗时,可能会影响下一帧的渲染,则可以放入新的 requestIdleCallback 中进行处理
// 除此之外,例如在子应用中可以先生成虚拟 DOM 树,预渲染不做 DOM 更改处理,真正切换应用的时候进行 DOM 挂载
// 也可以在挂载应用的时候放入 raF 中进行处理
this.active(true);
} else {
this.idlePrerender();
}
});
}
async active(isPrerender) {
if (!this.scriptText) {
this.scriptText = await this.fetchScript();
}
if (!this.sandbox) {
this.sandbox = new IframeSandbox({
rootElm: this.rootElm,
scriptText: this.scriptText,
url: this.app.script,
id: this.app.id,
});
}
isPrerender ? this.sandbox.prerender() : this.sandbox.active();
}温馨提示:真正在框架的设计中需要考虑微应用的运行状态,对运行状态进行防冲突处理,例如当前预渲染正在进行中,但是用户直接点击应用进行加载,需要处理两者的状态冲突问题,防止应用产生不必要的渲染。
其他预
| 类型 | 优化目标 | 示例 | 注意事项 |
|---|---|---|---|
| 预取回 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) |