diff --git a/__tests__/e2e/prefetch/html.md b/__tests__/e2e/prefetch/html.md new file mode 100644 index 000000000..6a41c3bb2 --- /dev/null +++ b/__tests__/e2e/prefetch/html.md @@ -0,0 +1 @@ +# HTML link destination diff --git a/__tests__/e2e/prefetch/index.md b/__tests__/e2e/prefetch/index.md new file mode 100644 index 000000000..31582a370 --- /dev/null +++ b/__tests__/e2e/prefetch/index.md @@ -0,0 +1,15 @@ +--- +sidebar: false +outline: false +--- + +# Link prefetching + + + SVG link + Same page, another hash + SVG xlink + New tab + + +[HTML link](./html.md) diff --git a/__tests__/e2e/prefetch/new-tab.md b/__tests__/e2e/prefetch/new-tab.md new file mode 100644 index 000000000..6a35f9657 --- /dev/null +++ b/__tests__/e2e/prefetch/new-tab.md @@ -0,0 +1 @@ +# New tab destination diff --git a/__tests__/e2e/prefetch/prefetch.test.ts b/__tests__/e2e/prefetch/prefetch.test.ts new file mode 100644 index 000000000..1a6e7ec9e --- /dev/null +++ b/__tests__/e2e/prefetch/prefetch.test.ts @@ -0,0 +1,43 @@ +test.runIf(process.env.VITE_TEST_BUILD)( + 'prefetches SVG links without errors and respects new-tab targets', + async () => { + const errors: string[] = [] + const onPageError = (error: Error) => errors.push(error.message) + page.on('pageerror', onPageError) + + try { + await goto('/prefetch/') + + await expect + .poll( + async () => ({ + errors, + pages: await page + .locator('link[rel="prefetch"]') + .evaluateAll((links) => + links + .map( + (link) => + link + .getAttribute('href') + ?.match(/prefetch_(.*?)\.md\./)?.[1] + ) + .filter(Boolean) + .sort() + ) + }), + { timeout: 10_000 } + ) + .toEqual({ errors: [], pages: ['html', 'svg', 'xlink'] }) + + await page.locator('svg a[href="./svg.html"]').click() + await page.waitForSelector('h1', { state: 'visible' }) + await expect + .poll(() => page.locator('h1').textContent()) + .toContain('SVG link destination') + expect(errors).toEqual([]) + } finally { + page.off('pageerror', onPageError) + } + } +) diff --git a/__tests__/e2e/prefetch/svg.md b/__tests__/e2e/prefetch/svg.md new file mode 100644 index 000000000..c3eeda434 --- /dev/null +++ b/__tests__/e2e/prefetch/svg.md @@ -0,0 +1,3 @@ +# SVG link destination + +## Section diff --git a/__tests__/e2e/prefetch/xlink.md b/__tests__/e2e/prefetch/xlink.md new file mode 100644 index 000000000..86a76f59c --- /dev/null +++ b/__tests__/e2e/prefetch/xlink.md @@ -0,0 +1 @@ +# SVG xlink destination diff --git a/src/client/app/composables/preFetch.ts b/src/client/app/composables/preFetch.ts index 35c23a810..f8085b63c 100644 --- a/src/client/app/composables/preFetch.ts +++ b/src/client/app/composables/preFetch.ts @@ -10,6 +10,12 @@ import { inBrowser, pathToFile } from '../utils' const hasFetched = new Set() const createLink = () => document.createElement('link') +const getLinkUrl = (link: HTMLAnchorElement | SVGAElement) => + new URL( + link.href instanceof SVGAnimatedString ? link.href.animVal : link.href, + link.baseURI + ) + const viaDOM = (url: string) => { const link = createLink() link.rel = `prefetch` @@ -64,9 +70,9 @@ export function usePrefetch() { observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { - const link = entry.target as HTMLAnchorElement + const link = entry.target as HTMLAnchorElement | SVGAElement observer!.unobserve(link) - const { pathname } = link + const { pathname } = getLinkUrl(link) if (!hasFetched.has(pathname)) { hasFetched.add(pathname) const pageChunkPath = pathToFile(pathname) @@ -80,12 +86,7 @@ export function usePrefetch() { document .querySelectorAll('#app a') .forEach((link) => { - const { hostname, pathname } = new URL( - link.href instanceof SVGAnimatedString - ? link.href.animVal - : link.href, - link.baseURI - ) + const { hostname, pathname } = getLinkUrl(link) const extMatch = pathname.match(/\.\w+$/) if (extMatch && extMatch[0] !== '.html') { return @@ -94,7 +95,7 @@ export function usePrefetch() { if ( // only prefetch same tab navigation, since a new tab will load // the lean js chunk instead. - link.target !== '_blank' && + link.getAttribute('target') !== '_blank' && // only prefetch inbound links hostname === location.hostname ) {