Skip to content

Commit

Permalink
fix(core): do not recreate ReactDOM Root, fix React warning on hot re…
Browse files Browse the repository at this point in the history
…load (#10103)
  • Loading branch information
slorber committed May 3, 2024
1 parent 4159b25 commit 2d8281f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 50 deletions.
1 change: 1 addition & 0 deletions packages/docusaurus-module-type-aliases/src/index.d.ts
Expand Up @@ -387,4 +387,5 @@ interface Window {
prefetch: (url: string) => false | Promise<void[]>;
preload: (url: string) => false | Promise<void[]>;
};
docusaurusRoot?: import('react-dom/client').Root;
}
21 changes: 12 additions & 9 deletions packages/docusaurus/src/client/clientEntry.tsx
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, {startTransition} from 'react';
import ReactDOM, {type ErrorInfo} from 'react-dom/client';
import {BrowserRouter} from 'react-router-dom';
import {HelmetProvider} from 'react-helmet-async';
Expand Down Expand Up @@ -46,21 +46,24 @@ if (ExecutionEnvironment.canUseDOM) {
};

const renderApp = () => {
if (window.docusaurusRoot) {
window.docusaurusRoot.render(app);
return;
}
if (hydrate) {
React.startTransition(() => {
ReactDOM.hydrateRoot(container, app, {
onRecoverableError,
});
window.docusaurusRoot = ReactDOM.hydrateRoot(container, app, {
onRecoverableError,
});
} else {
const root = ReactDOM.createRoot(container, {onRecoverableError});
React.startTransition(() => {
root.render(app);
});
root.render(app);
window.docusaurusRoot = root;
}
};

preload(window.location.pathname).then(renderApp);
preload(window.location.pathname).then(() => {
startTransition(renderApp);
});

// Webpack Hot Module Replacement API
if (module.hot) {
Expand Down
89 changes: 49 additions & 40 deletions packages/docusaurus/src/client/docusaurus.ts
Expand Up @@ -45,46 +45,55 @@ const getChunkNamesToLoad = (path: string): string[] =>
)
.flatMap(([, routeChunks]) => Object.values(flat(routeChunks)));

const docusaurus = {
prefetch(routePath: string): false | Promise<void[]> {
if (!canPrefetch(routePath)) {
return false;
}
fetched.add(routePath);

// Find all webpack chunk names needed.
const matches = matchRoutes(routes, routePath);

const chunkNamesNeeded = matches.flatMap((match) =>
getChunkNamesToLoad(match.route.path),
);

// Prefetch all webpack chunk assets file needed.
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 && !chunkAsset.includes('undefined')) {
return prefetchHelper(chunkAsset);
}
return Promise.resolve();
}),
);
},

preload(routePath: string): false | Promise<void[]> {
if (!canPreload(routePath)) {
return false;
}
loaded.add(routePath);
return preloadHelper(routePath);
},
type Docusaurus = Window['docusaurus'];

const prefetch: Docusaurus['prefetch'] = (
routePath: string,
): false | Promise<void[]> => {
if (!canPrefetch(routePath)) {
return false;
}
fetched.add(routePath);

// Find all webpack chunk names needed.
const matches = matchRoutes(routes, routePath);

const chunkNamesNeeded = matches.flatMap((match) =>
getChunkNamesToLoad(match.route.path),
);

// Prefetch all webpack chunk assets file needed.
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 && !chunkAsset.includes('undefined')) {
return prefetchHelper(chunkAsset);
}
return Promise.resolve();
}),
);
};

const preload: Docusaurus['preload'] = (
routePath: string,
): false | Promise<void[]> => {
if (!canPreload(routePath)) {
return false;
}
loaded.add(routePath);
return preloadHelper(routePath);
};

const docusaurus: Window['docusaurus'] = {
prefetch,
preload,
};

// This object is directly mounted onto window, better freeze it
Expand Down
1 change: 0 additions & 1 deletion project-words.txt
Expand Up @@ -67,7 +67,6 @@ datagit
Datagit
Datagit's
dedup
Déja
devto
dingers
Dmitry
Expand Down

0 comments on commit 2d8281f

Please sign in to comment.