preFetch.js 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. // Customized pre-fetch for page chunks based on
  2. // https://github.com/GoogleChromeLabs/quicklink
  3. import { useRoute } from '../router';
  4. import { onMounted, onUnmounted, watch } from 'vue';
  5. import { inBrowser, pathToFile } from '../utils';
  6. const hasFetched = new Set();
  7. const createLink = () => document.createElement('link');
  8. const viaDOM = (url) => {
  9. const link = createLink();
  10. link.rel = `prefetch`;
  11. link.href = url;
  12. document.head.appendChild(link);
  13. };
  14. const viaXHR = (url) => {
  15. const req = new XMLHttpRequest();
  16. req.open('GET', url, (req.withCredentials = true));
  17. req.send();
  18. };
  19. let link;
  20. const doFetch = inBrowser &&
  21. (link = createLink()) &&
  22. link.relList &&
  23. link.relList.supports &&
  24. link.relList.supports('prefetch')
  25. ? viaDOM
  26. : viaXHR;
  27. export function usePrefetch() {
  28. if (!inBrowser) {
  29. return;
  30. }
  31. if (!window.IntersectionObserver) {
  32. return;
  33. }
  34. let conn;
  35. if ((conn = navigator.connection) &&
  36. (conn.saveData || /2g/.test(conn.effectiveType))) {
  37. // Don't prefetch if using 2G or if Save-Data is enabled.
  38. return;
  39. }
  40. const rIC = window.requestIdleCallback || setTimeout;
  41. let observer = null;
  42. const observeLinks = () => {
  43. if (observer) {
  44. observer.disconnect();
  45. }
  46. observer = new IntersectionObserver((entries) => {
  47. entries.forEach((entry) => {
  48. if (entry.isIntersecting) {
  49. const link = entry.target;
  50. observer.unobserve(link);
  51. const { pathname } = link;
  52. if (!hasFetched.has(pathname)) {
  53. hasFetched.add(pathname);
  54. const pageChunkPath = pathToFile(pathname);
  55. doFetch(pageChunkPath);
  56. }
  57. }
  58. });
  59. });
  60. rIC(() => {
  61. document.querySelectorAll('#app a').forEach((link) => {
  62. const { target, hostname, pathname } = link;
  63. const extMatch = pathname.match(/\.\w+$/);
  64. if (extMatch && extMatch[0] !== '.html') {
  65. return;
  66. }
  67. if (
  68. // only prefetch same tab navigation, since a new tab will load
  69. // the lean js chunk instead.
  70. target !== `_blank` &&
  71. // only prefetch inbound links
  72. hostname === location.hostname) {
  73. if (pathname !== location.pathname) {
  74. observer.observe(link);
  75. }
  76. else {
  77. // No need to prefetch chunk for the current page, but also mark
  78. // it as already fetched. This is because the initial page uses its
  79. // lean chunk, and if we don't mark it, navigation to another page
  80. // with a link back to the first page will fetch its full chunk
  81. // which isn't needed.
  82. hasFetched.add(pathname);
  83. }
  84. }
  85. });
  86. });
  87. };
  88. onMounted(observeLinks);
  89. const route = useRoute();
  90. watch(() => route.path, observeLinks);
  91. onUnmounted(() => {
  92. observer && observer.disconnect();
  93. });
  94. }