quicklink源码浅析

1,737 阅读3分钟

前言

这些天估计大家都陆陆续续已经听说了 GoogleChromeLabs/quicklink 这个项目了,它由 Google 公司著名开发者 Addy Osmani 发起,实现了在空闲时间预获取页面可视区域内的链接,从而加快后续加载速度,从而来做到后续页面秒开都功能。

工作原理

Quicklink 通过以下方式加快后续页面的加载速度:

  • 检测视区中的链接(使用 Intersection Observer
  • 等待浏览器空闲(使用 requestIdleCallback
  • 检查用户是否处于慢速连接(使用 navigator.connection.effectiveType)或启用了省流模式(使用 navigator.connection.saveData)
  • 预获取视区内的 URL(使用或 XHR)。 可根据请求优先级进行控制(若支持 fetch() 可进行切换)。

触发条件

如果用户的有效连接类型数据保护程序首选项表明它有用的时候, 如果存在urls,则预取一系列URL,或者查看document的视口内链接。 如果进入窗口,就开始预加载

API

quicklink 接受带有以下参数的 option 对象(可选):

  • el:指定需要预获取的 DOM 元素视区
  • urls:预获取的静态 URL 数组(若此配置非空,则不会检测视区中 document 或 DOM 元素的链接)
  • timeout:为 requestIdleCallback 设置的超时整数。 浏览器必须在此之前进行预获取(以毫秒为单位), 默认取 2 秒。
  • timeoutFn:指定超时处理函数。 默认为 requestIdleCallback。 也可以替换为 networkIdleCallback 等自定义函数(github.com/pastelsky/n… demo)
  • priority:布尔值,指定 fetch 的优先级。 默认为 false。 若配置为 true 将会尝试使用 fetch() API(而非 rel = prefetch)
  • origins:允许预取的URL主机名字符串的数组。默认为相同的域源,可防止任何跨源请求。
  • ignores:在origin检查后运行的自定义过滤器,默认没有

源码解读

  1. 合并参数,并设置常量
  options = Object.assign({
    timeout: 2e3,
    priority: false,
    timeoutFn: requestIdleCallback,
    el: document,
  }, options);

  observer.priority = options.priority;

  const allowed = options.origins || [location.hostname];
  const ignores = options.ignores || [];
  1. 设置requestIdleCallback的callback和浏览器调用callback的最后期限

    这里回调函数提供了两种策略:

    • 如果参数中有urls,则只将urls所有的链接进行预加载,不会对dom下的其他链接进行预加载
    • 如果参数中没有urls,根据options的el来遍历其下的所有a标签,通过Intersection Observer来监控

    如果符合options.origins规则,且不符合options.ignores规则,将其放入预加载的列表toPrefetch中, 可以通过不传options.origins来匹配所有

const toPrefetch = new Set();
options.timeoutFn(() => {
    if (options.urls) {
      options.urls.forEach(prefetcher);
    } else {
      Array.from(options.el.querySelectorAll('a'), link => {
        // 把每一个a标签放入观察对象中,observer后面解释
        observer.observe(link);
        if (!allowed.length || allowed.includes(link.hostname)) {
          isIgnored(link, ignores) || toPrefetch.add(link.href);
        }
      });
    }
  }, {timeout: options.timeout})

  1. 预加载

    那么预加载会做些什么呢?

    首先它会从toPrefetch删除这个即将请求的url

    然后通过preFetched判断是否已经加载过了,来减少不必要的请求。

    然后它会判断当前是否为2g或者省流量模式,如果是,则不做任何操作。

    接着会判断请求类型,默认是rel = prefetch,为true的时候,将会用fetch去请求数据,并对fetch做兼容。

    最后,更新preFetched这个对象

// index.mjs
function prefetcher(url) {
  toPrefetch.delete(url);
  prefetch(new URL(url, location.href).toString(), observer.priority);
}

// prefetch.mjs
const preFetched = {};

function prefetch(url, isPriority, conn) {
  if (preFetched[url]) {
    return;
  }
  if (conn = navigator.connection) {
    if ((conn.effectiveType || '').includes('2g') || conn.saveData) return;
  }
  return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => {
    preFetched[url] = true;
  });
};
  1. Intersection Observer

    通过新建一个观察者,来观察放入观察的a标签,当a标签进入窗口的时候,则开始预加载这个链接


const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const url = entry.target.href;
      if (toPrefetch.has(url)) prefetcher(url);
    }
  });
});

扩展阅读

参考

最后

推荐一下自己的个人公众号:前端精读(每日定时推送一篇前端好文)

前端每日精读