From ca4cc16fd1f4fef142aec98688fc2deac0e9aa62 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sun, 3 Dec 2023 16:59:36 +0800 Subject: [PATCH 01/10] NextJS: Add RSC support --- code/frameworks/nextjs/src/preview.tsx | 5 +++++ code/frameworks/nextjs/src/rsc/decorator.tsx | 16 +++++++++++++++ .../nextjs/template/stories/RSC.jsx | 3 +++ .../nextjs/template/stories/RSC.stories.jsx | 20 +++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 code/frameworks/nextjs/src/rsc/decorator.tsx create mode 100644 code/frameworks/nextjs/template/stories/RSC.jsx create mode 100644 code/frameworks/nextjs/template/stories/RSC.stories.jsx diff --git a/code/frameworks/nextjs/src/preview.tsx b/code/frameworks/nextjs/src/preview.tsx index 9f30af9d787d..326bee0c0123 100644 --- a/code/frameworks/nextjs/src/preview.tsx +++ b/code/frameworks/nextjs/src/preview.tsx @@ -1,5 +1,6 @@ import type { Addon_DecoratorFunction } from '@storybook/types'; import './config/preview'; +import { ServerComponentDecorator } from './rsc/decorator'; import { ImageDecorator } from './images/decorator'; import { RouterDecorator } from './routing/decorator'; import { StyledJsxDecorator } from './styledJsx/decorator'; @@ -15,6 +16,7 @@ function addNextHeadCount() { addNextHeadCount(); export const decorators: Addon_DecoratorFunction[] = [ + ServerComponentDecorator, StyledJsxDecorator, ImageDecorator, RouterDecorator, @@ -22,6 +24,9 @@ export const decorators: Addon_DecoratorFunction[] = [ ]; export const parameters = { + nextjs: { + rsc: true, + }, docs: { source: { excludeDecorators: true, diff --git a/code/frameworks/nextjs/src/rsc/decorator.tsx b/code/frameworks/nextjs/src/rsc/decorator.tsx new file mode 100644 index 000000000000..ceaf9daca1bf --- /dev/null +++ b/code/frameworks/nextjs/src/rsc/decorator.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import type { StoryContext } from '@storybook/react'; + +export const ServerComponentDecorator = ( + Story: React.FC, + { parameters }: StoryContext +): React.ReactNode => { + console.log('ServerComponentDecorator', { rsc: parameters?.nextjs?.rsc }); + return parameters?.nextjs?.rsc ? ( + + + + ) : ( + + ); +}; diff --git a/code/frameworks/nextjs/template/stories/RSC.jsx b/code/frameworks/nextjs/template/stories/RSC.jsx new file mode 100644 index 000000000000..6399326e9af8 --- /dev/null +++ b/code/frameworks/nextjs/template/stories/RSC.jsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const RSC = async () => <>RSC; diff --git a/code/frameworks/nextjs/template/stories/RSC.stories.jsx b/code/frameworks/nextjs/template/stories/RSC.stories.jsx new file mode 100644 index 000000000000..46fd35dde1d5 --- /dev/null +++ b/code/frameworks/nextjs/template/stories/RSC.stories.jsx @@ -0,0 +1,20 @@ +import React, { Suspense } from 'react'; +import { RSC } from './RSC'; + +export default { + component: RSC, +}; + +export const Default = {}; + +export const DisableRSC = { + parameters: { + nextjs: { rsc: false }, + }, +}; + +export const Error = { + render: () => { + throw new Error('RSC Error'); + }, +}; From 2960d3d05ebbc4de08c753da666b95302ffaeedd Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sun, 3 Dec 2023 17:00:14 +0800 Subject: [PATCH 02/10] React: Add RSC type support --- code/renderers/react/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/renderers/react/src/types.ts b/code/renderers/react/src/types.ts index 637f1b5a5ebc..dae8d9c20949 100644 --- a/code/renderers/react/src/types.ts +++ b/code/renderers/react/src/types.ts @@ -17,4 +17,4 @@ export interface ShowErrorArgs { description: string; } -export type StoryFnReactReturnType = JSX.Element; +export type StoryFnReactReturnType = JSX.Element | Promise; From 048fa05a3110f3124934d8291c5c71619f4e5ca4 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sun, 3 Dec 2023 21:40:11 +0800 Subject: [PATCH 03/10] Revert "React: Add RSC type support" This reverts commit 2960d3d05ebbc4de08c753da666b95302ffaeedd. --- code/renderers/react/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/renderers/react/src/types.ts b/code/renderers/react/src/types.ts index dae8d9c20949..637f1b5a5ebc 100644 --- a/code/renderers/react/src/types.ts +++ b/code/renderers/react/src/types.ts @@ -17,4 +17,4 @@ export interface ShowErrorArgs { description: string; } -export type StoryFnReactReturnType = JSX.Element | Promise; +export type StoryFnReactReturnType = JSX.Element; From 0cfe04543bae8dc327bc3d67bbcbe69e4adcd306 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 4 Dec 2023 12:24:48 +0800 Subject: [PATCH 04/10] Skip intentional error stories --- code/frameworks/nextjs/template/stories/RSC.stories.jsx | 7 ++++++- scripts/tasks/test-runner-build.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/code/frameworks/nextjs/template/stories/RSC.stories.jsx b/code/frameworks/nextjs/template/stories/RSC.stories.jsx index 46fd35dde1d5..06d2596478c9 100644 --- a/code/frameworks/nextjs/template/stories/RSC.stories.jsx +++ b/code/frameworks/nextjs/template/stories/RSC.stories.jsx @@ -1,4 +1,3 @@ -import React, { Suspense } from 'react'; import { RSC } from './RSC'; export default { @@ -8,12 +7,18 @@ export default { export const Default = {}; export const DisableRSC = { + tags: ['test-skip'], parameters: { + chromatic: { disable: true }, nextjs: { rsc: false }, }, }; export const Error = { + tags: ['test-skip'], + parameters: { + chromatic: { disable: true }, + }, render: () => { throw new Error('RSC Error'); }, diff --git a/scripts/tasks/test-runner-build.ts b/scripts/tasks/test-runner-build.ts index b57d4c803233..f902777fc63c 100644 --- a/scripts/tasks/test-runner-build.ts +++ b/scripts/tasks/test-runner-build.ts @@ -17,6 +17,7 @@ export const testRunnerBuild: Task & { port: number } = { '--junit', '--maxWorkers=2', '--failOnConsole', + '--skipTags="test-skip"', ]; // index-json mode is only supported in ssv7 From ffbd24f6a6e72d254c6b9526cec502b6cbf7886e Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 4 Dec 2023 20:31:31 +0800 Subject: [PATCH 05/10] Add more RSC stories --- code/frameworks/nextjs/template/stories/RSC.jsx | 4 +++- .../nextjs/template/stories/RSC.stories.jsx | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/code/frameworks/nextjs/template/stories/RSC.jsx b/code/frameworks/nextjs/template/stories/RSC.jsx index 6399326e9af8..17a98b954919 100644 --- a/code/frameworks/nextjs/template/stories/RSC.jsx +++ b/code/frameworks/nextjs/template/stories/RSC.jsx @@ -1,3 +1,5 @@ import React from 'react'; -export const RSC = async () => <>RSC; +export const RSC = async ({ label }) => <>RSC {label}; + +export const Nested = async ({ children }) => <>Nested {children}; diff --git a/code/frameworks/nextjs/template/stories/RSC.stories.jsx b/code/frameworks/nextjs/template/stories/RSC.stories.jsx index 06d2596478c9..e14456b50e58 100644 --- a/code/frameworks/nextjs/template/stories/RSC.stories.jsx +++ b/code/frameworks/nextjs/template/stories/RSC.stories.jsx @@ -1,7 +1,9 @@ -import { RSC } from './RSC'; +import React from 'react'; +import { RSC, Nested } from './RSC'; export default { component: RSC, + args: { label: 'label' }, }; export const Default = {}; @@ -23,3 +25,11 @@ export const Error = { throw new Error('RSC Error'); }, }; + +export const NestedRSC = { + render: (args) => ( + + + + ), +}; From 4a0e468c105fd037154c974a7c99943cd8b1b45b Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 5 Dec 2023 00:06:22 +0800 Subject: [PATCH 06/10] NextJS: Add experimentalNextRSC feature flag --- code/frameworks/nextjs/package.json | 2 ++ code/frameworks/nextjs/src/preset.ts | 15 +++++++++++---- code/frameworks/nextjs/src/preview.tsx | 5 ----- code/frameworks/nextjs/src/previewRSC.tsx | 10 ++++++++++ code/lib/types/src/modules/core-common.ts | 5 +++++ 5 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 code/frameworks/nextjs/src/previewRSC.tsx diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index c0318bdc95d9..efd51fece53f 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -52,6 +52,7 @@ "import": "./dist/font/webpack/loader/storybook-nextjs-font-loader.mjs" }, "./dist/preview.mjs": "./dist/preview.mjs", + "./dist/previewRSC.mjs": "./dist/previewRSC.mjs", "./next-image-loader-stub.js": { "types": "./dist/next-image-loader-stub.d.ts", "require": "./dist/next-image-loader-stub.js", @@ -166,6 +167,7 @@ "./src/index.ts", "./src/preset.ts", "./src/preview.tsx", + "./src/previewRSC.tsx", "./src/next-image-loader-stub.ts", "./src/images/decorator.tsx", "./src/images/next-future-image.tsx", diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index f29290209281..6301f2e799c7 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -67,10 +67,17 @@ export const core: PresetProperty<'core'> = async (config, options) => { }; }; -export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry = []) => [ - ...entry, - join(dirname(require.resolve('@storybook/nextjs/package.json')), 'dist/preview.mjs'), -]; +export const previewAnnotations: PresetProperty<'previewAnnotations'> = ( + entry = [], + { features } +) => { + const nextDir = dirname(require.resolve('@storybook/nextjs/package.json')); + const result = [...entry, join(nextDir, 'dist/preview.mjs')]; + if (features?.experimentalNextRSC) { + result.unshift(join(nextDir, 'dist/previewRSC.mjs')); + } + return result; +}; // Not even sb init - automigrate - running dev // You're using a version of Nextjs prior to v10, which is unsupported by this framework. diff --git a/code/frameworks/nextjs/src/preview.tsx b/code/frameworks/nextjs/src/preview.tsx index 326bee0c0123..9f30af9d787d 100644 --- a/code/frameworks/nextjs/src/preview.tsx +++ b/code/frameworks/nextjs/src/preview.tsx @@ -1,6 +1,5 @@ import type { Addon_DecoratorFunction } from '@storybook/types'; import './config/preview'; -import { ServerComponentDecorator } from './rsc/decorator'; import { ImageDecorator } from './images/decorator'; import { RouterDecorator } from './routing/decorator'; import { StyledJsxDecorator } from './styledJsx/decorator'; @@ -16,7 +15,6 @@ function addNextHeadCount() { addNextHeadCount(); export const decorators: Addon_DecoratorFunction[] = [ - ServerComponentDecorator, StyledJsxDecorator, ImageDecorator, RouterDecorator, @@ -24,9 +22,6 @@ export const decorators: Addon_DecoratorFunction[] = [ ]; export const parameters = { - nextjs: { - rsc: true, - }, docs: { source: { excludeDecorators: true, diff --git a/code/frameworks/nextjs/src/previewRSC.tsx b/code/frameworks/nextjs/src/previewRSC.tsx new file mode 100644 index 000000000000..d605a96db980 --- /dev/null +++ b/code/frameworks/nextjs/src/previewRSC.tsx @@ -0,0 +1,10 @@ +import type { Addon_DecoratorFunction } from '@storybook/types'; +import { ServerComponentDecorator } from './rsc/decorator'; + +export const decorators: Addon_DecoratorFunction[] = [ServerComponentDecorator]; + +export const parameters = { + nextjs: { + rsc: true, + }, +}; diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index aaf96ddb2207..d81e605f1edd 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -395,6 +395,11 @@ export interface StorybookConfigRaw { * This will make sure that your story renders the same no matter if docgen is enabled or not. */ disallowImplicitActionsInRenderV8?: boolean; + + /** + * Eneable asynchronous component rendering in NextJS framework + */ + experimentalNextRSC?: boolean; }; build?: TestBuildConfig; From f27cb2bc6b11529bdcd4da644111767e1aa692da Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 5 Dec 2023 00:18:26 +0800 Subject: [PATCH 07/10] NextJS: Add RSC support to README --- code/frameworks/nextjs/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index 252fb329167d..b10e9700ba17 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -40,6 +40,7 @@ - [Runtime Config](#runtime-config) - [Custom Webpack Config](#custom-webpack-config) - [Typescript](#typescript) + - [Experimental React Server Components (RSC)](#experimental-react-server-components-rsc) - [Notes for Yarn v2 and v3 users](#notes-for-yarn-v2-and-v3-users) - [FAQ](#faq) - [Stories for pages/components which fetch data](#stories-for-pagescomponents-which-fetch-data) @@ -909,6 +910,37 @@ Storybook handles most [Typescript](https://www.typescriptlang.org/) configurati } ``` +### Experimental React Server Components (RSC) + +If your app uses [React Server Components (RSC)](https://nextjs.org/docs/app/building-your-application/rendering/server-components), Storybook can render them in stories in the browser. + +To enable this set the `experimentalNextRSC` feature flag in your `.storybook/main.js` config: + +```js +// main.js +export default { + features: { + experimentalNextRSC: true, + }, +}; +``` + +Setting this flag automatically wraps your story in a [Suspense](https://react.dev/reference/react/Suspense) wrapper, which is able to render asynchronous components in NextJS's version of React. + +If this wrapper causes problems in any of your existing stories, you can selectively disable it using the `nextjs.rsc` [parameter](https://storybook.js.org/docs/writing-stories/parameters) at the global/component/story level: + +```js +// MyServerComponent.stories.js +export default { + component: MyServerComponent, + parameters: { nextjs: { rsc: false } }, +}; +``` + +Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To deal work around this, you'll need to mock out your data access layer using [Webpack aliases](https://webpack.js.org/configuration/resolve/#resolvealias) or an addon like [storybook-addon-module-mock](https://storybook.js.org/addons/storybook-addon-module-mock). + +In the future we will provide better module mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). + ### Notes for Yarn v2 and v3 users If you're using [Yarn](https://yarnpkg.com/) v2 or v3, you may run into issues where Storybook can't resolve `style-loader` or `css-loader`. For example, you might get errors like: From bef036f2ad35399fa45f424706280d396dfaef70 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 5 Dec 2023 00:48:45 +0800 Subject: [PATCH 08/10] Enable RSC in NextJS sandboxes --- code/lib/cli/src/sandbox-templates.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index 86b28646fb10..263716ca6246 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -116,6 +116,11 @@ const baseTemplates = { renderer: '@storybook/react', builder: '@storybook/builder-webpack5', }, + modifications: { + mainConfig: { + features: { experimentalNextRSC: true }, + }, + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'nextjs/default-js': { @@ -127,6 +132,11 @@ const baseTemplates = { renderer: '@storybook/react', builder: '@storybook/builder-webpack5', }, + modifications: { + mainConfig: { + features: { experimentalNextRSC: true }, + }, + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'nextjs/default-ts': { @@ -138,6 +148,11 @@ const baseTemplates = { renderer: '@storybook/react', builder: '@storybook/builder-webpack5', }, + modifications: { + mainConfig: { + features: { experimentalNextRSC: true }, + }, + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'nextjs/prerelease': { @@ -149,6 +164,11 @@ const baseTemplates = { renderer: '@storybook/react', builder: '@storybook/builder-webpack5', }, + modifications: { + mainConfig: { + features: { experimentalNextRSC: true }, + }, + }, skipTasks: ['e2e-tests-dev', 'bench'], }, 'react-vite/default-js': { From 80b2a9e5486a07c50a9832a95a40943cbd9ebf7b Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 5 Dec 2023 01:13:44 +0800 Subject: [PATCH 09/10] Fix review comments --- code/frameworks/nextjs/README.md | 4 +++- code/frameworks/nextjs/src/rsc/decorator.tsx | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index b10e9700ba17..87c35de75126 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -939,7 +939,9 @@ export default { Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To deal work around this, you'll need to mock out your data access layer using [Webpack aliases](https://webpack.js.org/configuration/resolve/#resolvealias) or an addon like [storybook-addon-module-mock](https://storybook.js.org/addons/storybook-addon-module-mock). -In the future we will provide better module mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). +If your server components access data via the network, we recommend using the [MSW Storybook Addon](https://storybook.js.org/addons/msw-storybook-addon) to mock network requests. + +In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions). ### Notes for Yarn v2 and v3 users diff --git a/code/frameworks/nextjs/src/rsc/decorator.tsx b/code/frameworks/nextjs/src/rsc/decorator.tsx index ceaf9daca1bf..73b7e7b4d817 100644 --- a/code/frameworks/nextjs/src/rsc/decorator.tsx +++ b/code/frameworks/nextjs/src/rsc/decorator.tsx @@ -4,13 +4,11 @@ import type { StoryContext } from '@storybook/react'; export const ServerComponentDecorator = ( Story: React.FC, { parameters }: StoryContext -): React.ReactNode => { - console.log('ServerComponentDecorator', { rsc: parameters?.nextjs?.rsc }); - return parameters?.nextjs?.rsc ? ( +): React.ReactNode => + parameters?.nextjs?.rsc ? ( ) : ( ); -}; From 6433e19cad8cfe3a62f5f25dacdb1185d013ee04 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 6 Dec 2023 01:52:39 +0800 Subject: [PATCH 10/10] Fix typo --- code/lib/types/src/modules/core-common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index d81e605f1edd..4eb9119b74c4 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -397,7 +397,7 @@ export interface StorybookConfigRaw { disallowImplicitActionsInRenderV8?: boolean; /** - * Eneable asynchronous component rendering in NextJS framework + * Enable asynchronous component rendering in NextJS framework */ experimentalNextRSC?: boolean; };