From 2d8281fc0353b8d53e0094cabd8d8d90f4c78064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 3 May 2024 15:54:49 +0200 Subject: [PATCH] fix(core): do not recreate ReactDOM Root, fix React warning on hot reload (#10103) --- .../src/index.d.ts | 1 + .../docusaurus/src/client/clientEntry.tsx | 21 +++-- packages/docusaurus/src/client/docusaurus.ts | 89 ++++++++++--------- project-words.txt | 1 - 4 files changed, 62 insertions(+), 50 deletions(-) diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index 0819c6efeaad..1c4fb78360c6 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -387,4 +387,5 @@ interface Window { prefetch: (url: string) => false | Promise; preload: (url: string) => false | Promise; }; + docusaurusRoot?: import('react-dom/client').Root; } diff --git a/packages/docusaurus/src/client/clientEntry.tsx b/packages/docusaurus/src/client/clientEntry.tsx index d6d4d365659e..c5fcf5848af6 100644 --- a/packages/docusaurus/src/client/clientEntry.tsx +++ b/packages/docusaurus/src/client/clientEntry.tsx @@ -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'; @@ -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) { diff --git a/packages/docusaurus/src/client/docusaurus.ts b/packages/docusaurus/src/client/docusaurus.ts index da1236cacede..79bb4e21c779 100644 --- a/packages/docusaurus/src/client/docusaurus.ts +++ b/packages/docusaurus/src/client/docusaurus.ts @@ -45,46 +45,55 @@ const getChunkNamesToLoad = (path: string): string[] => ) .flatMap(([, routeChunks]) => Object.values(flat(routeChunks))); -const docusaurus = { - prefetch(routePath: string): false | Promise { - 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 { - if (!canPreload(routePath)) { - return false; - } - loaded.add(routePath); - return preloadHelper(routePath); - }, +type Docusaurus = Window['docusaurus']; + +const prefetch: Docusaurus['prefetch'] = ( + routePath: string, +): false | Promise => { + 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 => { + 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 diff --git a/project-words.txt b/project-words.txt index 04cd0255a75c..cc4e06e18cc5 100644 --- a/project-words.txt +++ b/project-words.txt @@ -67,7 +67,6 @@ datagit Datagit Datagit's dedup -Déja devto dingers Dmitry