-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: prefetch in viewport inbound page chunks
- Loading branch information
Showing
5 changed files
with
139 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import { h } from 'vue' | ||
import { useRoute } from '../router' | ||
import { usePrefetch } from '../composables/preFetch' | ||
|
||
export const Content = { | ||
setup() { | ||
const route = useRoute() | ||
if (!__DEV__) { | ||
// in prod mode, enable intersectionObserver based pre-fetch. | ||
usePrefetch() | ||
} | ||
return () => (route.contentComponent ? h(route.contentComponent) : null) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Customized pre-fetch for page chunks based on | ||
// https://github.com/GoogleChromeLabs/quicklink | ||
|
||
import { onMounted, onUnmounted, onUpdated } from 'vue' | ||
import { inBrowser, pathToFile } from '../utils' | ||
|
||
const hasFetched = new Set<string>() | ||
const createLink = () => document.createElement('link') | ||
|
||
const viaDOM = (url: string) => { | ||
const link = createLink() | ||
link.rel = `prefetch` | ||
link.href = url | ||
document.head.appendChild(link) | ||
} | ||
|
||
const viaXHR = (url: string) => { | ||
const req = new XMLHttpRequest() | ||
req.open('GET', url, (req.withCredentials = true)) | ||
req.send() | ||
} | ||
|
||
let link | ||
const doFetch: (url: string) => void = | ||
inBrowser && | ||
(link = createLink()) && | ||
link.relList && | ||
link.relList.supports && | ||
link.relList.supports('prefetch') | ||
? viaDOM | ||
: viaXHR | ||
|
||
export function usePrefetch() { | ||
if (!inBrowser) { | ||
return | ||
} | ||
|
||
if (!window.IntersectionObserver) { | ||
return | ||
} | ||
|
||
let conn | ||
if ( | ||
(conn = (navigator as any).connection) && | ||
(conn.saveData || /2g/.test(conn.effectiveType)) | ||
) { | ||
// Don't prefetch if using 2G or if Save-Data is enabled. | ||
return | ||
} | ||
|
||
const rIC = (window as any).requestIdleCallback || setTimeout | ||
let observer: IntersectionObserver | null = null | ||
|
||
const observeLinks = () => { | ||
if (observer) { | ||
observer.disconnect() | ||
} | ||
|
||
observer = new IntersectionObserver((entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting) { | ||
const link = entry.target as HTMLAnchorElement | ||
observer!.unobserve(link) | ||
const { pathname } = link | ||
if (!hasFetched.has(pathname)) { | ||
hasFetched.add(pathname) | ||
const pageChunkPath = pathToFile(pathname) | ||
doFetch(pageChunkPath) | ||
} | ||
} | ||
}) | ||
}) | ||
|
||
rIC(() => { | ||
document.querySelectorAll('.vitepress-content a').forEach((link) => { | ||
if ((link as HTMLAnchorElement).hostname === location.hostname) { | ||
observer!.observe(link) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
onMounted(observeLinks) | ||
onUpdated(observeLinks) | ||
|
||
onUnmounted(() => { | ||
observer && observer.disconnect() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
export const inBrowser = typeof window !== 'undefined' | ||
|
||
/** | ||
* Converts a url path to the corresponding js chunk filename. | ||
*/ | ||
export function pathToFile(path: string): string { | ||
let pagePath = path.replace(/\.html$/, '') | ||
if (pagePath.endsWith('/')) { | ||
pagePath += 'index' | ||
} | ||
|
||
if (__DEV__) { | ||
// awlays force re-fetch content in dev | ||
pagePath += `.md?t=${Date.now()}` | ||
} else { | ||
// in production, each .md file is built into a .md.js file following | ||
// the path conversion scheme. | ||
// /foo/bar.html -> ./foo_bar.md | ||
if (inBrowser) { | ||
pagePath = pagePath.slice(__BASE__.length).replace(/\//g, '_') + '.md' | ||
// client production build needs to account for page hash, which is | ||
// injected directly in the page's html | ||
const pageHash = __VP_HASH_MAP__[pagePath] | ||
pagePath = `${__BASE__}_assets/${pagePath}.${pageHash}.js` | ||
} else { | ||
// ssr build uses much simpler name mapping | ||
pagePath = `./${pagePath.slice(1).replace(/\//g, '_')}.md.js` | ||
} | ||
} | ||
|
||
return pagePath | ||
} |