From 4ba3a69c35b10264df8ea51af711330a827bd186 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 13 Jan 2024 22:07:50 +0800 Subject: [PATCH 1/4] WIP refactor RSC out of Next and into React --- code/frameworks/nextjs/src/preset.ts | 2 +- code/lib/cli/src/sandbox-templates.ts | 8 ++--- code/lib/types/src/modules/core-common.ts | 7 +++++ code/renderers/react/package.json | 2 ++ .../renderers/react/src/entry-preview-rsc.tsx | 29 +++++++++++++++++++ code/yarn.lock | 2 ++ 6 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 code/renderers/react/src/entry-preview-rsc.tsx diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index da332d81e4ef..a8360cc634da 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -170,7 +170,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, configureFastRefresh(baseConfig); } - if (options.features?.experimentalNextRSC) { + if (options.features?.experimentalNextRSC || options.features?.experimentalRSC) { configureRSC(baseConfig); } diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index a882d2be3bba..a7f73461060a 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -124,7 +124,7 @@ const baseTemplates = { }, modifications: { mainConfig: { - features: { experimentalNextRSC: true }, + features: { experimentalRSC: true }, }, extraDependencies: ['server-only'], }, @@ -142,7 +142,7 @@ const baseTemplates = { }, modifications: { mainConfig: { - features: { experimentalNextRSC: true }, + features: { experimentalRSC: true }, }, extraDependencies: ['server-only'], }, @@ -159,7 +159,7 @@ const baseTemplates = { }, modifications: { mainConfig: { - features: { experimentalNextRSC: true }, + features: { experimentalRSC: true }, }, extraDependencies: ['server-only'], }, @@ -176,7 +176,7 @@ const baseTemplates = { }, modifications: { mainConfig: { - features: { experimentalNextRSC: true }, + features: { experimentalRSC: true }, }, extraDependencies: ['server-only'], }, diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index 377081831972..dd178258c61f 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -374,8 +374,15 @@ export interface StorybookConfigRaw { /** * Enable asynchronous component rendering in NextJS framework + * + * @deprecated use `experimentalRSC` instead */ experimentalNextRSC?: boolean; + + /** + * Enable asynchronous component rendering in React renderer + */ + experimentalRSC?: boolean; }; build?: TestBuildConfig; diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index 4ddcbe7b4746..f9e958473401 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -64,6 +64,7 @@ "lodash": "^4.17.21", "prop-types": "^15.7.2", "react-element-to-jsx-string": "^15.0.0", + "semver": "^7.3.7", "ts-dedent": "^2.0.0", "type-fest": "~2.19", "util-deprecate": "^1.0.2" @@ -71,6 +72,7 @@ "devDependencies": { "@storybook/test": "workspace:*", "@types/babel-plugin-react-docgen": "^4", + "@types/semver": "^7.3.4", "@types/util-deprecate": "^1.0.0", "babel-plugin-react-docgen": "^4.2.1", "expect-type": "^0.15.0", diff --git a/code/renderers/react/src/entry-preview-rsc.tsx b/code/renderers/react/src/entry-preview-rsc.tsx new file mode 100644 index 000000000000..2d8fbd103721 --- /dev/null +++ b/code/renderers/react/src/entry-preview-rsc.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import semver from 'semver'; +import type { Addon_DecoratorFunction } from '@storybook/types'; +import type { StoryContext } from './types'; + +export const ServerComponentDecorator = ( + Story: React.FC, + { parameters }: StoryContext +): React.ReactNode => { + if (!parameters?.react?.rsc) return ; + + if (semver.major(React.version) < 18 || semver.minor(React.version) < 3) { + throw new Error('React Server Components require React 18.3'); + } + + return ( + + + + ); +}; + +export const decorators: Addon_DecoratorFunction[] = [ServerComponentDecorator]; + +export const parameters = { + react: { + rsc: true, + }, +}; diff --git a/code/yarn.lock b/code/yarn.lock index 2590e466330b..494c8cf95840 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6315,6 +6315,7 @@ __metadata: "@types/escodegen": "npm:^0.0.6" "@types/estree": "npm:^0.0.51" "@types/node": "npm:^18.0.0" + "@types/semver": "npm:^7.3.4" "@types/util-deprecate": "npm:^1.0.0" acorn: "npm:^7.4.1" acorn-jsx: "npm:^5.3.1" @@ -6327,6 +6328,7 @@ __metadata: prop-types: "npm:^15.7.2" react-element-to-jsx-string: "npm:^15.0.0" require-from-string: "npm:^2.0.2" + semver: "npm:^7.3.7" ts-dedent: "npm:^2.0.0" type-fest: "npm:~2.19" util-deprecate: "npm:^1.0.2" From 6452ff8c1f0a1a94e10d25c174dc7d4000e5eb2c Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 13 Jan 2024 22:20:52 +0800 Subject: [PATCH 2/4] React: Fix RSC preset --- code/renderers/react/package.json | 4 +++- code/renderers/react/src/preset.ts | 3 ++- code/renderers/react/src/typings.d.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index f9e958473401..43cf24891403 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -29,6 +29,7 @@ "./preset": "./preset.js", "./dist/entry-preview.mjs": "./dist/entry-preview.mjs", "./dist/entry-preview-docs.mjs": "./dist/entry-preview-docs.mjs", + "./dist/entry-preview-rsc.mjs": "./dist/entry-preview-rsc.mjs", "./package.json": "./package.json" }, "main": "dist/index.js", @@ -99,7 +100,8 @@ "./src/index.ts", "./src/preset.ts", "./src/entry-preview.ts", - "./src/entry-preview-docs.ts" + "./src/entry-preview-docs.ts", + "./src/entry-preview-rsc.tsx" ], "platform": "browser" }, diff --git a/code/renderers/react/src/preset.ts b/code/renderers/react/src/preset.ts index e9a5ac13ebf7..b7f374bd2790 100644 --- a/code/renderers/react/src/preset.ts +++ b/code/renderers/react/src/preset.ts @@ -17,7 +17,8 @@ export const previewAnnotations: PresetProperty<'previewAnnotations'> = async ( return result .concat(input) .concat([join(__dirname, 'entry-preview.mjs')]) - .concat(docsEnabled ? [join(__dirname, 'entry-preview-docs.mjs')] : []); + .concat(docsEnabled ? [join(__dirname, 'entry-preview-docs.mjs')] : []) + .concat(FEATURES?.experimentalRSC ? [join(__dirname, 'entry-preview-rsc.mjs')] : []); }; /** diff --git a/code/renderers/react/src/typings.d.ts b/code/renderers/react/src/typings.d.ts index a5e002d064ee..1823ff6bd9ad 100644 --- a/code/renderers/react/src/typings.d.ts +++ b/code/renderers/react/src/typings.d.ts @@ -1,3 +1,4 @@ declare var STORYBOOK_ENV: 'react'; declare var FRAMEWORK_OPTIONS: any; declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined; +declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features']; From 337ab52695405b06442e8f825206221d693f4812 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 13 Jan 2024 23:05:14 +0800 Subject: [PATCH 3/4] Fix formatting --- code/addons/docs/src/preview.ts | 17 ++++++++++------- code/lib/cli/src/upgrade.test.ts | 11 +++++++---- .../core-server/src/presets/common-manager.ts | 17 ++++++++++------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/code/addons/docs/src/preview.ts b/code/addons/docs/src/preview.ts index d983f454ccd2..991a7811b472 100644 --- a/code/addons/docs/src/preview.ts +++ b/code/addons/docs/src/preview.ts @@ -1,13 +1,16 @@ import type { PreparedStory } from '@storybook/types'; import { global } from '@storybook/global'; -const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => { - const [tag, option] = entry; - if ((option as any).excludeFromDocsStories) { - acc[tag] = true; - } - return acc; -}, {} as Record); +const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce( + (acc, entry) => { + const [tag, option] = entry; + if ((option as any).excludeFromDocsStories) { + acc[tag] = true; + } + return acc; + }, + {} as Record +); export const parameters: any = { docs: { diff --git a/code/lib/cli/src/upgrade.test.ts b/code/lib/cli/src/upgrade.test.ts index 69b85cbc1d2a..be025028c495 100644 --- a/code/lib/cli/src/upgrade.test.ts +++ b/code/lib/cli/src/upgrade.test.ts @@ -11,10 +11,13 @@ vi.mock('@storybook/telemetry'); vi.mock('./versions', async (importOriginal) => { const originalVersions = ((await importOriginal()) as { default: typeof versions }).default; return { - default: Object.keys(originalVersions).reduce((acc, key) => { - acc[key] = '8.0.0'; - return acc; - }, {} as Record), + default: Object.keys(originalVersions).reduce( + (acc, key) => { + acc[key] = '8.0.0'; + return acc; + }, + {} as Record + ), }; }); diff --git a/code/lib/core-server/src/presets/common-manager.ts b/code/lib/core-server/src/presets/common-manager.ts index 081722917468..0564f8e00b92 100644 --- a/code/lib/core-server/src/presets/common-manager.ts +++ b/code/lib/core-server/src/presets/common-manager.ts @@ -6,13 +6,16 @@ const STATIC_FILTER = 'static-filter'; addons.register(STATIC_FILTER, (api) => { // FIXME: this ensures the filter is applied after the first render // to avoid a strange race condition in Webkit only. - const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => { - const [tag, option] = entry; - if ((option as any).excludeFromSidebar) { - acc[tag] = true; - } - return acc; - }, {} as Record); + const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce( + (acc, entry) => { + const [tag, option] = entry; + if ((option as any).excludeFromSidebar) { + acc[tag] = true; + } + return acc; + }, + {} as Record + ); api.experimental_setFilter(STATIC_FILTER, (item) => { const tags = item.tags || []; From 4f183a941bf6cc12fced522cae103443c14769e7 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 16 Jan 2024 16:01:54 +0800 Subject: [PATCH 4/4] Fully switch to experimentalRSC --- MIGRATION.md | 25 ++++++++++++------- code/frameworks/nextjs/README.md | 4 +-- code/frameworks/nextjs/package.json | 2 -- code/frameworks/nextjs/src/preset.ts | 5 +--- code/frameworks/nextjs/src/rsc/decorator.tsx | 14 ----------- code/frameworks/nextjs/src/rsc/preview.tsx | 10 -------- code/lib/types/src/modules/core-common.ts | 7 ------ .../renderers/react/src/entry-preview-rsc.tsx | 6 +++-- 8 files changed, 23 insertions(+), 50 deletions(-) delete mode 100644 code/frameworks/nextjs/src/rsc/decorator.tsx delete mode 100644 code/frameworks/nextjs/src/rsc/preview.tsx diff --git a/MIGRATION.md b/MIGRATION.md index 670f89c77ac8..35760a8f15da 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -32,6 +32,7 @@ - [Next.js](#nextjs) - [Require Next.js 13.5 and up](#require-nextjs-135-and-up) - [Automatic SWC mode detection](#automatic-swc-mode-detection) + - [RSC config moved to React renderer](#rsc-config-moved-to-react-renderer) - [Angular](#angular) - [Require Angular 15 and up](#require-angular-15-and-up) - [Svelte](#svelte) @@ -66,17 +67,17 @@ - [`createChannel` from `@storybook/postmessage` and `@storybook/channel-websocket`](#createchannel-from-storybookpostmessage-and-storybookchannel-websocket) - [StoryStore and methods deprecated](#storystore-and-methods-deprecated) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) - - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) - - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) - - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) + - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) + - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) + - [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated) + - [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop) + - [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react) - [From version 7.4.0 to 7.5.0](#from-version-740-to-750) - - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) - - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) + - [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated) + - [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers) - [From version 7.0.0 to 7.2.0](#from-version-700-to-720) - - [Addon API is more type-strict](#addon-api-is-more-type-strict) - - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) + - [Addon API is more type-strict](#addon-api-is-more-type-strict) + - [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated) - [From version 6.5.x to 7.0.0](#from-version-65x-to-700) - [7.0 breaking changes](#70-breaking-changes) - [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below) @@ -728,6 +729,12 @@ Similar to how Next.js detects if SWC should be used, Storybook will follow more - If you use Next.js 14 or higher and you don't have a .babelrc file, Storybook will use SWC to transpile your code. - Even if you have a .babelrc file, Storybook will still use SWC to transpile your code if you set the experimental `experimental.forceSwcTransforms` flag to `true` in your `next.config.js`. +##### RSC config moved to React renderer + +Storybook 7.6 introduced a new feature flag, `experimentalNextRSC`, to enable React Server Components in a Next.js project. It also introduced a parameter `nextjs.rsc` to selectively disable it on particular components or stories. + +These flags have been renamed to `experimentalRSC` and `react.rsc`, respectively. This is a breaking change to accommodate RSC support in other, non-Next.js frameworks. For now, `@storybook/nextjs` is the only framework that supports it, and does so experimentally. + #### Angular ##### Require Angular 15 and up diff --git a/code/frameworks/nextjs/README.md b/code/frameworks/nextjs/README.md index ab6a8fc9cad5..d1d234ab89fc 100644 --- a/code/frameworks/nextjs/README.md +++ b/code/frameworks/nextjs/README.md @@ -907,13 +907,13 @@ Storybook handles most [Typescript](https://www.typescriptlang.org/) configurati 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: +To enable this set the `experimentalRSC` feature flag in your `.storybook/main.js` config: ```js // main.js export default { features: { - experimentalNextRSC: true, + experimentalRSC: true, }, }; ``` diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 052baf685f6c..5c7e73f7b8dc 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -42,7 +42,6 @@ "import": "./dist/font/webpack/loader/storybook-nextjs-font-loader.mjs" }, "./dist/preview.mjs": "./dist/preview.mjs", - "./dist/rsc/preview.mjs": "./dist/rsc/preview.mjs", "./next-image-loader-stub.js": { "types": "./dist/next-image-loader-stub.d.ts", "require": "./dist/next-image-loader-stub.js", @@ -164,7 +163,6 @@ "./src/images/next-legacy-image.tsx", "./src/images/next-image.tsx", "./src/font/webpack/loader/storybook-nextjs-font-loader.ts", - "./src/rsc/preview.tsx", "./src/rsc/server-only.ts", "./src/swc/next-swc-loader-patch.ts" ], diff --git a/code/frameworks/nextjs/src/preset.ts b/code/frameworks/nextjs/src/preset.ts index a8360cc634da..3d58706ad2b4 100644 --- a/code/frameworks/nextjs/src/preset.ts +++ b/code/frameworks/nextjs/src/preset.ts @@ -76,9 +76,6 @@ export const previewAnnotations: PresetProperty<'previewAnnotations'> = ( ) => { 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/rsc/preview.mjs')); - } return result; }; @@ -170,7 +167,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig, configureFastRefresh(baseConfig); } - if (options.features?.experimentalNextRSC || options.features?.experimentalRSC) { + if (options.features?.experimentalRSC) { configureRSC(baseConfig); } diff --git a/code/frameworks/nextjs/src/rsc/decorator.tsx b/code/frameworks/nextjs/src/rsc/decorator.tsx deleted file mode 100644 index 73b7e7b4d817..000000000000 --- a/code/frameworks/nextjs/src/rsc/decorator.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; -import type { StoryContext } from '@storybook/react'; - -export const ServerComponentDecorator = ( - Story: React.FC, - { parameters }: StoryContext -): React.ReactNode => - parameters?.nextjs?.rsc ? ( - - - - ) : ( - - ); diff --git a/code/frameworks/nextjs/src/rsc/preview.tsx b/code/frameworks/nextjs/src/rsc/preview.tsx deleted file mode 100644 index a26737ec1e06..000000000000 --- a/code/frameworks/nextjs/src/rsc/preview.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { Addon_DecoratorFunction } from '@storybook/types'; -import { ServerComponentDecorator } from './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 dd178258c61f..9e254a75a8a1 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -372,13 +372,6 @@ export interface StorybookConfigRaw { */ disallowImplicitActionsInRenderV8?: boolean; - /** - * Enable asynchronous component rendering in NextJS framework - * - * @deprecated use `experimentalRSC` instead - */ - experimentalNextRSC?: boolean; - /** * Enable asynchronous component rendering in React renderer */ diff --git a/code/renderers/react/src/entry-preview-rsc.tsx b/code/renderers/react/src/entry-preview-rsc.tsx index 2d8fbd103721..96b04ab996ba 100644 --- a/code/renderers/react/src/entry-preview-rsc.tsx +++ b/code/renderers/react/src/entry-preview-rsc.tsx @@ -9,8 +9,10 @@ export const ServerComponentDecorator = ( ): React.ReactNode => { if (!parameters?.react?.rsc) return ; - if (semver.major(React.version) < 18 || semver.minor(React.version) < 3) { - throw new Error('React Server Components require React 18.3'); + const major = semver.major(React.version); + const minor = semver.minor(React.version); + if (major < 18 || (major === 18 && minor < 3)) { + throw new Error('React Server Components require React >= 18.3'); } return (