quicklink解析


概要
quicklinkはjsライブラリで、ビューポートに表示されるWebリンクをプリロードし、ユーザー体験を向上させることができます.ロードプロセスは次のとおりです.1.Webページのリンクがビューポートに表示されるかどうかを検出し、リンクがビューポートに表示されるのを待って、手順2を実行します.2.ブラウザの空きを待って3を実行します.3.現在のネットワーク接続が2 Gであるか否かを判断し、そうであれば実行を停止し、2 Gネットワークでなければステップ4を実行する.4.プリロードリンクはリソースを指します.
使用方法
リファレンスリンクhttps://github.com/GoogleChro...
quicklinkソースコード解析
quicklinkの入口関数は入力された配置パラメータを受け取り、Objectを通過する.assign関数とデフォルトの構成オプションをマージします.次にtimeoutFn非同期方法を実行し、この方法はコールバック関数を受信し、コールバックの主な論理は以下の通りである:入力optionsパラメータにurls属性があれば、直接プレロードを実行し、そうでなければdocumentを通過する.QuerySelectorAllメソッドは、すべてのaラベル要素のNodeListを取得し、要素ノードのリストを便利にし、要素ノードを監視します.
observer.observe(link);

次に、a要素オブジェクトのhref属性値が属するドメイン名にアクセスが許可されているかどうかを判断し、アクセスが許可されている場合は、リンクが無視されるべきかどうかを判断し続け、判断ロジックは以下の通りである.
if (!allowed.length || allowed.includes(link.hostname)) {
   // If there are any filters, the link must not match any of them
   isIgnored(link, ignores) || toPrefetch.add(link.href);
}

リンクが無視されていない場合は、ノードのhrefプロパティ値をtoPrefetchに追加します.
const toPrefetch = new Set();
toPrefetch.add(link.href);

全体的なコードロジックは以下の通りです.
export default function (options) {
  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 || [];

  options.timeoutFn(() => {
    // If URLs are given, prefetch them.
    if (options.urls) {
      options.urls.forEach(prefetcher);
    } else {
      // If not, find all links and use IntersectionObserver.
      Array.from(options.el.querySelectorAll('a'), link => {
        observer.observe(link);
        // If the anchor matches a permitted origin
        // ~> A `[]` or `true` means everything is allowed
        if (!allowed.length || allowed.includes(link.hostname)) {
          // If there are any filters, the link must not match any of them
          isIgnored(link, ignores) || toPrefetch.add(link.href);
        }
      });
    }
  }, {timeout: options.timeout});
}

ビューポートにlinkが表示されることを検出
上はobserverを通ります.observe(link)はノード要素を監視し、observerはIntersectionObserverオブジェクトのインスタンスであり、リスニングされたノードオブジェクトがビューポートに表示されるとnew操作時に入力されるコールバック関数が実行され、ビューポートに表示されるノードオブジェクトが配列によってコールバックされる.その後、コールバックで入力された配列が便利になり、配列内の要素がtoPrefetchオブジェクトに含まれている場合、要素の監視が解除され、aラベル要素に対応するリソースがプリロードされます.
const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const link = entry.target;
      if (toPrefetch.has(link.href)) {
        observer.unobserve(link);
        prefetcher(link.href);
      }
    }
  });
});

非同期関数の実装
ブラウザでrequestIdleCallbackがサポートされている場合はオリジナルの関数、サポートされていない場合はsettimeout関数をployfillとして使用します.
const requestIdleCallback = requestIdleCallback ||
  function (cb) {
    const start = Date.now();
    return setTimeout(function () {
      cb({
        didTimeout: false,
        timeRemaining: function () {
          return Math.max(0, 50 - (Date.now() - start));
        },
      });
    }, 1);
  };

export default requestIdleCallback;

リソース要求関数の実装
プリロードポリシーは主に3つあります
1. prefetch
function linkPrefetchStrategy(url) {
  return new Promise((resolve, reject) => {
    const link = document.createElement(`link`);
    link.rel = `prefetch`;
    link.href = url;

    link.onload = resolve;
    link.onerror = reject;

    document.head.appendChild(link);
  });
};

2.ajaxロード
function xhrPrefetchStrategy(url) {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();

    req.open(`GET`, url, req.withCredentials=true);

    req.onload = () => {
      (req.status === 200) ? resolve() : reject();
    };

    req.send();
  });
}

3.Fetch要求ロード
function highPriFetchStrategy(url) {
  // TODO: Investigate using preload for high-priority
  // fetches. May have to sniff file-extension to provide
  // valid 'as' values. In the future, we may be able to
  // use Priority Hints here.
  //
  // As of 2018, fetch() is high-priority in Chrome
  // and medium-priority in Safari.
  return self.fetch == null
    ? xhrPrefetchStrategy(url)
    : fetch(url, {credentials: `include`});
}

ネットワークタイプ判定
if (conn = navigator.connection) {
    // Don't prefetch if the user is on 2G. or if Save-Data is enabled..
    if ((conn.effectiveType || '').includes('2g') || conn.saveData) return;
  }

上記の3つのプリロード方法を関数にカプセル化し、外部に露出して使用します.
const supportedPrefetchStrategy = support('prefetch')
  ? linkPrefetchStrategy
  : xhrPrefetchStrategy;

function prefetcher(url, isPriority, conn) {
  if (preFetched[url]) {
    return;
  }

  if (conn = navigator.connection) {
    // Don't prefetch if the user is on 2G. or if Save-Data is enabled..
    if ((conn.effectiveType || '').includes('2g') || conn.saveData) return;
  }

  // Wanna do something on catch()?
  return (isPriority ? highPriFetchStrategy : supportedPrefetchStrategy)(url).then(() => {
    preFetched[url] = true;
  });
};

export default prefetcher;