Skip to content

Commit

Permalink
refactor(core): prefetch/preload refactor (#7282)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena authored May 2, 2022
1 parent 3c24cbc commit 53564f3
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 58 deletions.
7 changes: 7 additions & 0 deletions packages/docusaurus-module-type-aliases/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]>;
};
}
4 changes: 3 additions & 1 deletion packages/docusaurus/src/client/PendingNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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?.();
Expand Down
55 changes: 26 additions & 29 deletions packages/docusaurus/src/client/docusaurus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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);

// 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;
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, 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);
},
};

Expand Down
7 changes: 0 additions & 7 deletions packages/docusaurus/src/client/exports/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions packages/docusaurus/src/client/flat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
22 changes: 4 additions & 18 deletions packages/docusaurus/src/client/prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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] ??
Expand Down Expand Up @@ -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.
}

0 comments on commit 53564f3

Please sign in to comment.