预加载

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)

参考链接