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;
|
||||
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,
|
||||
})!;
|
||||
|
||||
// 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)
|
||||
.then(() => {
|
||||
this.routeUpdateCleanupCb?.();
|
||||
|
|
|
@ -12,8 +12,8 @@ import prefetchHelper from './prefetch';
|
|||
import preloadHelper from './preload';
|
||||
import flat from './flat';
|
||||
|
||||
const fetched: {[key: string]: boolean} = {};
|
||||
const loaded: {[key: string]: boolean} = {};
|
||||
const fetched = new Set<string>();
|
||||
const loaded = new Set<string>();
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line camelcase, no-underscore-dangle
|
||||
|
@ -25,14 +25,14 @@ declare global {
|
|||
|
||||
// If user is on slow or constrained connection.
|
||||
const isSlowConnection = () =>
|
||||
navigator.connection?.effectiveType.includes('2g') &&
|
||||
navigator.connection?.effectiveType.includes('2g') ||
|
||||
navigator.connection?.saveData;
|
||||
|
||||
const canPrefetch = (routePath: string) =>
|
||||
!isSlowConnection() && !loaded[routePath] && !fetched[routePath];
|
||||
!isSlowConnection() && !loaded.has(routePath) && !fetched.has(routePath);
|
||||
|
||||
const canPreload = (routePath: string) =>
|
||||
!isSlowConnection() && !loaded[routePath];
|
||||
!isSlowConnection() && !loaded.has(routePath);
|
||||
|
||||
const getChunkNamesToLoad = (path: string): string[] =>
|
||||
Object.entries(routesChunkNames)
|
||||
|
@ -46,12 +46,11 @@ const getChunkNamesToLoad = (path: string): string[] =>
|
|||
.flatMap(([, routeChunks]) => Object.values(flat(routeChunks)));
|
||||
|
||||
const docusaurus = {
|
||||
prefetch: (routePath: string): boolean => {
|
||||
prefetch(routePath: string): false | Promise<void[]> {
|
||||
if (!canPrefetch(routePath)) {
|
||||
return false;
|
||||
}
|
||||
// Prevent future duplicate prefetch of routePath.
|
||||
fetched[routePath] = true;
|
||||
fetched.add(routePath);
|
||||
|
||||
// Find all webpack chunk names needed.
|
||||
const matches = matchRoutes(routes, routePath);
|
||||
|
@ -61,32 +60,30 @@ const docusaurus = {
|
|||
);
|
||||
|
||||
// Prefetch all webpack chunk assets file needed.
|
||||
chunkNamesNeeded.forEach((chunkName) => {
|
||||
// "__webpack_require__.gca" is a custom function provided by
|
||||
// ChunkAssetPlugin. Pass it the chunkName or chunkId you want to load and
|
||||
// it will return the URL for that chunk.
|
||||
// eslint-disable-next-line camelcase
|
||||
const chunkAsset = __webpack_require__.gca(chunkName);
|
||||
return Promise.all(
|
||||
chunkNamesNeeded.map((chunkName) => {
|
||||
// "__webpack_require__.gca" is injected by ChunkAssetPlugin. Pass it
|
||||
// the name of the chunk you want to load and it will return its URL.
|
||||
// eslint-disable-next-line camelcase
|
||||
const chunkAsset = __webpack_require__.gca(chunkName);
|
||||
|
||||
// In some cases, webpack might decide to optimize further & hence the
|
||||
// chunk assets are merged to another chunk/previous chunk.
|
||||
// Hence, we can safely filter it out/don't need to load it.
|
||||
if (chunkAsset && !/undefined/.test(chunkAsset)) {
|
||||
prefetchHelper(chunkAsset);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
// In some cases, webpack might decide to optimize further, leading to
|
||||
// the chunk assets being merged to another chunk. In this case, we can
|
||||
// safely filter it out and don't need to load it.
|
||||
if (chunkAsset && !/undefined/.test(chunkAsset)) {
|
||||
return prefetchHelper(chunkAsset);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
preload: (routePath: string): boolean => {
|
||||
preload(routePath: string): false | Promise<void[]> {
|
||||
if (!canPreload(routePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loaded[routePath] = true;
|
||||
preloadHelper(routePath);
|
||||
return true;
|
||||
loaded.add(routePath);
|
||||
return preloadHelper(routePath);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -21,13 +21,6 @@ import {useBaseUrlUtils} from './useBaseUrl';
|
|||
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
||||
|
||||
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
|
||||
// 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 output: {[keyPath: string]: string} = {};
|
||||
|
||||
function step(object: Tree, prefix?: string | number) {
|
||||
function dfs(object: Tree, prefix?: string | number) {
|
||||
Object.entries(object).forEach(([key, value]) => {
|
||||
const newKey = prefix ? `${prefix}${delimiter}${key}` : key;
|
||||
|
||||
if (isTree(value)) {
|
||||
step(value, newKey);
|
||||
dfs(value, newKey);
|
||||
} else {
|
||||
output[newKey] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
step(target);
|
||||
dfs(target);
|
||||
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) => {
|
||||
if (typeof document === 'undefined') {
|
||||
reject();
|
||||
|
@ -25,8 +25,8 @@ function linkPrefetchStrategy(url: string) {
|
|||
link.setAttribute('rel', 'prefetch');
|
||||
link.setAttribute('href', url);
|
||||
|
||||
link.onload = resolve;
|
||||
link.onerror = reject;
|
||||
link.onload = () => resolve();
|
||||
link.onerror = () => reject();
|
||||
|
||||
const parentElement =
|
||||
document.getElementsByTagName('head')[0] ??
|
||||
|
@ -57,20 +57,6 @@ const supportedPrefetchStrategy = supports('prefetch')
|
|||
? linkPrefetchStrategy
|
||||
: xhrPrefetchStrategy;
|
||||
|
||||
const preFetched: {[url: string]: boolean} = {};
|
||||
|
||||
export default function prefetch(url: string): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (preFetched[url]) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
supportedPrefetchStrategy(url)
|
||||
.then(() => {
|
||||
resolve();
|
||||
preFetched[url] = true;
|
||||
})
|
||||
.catch(() => {}); // 404s are logged to the console anyway.
|
||||
});
|
||||
return supportedPrefetchStrategy(url).catch(() => {}); // 404s are logged to the console anyway.
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue