preFetch.js 3.1 KB

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