From ae474c2bab7dc269ca9ce292066e90fccdac2efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Wed, 12 Jan 2022 12:04:21 +0100 Subject: [PATCH 01/27] Add possibility of using new React Root Api The new React Root Api (ReactDOM.createRoot) allows to use the newest concurrent features of React 18. A feature flag was added to enable/disable the new Root API via storybook options. --- app/react/package.json | 4 +- app/react/src/client/preview/render.tsx | 82 ++++++++++++++++++++++++- lib/core-common/src/types.ts | 5 ++ yarn.lock | 4 +- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/app/react/package.json b/app/react/package.json index db5af8dbe873..ae3afb764adb 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -79,8 +79,8 @@ }, "peerDependencies": { "@babel/core": "^7.11.5", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" + "react": "^16.8.0 || >=17.0.0", + "react-dom": "^16.8.0 || >=17.0.0" }, "peerDependenciesMeta": { "@babel/core": { diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 1a5e10e556ba..29c911b2ab54 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -13,7 +13,18 @@ import { ArgsStoryFn } from '@storybook/csf'; import { StoryContext } from './types'; import { ReactFramework } from './types-6-0'; -const { FRAMEWORK_OPTIONS } = global; +const { FRAMEWORK_OPTIONS, FEATURES } = global; + +const isNewReactRootApiEnabled: boolean = FEATURES?.newReactRootApi ?? false; + +// TODO: Remove IRoot declaration as soon as @types/react v17.x is used +interface IRoot { + render(children: React.ReactChild | Iterable): void; + unmount(): void; +} + +// A map of all rendered React 18 nodes +const nodes = new Map(); export const render: ArgsStoryFn = (args, context) => { const { id, component: Component } = context; @@ -28,9 +39,51 @@ export const render: ArgsStoryFn = (args, context) => { const renderElement = async (node: ReactElement, el: Element) => new Promise((resolve) => { - ReactDOM.render(node, el, () => resolve(null)); + // Create Root Element conditionally for new React 18 Root Api + const root = getReactRoot(el); + + if (root) { + root.render( + { + resolve(null); + }} + > + {node} + + ); + } else { + ReactDOM.render(node, el, () => resolve(null)); + } }); +const unmountElement = (el: Element) => { + const root = nodes.get(el); + if (root) { + root.unmount(); + } else { + ReactDOM.unmountComponentAtNode(el); + } +}; + +const getReactRoot = (el: Element): IRoot | null => { + if (isNewReactRootApiEnabled) { + if (!(ReactDOM as any).createRoot) { + throw new Error( + "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newReactRootApi' to false" + ); + } + let root = nodes.get(el); + if (!root) { + root = (ReactDOM as any).createRoot(el) as IRoot; + nodes.set(el, root); + } + return root; + } + + return null; +}; + class ErrorBoundary extends ReactComponent<{ showException: (err: Error) => void; showMain: () => void; @@ -63,6 +116,29 @@ class ErrorBoundary extends ReactComponent<{ } } +// Will be used to execute a callback function as soon as the React Elements are mounted. +// This is necessary for the new React Root Api, because passing a callback function to +// the Root API's render function is not possible anymore. +class CallbackWrapper extends ReactComponent<{ callback: () => void }, { isDivVisible: boolean }> { + state = { + isDivVisible: false, + }; + + onMount() { + this.props.callback(); + this.setState({ isDivVisible: true }); + } + + render() { + return ( + <> + {this.props.children} + {this.state.isDivVisible ?
: null} + + ); + } +} + const Wrapper = FRAMEWORK_OPTIONS?.strictMode ? StrictMode : Fragment; export async function renderToDOM( @@ -92,7 +168,7 @@ export async function renderToDOM( // https://github.com/storybookjs/react-storybook/issues/81 // (This is not the case when we change args or globals to the story however) if (forceRemount) { - ReactDOM.unmountComponentAtNode(domElement); + unmountElement(domElement); } await renderElement(element, domElement); diff --git a/lib/core-common/src/types.ts b/lib/core-common/src/types.ts index 94155d56c345..97eb5f76cb03 100644 --- a/lib/core-common/src/types.ts +++ b/lib/core-common/src/types.ts @@ -373,6 +373,11 @@ export interface StorybookConfig { * Will be removed in 7.0. */ warnOnLegacyHierarchySeparator?: boolean; + /** + * Uses React 18's new root API (ReactDOM.createRoot) + * The new root API happens to be the gateway for accessing new features of React 18 and adds out-of-the-box improvements. + */ + newReactRootApi?: boolean; }; /** diff --git a/yarn.lock b/yarn.lock index 4091555e415e..dd2c3a02a792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8233,8 +8233,8 @@ __metadata: webpack: 4 peerDependencies: "@babel/core": ^7.11.5 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 + react: ^16.8.0 || >=17.0.0 + react-dom: ^16.8.0 || >=17.0.0 peerDependenciesMeta: "@babel/core": optional: true From fee84592a0b1267f6a51b5b9d0febf231adbde49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 13 Jan 2022 08:42:02 +0100 Subject: [PATCH 02/27] Reverse logic of callback div in CallbackWrapper --- app/react/src/client/preview/render.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 29c911b2ab54..6953edb4771d 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -121,12 +121,12 @@ class ErrorBoundary extends ReactComponent<{ // the Root API's render function is not possible anymore. class CallbackWrapper extends ReactComponent<{ callback: () => void }, { isDivVisible: boolean }> { state = { - isDivVisible: false, + isDivVisible: true, }; onMount() { this.props.callback(); - this.setState({ isDivVisible: true }); + this.setState({ isDivVisible: false }); } render() { From fc5128fd805aa4e413bbb4198cc61eeb24b4542f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 13 Jan 2022 08:48:10 +0100 Subject: [PATCH 03/27] Use FRAMEWORK_OPTIONS to use new React Root API conditionally --- app/react/src/client/preview/render.tsx | 6 ++---- app/react/types/index.ts | 5 +++++ lib/core-common/src/types.ts | 5 ----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 6953edb4771d..e3383b5d5e45 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -13,9 +13,7 @@ import { ArgsStoryFn } from '@storybook/csf'; import { StoryContext } from './types'; import { ReactFramework } from './types-6-0'; -const { FRAMEWORK_OPTIONS, FEATURES } = global; - -const isNewReactRootApiEnabled: boolean = FEATURES?.newReactRootApi ?? false; +const { FRAMEWORK_OPTIONS } = global; // TODO: Remove IRoot declaration as soon as @types/react v17.x is used interface IRoot { @@ -67,7 +65,7 @@ const unmountElement = (el: Element) => { }; const getReactRoot = (el: Element): IRoot | null => { - if (isNewReactRootApiEnabled) { + if (FRAMEWORK_OPTIONS?.newReactRootApi) { if (!(ReactDOM as any).createRoot) { throw new Error( "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newReactRootApi' to false" diff --git a/app/react/types/index.ts b/app/react/types/index.ts index 3e1ddcf15273..c2eb198f04cd 100644 --- a/app/react/types/index.ts +++ b/app/react/types/index.ts @@ -7,5 +7,10 @@ export interface StorybookConfig extends BaseConfig { reactOptions?: { fastRefresh?: boolean; strictMode?: boolean; + /** + * Uses React 18's new root API (ReactDOM.createRoot) + * The new root API happens to be the gateway for accessing new features of React 18 and adds out-of-the-box improvements. + */ + newReactRootApi?: boolean; }; } diff --git a/lib/core-common/src/types.ts b/lib/core-common/src/types.ts index 97eb5f76cb03..94155d56c345 100644 --- a/lib/core-common/src/types.ts +++ b/lib/core-common/src/types.ts @@ -373,11 +373,6 @@ export interface StorybookConfig { * Will be removed in 7.0. */ warnOnLegacyHierarchySeparator?: boolean; - /** - * Uses React 18's new root API (ReactDOM.createRoot) - * The new root API happens to be the gateway for accessing new features of React 18 and adds out-of-the-box improvements. - */ - newReactRootApi?: boolean; }; /** From 983534d64ef3520f06d3d29299535d1526cec680 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 26 Jan 2022 20:21:51 +0100 Subject: [PATCH 04/27] docs: Add documentation to faq page --- docs/faq.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index c6f2923e686d..83b72f51329c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -83,6 +83,27 @@ module.exports = { 💡 Fast Refresh only works in development mode with React 16.10 or higher.
+### How do I setup the new React Context Root API with Storybook? + +The new React Context Root API which was introduced in React 17/18 is an opt-in feature that can be used in Storybook React. + +You can set the following properties in your `.storybook/main.js` files: + +```js +module.exports = { + reactOptions: { + newReactRootApi: true, + }, +}; +``` + +After enabling it, it is possible to use React's newest [concurrent features](https://reactjs.org/docs/concurrent-mode-intro.html). + + +
+💡 The new React Context API (React.createRoot) only works with React 17.x and above. +
+ ### Why is there no addons channel? A common error is that an addon tries to access the "channel", but the channel is not set. It can happen in a few different cases: From 04791393b58514dbe6762d4abd14ab7a5a5de40f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 10 Feb 2022 12:15:27 +0100 Subject: [PATCH 05/27] Update docs/faq.md Co-authored-by: Michael Shilman --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 83b72f51329c..f8c0528ccfb5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -85,7 +85,7 @@ module.exports = { ### How do I setup the new React Context Root API with Storybook? -The new React Context Root API which was introduced in React 17/18 is an opt-in feature that can be used in Storybook React. +The new [React Root API](https://reactjs.org/docs/concurrent-mode-reference.html) which was introduced in React 18 is an opt-in feature that can be used in Storybook React. You can set the following properties in your `.storybook/main.js` files: From d1d8cffb972b2b874bf31324764d67ff4759e81b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 10 Feb 2022 12:16:00 +0100 Subject: [PATCH 06/27] Update docs/faq.md Co-authored-by: Michael Shilman --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index f8c0528ccfb5..2cfc32b7a84f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -101,7 +101,7 @@ After enabling it, it is possible to use React's newest [concurrent features](ht
-💡 The new React Context API (React.createRoot) only works with React 17.x and above. +💡 The new React Root API (React.createRoot) only works with React 18 and above.
### Why is there no addons channel? From b50177d62a9dba0456db1d84ad0edf0d3746dc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 12:20:18 +0100 Subject: [PATCH 07/27] Rename newREactRootApi to newRootApi --- app/react/src/client/preview/render.tsx | 4 ++-- docs/faq.md | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index e3383b5d5e45..c7e4393a0b66 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -65,10 +65,10 @@ const unmountElement = (el: Element) => { }; const getReactRoot = (el: Element): IRoot | null => { - if (FRAMEWORK_OPTIONS?.newReactRootApi) { + if (FRAMEWORK_OPTIONS?.newRootApi) { if (!(ReactDOM as any).createRoot) { throw new Error( - "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newReactRootApi' to false" + "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newRootApi' to false" ); } let root = nodes.get(el); diff --git a/docs/faq.md b/docs/faq.md index 2cfc32b7a84f..3222f2a5a046 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -92,14 +92,13 @@ You can set the following properties in your `.storybook/main.js` files: ```js module.exports = { reactOptions: { - newReactRootApi: true, + newRootApi: true, }, }; ``` After enabling it, it is possible to use React's newest [concurrent features](https://reactjs.org/docs/concurrent-mode-intro.html). -
💡 The new React Root API (React.createRoot) only works with React 18 and above.
@@ -398,4 +397,4 @@ const StoryMeta: ComponentMeta = { export default meta; ``` -Although valid, it introduces additional boilerplate code to the story definition. Instead, we're working towards implementing a safer mechanism based on what's currently being discussed in the following [issue](https://github.com/microsoft/TypeScript/issues/7481). Once the feature is released, we'll migrate our existing examples and documentation accordingly. \ No newline at end of file +Although valid, it introduces additional boilerplate code to the story definition. Instead, we're working towards implementing a safer mechanism based on what's currently being discussed in the following [issue](https://github.com/microsoft/TypeScript/issues/7481). Once the feature is released, we'll migrate our existing examples and documentation accordingly. From afed20a03231998e1a751afa560aefe08f18ee97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 12:21:00 +0100 Subject: [PATCH 08/27] Prevent memory leaks and possible race conditions --- app/react/src/client/preview/render.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index c7e4393a0b66..50a07e953b44 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -57,8 +57,9 @@ const renderElement = async (node: ReactElement, el: Element) => const unmountElement = (el: Element) => { const root = nodes.get(el); - if (root) { + if (root && FRAMEWORK_OPTIONS?.newRootApi) { root.unmount(); + nodes.delete(el); } else { ReactDOM.unmountComponentAtNode(el); } From 505f99b1f3c16c37f103bd08d95417679c12f2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 12:43:39 +0100 Subject: [PATCH 09/27] Transform class-based CallbackWrapper to function-based component --- app/react/src/client/preview/render.tsx | 49 ++++++++++++++++--------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 50a07e953b44..83da2dabc14f 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -5,6 +5,9 @@ import React, { ReactElement, StrictMode, Fragment, + useState, + useCallback, + useEffect, } from 'react'; import ReactDOM from 'react-dom'; import { RenderContext } from '@storybook/store'; @@ -118,24 +121,34 @@ class ErrorBoundary extends ReactComponent<{ // Will be used to execute a callback function as soon as the React Elements are mounted. // This is necessary for the new React Root Api, because passing a callback function to // the Root API's render function is not possible anymore. -class CallbackWrapper extends ReactComponent<{ callback: () => void }, { isDivVisible: boolean }> { - state = { - isDivVisible: true, - }; - - onMount() { - this.props.callback(); - this.setState({ isDivVisible: false }); - } - - render() { - return ( - <> - {this.props.children} - {this.state.isDivVisible ?
: null} - - ); - } +function CallbackWrapper({ + callback, + children, +}: { + callback: () => void; + children: React.ReactNode; +}) { + const [isRefApplied, setRefApplied] = useState(false); + + useEffect(() => { + if (isRefApplied) { + callback(); + } + }, [isRefApplied]); + + return ( + <> + {children} + {!isRefApplied ? ( +
{ + setRefApplied(true); + }} + style={{ display: 'none' }} + /> + ) : null} + + ); } const Wrapper = FRAMEWORK_OPTIONS?.strictMode ? StrictMode : Fragment; From ca4bceda86b9994dfaef4b389f03e1ecf4452945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 12:58:00 +0100 Subject: [PATCH 10/27] Simplify structure of getReactRoot --- app/react/src/client/preview/render.tsx | 29 ++++++++++++++----------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 83da2dabc14f..63efab24bb50 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -69,21 +69,24 @@ const unmountElement = (el: Element) => { }; const getReactRoot = (el: Element): IRoot | null => { - if (FRAMEWORK_OPTIONS?.newRootApi) { - if (!(ReactDOM as any).createRoot) { - throw new Error( - "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newRootApi' to false" - ); - } - let root = nodes.get(el); - if (!root) { - root = (ReactDOM as any).createRoot(el) as IRoot; - nodes.set(el, root); - } - return root; + if (!FRAMEWORK_OPTIONS?.newRootApi) { + return null; + } + + if (!(ReactDOM as any).createRoot) { + throw new Error( + "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newRootApi' to false" + ); + } + + let root = nodes.get(el); + + if (!root) { + root = (ReactDOM as any).createRoot(el) as IRoot; + nodes.set(el, root); } - return null; + return root; }; class ErrorBoundary extends ReactComponent<{ From 4899f371ccf4a6f1c75c37eb8f53fc4b66d44305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 13:02:05 +0100 Subject: [PATCH 11/27] Use setTimeout instead of CallbackWrapper --- app/react/src/client/preview/render.tsx | 49 ++----------------------- 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 63efab24bb50..405213cea258 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -5,9 +5,6 @@ import React, { ReactElement, StrictMode, Fragment, - useState, - useCallback, - useEffect, } from 'react'; import ReactDOM from 'react-dom'; import { RenderContext } from '@storybook/store'; @@ -44,15 +41,10 @@ const renderElement = async (node: ReactElement, el: Element) => const root = getReactRoot(el); if (root) { - root.render( - { - resolve(null); - }} - > - {node} - - ); + root.render(node); + setTimeout(() => { + resolve(null); + }, 0); } else { ReactDOM.render(node, el, () => resolve(null)); } @@ -121,39 +113,6 @@ class ErrorBoundary extends ReactComponent<{ } } -// Will be used to execute a callback function as soon as the React Elements are mounted. -// This is necessary for the new React Root Api, because passing a callback function to -// the Root API's render function is not possible anymore. -function CallbackWrapper({ - callback, - children, -}: { - callback: () => void; - children: React.ReactNode; -}) { - const [isRefApplied, setRefApplied] = useState(false); - - useEffect(() => { - if (isRefApplied) { - callback(); - } - }, [isRefApplied]); - - return ( - <> - {children} - {!isRefApplied ? ( -
{ - setRefApplied(true); - }} - style={{ display: 'none' }} - /> - ) : null} - - ); -} - const Wrapper = FRAMEWORK_OPTIONS?.strictMode ? StrictMode : Fragment; export async function renderToDOM( From 5e3f7110e7d28e188223bda55016f6c75655d798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Thu, 10 Feb 2022 13:16:49 +0100 Subject: [PATCH 12/27] Rename types field from newReactRootApi to newRootApi --- app/react/types/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/react/types/index.ts b/app/react/types/index.ts index c2eb198f04cd..b758001160a4 100644 --- a/app/react/types/index.ts +++ b/app/react/types/index.ts @@ -11,6 +11,6 @@ export interface StorybookConfig extends BaseConfig { * Uses React 18's new root API (ReactDOM.createRoot) * The new root API happens to be the gateway for accessing new features of React 18 and adds out-of-the-box improvements. */ - newReactRootApi?: boolean; + newRootApi?: boolean; }; } From b908cf3b663058b2292be25e548394274bb6c03b Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 25 Mar 2022 21:02:59 +0100 Subject: [PATCH 13/27] add react 18 config to e2e scripts --- .circleci/config.yml | 2 +- lib/cli/src/repro-generators/configs.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d76302965bb3..581437df6375 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,7 +221,7 @@ jobs: name: Run E2E tests # Do not test CRA here because it's done in PnP part # TODO: Remove `web_components_typescript` as soon as Lit 2 stable is released - command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react + command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react react_18 no_output_timeout: 5m - store_artifacts: path: /tmp/cypress-record diff --git a/lib/cli/src/repro-generators/configs.ts b/lib/cli/src/repro-generators/configs.ts index c73d473c838f..9d608e6dbe69 100644 --- a/lib/cli/src/repro-generators/configs.ts +++ b/lib/cli/src/repro-generators/configs.ts @@ -64,6 +64,19 @@ export const react: Parameters = { additionalDeps: ['prop-types'], }; +export const react_18: Parameters = { + framework: 'react', + name: 'react_18', + version: 'rc', + generator: fromDeps('react@rc', 'react-dom@rc'), + additionalDeps: ['prop-types'], + mainOverrides: { + reactOptions: { + newRootApi: true, + }, + }, +}; + export const react_typescript: Parameters = { framework: 'react', name: 'react_typescript', From 544dfd58f90a5824f173d41b409729c134b16926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Fri, 1 Apr 2022 16:19:05 +0200 Subject: [PATCH 14/27] Adjust loading of new root api to newest React 18.0.0 release --- .circleci/config.yml | 2 +- app/react/src/client/preview/render.tsx | 12 +++++++++--- lib/cli/src/repro-generators/configs.ts | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 581437df6375..20168975d5e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,7 +221,7 @@ jobs: name: Run E2E tests # Do not test CRA here because it's done in PnP part # TODO: Remove `web_components_typescript` as soon as Lit 2 stable is released - command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react react_18 + command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react react_new_root_api no_output_timeout: 5m - store_artifacts: path: /tmp/cypress-record diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 405213cea258..65c69a24915e 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -6,9 +6,11 @@ import React, { StrictMode, Fragment, } from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM, { version as reactDomVersion } from 'react-dom'; + import { RenderContext } from '@storybook/store'; import { ArgsStoryFn } from '@storybook/csf'; +import { gte, coerce } from '@storybook/semver'; import { StoryContext } from './types'; import { ReactFramework } from './types-6-0'; @@ -60,12 +62,15 @@ const unmountElement = (el: Element) => { } }; +const canUseReactRoot = + gte(reactDomVersion, '18.0.0') || coerce(reactDomVersion)?.version === '18.0.0'; + const getReactRoot = (el: Element): IRoot | null => { if (!FRAMEWORK_OPTIONS?.newRootApi) { return null; } - if (!(ReactDOM as any).createRoot) { + if (!canUseReactRoot) { throw new Error( "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newRootApi' to false" ); @@ -74,7 +79,8 @@ const getReactRoot = (el: Element): IRoot | null => { let root = nodes.get(el); if (!root) { - root = (ReactDOM as any).createRoot(el) as IRoot; + // eslint-disable-next-line global-require + root = require('react-dom/client').createRoot(el) as IRoot; nodes.set(el, root); } diff --git a/lib/cli/src/repro-generators/configs.ts b/lib/cli/src/repro-generators/configs.ts index 9d608e6dbe69..aec2b17d6c26 100644 --- a/lib/cli/src/repro-generators/configs.ts +++ b/lib/cli/src/repro-generators/configs.ts @@ -64,11 +64,11 @@ export const react: Parameters = { additionalDeps: ['prop-types'], }; -export const react_18: Parameters = { +export const react_new_root_api: Parameters = { framework: 'react', - name: 'react_18', - version: 'rc', - generator: fromDeps('react@rc', 'react-dom@rc'), + name: 'react_new_root_api', + version: 'latest', + generator: fromDeps('react', 'react-dom'), additionalDeps: ['prop-types'], mainOverrides: { reactOptions: { From aedb6f6956b20a6522465d1d69531c0454a9d2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Tue, 5 Apr 2022 11:24:57 +0200 Subject: [PATCH 15/27] Replace opt-in ReactRoot Api flag by opt-out flag --- .circleci/config.yml | 2 +- app/react/src/client/preview/render.tsx | 45 +++++++++++-------------- app/react/src/typings.d.ts | 42 +++++++++++++++++++++++ app/react/types/index.ts | 9 +++-- docs/faq.md | 18 +++------- lib/cli/src/repro-generators/configs.ts | 6 ++-- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 057bca9f3328..1be99922e2ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -207,7 +207,7 @@ jobs: name: Run E2E tests # Do not test CRA here because it's done in PnP part # TODO: Remove `web_components_typescript` as soon as Lit 2 stable is released - command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react react_new_root_api + command: yarn test:e2e-framework vue3 angular130 angular13 angular12 angular11 web_components_typescript web_components_lit2 react react_legacy_root_api no_output_timeout: 5m - store_artifacts: path: /tmp/cypress-record diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 65c69a24915e..6ceb90ab0433 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -7,6 +7,7 @@ import React, { Fragment, } from 'react'; import ReactDOM, { version as reactDomVersion } from 'react-dom'; +import type { Root as ReactRoot } from 'react-dom/client'; import { RenderContext } from '@storybook/store'; import { ArgsStoryFn } from '@storybook/csf'; @@ -17,14 +18,8 @@ import { ReactFramework } from './types-6-0'; const { FRAMEWORK_OPTIONS } = global; -// TODO: Remove IRoot declaration as soon as @types/react v17.x is used -interface IRoot { - render(children: React.ReactChild | Iterable): void; - unmount(): void; -} - // A map of all rendered React 18 nodes -const nodes = new Map(); +const nodes = new Map(); export const render: ArgsStoryFn = (args, context) => { const { id, component: Component } = context; @@ -37,11 +32,11 @@ export const render: ArgsStoryFn = (args, context) => { return ; }; -const renderElement = async (node: ReactElement, el: Element) => - new Promise((resolve) => { - // Create Root Element conditionally for new React 18 Root Api - const root = getReactRoot(el); +const renderElement = async (node: ReactElement, el: Element) => { + // Create Root Element conditionally for new React 18 Root Api + const root = await getReactRoot(el); + return new Promise((resolve) => { if (root) { root.render(node); setTimeout(() => { @@ -51,10 +46,18 @@ const renderElement = async (node: ReactElement, el: Element) => ReactDOM.render(node, el, () => resolve(null)); } }); +}; + +const canUseNewReactRootApi = + gte(reactDomVersion, '18.0.0') || coerce(reactDomVersion)?.version === '18.0.0'; + +const shouldUseNewRootApi = FRAMEWORK_OPTIONS?.legacyRootApi !== true; + +const isUsingNewReactRootApi = shouldUseNewRootApi && canUseNewReactRootApi; const unmountElement = (el: Element) => { const root = nodes.get(el); - if (root && FRAMEWORK_OPTIONS?.newRootApi) { + if (root && isUsingNewReactRootApi) { root.unmount(); nodes.delete(el); } else { @@ -62,25 +65,17 @@ const unmountElement = (el: Element) => { } }; -const canUseReactRoot = - gte(reactDomVersion, '18.0.0') || coerce(reactDomVersion)?.version === '18.0.0'; - -const getReactRoot = (el: Element): IRoot | null => { - if (!FRAMEWORK_OPTIONS?.newRootApi) { +const getReactRoot = async (el: Element): Promise => { + if (!isUsingNewReactRootApi) { return null; } - if (!canUseReactRoot) { - throw new Error( - "Your React version doesn't support the new React Root Api. Please use react and react-dom in version 18.x or set the storybook feature 'newRootApi' to false" - ); - } - let root = nodes.get(el); if (!root) { - // eslint-disable-next-line global-require - root = require('react-dom/client').createRoot(el) as IRoot; + const reactDomClient = await import('react-dom/client'); + root = reactDomClient.createRoot(el); + nodes.set(el, root); } diff --git a/app/react/src/typings.d.ts b/app/react/src/typings.d.ts index 4ff88fa9018c..cd1929c868b2 100644 --- a/app/react/src/typings.d.ts +++ b/app/react/src/typings.d.ts @@ -1,2 +1,44 @@ declare module '@storybook/semver'; declare module 'global'; + +// TODO: Replace, as soon as @types/react-dom 17.0.14 is used +// Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fb0f14b7a35cde26ffaa82e7536c062e593e9ae6/types/react-dom/client.d.ts +declare module 'react-dom/client' { + +import React = require('react'); + export interface HydrationOptions { + onHydrated?(suspenseInstance: Comment): void; + onDeleted?(suspenseInstance: Comment): void; + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface RootOptions { + /** + * Prefix for `useId`. + */ + identifierPrefix?: string; + onRecoverableError?: (error: unknown) => void; + } + + export interface Root { + render(children: React.ReactChild | Iterable): void; + unmount(): void; + } + + /** + * Replaces `ReactDOM.render` when the `.render` method is called and enables Concurrent Mode. + * + * @see https://reactjs.org/docs/concurrent-mode-reference.html#createroot + */ + export function createRoot(container: Element | Document | DocumentFragment | Comment, options?: RootOptions): Root; + + export function hydrateRoot( + container: Element | Document | DocumentFragment | Comment, + initialChildren: React.ReactChild | Iterable, + options?: HydrationOptions, + ): Root; +} diff --git a/app/react/types/index.ts b/app/react/types/index.ts index 9038d1d8d9f4..f2f6ac0a2fed 100644 --- a/app/react/types/index.ts +++ b/app/react/types/index.ts @@ -8,9 +8,12 @@ export interface StorybookConfig extends BaseConfig { fastRefresh?: boolean; strictMode?: boolean; /** - * Uses React 18's new root API (ReactDOM.createRoot) - * The new root API happens to be the gateway for accessing new features of React 18 and adds out-of-the-box improvements. + * Use React's legacy root API to mount components + * @description + * React has introduced a new root API with React 18.x to enable a whole set of new features (e.g. concurrent features) + * If this flag is true, the legacy Root API is used to mount components to make it easier to migrate step by step to React 18. + * @default false */ - newRootApi?: boolean; + legacyRootApi?: boolean; }; } diff --git a/docs/faq.md b/docs/faq.md index fa90beffabfc..c7e9e1f49bd3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -85,24 +85,18 @@ module.exports = { ### How do I setup the new React Context Root API with Storybook? -The new [React Root API](https://reactjs.org/docs/concurrent-mode-reference.html) which was introduced in React 18 is an opt-in feature that can be used in Storybook React. +If your installed React Version equals or is higher than 18.0.0, the new React Root API is automatically used and the newest React [concurrent features](https://reactjs.org/docs/concurrent-mode-intro.html) can be used. -You can set the following properties in your `.storybook/main.js` files: +You can opt-out from the new React Root API by setting the following property in your `.storybook/main.js` file: ```js module.exports = { reactOptions: { - newRootApi: true, + legacyRootApi: true, }, }; ``` -After enabling it, it is possible to use React's newest [concurrent features](https://reactjs.org/docs/concurrent-mode-intro.html). - -
-💡 The new React Root API (React.createRoot) only works with React 18 and above. -
- ### Why is there no addons channel? A common error is that an addon tries to access the "channel", but the channel is not set. It can happen in a few different cases: @@ -117,7 +111,6 @@ A common error is that an addon tries to access the "channel", but the channel i 2. In React Native, it's a special case documented in [#1192](https://github.com/storybookjs/storybook/issues/1192) - ### Why aren't Controls visible in the Canvas panel but visible in the Docs panel? If you're adding Storybook's dependencies manually, make sure you include the [`@storybook/addon-controls`](https://www.npmjs.com/package/@storybook/addon-controls) dependency in your project and reference it in your `.storybook/main.js` as follows: @@ -153,7 +146,7 @@ With the release of version 6.0, we updated our documentation as well. That does We're only covering versions 5.3 and 5.0 as they were important milestones for Storybook. If you want to go back in time a little more, you'll have to check the specific release in the monorepo. | Section | Page | Current Location | Version 5.3 location | Version 5.0 location | -|------------------|-------------------------------------------|------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------- | ----------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | Get started | Install | [See current documentation](../get-started/install.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/guides/quick-start-guide) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/guides/quick-start-guide) | | | What's a story | [See current documentation](../get-started/whats-a-story.md) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/guides) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/guides) | | | Browse Stories | [See current documentation](../get-started/browse-stories.md) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.3/docs/src/pages/guides) | [See versioned documentation for your framework](https://github.com/storybookjs/storybook/blob/release/5.0/docs/src/pages/guides) | @@ -213,6 +206,7 @@ We're only covering versions 5.3 and 5.0 as they were important milestones for S | | Stories/StoriesOF format (see note below) | [See current documentation](../../lib/core/docs/storiesOf.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/formats/storiesof-api) | Non existing feature or undocumented | | | Frameworks | [See current documentation](../api/new-frameworks.md) | Non existing feature or undocumented | Non existing feature or undocumented | | | CLI options | [See current documentation](../api/cli-options.md) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.3/docs/src/pages/configurations/cli-options) | [See versioned documentation](https://github.com/storybookjs/storybook/tree/release/5.0/docs/src/pages/configurations/cli-options) | +
With the release of version 5.3, we've updated how you can write your stories more compactly and easily. It doesn't mean that the storiesOf format has been removed. For the time being, we're still supporting it, and we have documentation for it. But be advised that this is bound to change in the future.
@@ -380,12 +374,10 @@ export default meta; Although valid, it introduces additional boilerplate code to the story definition. Instead, we're working towards implementing a safer mechanism based on what's currently being discussed in the following [issue](https://github.com/microsoft/TypeScript/issues/7481). Once the feature is released, we'll migrate our existing examples and documentation accordingly. - ## Why is Storybook's source loader returning undefined with curried functions? This is a known issue with Storybook. If you're interested in getting it fixed, open an issue with a [working reproduction](./contribute/how-to-reproduce) so that it can be triaged and fixed in future releases. - ## Why are my args no longer displaying the default values? Before version 6.3, unset args were set to the `argTypes.defaultValue` if specified or inferred from the component's properties (e.g., React's prop types, Angular inputs, Vue props). Starting with version 6.3, Storybook no longer infers default values but instead defines the arg's value as `undefined` when unset, allowing the framework to supply its default value. diff --git a/lib/cli/src/repro-generators/configs.ts b/lib/cli/src/repro-generators/configs.ts index aec2b17d6c26..69b89cf29c73 100644 --- a/lib/cli/src/repro-generators/configs.ts +++ b/lib/cli/src/repro-generators/configs.ts @@ -64,15 +64,15 @@ export const react: Parameters = { additionalDeps: ['prop-types'], }; -export const react_new_root_api: Parameters = { +export const react_legacy_root_api: Parameters = { framework: 'react', - name: 'react_new_root_api', + name: 'react_legacy_root_api', version: 'latest', generator: fromDeps('react', 'react-dom'), additionalDeps: ['prop-types'], mainOverrides: { reactOptions: { - newRootApi: true, + legacyRootApi: true, }, }, }; From c0b53a529601d0bed83cecc01a2bf493089cc26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Tue, 5 Apr 2022 12:42:43 +0200 Subject: [PATCH 16/27] Fix dynamic import resolution --- app/react/src/client/preview/render.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 6ceb90ab0433..569fbca458f0 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -73,7 +73,9 @@ const getReactRoot = async (el: Element): Promise => { let root = nodes.get(el); if (!root) { - const reactDomClient = await import('react-dom/client'); + // Skipping webpack's static analysis of import paths by defining the path value outside the import statement. + const reactDOMClientPath = 'react-dom/client'; + const reactDomClient = await import(reactDOMClientPath); root = reactDomClient.createRoot(el); nodes.set(el, root); From 677c189c413beee9b745a409e4fc04d559aeb3f4 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 5 Apr 2022 19:26:09 +0200 Subject: [PATCH 17/27] wip --- app/react/package.json | 1 + app/react/src/client/preview/render.tsx | 6 +-- .../src/server/framework-preset-react.ts | 11 +++++- examples/react-ts/package.json | 4 +- .../src/preview/iframe-webpack.config.ts | 2 +- .../src/preview/iframe-webpack.config.ts | 8 +++- package.json | 2 + yarn.lock | 37 ++++++++++++++++++- 8 files changed, 60 insertions(+), 11 deletions(-) diff --git a/app/react/package.json b/app/react/package.json index 3d4907dd1259..4c8f9d07d200 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -69,6 +69,7 @@ "babel-plugin-react-docgen": "^4.2.1", "core-js": "^3.8.2", "escodegen": "^2.0.0", + "fs-extra": "^9.0.1", "global": "^4.4.0", "html-tags": "^3.1.0", "lodash": "^4.17.21", diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 569fbca458f0..45704a75a45e 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -48,8 +48,7 @@ const renderElement = async (node: ReactElement, el: Element) => { }); }; -const canUseNewReactRootApi = - gte(reactDomVersion, '18.0.0') || coerce(reactDomVersion)?.version === '18.0.0'; +const canUseNewReactRootApi = reactDomVersion.startsWith('18'); const shouldUseNewRootApi = FRAMEWORK_OPTIONS?.legacyRootApi !== true; @@ -74,8 +73,7 @@ const getReactRoot = async (el: Element): Promise => { if (!root) { // Skipping webpack's static analysis of import paths by defining the path value outside the import statement. - const reactDOMClientPath = 'react-dom/client'; - const reactDomClient = await import(reactDOMClientPath); + const reactDomClient = await import('react-dom/client'); root = reactDomClient.createRoot(el); nodes.set(el, root); diff --git a/app/react/src/server/framework-preset-react.ts b/app/react/src/server/framework-preset-react.ts index 3fb207b1d263..7ca3b0cc2151 100644 --- a/app/react/src/server/framework-preset-react.ts +++ b/app/react/src/server/framework-preset-react.ts @@ -1,7 +1,8 @@ import path from 'path'; +import { readJSON } from 'fs-extra'; import { TransformOptions } from '@babel/core'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; -import type { Configuration } from 'webpack'; +import { Configuration, IgnorePlugin } from 'webpack'; import { logger } from '@storybook/node-logger'; import type { Options } from '@storybook/core-common'; @@ -82,11 +83,19 @@ export async function webpackFinal(config: Configuration, options: Options) { } logger.info('=> Using React fast refresh'); + const reactDomPkg = await readJSON(require.resolve('react-dom/package.json')); return { ...config, plugins: [ ...config.plugins, + reactDomPkg.version.startsWith('18') + ? null + : new IgnorePlugin({ + resourceRegExp: /react-dom\/client$/, + contextRegExp: /@storybook\/react/, + }), + // Storybook uses webpack-hot-middleware https://github.com/storybookjs/storybook/issues/14114 new ReactRefreshWebpackPlugin({ overlay: { diff --git a/examples/react-ts/package.json b/examples/react-ts/package.json index 5799d47e03ec..81277c61d8dc 100644 --- a/examples/react-ts/package.json +++ b/examples/react-ts/package.json @@ -11,8 +11,8 @@ "dependencies": { "formik": "^2.2.9", "prop-types": "15.7.2", - "react": "16.14.0", - "react-dom": "16.14.0" + "react": "18", + "react-dom": "18" }, "devDependencies": { "@babel/preset-env": "^7.12.11", diff --git a/lib/builder-webpack4/src/preview/iframe-webpack.config.ts b/lib/builder-webpack4/src/preview/iframe-webpack.config.ts index 66c2fbee4def..c929917cffb7 100644 --- a/lib/builder-webpack4/src/preview/iframe-webpack.config.ts +++ b/lib/builder-webpack4/src/preview/iframe-webpack.config.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin } from 'webpack'; +import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, IgnorePlugin } from 'webpack'; // @ts-ignore import type { Configuration, RuleSetRule } from '@types/webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; diff --git a/lib/builder-webpack5/src/preview/iframe-webpack.config.ts b/lib/builder-webpack5/src/preview/iframe-webpack.config.ts index 7d2a18a7bf2e..a26d497b0eda 100644 --- a/lib/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/lib/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -1,5 +1,11 @@ import path from 'path'; -import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; +import { + DefinePlugin, + HotModuleReplacementPlugin, + ProgressPlugin, + ProvidePlugin, + IgnorePlugin, +} from 'webpack'; import type { Configuration } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; diff --git a/package.json b/package.json index d67eb462a476..0278203cd844 100644 --- a/package.json +++ b/package.json @@ -273,6 +273,8 @@ "prettier": ">=2.2.1 <=2.3.0", "prompts": "^2.4.0", "raf": "^3.4.1", + "react": "18", + "react-dom": "18", "read-pkg-up": "^7.0.1", "regenerator-runtime": "^0.13.7", "remark": "^13.0.0", diff --git a/yarn.lock b/yarn.lock index db517d03b24b..c8ce77a514bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7710,8 +7710,8 @@ __metadata: cross-env: ^7.0.3 formik: ^2.2.9 prop-types: 15.7.2 - react: 16.14.0 - react-dom: 16.14.0 + react: 18 + react-dom: 18 typescript: ^3.9.7 webpack: 4 languageName: unknown @@ -8127,6 +8127,7 @@ __metadata: babel-plugin-react-docgen: ^4.2.1 core-js: ^3.8.2 escodegen: ^2.0.0 + fs-extra: ^9.0.1 global: ^4.4.0 html-tags: ^3.1.0 lodash: ^4.17.21 @@ -8342,6 +8343,8 @@ __metadata: prompts: ^2.4.0 puppeteer: ^2.1.1 raf: ^3.4.1 + react: 18 + react-dom: 18 read-pkg-up: ^7.0.1 regenerator-runtime: ^0.13.7 remark: ^13.0.0 @@ -37702,6 +37705,18 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:18": + version: 18.0.0 + resolution: "react-dom@npm:18.0.0" + dependencies: + loose-envify: ^1.1.0 + scheduler: ^0.21.0 + peerDependencies: + react: ^18.0.0 + checksum: 78aa393ab4ae35f7266ba5cd761ebd35cab8375f63051bda2bb97a27be2622dbd661b24b32b8e55f9365aa027e732251e7d0d754c755f8efb6bd38a72ba03df8 + languageName: node + linkType: hard + "react-dom@npm:^15.7.0": version: 15.7.0 resolution: "react-dom@npm:15.7.0" @@ -38228,6 +38243,15 @@ __metadata: languageName: node linkType: hard +"react@npm:18": + version: 18.0.0 + resolution: "react@npm:18.0.0" + dependencies: + loose-envify: ^1.1.0 + checksum: cf55bc7b4315f54321d04cb0b69241c4973a5182d679551b3e6c3f57b312a4fdeac05038ca9502d48ea4d040d7550b0b7f07b3872a19589a5995ee899cc19d2b + languageName: node + linkType: hard + "react@npm:^15.7.0": version: 15.7.0 resolution: "react@npm:15.7.0" @@ -40473,6 +40497,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.21.0": + version: 0.21.0 + resolution: "scheduler@npm:0.21.0" + dependencies: + loose-envify: ^1.1.0 + checksum: 083a9a0c83f4923f7f5bb28d8bcf13cff42c90f4303bc187166520fcfc576c97e946d426c707d5a9c0aa0a655819605dd0c741467c626824bbf191251c126f1b + languageName: node + linkType: hard + "schema-utils@npm:2.7.0": version: 2.7.0 resolution: "schema-utils@npm:2.7.0" From eb73376b32f039def37f4e0943ad39c8741d6468 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Tue, 5 Apr 2022 19:46:04 +0200 Subject: [PATCH 18/27] I think this makes michael happy --- .../server/framework-preset-react-dom-hack.ts | 22 ++++++++++++ .../src/server/framework-preset-react.ts | 7 ---- app/react/src/server/preset.ts | 1 + examples/react-ts/package.json | 4 +-- package.json | 2 -- yarn.lock | 36 ++----------------- 6 files changed, 27 insertions(+), 45 deletions(-) create mode 100644 app/react/src/server/framework-preset-react-dom-hack.ts diff --git a/app/react/src/server/framework-preset-react-dom-hack.ts b/app/react/src/server/framework-preset-react-dom-hack.ts new file mode 100644 index 000000000000..6f32f3f1c172 --- /dev/null +++ b/app/react/src/server/framework-preset-react-dom-hack.ts @@ -0,0 +1,22 @@ +import { readJSON } from 'fs-extra'; +import { Configuration, IgnorePlugin } from 'webpack'; + +// this is a hack to allow importing react-dom/client even when it's not available +// this should be removed once we drop support for react-dom < 18 + +export async function webpackFinal(config: Configuration) { + const reactDomPkg = await readJSON(require.resolve('react-dom/package.json')); + + return { + ...config, + plugins: [ + ...config.plugins, + reactDomPkg.version.startsWith('18') + ? null + : new IgnorePlugin({ + resourceRegExp: /react-dom\/client$/, + contextRegExp: /(app\/react|@storybook\/react)/, // TODO this needs to work for both in our MONOREPO and in the user's NODE_MODULES + }), + ], + }; +} diff --git a/app/react/src/server/framework-preset-react.ts b/app/react/src/server/framework-preset-react.ts index 7ca3b0cc2151..8542f33cd38d 100644 --- a/app/react/src/server/framework-preset-react.ts +++ b/app/react/src/server/framework-preset-react.ts @@ -83,18 +83,11 @@ export async function webpackFinal(config: Configuration, options: Options) { } logger.info('=> Using React fast refresh'); - const reactDomPkg = await readJSON(require.resolve('react-dom/package.json')); return { ...config, plugins: [ ...config.plugins, - reactDomPkg.version.startsWith('18') - ? null - : new IgnorePlugin({ - resourceRegExp: /react-dom\/client$/, - contextRegExp: /@storybook\/react/, - }), // Storybook uses webpack-hot-middleware https://github.com/storybookjs/storybook/issues/14114 new ReactRefreshWebpackPlugin({ diff --git a/app/react/src/server/preset.ts b/app/react/src/server/preset.ts index 87f491ca3ae8..081c6073e26b 100644 --- a/app/react/src/server/preset.ts +++ b/app/react/src/server/preset.ts @@ -8,6 +8,7 @@ export const previewAnnotations: StorybookConfig['previewAnnotations'] = (entrie export const addons: StorybookConfig['addons'] = [ require.resolve('./framework-preset-react'), + require.resolve('./framework-preset-react-dom-hack'), require.resolve('./framework-preset-cra'), require.resolve('./framework-preset-react-docs'), ]; diff --git a/examples/react-ts/package.json b/examples/react-ts/package.json index 81277c61d8dc..5799d47e03ec 100644 --- a/examples/react-ts/package.json +++ b/examples/react-ts/package.json @@ -11,8 +11,8 @@ "dependencies": { "formik": "^2.2.9", "prop-types": "15.7.2", - "react": "18", - "react-dom": "18" + "react": "16.14.0", + "react-dom": "16.14.0" }, "devDependencies": { "@babel/preset-env": "^7.12.11", diff --git a/package.json b/package.json index 0278203cd844..d67eb462a476 100644 --- a/package.json +++ b/package.json @@ -273,8 +273,6 @@ "prettier": ">=2.2.1 <=2.3.0", "prompts": "^2.4.0", "raf": "^3.4.1", - "react": "18", - "react-dom": "18", "read-pkg-up": "^7.0.1", "regenerator-runtime": "^0.13.7", "remark": "^13.0.0", diff --git a/yarn.lock b/yarn.lock index c8ce77a514bb..ae0fd8d4e035 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7710,8 +7710,8 @@ __metadata: cross-env: ^7.0.3 formik: ^2.2.9 prop-types: 15.7.2 - react: 18 - react-dom: 18 + react: 16.14.0 + react-dom: 16.14.0 typescript: ^3.9.7 webpack: 4 languageName: unknown @@ -8343,8 +8343,6 @@ __metadata: prompts: ^2.4.0 puppeteer: ^2.1.1 raf: ^3.4.1 - react: 18 - react-dom: 18 read-pkg-up: ^7.0.1 regenerator-runtime: ^0.13.7 remark: ^13.0.0 @@ -37705,18 +37703,6 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:18": - version: 18.0.0 - resolution: "react-dom@npm:18.0.0" - dependencies: - loose-envify: ^1.1.0 - scheduler: ^0.21.0 - peerDependencies: - react: ^18.0.0 - checksum: 78aa393ab4ae35f7266ba5cd761ebd35cab8375f63051bda2bb97a27be2622dbd661b24b32b8e55f9365aa027e732251e7d0d754c755f8efb6bd38a72ba03df8 - languageName: node - linkType: hard - "react-dom@npm:^15.7.0": version: 15.7.0 resolution: "react-dom@npm:15.7.0" @@ -38243,15 +38229,6 @@ __metadata: languageName: node linkType: hard -"react@npm:18": - version: 18.0.0 - resolution: "react@npm:18.0.0" - dependencies: - loose-envify: ^1.1.0 - checksum: cf55bc7b4315f54321d04cb0b69241c4973a5182d679551b3e6c3f57b312a4fdeac05038ca9502d48ea4d040d7550b0b7f07b3872a19589a5995ee899cc19d2b - languageName: node - linkType: hard - "react@npm:^15.7.0": version: 15.7.0 resolution: "react@npm:15.7.0" @@ -40497,15 +40474,6 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.21.0": - version: 0.21.0 - resolution: "scheduler@npm:0.21.0" - dependencies: - loose-envify: ^1.1.0 - checksum: 083a9a0c83f4923f7f5bb28d8bcf13cff42c90f4303bc187166520fcfc576c97e946d426c707d5a9c0aa0a655819605dd0c741467c626824bbf191251c126f1b - languageName: node - linkType: hard - "schema-utils@npm:2.7.0": version: 2.7.0 resolution: "schema-utils@npm:2.7.0" From 262409bd0104071643904d85c1055d2daa038589 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 6 Apr 2022 08:50:57 +0800 Subject: [PATCH 19/27] Fix lint --- app/react/src/client/preview/render.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 45704a75a45e..8d44359d161e 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -73,6 +73,7 @@ const getReactRoot = async (el: Element): Promise => { if (!root) { // Skipping webpack's static analysis of import paths by defining the path value outside the import statement. + // eslint-disable-next-line import/no-unresolved const reactDomClient = await import('react-dom/client'); root = reactDomClient.createRoot(el); From 699cd795ec11791e26d9cc0da4a4604e2735505d Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 6 Apr 2022 08:43:46 +0200 Subject: [PATCH 20/27] cleanup --- app/react/src/server/framework-preset-react.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/react/src/server/framework-preset-react.ts b/app/react/src/server/framework-preset-react.ts index 8542f33cd38d..7559e61a9251 100644 --- a/app/react/src/server/framework-preset-react.ts +++ b/app/react/src/server/framework-preset-react.ts @@ -1,8 +1,7 @@ import path from 'path'; -import { readJSON } from 'fs-extra'; import { TransformOptions } from '@babel/core'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; -import { Configuration, IgnorePlugin } from 'webpack'; +import type { Configuration } from 'webpack'; import { logger } from '@storybook/node-logger'; import type { Options } from '@storybook/core-common'; From 465dc924fe7233347c54a6544b4711d569b35d71 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 6 Apr 2022 08:44:43 +0200 Subject: [PATCH 21/27] cleanup --- lib/builder-webpack4/src/preview/iframe-webpack.config.ts | 2 +- lib/builder-webpack5/src/preview/iframe-webpack.config.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/builder-webpack4/src/preview/iframe-webpack.config.ts b/lib/builder-webpack4/src/preview/iframe-webpack.config.ts index c929917cffb7..66c2fbee4def 100644 --- a/lib/builder-webpack4/src/preview/iframe-webpack.config.ts +++ b/lib/builder-webpack4/src/preview/iframe-webpack.config.ts @@ -1,5 +1,5 @@ import path from 'path'; -import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, IgnorePlugin } from 'webpack'; +import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin } from 'webpack'; // @ts-ignore import type { Configuration, RuleSetRule } from '@types/webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; diff --git a/lib/builder-webpack5/src/preview/iframe-webpack.config.ts b/lib/builder-webpack5/src/preview/iframe-webpack.config.ts index a26d497b0eda..7d2a18a7bf2e 100644 --- a/lib/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/lib/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -1,11 +1,5 @@ import path from 'path'; -import { - DefinePlugin, - HotModuleReplacementPlugin, - ProgressPlugin, - ProvidePlugin, - IgnorePlugin, -} from 'webpack'; +import { DefinePlugin, HotModuleReplacementPlugin, ProgressPlugin, ProvidePlugin } from 'webpack'; import type { Configuration } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; From 3f2d50a7312e531b283a750973cf7774b2685d59 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 6 Apr 2022 08:50:27 +0200 Subject: [PATCH 22/27] cleanup --- lib/cli/src/versions.ts | 114 ++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/lib/cli/src/versions.ts b/lib/cli/src/versions.ts index 594b355ddbd1..e8f437411146 100644 --- a/lib/cli/src/versions.ts +++ b/lib/cli/src/versions.ts @@ -1,59 +1,59 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '6.5.0-alpha.57', - '@storybook/addon-actions': '6.5.0-alpha.57', - '@storybook/addon-backgrounds': '6.5.0-alpha.57', - '@storybook/addon-controls': '6.5.0-alpha.57', - '@storybook/addon-docs': '6.5.0-alpha.57', - '@storybook/addon-essentials': '6.5.0-alpha.57', - '@storybook/addon-interactions': '6.5.0-alpha.57', - '@storybook/addon-jest': '6.5.0-alpha.57', - '@storybook/addon-links': '6.5.0-alpha.57', - '@storybook/addon-measure': '6.5.0-alpha.57', - '@storybook/addon-outline': '6.5.0-alpha.57', - '@storybook/addon-storyshots': '6.5.0-alpha.57', - '@storybook/addon-storyshots-puppeteer': '6.5.0-alpha.57', - '@storybook/addon-storysource': '6.5.0-alpha.57', - '@storybook/addon-toolbars': '6.5.0-alpha.57', - '@storybook/addon-viewport': '6.5.0-alpha.57', - '@storybook/addons': '6.5.0-alpha.57', - '@storybook/angular': '6.5.0-alpha.57', - '@storybook/api': '6.5.0-alpha.57', - '@storybook/builder-webpack4': '6.5.0-alpha.57', - '@storybook/builder-webpack5': '6.5.0-alpha.57', - '@storybook/channel-postmessage': '6.5.0-alpha.57', - '@storybook/channel-websocket': '6.5.0-alpha.57', - '@storybook/channels': '6.5.0-alpha.57', - '@storybook/cli': '6.5.0-alpha.57', - '@storybook/client-api': '6.5.0-alpha.57', - '@storybook/client-logger': '6.5.0-alpha.57', - '@storybook/codemod': '6.5.0-alpha.57', - '@storybook/components': '6.5.0-alpha.57', - '@storybook/core': '6.5.0-alpha.57', - '@storybook/core-client': '6.5.0-alpha.57', - '@storybook/core-common': '6.5.0-alpha.57', - '@storybook/core-events': '6.5.0-alpha.57', - '@storybook/core-server': '6.5.0-alpha.57', - '@storybook/csf-tools': '6.5.0-alpha.57', - '@storybook/docs-tools': '6.5.0-alpha.57', - '@storybook/ember': '6.5.0-alpha.57', - '@storybook/html': '6.5.0-alpha.57', - '@storybook/instrumenter': '6.5.0-alpha.57', - '@storybook/manager-webpack4': '6.5.0-alpha.57', - '@storybook/manager-webpack5': '6.5.0-alpha.57', - '@storybook/node-logger': '6.5.0-alpha.57', - '@storybook/postinstall': '6.5.0-alpha.57', - '@storybook/preact': '6.5.0-alpha.57', - '@storybook/preview-web': '6.5.0-alpha.57', - '@storybook/react': '6.5.0-alpha.57', - '@storybook/router': '6.5.0-alpha.57', - '@storybook/server': '6.5.0-alpha.57', - '@storybook/source-loader': '6.5.0-alpha.57', - '@storybook/store': '6.5.0-alpha.57', - '@storybook/svelte': '6.5.0-alpha.57', - '@storybook/theming': '6.5.0-alpha.57', - '@storybook/ui': '6.5.0-alpha.57', - '@storybook/vue': '6.5.0-alpha.57', - '@storybook/vue3': '6.5.0-alpha.57', - '@storybook/web-components': '6.5.0-alpha.57', -}; + "@storybook/addon-a11y": "6.5.0-alpha.57", + "@storybook/addon-actions": "6.5.0-alpha.57", + "@storybook/addon-backgrounds": "6.5.0-alpha.57", + "@storybook/addon-controls": "6.5.0-alpha.57", + "@storybook/addon-docs": "6.5.0-alpha.57", + "@storybook/addon-essentials": "6.5.0-alpha.57", + "@storybook/addon-interactions": "6.5.0-alpha.57", + "@storybook/addon-jest": "6.5.0-alpha.57", + "@storybook/addon-links": "6.5.0-alpha.57", + "@storybook/addon-measure": "6.5.0-alpha.57", + "@storybook/addon-outline": "6.5.0-alpha.57", + "@storybook/addon-storyshots": "6.5.0-alpha.57", + "@storybook/addon-storyshots-puppeteer": "6.5.0-alpha.57", + "@storybook/addon-storysource": "6.5.0-alpha.57", + "@storybook/addon-toolbars": "6.5.0-alpha.57", + "@storybook/addon-viewport": "6.5.0-alpha.57", + "@storybook/addons": "6.5.0-alpha.57", + "@storybook/angular": "6.5.0-alpha.57", + "@storybook/api": "6.5.0-alpha.57", + "@storybook/builder-webpack4": "6.5.0-alpha.57", + "@storybook/builder-webpack5": "6.5.0-alpha.57", + "@storybook/channel-postmessage": "6.5.0-alpha.57", + "@storybook/channel-websocket": "6.5.0-alpha.57", + "@storybook/channels": "6.5.0-alpha.57", + "@storybook/cli": "6.5.0-alpha.57", + "@storybook/client-api": "6.5.0-alpha.57", + "@storybook/client-logger": "6.5.0-alpha.57", + "@storybook/codemod": "6.5.0-alpha.57", + "@storybook/components": "6.5.0-alpha.57", + "@storybook/core": "6.5.0-alpha.57", + "@storybook/core-client": "6.5.0-alpha.57", + "@storybook/core-common": "6.5.0-alpha.57", + "@storybook/core-events": "6.5.0-alpha.57", + "@storybook/core-server": "6.5.0-alpha.57", + "@storybook/csf-tools": "6.5.0-alpha.57", + "@storybook/docs-tools": "6.5.0-alpha.57", + "@storybook/ember": "6.5.0-alpha.57", + "@storybook/html": "6.5.0-alpha.57", + "@storybook/instrumenter": "6.5.0-alpha.57", + "@storybook/manager-webpack4": "6.5.0-alpha.57", + "@storybook/manager-webpack5": "6.5.0-alpha.57", + "@storybook/node-logger": "6.5.0-alpha.57", + "@storybook/postinstall": "6.5.0-alpha.57", + "@storybook/preact": "6.5.0-alpha.57", + "@storybook/preview-web": "6.5.0-alpha.57", + "@storybook/react": "6.5.0-alpha.57", + "@storybook/router": "6.5.0-alpha.57", + "@storybook/server": "6.5.0-alpha.57", + "@storybook/source-loader": "6.5.0-alpha.57", + "@storybook/store": "6.5.0-alpha.57", + "@storybook/svelte": "6.5.0-alpha.57", + "@storybook/theming": "6.5.0-alpha.57", + "@storybook/ui": "6.5.0-alpha.57", + "@storybook/vue": "6.5.0-alpha.57", + "@storybook/vue3": "6.5.0-alpha.57", + "@storybook/web-components": "6.5.0-alpha.57" +} \ No newline at end of file From 9a8ebd5e65fe1a033b36e73bd529db3c275f5dd2 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 6 Apr 2022 08:52:33 +0200 Subject: [PATCH 23/27] cleanup --- app/react/src/client/preview/render.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 8d44359d161e..9d85a4be8bcb 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -9,9 +9,8 @@ import React, { import ReactDOM, { version as reactDomVersion } from 'react-dom'; import type { Root as ReactRoot } from 'react-dom/client'; -import { RenderContext } from '@storybook/store'; +import type { RenderContext } from '@storybook/store'; import { ArgsStoryFn } from '@storybook/csf'; -import { gte, coerce } from '@storybook/semver'; import { StoryContext } from './types'; import { ReactFramework } from './types-6-0'; From b907b9e8143342268b157036e0d0e69b4f1ca35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Wed, 6 Apr 2022 13:54:34 +0200 Subject: [PATCH 24/27] Fix corrupted webpack configuration --- app/react/src/server/framework-preset-react-dom-hack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/react/src/server/framework-preset-react-dom-hack.ts b/app/react/src/server/framework-preset-react-dom-hack.ts index 6f32f3f1c172..9d5268f35940 100644 --- a/app/react/src/server/framework-preset-react-dom-hack.ts +++ b/app/react/src/server/framework-preset-react-dom-hack.ts @@ -17,6 +17,6 @@ export async function webpackFinal(config: Configuration) { resourceRegExp: /react-dom\/client$/, contextRegExp: /(app\/react|@storybook\/react)/, // TODO this needs to work for both in our MONOREPO and in the user's NODE_MODULES }), - ], + ].filter(Boolean), }; } From b7422ff8257bbbc90f3aa4648836ca0dfe82a7ca Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Thu, 7 Apr 2022 00:12:48 +0800 Subject: [PATCH 25/27] Update snapshots --- .../src/__snapshots__/cra-ts-essentials_preview-dev-posix | 1 + .../src/__snapshots__/cra-ts-essentials_preview-prod-posix | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix index 11aced08f4fb..45f3199dcaa1 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix @@ -474,6 +474,7 @@ Object { "IgnorePlugin", "ForkTsCheckerWebpackPlugin", "ESLintWebpackPlugin", + "IgnorePlugin", "DocgenPlugin", ], } diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix index e08d547c90c0..9eecd1ba125f 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix @@ -491,6 +491,7 @@ Object { "IgnorePlugin", "ForkTsCheckerWebpackPlugin", "ESLintWebpackPlugin", + "IgnorePlugin", "DocgenPlugin", ], } From 0f118a38dd5cfc7c0c0b5297570aae0be4057527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Palkovi=C4=8D?= Date: Wed, 6 Apr 2022 20:54:01 +0200 Subject: [PATCH 26/27] Support react experimental versions --- app/react/src/client/preview/render.tsx | 3 ++- app/react/src/server/framework-preset-react-dom-hack.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/react/src/client/preview/render.tsx b/app/react/src/client/preview/render.tsx index 9d85a4be8bcb..6ed1326be024 100644 --- a/app/react/src/client/preview/render.tsx +++ b/app/react/src/client/preview/render.tsx @@ -47,7 +47,8 @@ const renderElement = async (node: ReactElement, el: Element) => { }); }; -const canUseNewReactRootApi = reactDomVersion.startsWith('18'); +const canUseNewReactRootApi = + reactDomVersion.startsWith('18') || reactDomVersion.startsWith('0.0.0'); const shouldUseNewRootApi = FRAMEWORK_OPTIONS?.legacyRootApi !== true; diff --git a/app/react/src/server/framework-preset-react-dom-hack.ts b/app/react/src/server/framework-preset-react-dom-hack.ts index 9d5268f35940..6746dd5d871e 100644 --- a/app/react/src/server/framework-preset-react-dom-hack.ts +++ b/app/react/src/server/framework-preset-react-dom-hack.ts @@ -11,7 +11,7 @@ export async function webpackFinal(config: Configuration) { ...config, plugins: [ ...config.plugins, - reactDomPkg.version.startsWith('18') + reactDomPkg.version.startsWith('18') || reactDomPkg.version.startsWith('0.0.0') ? null : new IgnorePlugin({ resourceRegExp: /react-dom\/client$/, From bcb93ecfd3b85c3573417775eda71db718345760 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Thu, 7 Apr 2022 10:44:56 +0800 Subject: [PATCH 27/27] React: Add react18 new root API migration instructions --- MIGRATION.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index c82b46fddde0..32b3877ed32e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,7 @@

Migration

- [From version 6.4.x to 6.5.0](#from-version-64x-to-650) + - [React18 new root API](#react18-new-root-api) - [Deprecated register.js](#deprecated-registerjs) - [Dropped support for addon-actions addDecorators](#dropped-support-for-addon-actions-adddecorators) - [Vite builder renamed](#vite-builder-renamed) @@ -199,20 +200,32 @@ ## From version 6.4.x to 6.5.0 +### React18 new root API + +React 18 introduces a [new root API](https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis). Starting in 6.5, Storybook for React will auto-detect your react version and use the new root API automatically if you're on React18. + +If you wish to opt out of the new root API, set the `reactOptions.legacyRootApi` flag in your `.storybook/main.js` config: + +```js +module.exports = { + reactOptions: { legacyRootApi: true }, +}; +``` + ### Deprecated register.js In ancient versions of Storybook, addons were registered by referring to `addon-name/register.js`. This is going away in SB7.0. Instead you should just add `addon-name` to the `addons` array in `.storybook/main.js`. -Before: +Before: ```js -module.exports = { addons: ['my-addon/register.js'] } +module.exports = { addons: ['my-addon/register.js'] }; ``` After: ```js -module.exports = { addons: ['my-addon'] } +module.exports = { addons: ['my-addon'] }; ``` ### Dropped support for addon-actions addDecorators