From 187e5aa218e764b06dcbbb50f22ced72159a8532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 8 Jun 2023 19:40:15 +0200 Subject: [PATCH] feat: React 18 + automatic JSX runtime + build --dev (#8961) --- .eslintrc.js | 5 ++ jest.config.mjs | 1 + jest/setup.js | 11 +++ package.json | 6 +- .../templates/classic-typescript/package.json | 6 +- .../templates/classic/package.json | 6 +- packages/docusaurus-mdx-loader/package.json | 4 +- .../src/index.d.ts | 6 ++ .../package.json | 4 +- .../package.json | 4 +- .../package.json | 4 +- .../package.json | 4 +- packages/docusaurus-plugin-debug/package.json | 4 +- .../package.json | 4 +- .../package.json | 4 +- .../package.json | 4 +- .../package.json | 6 +- .../src/deps.d.ts | 2 +- .../src/theme/IdealImage/index.tsx | 2 +- packages/docusaurus-plugin-pwa/package.json | 4 +- .../docusaurus-plugin-pwa/src/registerSw.ts | 10 ++- .../docusaurus-plugin-sitemap/package.json | 4 +- .../docusaurus-preset-classic/package.json | 4 +- .../docusaurus-theme-classic/package.json | 6 +- packages/docusaurus-theme-common/package.json | 4 +- .../src/components/Collapsible/index.tsx | 6 +- packages/docusaurus-theme-common/src/index.ts | 1 - .../src/utils/reactUtils.tsx | 16 +--- .../src/utils/scrollUtils.tsx | 4 +- .../src/utils/tabsUtils.tsx | 4 +- .../package.json | 4 +- .../docusaurus-theme-mermaid/package.json | 6 +- .../package.json | 4 +- packages/docusaurus-types/package.json | 4 +- packages/docusaurus/bin/docusaurus.mjs | 4 + packages/docusaurus/package.json | 6 +- packages/docusaurus/src/babel/preset.ts | 7 +- .../src/client/BaseUrlIssueBanner/index.tsx | 34 ++------ .../src/client/ClientLifecyclesDispatcher.tsx | 5 +- .../client/__tests__/browserContext.test.tsx | 2 + .../__tests__/docusaurusContext.test.tsx | 2 + .../client/__tests__/routeContext.test.tsx | 2 + .../docusaurus/src/client/clientEntry.tsx | 46 +++++++---- .../__tests__/useRouteContext.test.tsx | 2 + .../exports/useIsomorphicLayoutEffect.tsx | 27 +++++++ .../docusaurus/src/client/serverEntry.tsx | 9 ++- .../docusaurus/src/client/serverRenderer.tsx | 80 +++++++++++++++++++ packages/docusaurus/src/commands/build.ts | 8 +- packages/docusaurus/src/commands/start.ts | 2 +- .../__tests__/__snapshots__/base.test.ts.snap | 1 + .../__snapshots__/index.test.ts.snap | 1 + packages/docusaurus/src/webpack/client.ts | 5 ++ .../webpack/templates/ssr.html.template.ts | 4 +- project-words.txt | 1 + .../_docs tests/tests/custom-props/index.mdx | 2 +- website/_dogfooding/_pages tests/index.mdx | 1 + .../react-18/_components/heavyComponent.tsx | 18 +++++ .../_pages tests/react-18/index.tsx | 51 ++++++++++++ website/docs/cli.mdx | 1 + website/docusaurus.config.js | 7 +- website/package.json | 6 +- .../src/components/BrowserWindow/index.tsx | 14 ---- website/src/components/ConfigTabs.tsx | 16 ++-- .../NavbarItems/CustomDogfoodNavbarItem.tsx | 2 +- website/src/pages/index.tsx | 1 - website/src/pages/showcase/index.tsx | 2 +- website/src/pages/versions.tsx | 1 - website/tsconfig.json | 2 + yarn.lock | 73 ++++++++--------- 69 files changed, 404 insertions(+), 209 deletions(-) create mode 100644 jest/setup.js create mode 100644 packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx create mode 100644 packages/docusaurus/src/client/serverRenderer.tsx create mode 100644 website/_dogfooding/_pages tests/react-18/_components/heavyComponent.tsx create mode 100644 website/_dogfooding/_pages tests/react-18/index.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 1acb68f82e64..6a0b15228fd0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -66,6 +66,8 @@ module.exports = { '@docusaurus', ], rules: { + 'react/jsx-uses-react': OFF, // JSX runtime: automatic + 'react/react-in-jsx-scope': OFF, // JSX runtime: automatic 'array-callback-return': WARNING, camelcase: WARNING, 'class-methods-use-this': OFF, // It's a way of allowing private variables. @@ -259,6 +261,9 @@ module.exports = { }, {pattern: '@jest/globals', group: 'builtin', position: 'before'}, {pattern: 'react', group: 'builtin', position: 'before'}, + {pattern: 'react-dom', group: 'builtin', position: 'before'}, + {pattern: 'react-dom/**', group: 'builtin', position: 'before'}, + {pattern: 'stream', group: 'builtin', position: 'before'}, {pattern: 'fs-extra', group: 'builtin'}, {pattern: 'lodash', group: 'external', position: 'before'}, {pattern: 'clsx', group: 'external', position: 'before'}, diff --git a/jest.config.mjs b/jest.config.mjs index 73397043140e..564e8a458271 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -32,6 +32,7 @@ const ignorePatterns = [ export default { rootDir: fileURLToPath(new URL('.', import.meta.url)), verbose: true, + setupFiles: ['./jest/setup.js'], testEnvironmentOptions: { url: 'https://docusaurus.io/', }, diff --git a/jest/setup.js b/jest/setup.js new file mode 100644 index 000000000000..04cb06b8028a --- /dev/null +++ b/jest/setup.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {TextEncoder} from 'util'; + +// Required for RTL renderHook SSR tests with React-18 +// See also https://github.com/testing-library/react-testing-library/issues/1120#issuecomment-1516132279 +global.TextEncoder = TextEncoder; diff --git a/package.json b/package.json index d4f499a7c11c..4448afe77805 100644 --- a/package.json +++ b/package.json @@ -105,10 +105,10 @@ "lint-staged": "^13.1.2", "npm-run-all": "^4.1.5", "prettier": "^2.8.4", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-helmet-async": "^1.3.0", - "react-test-renderer": "^17.0.2", + "react-test-renderer": "^18.0.0", "remark-parse": "^8.0.2", "rimraf": "^3.0.2", "sharp": "^0.31.3", diff --git a/packages/create-docusaurus/templates/classic-typescript/package.json b/packages/create-docusaurus/templates/classic-typescript/package.json index 1c7ce26d70b2..5112974352e2 100644 --- a/packages/create-docusaurus/templates/classic-typescript/package.json +++ b/packages/create-docusaurus/templates/classic-typescript/package.json @@ -17,11 +17,11 @@ "dependencies": { "@docusaurus/core": "^3.0.0-alpha.0", "@docusaurus/preset-classic": "^3.0.0-alpha.0", - "@mdx-js/react": "^2.1.5", + "@mdx-js/react": "^2.3.0", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.0.0-alpha.0", diff --git a/packages/create-docusaurus/templates/classic/package.json b/packages/create-docusaurus/templates/classic/package.json index d0a06d1bd55c..73111842fe12 100644 --- a/packages/create-docusaurus/templates/classic/package.json +++ b/packages/create-docusaurus/templates/classic/package.json @@ -16,11 +16,11 @@ "dependencies": { "@docusaurus/core": "^3.0.0-alpha.0", "@docusaurus/preset-classic": "^3.0.0-alpha.0", - "@mdx-js/react": "^2.1.5", + "@mdx-js/react": "^2.3.0", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.0.0-alpha.0" diff --git a/packages/docusaurus-mdx-loader/package.json b/packages/docusaurus-mdx-loader/package.json index ea035b4fb546..01e7abc64f0b 100644 --- a/packages/docusaurus-mdx-loader/package.json +++ b/packages/docusaurus-mdx-loader/package.json @@ -59,8 +59,8 @@ "unist-util-remove-position": "^3.0.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index c8f2c995f012..09c3f2fb3d59 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -242,6 +242,12 @@ declare module '@docusaurus/router' { export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom'; } +declare module '@docusaurus/useIsomorphicLayoutEffect' { + import {useLayoutEffect} from 'react'; + + export = useLayoutEffect; +} + declare module '@docusaurus/useDocusaurusContext' { import type {DocusaurusContext} from '@docusaurus/types'; diff --git a/packages/docusaurus-plugin-client-redirects/package.json b/packages/docusaurus-plugin-client-redirects/package.json index f9d4df0dbd22..f62eca571eaf 100644 --- a/packages/docusaurus-plugin-client-redirects/package.json +++ b/packages/docusaurus-plugin-client-redirects/package.json @@ -32,8 +32,8 @@ "@docusaurus/types": "^3.0.0-alpha.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index 6b67ec52b2f4..5855868fcad1 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -36,8 +36,8 @@ "webpack": "^5.76.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index 1596c3d82c72..41def9e00e9f 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -60,8 +60,8 @@ "shelljs": "^0.8.5" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-content-pages/package.json b/packages/docusaurus-plugin-content-pages/package.json index 6d61c3b9aa3b..79c4da3cb2e1 100644 --- a/packages/docusaurus-plugin-content-pages/package.json +++ b/packages/docusaurus-plugin-content-pages/package.json @@ -28,8 +28,8 @@ "webpack": "^5.76.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-debug/package.json b/packages/docusaurus-plugin-debug/package.json index 3cacaf9ca849..245af03bd75d 100644 --- a/packages/docusaurus-plugin-debug/package.json +++ b/packages/docusaurus-plugin-debug/package.json @@ -28,8 +28,8 @@ "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-google-analytics/package.json b/packages/docusaurus-plugin-google-analytics/package.json index fd2e81349ae3..034ad4b90d07 100644 --- a/packages/docusaurus-plugin-google-analytics/package.json +++ b/packages/docusaurus-plugin-google-analytics/package.json @@ -24,8 +24,8 @@ "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-google-gtag/package.json b/packages/docusaurus-plugin-google-gtag/package.json index afdfab1e9115..f60ab109314b 100644 --- a/packages/docusaurus-plugin-google-gtag/package.json +++ b/packages/docusaurus-plugin-google-gtag/package.json @@ -25,8 +25,8 @@ "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-google-tag-manager/package.json b/packages/docusaurus-plugin-google-tag-manager/package.json index 018d0c418b3f..a410ca665a61 100644 --- a/packages/docusaurus-plugin-google-tag-manager/package.json +++ b/packages/docusaurus-plugin-google-tag-manager/package.json @@ -24,8 +24,8 @@ "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-ideal-image/package.json b/packages/docusaurus-plugin-ideal-image/package.json index 3c0d58fe000e..60289970cb66 100644 --- a/packages/docusaurus-plugin-ideal-image/package.json +++ b/packages/docusaurus-plugin-ideal-image/package.json @@ -26,7 +26,7 @@ "@docusaurus/theme-translations": "^3.0.0-alpha.0", "@docusaurus/types": "^3.0.0-alpha.0", "@docusaurus/utils-validation": "^3.0.0-alpha.0", - "@endiliey/react-ideal-image": "^0.0.11", + "@slorber/react-ideal-image": "^0.0.12", "react-waypoint": "^10.3.0", "sharp": "^0.31.3", "tslib": "^2.5.0", @@ -38,8 +38,8 @@ }, "peerDependencies": { "jimp": "*", - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "peerDependenciesMeta": { "jimp": { diff --git a/packages/docusaurus-plugin-ideal-image/src/deps.d.ts b/packages/docusaurus-plugin-ideal-image/src/deps.d.ts index f5a6da75b378..654dd1622128 100644 --- a/packages/docusaurus-plugin-ideal-image/src/deps.d.ts +++ b/packages/docusaurus-plugin-ideal-image/src/deps.d.ts @@ -12,7 +12,7 @@ * Note: the original type definition is WRONG. getIcon & getMessage receive * full state object. */ -declare module '@endiliey/react-ideal-image' { +declare module '@slorber/react-ideal-image' { import type {ComponentProps, ComponentType, CSSProperties} from 'react'; export type LoadingState = 'initial' | 'loading' | 'loaded' | 'error'; diff --git a/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx b/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx index 8886a6e7992e..63d40cf63c6e 100644 --- a/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx +++ b/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import ReactIdealImage, { type IconKey, type State, -} from '@endiliey/react-ideal-image'; +} from '@slorber/react-ideal-image'; import {translate} from '@docusaurus/Translate'; import type {Props} from '@theme/IdealImage'; diff --git a/packages/docusaurus-plugin-pwa/package.json b/packages/docusaurus-plugin-pwa/package.json index de626bda1cb7..c87add241d49 100644 --- a/packages/docusaurus-plugin-pwa/package.json +++ b/packages/docusaurus-plugin-pwa/package.json @@ -45,8 +45,8 @@ "fs-extra": "^11.1.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-plugin-pwa/src/registerSw.ts b/packages/docusaurus-plugin-pwa/src/registerSw.ts index 1c834d3be843..d367df63a3c4 100644 --- a/packages/docusaurus-plugin-pwa/src/registerSw.ts +++ b/packages/docusaurus-plugin-pwa/src/registerSw.ts @@ -72,8 +72,14 @@ async function getIsAppInstalledRelatedApps() { if (!('getInstalledRelatedApps' in window.navigator)) { return false; } - const relatedApps = await navigator.getInstalledRelatedApps(); - return relatedApps.some((app) => app.platform === 'webapp'); + try { + const relatedApps = await navigator.getInstalledRelatedApps(); + return relatedApps.some((app) => app.platform === 'webapp'); + } catch (e) { + // Error might be thrown when Docusaurus is embedded in an iframe: + // registerSW failed DOMException: Failed to execute 'getInstalledRelatedApps' on 'Navigator': getInstalledRelatedApps() is only supported in top-level browsing contexts. + return false; + } } function isStandaloneDisplayMode() { return window.matchMedia('(display-mode: standalone)').matches; diff --git a/packages/docusaurus-plugin-sitemap/package.json b/packages/docusaurus-plugin-sitemap/package.json index 002e3b3c38be..7fd1e25d06d1 100644 --- a/packages/docusaurus-plugin-sitemap/package.json +++ b/packages/docusaurus-plugin-sitemap/package.json @@ -29,8 +29,8 @@ "tslib": "^2.5.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-preset-classic/package.json b/packages/docusaurus-preset-classic/package.json index a26cd69a62b4..e882bcccbe27 100644 --- a/packages/docusaurus-preset-classic/package.json +++ b/packages/docusaurus-preset-classic/package.json @@ -33,8 +33,8 @@ "@docusaurus/types": "^3.0.0-alpha.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 92dc35b541a6..850be79b9e68 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -52,12 +52,12 @@ "@types/prismjs": "^1.26.0", "@types/rtlcss": "^3.5.0", "fs-extra": "^11.1.0", - "react-test-renderer": "^17.0.2", + "react-test-renderer": "^18.0.0", "utility-types": "^3.10.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json index 94145ba8407c..6b0ec6428489 100644 --- a/packages/docusaurus-theme-common/package.json +++ b/packages/docusaurus-theme-common/package.json @@ -54,8 +54,8 @@ "lodash": "^4.17.21" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx b/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx index 4d5ee798c1e6..9668fe278fbc 100644 --- a/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx +++ b/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx @@ -10,13 +10,13 @@ import React, { useEffect, useRef, useCallback, - useLayoutEffect, type RefObject, type Dispatch, type SetStateAction, type ReactNode, } from 'react'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; import {prefersReducedMotion} from '../../utils/accessibilityUtils'; const DefaultAnimationEasing = 'ease-in-out'; @@ -231,13 +231,13 @@ function CollapsibleLazy({collapsed, ...props}: CollapsibleBaseProps) { // Updated in effect so that first expansion transition can work const [lazyCollapsed, setLazyCollapsed] = useState(collapsed); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (!collapsed) { setMounted(true); } }, [collapsed]); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (mounted) { setLazyCollapsed(collapsed); } diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index cf3a66c03aa6..ad0a184fec55 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -48,7 +48,6 @@ export {ThemeClassNames} from './utils/ThemeClassNames'; export {prefersReducedMotion} from './utils/accessibilityUtils'; export { - useIsomorphicLayoutEffect, useEvent, usePrevious, composeProviders, diff --git a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx index 95ea9379095a..764b34ec98f5 100644 --- a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx @@ -7,26 +7,12 @@ import React, { useCallback, - useEffect, - useLayoutEffect, useMemo, useRef, type ComponentType, type ReactNode, } from 'react'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; - -/** - * This hook is like `useLayoutEffect`, but without the SSR warning. - * It seems hacky but it's used in many React libs (Redux, Formik...). - * Also mentioned here: https://github.com/facebook/react/issues/16956 - * - * It is useful when you need to update a ref as soon as possible after a React - * render (before `useEffect`). - */ -export const useIsomorphicLayoutEffect = ExecutionEnvironment.canUseDOM - ? useLayoutEffect - : useEffect; +import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; /** * Temporary userland implementation until an official hook is implemented diff --git a/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx b/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx index 78e68a04b1dc..e3cdf4e9bee8 100644 --- a/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx @@ -9,13 +9,13 @@ import React, { useCallback, useContext, useEffect, - useLayoutEffect, useMemo, useRef, type ReactNode, } from 'react'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import useIsBrowser from '@docusaurus/useIsBrowser'; +import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; import {useEvent, ReactContextError} from './reactUtils'; type ScrollController = { @@ -221,7 +221,7 @@ export function useScrollPositionBlocker(): { [scrollController, scrollPositionSaver], ); - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { // Queuing permits to restore scroll position after all useLayoutEffect // have run, and yet preserve the sync nature of the scroll restoration // See https://github.com/facebook/docusaurus/issues/8625 diff --git a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx index 42ad4fe4d022..6757868e0696 100644 --- a/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/tabsUtils.tsx @@ -12,9 +12,9 @@ import React, { useMemo, type ReactNode, type ReactElement, - useLayoutEffect, } from 'react'; import {useHistory} from '@docusaurus/router'; +import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; import {useQueryStringValue} from '@docusaurus/theme-common/internal'; import {duplicates, useStorageSlot} from '../index'; @@ -252,7 +252,7 @@ export function useTabs(props: TabsProps): { })(); // Sync in a layout/sync effect is important, for useScrollPositionBlocker // See https://github.com/facebook/docusaurus/issues/8625 - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (valueToSync) { setSelectedValue(valueToSync); } diff --git a/packages/docusaurus-theme-live-codeblock/package.json b/packages/docusaurus-theme-live-codeblock/package.json index 2881a6ae9395..fc7d1ff4764c 100644 --- a/packages/docusaurus-theme-live-codeblock/package.json +++ b/packages/docusaurus-theme-live-codeblock/package.json @@ -38,8 +38,8 @@ "@types/buble": "^0.20.1" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-theme-mermaid/package.json b/packages/docusaurus-theme-mermaid/package.json index 53aa3a76e9c3..5a142fc76bf5 100644 --- a/packages/docusaurus-theme-mermaid/package.json +++ b/packages/docusaurus-theme-mermaid/package.json @@ -43,11 +43,11 @@ }, "devDependencies": { "@types/mdx-js__react": "^1.5.5", - "react-test-renderer": "^17.0.2" + "react-test-renderer": "^18.0.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-theme-search-algolia/package.json b/packages/docusaurus-theme-search-algolia/package.json index 7cbcb23b3ce5..d67ee3464cf9 100644 --- a/packages/docusaurus-theme-search-algolia/package.json +++ b/packages/docusaurus-theme-search-algolia/package.json @@ -54,8 +54,8 @@ "@docusaurus/module-type-aliases": "^3.0.0-alpha.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus-types/package.json b/packages/docusaurus-types/package.json index 1fcb35a549d6..3623e33c2968 100644 --- a/packages/docusaurus-types/package.json +++ b/packages/docusaurus-types/package.json @@ -23,7 +23,7 @@ "webpack-merge": "^5.8.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" } } diff --git a/packages/docusaurus/bin/docusaurus.mjs b/packages/docusaurus/bin/docusaurus.mjs index 4016fbd30bab..a2738c3866b6 100755 --- a/packages/docusaurus/bin/docusaurus.mjs +++ b/packages/docusaurus/bin/docusaurus.mjs @@ -37,6 +37,10 @@ cli.version(DOCUSAURUS_VERSION).usage(' [options]'); cli .command('build [siteDir]') .description('Build website.') + .option( + '--dev', + 'Builds the website in dev mode, including full React error messages.', + ) .option( '--bundle-analyzer', 'visualize size of webpack output files with an interactive zoomable tree map (default: false)', diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index 86055600affc..778c615f99e7 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -116,13 +116,13 @@ "@types/update-notifier": "^6.0.2", "@types/wait-on": "^5.3.1", "@types/webpack-bundle-analyzer": "^4.6.0", - "react-test-renderer": "^17.0.2", + "react-test-renderer": "^18.0.0", "tmp-promise": "^3.0.3", "tree-node-cli": "^1.6.0" }, "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "react": "^18.0.0", + "react-dom": "^18.0.0" }, "engines": { "node": ">=16.14" diff --git a/packages/docusaurus/src/babel/preset.ts b/packages/docusaurus/src/babel/preset.ts index 3d842007757e..cbbeb2207423 100644 --- a/packages/docusaurus/src/babel/preset.ts +++ b/packages/docusaurus/src/babel/preset.ts @@ -38,7 +38,12 @@ function getTransformOptions(isServer: boolean): TransformOptions { exclude: ['transform-typeof-symbol'], }, ], - require.resolve('@babel/preset-react'), + [ + require.resolve('@babel/preset-react'), + { + runtime: 'automatic', + }, + ], require.resolve('@babel/preset-typescript'), ], plugins: [ diff --git a/packages/docusaurus/src/client/BaseUrlIssueBanner/index.tsx b/packages/docusaurus/src/client/BaseUrlIssueBanner/index.tsx index ffbfdae2b7a2..0e8ac2bbf2eb 100644 --- a/packages/docusaurus/src/client/BaseUrlIssueBanner/index.tsx +++ b/packages/docusaurus/src/client/BaseUrlIssueBanner/index.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useLayoutEffect} from 'react'; +import React from 'react'; import {useLocation} from '@docusaurus/router'; import Head from '@docusaurus/Head'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; @@ -21,8 +21,6 @@ const BannerId = '__docusaurus-base-url-issue-banner'; const SuggestionContainerId = '__docusaurus-base-url-issue-banner-suggestion-container'; -const InsertBannerWindowAttribute = '__DOCUSAURUS_INSERT_BASEURL_BANNER'; - // It is important to not use React to render this banner // otherwise Google would index it, even if it's hidden with some critical CSS! // See https://github.com/facebook/docusaurus/issues/4028 @@ -45,24 +43,19 @@ function createInlineHtmlBanner(baseUrl: string) { function createInlineScript(baseUrl: string) { /* language=js */ return ` -window['${InsertBannerWindowAttribute}'] = true; - -document.addEventListener('DOMContentLoaded', maybeInsertBanner); - -function maybeInsertBanner() { - var shouldInsert = window['${InsertBannerWindowAttribute}']; +document.addEventListener('DOMContentLoaded', function maybeInsertBanner() { + var shouldInsert = typeof window['docusaurus'] === 'undefined'; shouldInsert && insertBanner(); -} +}); function insertBanner() { - var bannerContainer = document.getElementById('${BannerContainerId}'); - if (!bannerContainer) { - return; - } + var bannerContainer = document.createElement('div'); + bannerContainer.id = '${BannerContainerId}'; var bannerHtml = ${JSON.stringify(createInlineHtmlBanner(baseUrl)) // See https://redux.js.org/recipes/server-rendering/#security-considerations .replace(/ { - window[InsertBannerWindowAttribute] = false; - }, []); - return ( <> {!ExecutionEnvironment.canUseDOM && ( @@ -99,7 +80,6 @@ function BaseUrlIssueBanner() { )} -
); } diff --git a/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx index c7b433686421..3a37010beaed 100644 --- a/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx +++ b/packages/docusaurus/src/client/ClientLifecyclesDispatcher.tsx @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import {useLayoutEffect, type ReactElement} from 'react'; +import {type ReactElement} from 'react'; import clientModules from '@generated/client-modules'; +import useIsomorphicLayoutEffect from './exports/useIsomorphicLayoutEffect'; import type {ClientModule} from '@docusaurus/types'; import type {Location} from 'history'; @@ -66,7 +67,7 @@ function ClientLifecyclesDispatcher({ location: Location; previousLocation: Location | null; }): JSX.Element { - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (previousLocation !== location) { scrollAfterNavigation({location, previousLocation}); dispatchLifecycleAction('onRouteDidUpdate', {previousLocation, location}); diff --git a/packages/docusaurus/src/client/__tests__/browserContext.test.tsx b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx index bfb8c211cf03..40fb454fbee9 100644 --- a/packages/docusaurus/src/client/__tests__/browserContext.test.tsx +++ b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx @@ -10,6 +10,8 @@ // Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573 // eslint-disable-next-line header/header import React from 'react'; +// TODO migrate to @testing-library/react when SSR rendering possible +// See https://github.com/testing-library/react-testing-library/issues/1120 import {renderHook} from '@testing-library/react-hooks/server'; import {BrowserContextProvider} from '../browserContext'; import useIsBrowser from '../exports/useIsBrowser'; diff --git a/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx index d11bb52a68aa..24713c596f5c 100644 --- a/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx +++ b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx @@ -10,6 +10,8 @@ // Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573 // eslint-disable-next-line header/header import React from 'react'; +// TODO migrate to @testing-library/react when SSR rendering possible +// See https://github.com/testing-library/react-testing-library/issues/1120 import {renderHook} from '@testing-library/react-hooks/server'; import {DocusaurusContextProvider} from '../docusaurusContext'; import useDocusaurusContext from '../exports/useDocusaurusContext'; diff --git a/packages/docusaurus/src/client/__tests__/routeContext.test.tsx b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx index 558cf157a66f..bb9e3078b5eb 100644 --- a/packages/docusaurus/src/client/__tests__/routeContext.test.tsx +++ b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx @@ -6,6 +6,8 @@ */ import React from 'react'; +// TODO migrate to @testing-library/react when SSR rendering possible +// See https://github.com/testing-library/react-testing-library/issues/1120 import {renderHook} from '@testing-library/react-hooks/server'; import {RouteContextProvider} from '../routeContext'; import useRouteContext from '../exports/useRouteContext'; diff --git a/packages/docusaurus/src/client/clientEntry.tsx b/packages/docusaurus/src/client/clientEntry.tsx index 8e53d92e1021..f8145135c14f 100644 --- a/packages/docusaurus/src/client/clientEntry.tsx +++ b/packages/docusaurus/src/client/clientEntry.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import {BrowserRouter} from 'react-router-dom'; import {HelmetProvider} from 'react-helmet-async'; @@ -21,26 +21,38 @@ declare global { } } +const hydrate = Boolean(process.env.HYDRATE_CLIENT_ENTRY); + // Client-side render (e.g: running in browser) to become single-page // application (SPA). if (ExecutionEnvironment.canUseDOM) { window.docusaurus = docusaurus; - // For production, attempt to hydrate existing markup for performant - // first-load experience. - // For development, there is no existing markup so we had to render it. - // We also preload async component to avoid first-load loading screen. - const renderMethod = - process.env.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render; - preload(window.location.pathname).then(() => { - renderMethod( - - - - - , - document.getElementById('__docusaurus'), - ); - }); + const container = document.getElementById('__docusaurus')!; + + const app = ( + + + + + + ); + + const onRecoverableError = (error: unknown): void => { + console.error('Docusaurus React Root onRecoverableError:', error); + }; + + const renderApp = () => { + if (hydrate) { + ReactDOM.hydrateRoot(container, app, { + onRecoverableError, + }); + } else { + const root = ReactDOM.createRoot(container, {onRecoverableError}); + root.render(app); + } + }; + + preload(window.location.pathname).then(renderApp); // Webpack Hot Module Replacement API if (module.hot) { diff --git a/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx b/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx index d1c469cdc0ed..61b3bd7231d6 100644 --- a/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx @@ -6,6 +6,8 @@ */ import React from 'react'; +// TODO migrate to @testing-library/react when SSR rendering possible +// See https://github.com/testing-library/react-testing-library/issues/1120 import {renderHook} from '@testing-library/react-hooks/server'; import {RouteContextProvider} from '../../routeContext'; import useRouteContext from '../useRouteContext'; diff --git a/packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx b/packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx new file mode 100644 index 000000000000..c816b9c02a8b --- /dev/null +++ b/packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {useEffect, useLayoutEffect} from 'react'; +import ExecutionEnvironment from './ExecutionEnvironment'; + +/** + * This hook is like `useLayoutEffect`, but without the SSR warning. + * It seems hacky but it's used in many React libs (Redux, Formik...). + * Also mentioned here: https://github.com/facebook/react/issues/16956 + * + * It is useful when you need to update a ref as soon as possible after a React + * render (before `useEffect`). + * + * TODO should become unnecessary in React v19? + * https://github.com/facebook/react/pull/26395 + * This was added in core with Docusaurus v3 but kept undocumented on purpose + */ +const useIsomorphicLayoutEffect = ExecutionEnvironment.canUseDOM + ? useLayoutEffect + : useEffect; + +export default useIsomorphicLayoutEffect; diff --git a/packages/docusaurus/src/client/serverEntry.tsx b/packages/docusaurus/src/client/serverEntry.tsx index 975cb15a468a..2d67558926e7 100644 --- a/packages/docusaurus/src/client/serverEntry.tsx +++ b/packages/docusaurus/src/client/serverEntry.tsx @@ -12,11 +12,11 @@ import fs from 'fs-extra'; import _ from 'lodash'; import * as eta from 'eta'; import {StaticRouter} from 'react-router-dom'; -import ReactDOMServer from 'react-dom/server'; import {HelmetProvider, type FilledContext} from 'react-helmet-async'; import {getBundles, type Manifest} from 'react-loadable-ssr-addon-v5-slorber'; import Loadable from 'react-loadable'; import {minify} from 'html-minifier-terser'; +import {renderStaticApp} from './serverRenderer'; import preload from './preload'; import App from './App'; import { @@ -97,7 +97,8 @@ async function doRender(locals: Locals & {path: string}) { const helmetContext = {}; const linksCollector = createStatefulLinksCollector(); - const appHtml = ReactDOMServer.renderToString( + + const app = ( // @ts-expect-error: we are migrating away from react-loadable anyways modules.add(moduleName)}> @@ -107,8 +108,10 @@ async function doRender(locals: Locals & {path: string}) { - , + ); + + const appHtml = await renderStaticApp(app); onLinksCollected(location, linksCollector.getCollectedLinks()); const {helmet} = helmetContext as FilledContext; diff --git a/packages/docusaurus/src/client/serverRenderer.tsx b/packages/docusaurus/src/client/serverRenderer.tsx new file mode 100644 index 000000000000..435418024f3a --- /dev/null +++ b/packages/docusaurus/src/client/serverRenderer.tsx @@ -0,0 +1,80 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {ReactNode} from 'react'; +import {renderToPipeableStream} from 'react-dom/server'; +import {Writable} from 'stream'; + +export async function renderStaticApp(app: ReactNode): Promise { + // Inspired from + // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation + // https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/static-entry.js + const writableStream = new WritableAsPromise(); + + const {pipe} = renderToPipeableStream(app, { + onError(error) { + writableStream.destroy(error as Error); + }, + onAllReady() { + pipe(writableStream); + }, + }); + + return writableStream.getPromise(); +} + +// WritableAsPromise inspired by https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/server-utils/writable-as-promise.js + +/* eslint-disable no-underscore-dangle */ +class WritableAsPromise extends Writable { + private _output: string; + private _deferred: { + promise: Promise | null; + resolve: (value: string) => void; + reject: (reason: Error) => void; + }; + + constructor() { + super(); + this._output = ``; + this._deferred = { + promise: null, + resolve: () => null, + reject: () => null, + }; + this._deferred.promise = new Promise((resolve, reject) => { + this._deferred.resolve = resolve; + this._deferred.reject = reject; + }); + } + + override _write( + chunk: {toString: () => string}, + _enc: unknown, + next: () => void, + ) { + this._output += chunk.toString(); + next(); + } + + override _destroy(error: Error | null, next: (error?: Error | null) => void) { + if (error instanceof Error) { + this._deferred.reject(error); + } else { + next(); + } + } + + override end() { + this._deferred.resolve(this._output); + return this.destroy(); + } + + getPromise(): Promise { + return this._deferred.promise!; + } +} diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index f79912cc545b..3b20912150ec 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -35,6 +35,7 @@ export type BuildCLIOptions = Pick< > & { bundleAnalyzer?: boolean; minify?: boolean; + dev?: boolean; }; export async function build( @@ -49,6 +50,11 @@ export async function build( process.env.BABEL_ENV = 'production'; process.env.NODE_ENV = 'production'; process.env.DOCUSAURUS_CURRENT_LOCALE = cliOptions.locale; + if (cliOptions.dev) { + logger.info`Building in dev mode`; + process.env.BABEL_ENV = 'development'; + process.env.NODE_ENV = 'development'; + } const siteDir = await fs.realpath(siteDirParam); @@ -158,7 +164,7 @@ async function buildLocale({ 'client-manifest.json', ); let clientConfig: Configuration = merge( - await createClientConfig(props, cliOptions.minify), + await createClientConfig(props, cliOptions.minify, true), { plugins: [ // Remove/clean build folders before building bundles. diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts index 9a12e15eef45..1e0e7bc106b0 100644 --- a/packages/docusaurus/src/commands/start.ts +++ b/packages/docusaurus/src/commands/start.ts @@ -123,7 +123,7 @@ export async function start( ); let config: webpack.Configuration = merge( - await createClientConfig(props, cliOptions.minify), + await createClientConfig(props, cliOptions.minify, false), { watchOptions: { ignored: /node_modules\/(?!@docusaurus)/, diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap index 6fdffc625968..9567720d587f 100644 --- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap +++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap @@ -19,6 +19,7 @@ exports[`base webpack config creates webpack aliases 1`] = ` "@docusaurus/useDocusaurusContext": "../../../../client/exports/useDocusaurusContext.ts", "@docusaurus/useGlobalData": "../../../../client/exports/useGlobalData.ts", "@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts", + "@docusaurus/useIsomorphicLayoutEffect": "../../../../client/exports/useIsomorphicLayoutEffect.tsx", "@docusaurus/useRouteContext": "../../../../client/exports/useRouteContext.tsx", "@generated": "../../../../../../..", "@site": "", diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap index 38a22a306e14..c9738c847d56 100644 --- a/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap @@ -19,6 +19,7 @@ exports[`getDocusaurusAliases returns appropriate webpack aliases 1`] = ` "@docusaurus/useDocusaurusContext": "/packages/docusaurus/src/client/exports/useDocusaurusContext.ts", "@docusaurus/useGlobalData": "/packages/docusaurus/src/client/exports/useGlobalData.ts", "@docusaurus/useIsBrowser": "/packages/docusaurus/src/client/exports/useIsBrowser.ts", + "@docusaurus/useIsomorphicLayoutEffect": "/packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx", "@docusaurus/useRouteContext": "/packages/docusaurus/src/client/exports/useRouteContext.tsx", } `; diff --git a/packages/docusaurus/src/webpack/client.ts b/packages/docusaurus/src/webpack/client.ts index 86274d1b6991..4ca68b68b9bc 100644 --- a/packages/docusaurus/src/webpack/client.ts +++ b/packages/docusaurus/src/webpack/client.ts @@ -9,6 +9,7 @@ import path from 'path'; import logger from '@docusaurus/logger'; import merge from 'webpack-merge'; import WebpackBar from 'webpackbar'; +import {DefinePlugin} from 'webpack'; import {createBaseConfig} from './base'; import ChunkAssetPlugin from './plugins/ChunkAssetPlugin'; import {formatStatsErrorMessage} from './utils'; @@ -18,6 +19,7 @@ import type {Configuration} from 'webpack'; export default async function createClientConfig( props: Props, minify: boolean = true, + hydrate: boolean = true, ): Promise { const isBuilding = process.argv[2] === 'build'; const config = await createBaseConfig(props, false, minify); @@ -33,6 +35,9 @@ export default async function createClientConfig( runtimeChunk: true, }, plugins: [ + new DefinePlugin({ + 'process.env.HYDRATE_CLIENT_ENTRY': JSON.stringify(hydrate), + }), new ChunkAssetPlugin(), // Show compilation progress bar and build time. new WebpackBar({ diff --git a/packages/docusaurus/src/webpack/templates/ssr.html.template.ts b/packages/docusaurus/src/webpack/templates/ssr.html.template.ts index d72872abea00..f885f89c11fa 100644 --- a/packages/docusaurus/src/webpack/templates/ssr.html.template.ts +++ b/packages/docusaurus/src/webpack/templates/ssr.html.template.ts @@ -24,9 +24,7 @@ export default ` > <%~ it.preBodyTags %> -
- <%~ it.appHtml %> -
+
<%~ it.appHtml %>
<%~ it.postBodyTags %> diff --git a/project-words.txt b/project-words.txt index 0494a3c0575b..00473d3680e6 100644 --- a/project-words.txt +++ b/project-words.txt @@ -245,6 +245,7 @@ philpl photoshop picocolors picomatch +Pipeable playbtn pluggable plushie diff --git a/website/_dogfooding/_docs tests/tests/custom-props/index.mdx b/website/_dogfooding/_docs tests/tests/custom-props/index.mdx index 5204dd5cccf5..55d90dfb0fa5 100644 --- a/website/_dogfooding/_docs tests/tests/custom-props/index.mdx +++ b/website/_dogfooding/_docs tests/tests/custom-props/index.mdx @@ -10,7 +10,7 @@ export const DocPropsList = ({items}) => ( Custom Props {items.map((item, index) => ( - + {item.label} {JSON.stringify(item.customProps)} diff --git a/website/_dogfooding/_pages tests/index.mdx b/website/_dogfooding/_pages tests/index.mdx index 09c2b2cc4f51..4f7c2889bff1 100644 --- a/website/_dogfooding/_pages tests/index.mdx +++ b/website/_dogfooding/_pages tests/index.mdx @@ -25,6 +25,7 @@ import Readme from "../README.mdx" ### Other tests +- [React 18](/tests/pages/react-18) - [Crash test](/tests/pages/crashTest) - [Code block tests](/tests/pages/code-block-tests) - [Link tests](/tests/pages/link-tests) diff --git a/website/_dogfooding/_pages tests/react-18/_components/heavyComponent.tsx b/website/_dogfooding/_pages tests/react-18/_components/heavyComponent.tsx new file mode 100644 index 000000000000..f7b9dbda167a --- /dev/null +++ b/website/_dogfooding/_pages tests/react-18/_components/heavyComponent.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +export default function HeavyComponent(): JSX.Element { + return ( +
+ +
+ ); +} diff --git a/website/_dogfooding/_pages tests/react-18/index.tsx b/website/_dogfooding/_pages tests/react-18/index.tsx new file mode 100644 index 000000000000..1f44692b5247 --- /dev/null +++ b/website/_dogfooding/_pages tests/react-18/index.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {Suspense} from 'react'; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import Layout from '@theme/Layout'; +import Heading from '@theme/Heading'; + +const HeavyComponentLazy = React.lazy( + // @ts-expect-error: not sure why TS is unhappy about this... + () => import('./_components/heavyComponent'), +); + +export default function React18Tests(): JSX.Element { + return ( + +
+ React 18 tests + +
+ + {'Suspense > HeavyComponent'} + + + + +
+ + {'BrowserOnly > Suspense > HeavyComponent'} + + {() => ( + + + + )} + + +
+ + {'Suspense > BrowserOnly > HeavyComponent'} + + {() => } + +
+
+ ); +} diff --git a/website/docs/cli.mdx b/website/docs/cli.mdx index f6ca2dee2293..de6e4babb64a 100644 --- a/website/docs/cli.mdx +++ b/website/docs/cli.mdx @@ -37,6 +37,7 @@ Builds and serves a preview of your site locally with [Webpack Dev Server](https | Name | Default | Description | | --- | --- | --- | +| `--dev` | | Builds in dev mode, including full React error messages. | | `--port` | `3000` | Specifies the port of the dev server. | | `--host` | `localhost` | Specify a host to use. For example, if you want your server to be accessible externally, you can use `--host 0.0.0.0`. | | `--hot-only` | `false` | Enables Hot Module Replacement without page refresh as a fallback in case of build failures. More information [here](https://webpack.js.org/configuration/dev-server/#devserverhotonly). | diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 24b5b7be34ab..3fc8ff528aac 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -129,6 +129,11 @@ module.exports = async function createConfigAsync() { syntax: 'typescript', tsx: true, }, + transform: { + react: { + runtime: 'automatic', + }, + }, target: 'es2017', }, module: { @@ -258,7 +263,7 @@ module.exports = async function createConfigAsync() { [ 'pwa', { - debug: isDeployPreview, + // debug: isDeployPreview, offlineModeActivationStrategies: [ 'appInstalled', 'standalone', diff --git a/website/package.json b/website/package.json index a1009bf646a1..6fa5f035f3a0 100644 --- a/website/package.json +++ b/website/package.json @@ -57,8 +57,8 @@ "netlify-plugin-cache": "^1.0.3", "pure-react-carousel": "^1.30.1", "raw-loader": "^4.0.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-lite-youtube-embed": "^2.3.52", "react-medium-image-zoom": "^5.1.3", "react-popper": "^2.3.0", @@ -84,7 +84,7 @@ }, "devDependencies": { "@docusaurus/eslint-plugin": "^3.0.0-alpha.0", - "@tsconfig/docusaurus": "^1.0.5", + "@tsconfig/docusaurus": "^1.0.7", "@types/jest": "^29.4.0", "cross-env": "^7.0.3", "rimraf": "^3.0.2" diff --git a/website/src/components/BrowserWindow/index.tsx b/website/src/components/BrowserWindow/index.tsx index b060a4b3a74f..0d2312ee8e6b 100644 --- a/website/src/components/BrowserWindow/index.tsx +++ b/website/src/components/BrowserWindow/index.tsx @@ -51,17 +51,3 @@ export default function BrowserWindow({
); } - -// Quick and dirty component, to improve later if needed -export function IframeWindow({url}: {url: string}): JSX.Element { - return ( -
- -