Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions code/builders/builder-vite/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function findPlugin(config: InlineConfig, name: string) {
}

export async function build(options: Options) {
const { build: viteBuild, mergeConfig } = await import('vite');
const { build: viteBuild, mergeConfig, createBuilder } = await import('vite');
const { presets } = options;

const config = await commonConfig(options, 'build');
Expand Down Expand Up @@ -64,7 +64,7 @@ export async function build(options: Options) {
finalConfig.plugins = await withoutVitePlugins(finalConfig.plugins, [turbosnapPluginName]);
}

await viteBuild(await sanitizeEnvVars(options, finalConfig));
await (await createBuilder(await sanitizeEnvVars(options, finalConfig), null)).buildApp();

const statsPlugin = findPlugin(
finalConfig,
Expand Down
1 change: 1 addition & 0 deletions code/core/src/cli/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const frameworkToDefaultBuilder: Record<
nextjs: CoreBuilder.Webpack5,
nuxt: CoreBuilder.Vite,
'nextjs-vite': CoreBuilder.Vite,
'nextjs-vite-rsc': CoreBuilder.Vite,
'preact-vite': CoreBuilder.Vite,
qwik: CoreBuilder.Vite,
'react-native-web-vite': CoreBuilder.Vite,
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/utils/framework-to-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const frameworkToRenderer: Record<
'html-vite': 'html',
nextjs: 'react',
'nextjs-vite': 'react',
'nextjs-vite-rsc': 'react',
'preact-vite': 'preact',
qwik: 'qwik',
'react-vite': 'react',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/utils/get-storybook-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const frameworkPackages: Record<string, SupportedFrameworks> = {
'@storybook/sveltekit': 'sveltekit',
'@storybook/vue3-vite': 'vue3-vite',
'@storybook/nextjs-vite': 'nextjs-vite',
'@storybook/nextjs-vite-rsc': 'nextjs-vite-rsc',
'@storybook/react-native-web-vite': 'react-native-web-vite',
'@storybook/web-components-vite': 'web-components-vite',
// community (outside of monorepo)
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
'@storybook/html-vite': '10.0.0-beta.9',
'@storybook/nextjs': '10.0.0-beta.9',
'@storybook/nextjs-vite': '10.0.0-beta.9',
'@storybook/nextjs-vite-rsc': '10.0.0-beta.9',
'@storybook/preact-vite': '10.0.0-beta.9',
'@storybook/react-native-web-vite': '10.0.0-beta.9',
'@storybook/react-vite': '10.0.0-beta.9',
Expand Down
3 changes: 2 additions & 1 deletion code/core/src/csf/csf-factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ function defineStory<

const play =
mountDestructured(this.play) || mountDestructured(testFunction)
? async ({ context }: StoryContext<TRenderer>) => {
? // eslint-disable-next-line @typescript-eslint/no-unused-vars
async ({ context, mount }: StoryContext<TRenderer>) => {
await this.play?.(context);
await testFunction(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import type { PlayFunction } from 'storybook/internal/csf';
import { type Renderer } from 'storybook/internal/types';

export function mountDestructured<TRenderer extends Renderer>(
playFunction?: PlayFunction<TRenderer>
): boolean {
export function mountDestructured(playFunction?: (...args: any[]) => any): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type safety regression: prefer precise typings over any.

The function signature changed from PlayFunction<TRenderer> to (...args: any[]) => any, eliminating type safety. This violates the coding guideline to "prefer precise typings instead of using any or suppressions, consistent with strict mode."

If framework flexibility is required, consider alternatives that preserve type safety:

  • Use a union of known function types
  • Introduce a base type that captures common structure
  • Use generics with constraints that accommodate the new frameworks

As per coding guidelines.

🤖 Prompt for AI Agents
In code/core/src/preview-api/modules/preview-web/render/mount-utils.ts around
line 6, the mountDestructured parameter was changed to a loose (...args: any[])
=> any which loses type safety; restore precise typing by reverting to the
original PlayFunction<TRenderer> signature or introduce a generic constraint
(e.g., <TRenderer> and accept playFunction?: PlayFunction<TRenderer> or a
constrained union/base function type) so callers keep strict-mode type
guarantees; update the function signature and any related generics/imports
accordingly, avoiding use of any.

return playFunction != null && getUsedProps(playFunction).includes('mount');
}
export function getUsedProps(fn: Function) {
Expand Down
1 change: 1 addition & 0 deletions code/core/src/types/modules/frameworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type SupportedFrameworks =
| 'html-vite'
| 'nextjs'
| 'nextjs-vite'
| 'nextjs-vite-rsc'
| 'preact-vite'
| 'react-native-web-vite'
| 'react-vite'
Expand Down
33 changes: 33 additions & 0 deletions code/frameworks/nextjs-vite-rsc/build-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { BuildEntries } from '../../../scripts/build/utils/entry-utils';

const config: BuildEntries = {
extraOutputs: {
'./navigation': {
types: './dist/rsc/navigation.react-server.d.ts',
'react-server': './dist/rsc/navigation.react-server.js',
default: './dist/rsc/navigation.js',
},
},
entries: {
browser: [
{
Expand All @@ -27,6 +34,32 @@ const config: BuildEntries = {
exportEntries: ['./router.mock'],
entryPoint: './src/export-mocks/router/index.ts',
},
{
exportEntries: ['./load-client-dev'],
entryPoint: './src/load-client-dev.tsx',
},
{
exportEntries: ['./react-client'],
entryPoint: './src/react-client.tsx',
},
{
exportEntries: ['./headers'],
entryPoint: './src/rsc/headers.ts',
},
{
entryPoint: './src/rsc/navigation.ts',
},
{
entryPoint: './src/rsc/navigation.react-server.ts',
},
{
exportEntries: ['./rsc/client'],
entryPoint: './src/rsc/client.tsx',
},
{
exportEntries: ['./repro'],
entryPoint: './src/repro.tsx',
},
],
node: [
{
Expand Down
26 changes: 24 additions & 2 deletions code/frameworks/nextjs-vite-rsc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@storybook/nextjs-vite",
"version": "10.0.0-beta.2",
"name": "@storybook/nextjs-vite-rsc",
"version": "10.0.0-beta.9",
"description": "Storybook for Next.js and Vite: Develop, document, and test UI components in isolation",
"keywords": [
"storybook",
Expand Down Expand Up @@ -35,10 +35,23 @@
"types": "./dist/export-mocks/cache/index.d.ts",
"default": "./dist/export-mocks/cache/index.js"
},
"./headers": {
"types": "./dist/rsc/headers.d.ts",
"default": "./dist/rsc/headers.js"
},
"./headers.mock": {
"types": "./dist/export-mocks/headers/index.d.ts",
"default": "./dist/export-mocks/headers/index.js"
},
"./load-client-dev": {
"types": "./dist/load-client-dev.d.ts",
"default": "./dist/load-client-dev.js"
},
"./navigation": {
"types": "./dist/rsc/navigation.react-server.d.ts",
"react-server": "./dist/rsc/navigation.react-server.js",
"default": "./dist/rsc/navigation.js"
},
"./navigation.mock": {
"types": "./dist/export-mocks/navigation/index.d.ts",
"default": "./dist/export-mocks/navigation/index.js"
Expand All @@ -53,10 +66,18 @@
"types": "./dist/preview.d.ts",
"default": "./dist/preview.js"
},
"./react-client": {
"types": "./dist/react-client.d.ts",
"default": "./dist/react-client.js"
},
"./router.mock": {
"types": "./dist/export-mocks/router/index.d.ts",
"default": "./dist/export-mocks/router/index.js"
},
"./rsc/client": {
"types": "./dist/rsc/client.d.ts",
"default": "./dist/rsc/client.js"
},
"./vite-plugin": {
"types": "./dist/vite-plugin/index.d.ts",
"default": "./dist/vite-plugin/index.js"
Expand All @@ -81,6 +102,7 @@
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react-vite": "workspace:*",
"@vitejs/plugin-rsc": "^0.4.31",
"styled-jsx": "5.1.6",
"vite-plugin-storybook-nextjs": "^2.0.5"
},
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/nextjs-vite-rsc/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "nextjs-vite",
"name": "nextjs-vite-rsc",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"targets": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// is the only way to achieve it actually being a singleton
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore we must ignore types here as during compilation they are not generated yet
import { headers } from '@storybook/nextjs-vite/headers.mock';
import { headers } from '@storybook/nextjs-vite-rsc/headers.mock';

import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import { fn } from 'storybook/test';
Expand Down
10 changes: 4 additions & 6 deletions code/frameworks/nextjs-vite-rsc/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
import type { AddonTypes, InferTypes, PreviewAddon } from 'storybook/internal/csf';
import { definePreview as definePreviewBase } from 'storybook/internal/csf';
import type { ProjectAnnotations } from 'storybook/internal/types';

import type { ReactPreview } from '@storybook/react';
import { __definePreview } from '@storybook/react';
import type { ReactTypes } from '@storybook/react';

import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs';

import * as nextPreview from './preview';
import type { NextJsTypes } from './types';

export * from '@storybook/react';
// @ts-expect-error (double exports)
export * from './portable-stories';
// export * from '@storybook/react';
export * from './types';

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
declare module '@storybook/nextjs-vite/vite-plugin' {
declare module '@storybook/nextjs-vite-rsc/vite-plugin' {
export const storybookNextJsPlugin: typeof vitePluginStorybookNextJs;
}

export function definePreview<Addons extends PreviewAddon<never>[]>(
preview: { addons?: Addons } & ProjectAnnotations<ReactTypes & NextJsTypes & InferTypes<Addons>>
): NextPreview<InferTypes<Addons>> {
// @ts-expect-error hard
return __definePreview({
return definePreviewBase({
...preview,
addons: [nextPreview, ...(preview.addons ?? [])],
});
Expand Down
25 changes: 25 additions & 0 deletions code/frameworks/nextjs-vite-rsc/src/load-client-dev.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner';

export default async function loadClient() {
const runner = new ModuleRunner(
{
sourcemapInterceptor: false,
transport: {
invoke: async (payload) => {
const response = await fetch(
'/@vite/invoke-react-client?' +
new URLSearchParams({
data: JSON.stringify(payload),
})
);
return response.json();
},
},
hmr: false,
},
new ESModulesEvaluator()
);
return await runner.import<typeof import('@storybook/nextjs-vite-rsc/react-client')>(
'@storybook/nextjs-vite-rsc/react-client'
);
}
6 changes: 3 additions & 3 deletions code/frameworks/nextjs-vite-rsc/src/portable-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import * as nextJsAnnotations from './preview';
*
* ```jsx
* // setup-file.js
* import { setProjectAnnotations } from '@storybook/nextjs-vite';
* import { setProjectAnnotations } from '@storybook/nextjs-vite-rsc';
* import projectAnnotations from './.storybook/preview';
*
* setProjectAnnotations(projectAnnotations);
Expand Down Expand Up @@ -70,7 +70,7 @@ const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations<ReactRenderer> =
*
* ```jsx
* import { render } from '@testing-library/react';
* import { composeStory } from '@storybook/nextjs-vite';
* import { composeStory } from '@storybook/nextjs-vite-rsc';
* import Meta, { Primary as PrimaryStory } from './Button.stories';
*
* const Primary = composeStory(PrimaryStory, Meta);
Expand Down Expand Up @@ -114,7 +114,7 @@ export function composeStory<TArgs extends Args = Args>(
*
* ```jsx
* import { render } from '@testing-library/react';
* import { composeStories } from '@storybook/nextjs-vite';
* import { composeStories } from '@storybook/nextjs-vite-rsc';
* import * as stories from './Button.stories';
*
* const { Primary, Secondary } = composeStories(stories);
Expand Down
31 changes: 12 additions & 19 deletions code/frameworks/nextjs-vite-rsc/src/preset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// https://storybook.js.org/docs/react/addons/writing-presets
import { createRequire } from 'node:module';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

Expand All @@ -12,12 +10,7 @@ import { viteFinal as reactViteFinal } from '@storybook/react-vite/preset';

import postCssLoadConfig from 'postcss-load-config';

import type { FrameworkOptions } from './types';

const require = createRequire(import.meta.url);

// the ESM output of this package is broken, so I had to force it to use the CJS version it's shipping.
const vitePluginStorybookNextjs = require('vite-plugin-storybook-nextjs');
import { rscBrowserModePlugin, vitestPluginNext } from './rsc-plugin';

export const core: PresetProperty<'core'> = async (config, options) => {
const framework = await options.presets.apply('framework');
Expand All @@ -30,20 +23,24 @@ export const core: PresetProperty<'core'> = async (config, options) => {
...(typeof framework === 'string' ? {} : framework.options.builder || {}),
},
},
renderer: fileURLToPath(import.meta.resolve('@storybook/react/preset')),
renderer: undefined,
};
};

export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry = []) => {
const result = [...entry, fileURLToPath(import.meta.resolve('@storybook/nextjs-vite/preview'))];
const result = [
...entry,
fileURLToPath(import.meta.resolve('@storybook/nextjs-vite-rsc/preview')),
];
return result;
};

export const optimizeViteDeps = [
'@storybook/nextjs-vite/navigation.mock',
'@storybook/nextjs-vite/router.mock',
'@storybook/nextjs-vite > styled-jsx',
'@storybook/nextjs-vite > styled-jsx/style',
'next/dist/client/components/is-next-router-error',
'@storybook/nextjs-vite-rsc/navigation.mock',
'@storybook/nextjs-vite-rsc/router.mock',
'@storybook/nextjs-vite-rsc > styled-jsx',
'@storybook/nextjs-vite-rsc > styled-jsx/style',
];
Comment on lines 38 to 44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Double‑check optimizeDeps include syntax for transitive deps.

Entries like @storybook/nextjs-vite-rsc > styled-jsx/... > styled-jsx/style are unconventional for Vite’s optimizeDeps.include. Ensure the builder/preset pipeline consumes this list and expands the dep > subdep form as intended (or switch to plain bare imports if not needed). Note that vitestPluginNext also includes is-next-router-error, which may duplicate this include.

Run:


🏁 Script executed:

#!/bin/bash
# See how other frameworks structure this and whether '>' is used project-wide
rg -n -C2 'optimizeViteDeps|optimizeDeps' code/ | sed -n '1,200p'
rg -nP '>\s*styled-jsx' code/

Length of output: 7058


Replace unconventional '>’ entries with bare imports
Vite’s optimizeDeps.include won’t expand "@storybook/nextjs-vite-rsc > styled-jsx". In code/frameworks/nextjs-vite-rsc/src/preset.ts update:

  • '@storybook/nextjs-vite-rsc > styled-jsx''styled-jsx'
  • '@storybook/nextjs-vite-rsc > styled-jsx/style''styled-jsx/style'
    Also remove the duplicate 'next/dist/client/components/is-next-router-error' since it’s already included by vitestPluginNext.
🤖 Prompt for AI Agents
In code/frameworks/nextjs-vite-rsc/src/preset.ts around lines 38 to 44, the
optimizeViteDeps array contains unconventional entries using the " > " scoping
and a duplicate entry; replace '@storybook/nextjs-vite-rsc > styled-jsx' with
'styled-jsx' and '@storybook/nextjs-vite-rsc > styled-jsx/style' with
'styled-jsx/style', and remove the duplicate
'next/dist/client/components/is-next-router-error' entry so optimizeViteDeps
uses the bare imports and has no duplicates.


export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, options) => {
Expand All @@ -62,10 +59,6 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, option
}
}

const { nextConfigPath } = await options.presets.apply<FrameworkOptions>('frameworkOptions');

const nextDir = nextConfigPath ? dirname(nextConfigPath) : undefined;

return {
...reactConfig,
resolve: {
Expand All @@ -77,6 +70,6 @@ export const viteFinal: StorybookConfigVite['viteFinal'] = async (config, option
'styled-jsx/style.js': fileURLToPath(import.meta.resolve('styled-jsx/style')),
},
},
plugins: [...(reactConfig?.plugins ?? []), vitePluginStorybookNextjs({ dir: nextDir })],
plugins: [...(reactConfig?.plugins ?? []), rscBrowserModePlugin(), vitestPluginNext()],
};
};
Loading