mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-31 23:40:39 +02:00
refactor(core): prefetch/preload refactor (#7282)
This commit is contained in:
parent
3c24cbc2c0
commit
53564f33ab
6 changed files with 42 additions and 57 deletions
|
@ -346,3 +346,10 @@ declare module '*.css' {
|
||||||
const src: string;
|
const src: string;
|
||||||
export default src;
|
export default src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
docusaurus: {
|
||||||
|
prefetch: (url: string) => false | Promise<void[]>;
|
||||||
|
preload: (url: string) => false | Promise<void[]>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -62,7 +62,9 @@ class PendingNavigation extends React.Component<Props, State> {
|
||||||
location: nextLocation,
|
location: nextLocation,
|
||||||
})!;
|
})!;
|
||||||
|
|
||||||
// Load data while the old screen remains.
|
// Load data while the old screen remains. Force preload instead of using
|
||||||
|
// `window.docusaurus`, because we want to avoid loading screen even when
|
||||||
|
// user is on saveData
|
||||||
preload(nextLocation.pathname)
|
preload(nextLocation.pathname)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.routeUpdateCleanupCb?.();
|
this.routeUpdateCleanupCb?.();
|
||||||
|
|
|
@ -12,8 +12,8 @@ import prefetchHelper from './prefetch';
|
||||||
import preloadHelper from './preload';
|
import preloadHelper from './preload';
|
||||||
import flat from './flat';
|
import flat from './flat';
|
||||||
|
|
||||||
const fetched: {[key: string]: boolean} = {};
|
const fetched = new Set<string>();
|
||||||
const loaded: {[key: string]: boolean} = {};
|
const loaded = new Set<string>();
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line camelcase, no-underscore-dangle
|
// eslint-disable-next-line camelcase, no-underscore-dangle
|
||||||
|
@ -25,14 +25,14 @@ declare global {
|
||||||
|
|
||||||
// If user is on slow or constrained connection.
|
// If user is on slow or constrained connection.
|
||||||
const isSlowConnection = () =>
|
const isSlowConnection = () =>
|
||||||
navigator.connection?.effectiveType.includes('2g') &&
|
navigator.connection?.effectiveType.includes('2g') ||
|
||||||
navigator.connection?.saveData;
|
navigator.connection?.saveData;
|
||||||
|
|
||||||
const canPrefetch = (routePath: string) =>
|
const canPrefetch = (routePath: string) =>
|
||||||
!isSlowConnection() && !loaded[routePath] && !fetched[routePath];
|
!isSlowConnection() && !loaded.has(routePath) && !fetched.has(routePath);
|
||||||
|
|
||||||
const canPreload = (routePath: string) =>
|
const canPreload = (routePath: string) =>
|
||||||
!isSlowConnection() && !loaded[routePath];
|
!isSlowConnection() && !loaded.has(routePath);
|
||||||
|
|
||||||
const getChunkNamesToLoad = (path: string): string[] =>
|
const getChunkNamesToLoad = (path: string): string[] =>
|
||||||
Object.entries(routesChunkNames)
|
Object.entries(routesChunkNames)
|
||||||
|
@ -46,12 +46,11 @@ const getChunkNamesToLoad = (path: string): string[] =>
|
||||||
.flatMap(([, routeChunks]) => Object.values(flat(routeChunks)));
|
.flatMap(([, routeChunks]) => Object.values(flat(routeChunks)));
|
||||||
|
|
||||||
const docusaurus = {
|
const docusaurus = {
|
||||||
prefetch: (routePath: string): boolean => {
|
prefetch(routePath: string): false | Promise<void[]> {
|
||||||
if (!canPrefetch(routePath)) {
|
if (!canPrefetch(routePath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Prevent future duplicate prefetch of routePath.
|
fetched.add(routePath);
|
||||||
fetched[routePath] = true;
|
|
||||||
|
|
||||||
// Find all webpack chunk names needed.
|
// Find all webpack chunk names needed.
|
||||||
const matches = matchRoutes(routes, routePath);
|
const matches = matchRoutes(routes, routePath);
|
||||||
|
@ -61,32 +60,30 @@ const docusaurus = {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prefetch all webpack chunk assets file needed.
|
// Prefetch all webpack chunk assets file needed.
|
||||||
chunkNamesNeeded.forEach((chunkName) => {
|
return Promise.all(
|
||||||
// "__webpack_require__.gca" is a custom function provided by
|
chunkNamesNeeded.map((chunkName) => {
|
||||||
// ChunkAssetPlugin. Pass it the chunkName or chunkId you want to load and
|
// "__webpack_require__.gca" is injected by ChunkAssetPlugin. Pass it
|
||||||
// it will return the URL for that chunk.
|
// the name of the chunk you want to load and it will return its URL.
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
const chunkAsset = __webpack_require__.gca(chunkName);
|
const chunkAsset = __webpack_require__.gca(chunkName);
|
||||||
|
|
||||||
// In some cases, webpack might decide to optimize further & hence the
|
// In some cases, webpack might decide to optimize further, leading to
|
||||||
// chunk assets are merged to another chunk/previous chunk.
|
// the chunk assets being merged to another chunk. In this case, we can
|
||||||
// Hence, we can safely filter it out/don't need to load it.
|
// safely filter it out and don't need to load it.
|
||||||
if (chunkAsset && !/undefined/.test(chunkAsset)) {
|
if (chunkAsset && !/undefined/.test(chunkAsset)) {
|
||||||
prefetchHelper(chunkAsset);
|
return prefetchHelper(chunkAsset);
|
||||||
}
|
}
|
||||||
});
|
return Promise.resolve();
|
||||||
|
}),
|
||||||
return true;
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
preload: (routePath: string): boolean => {
|
preload(routePath: string): false | Promise<void[]> {
|
||||||
if (!canPreload(routePath)) {
|
if (!canPreload(routePath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
loaded.add(routePath);
|
||||||
loaded[routePath] = true;
|
return preloadHelper(routePath);
|
||||||
preloadHelper(routePath);
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,6 @@ import {useBaseUrlUtils} from './useBaseUrl';
|
||||||
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
||||||
|
|
||||||
import type {Props} from '@docusaurus/Link';
|
import type {Props} from '@docusaurus/Link';
|
||||||
import type docusaurus from '../docusaurus';
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
docusaurus: typeof docusaurus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO all this wouldn't be necessary if we used ReactRouter basename feature
|
// TODO all this wouldn't be necessary if we used ReactRouter basename feature
|
||||||
// We don't automatically add base urls to all links,
|
// We don't automatically add base urls to all links,
|
||||||
|
|
|
@ -25,18 +25,18 @@ export default function flat(target: ChunkNames): {[keyPath: string]: string} {
|
||||||
const delimiter = '.';
|
const delimiter = '.';
|
||||||
const output: {[keyPath: string]: string} = {};
|
const output: {[keyPath: string]: string} = {};
|
||||||
|
|
||||||
function step(object: Tree, prefix?: string | number) {
|
function dfs(object: Tree, prefix?: string | number) {
|
||||||
Object.entries(object).forEach(([key, value]) => {
|
Object.entries(object).forEach(([key, value]) => {
|
||||||
const newKey = prefix ? `${prefix}${delimiter}${key}` : key;
|
const newKey = prefix ? `${prefix}${delimiter}${key}` : key;
|
||||||
|
|
||||||
if (isTree(value)) {
|
if (isTree(value)) {
|
||||||
step(value, newKey);
|
dfs(value, newKey);
|
||||||
} else {
|
} else {
|
||||||
output[newKey] = value;
|
output[newKey] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
step(target);
|
dfs(target);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ function supports(feature: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkPrefetchStrategy(url: string) {
|
function linkPrefetchStrategy(url: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (typeof document === 'undefined') {
|
if (typeof document === 'undefined') {
|
||||||
reject();
|
reject();
|
||||||
|
@ -25,8 +25,8 @@ function linkPrefetchStrategy(url: string) {
|
||||||
link.setAttribute('rel', 'prefetch');
|
link.setAttribute('rel', 'prefetch');
|
||||||
link.setAttribute('href', url);
|
link.setAttribute('href', url);
|
||||||
|
|
||||||
link.onload = resolve;
|
link.onload = () => resolve();
|
||||||
link.onerror = reject;
|
link.onerror = () => reject();
|
||||||
|
|
||||||
const parentElement =
|
const parentElement =
|
||||||
document.getElementsByTagName('head')[0] ??
|
document.getElementsByTagName('head')[0] ??
|
||||||
|
@ -57,20 +57,6 @@ const supportedPrefetchStrategy = supports('prefetch')
|
||||||
? linkPrefetchStrategy
|
? linkPrefetchStrategy
|
||||||
: xhrPrefetchStrategy;
|
: xhrPrefetchStrategy;
|
||||||
|
|
||||||
const preFetched: {[url: string]: boolean} = {};
|
|
||||||
|
|
||||||
export default function prefetch(url: string): Promise<void> {
|
export default function prefetch(url: string): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
return supportedPrefetchStrategy(url).catch(() => {}); // 404s are logged to the console anyway.
|
||||||
if (preFetched[url]) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedPrefetchStrategy(url)
|
|
||||||
.then(() => {
|
|
||||||
resolve();
|
|
||||||
preFetched[url] = true;
|
|
||||||
})
|
|
||||||
.catch(() => {}); // 404s are logged to the console anyway.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue