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
2 changes: 0 additions & 2 deletions code/core/src/common/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export default {
'eslint-plugin-storybook': '10.4.0-alpha.10',
'@storybook/react-dom-shim': '10.4.0-alpha.10',
'@storybook/preset-create-react-app': '10.4.0-alpha.10',
'@storybook/preset-react-webpack': '10.4.0-alpha.10',
'@storybook/preset-server-webpack': '10.4.0-alpha.10',
'@storybook/html': '10.4.0-alpha.10',
'@storybook/preact': '10.4.0-alpha.10',
'@storybook/react': '10.4.0-alpha.10',
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@
"@babel/runtime": "^7.28.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preset-react-webpack": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react-webpack5": "workspace:*",
"@types/semver": "^7.7.1",
"babel-loader": "^9.1.3",
"css-loader": "^6.7.3",
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/nextjs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { FrameworkOptions, StorybookConfig } from './types.ts';
import { isNextVersionGte } from './utils.ts';

export const addons: PresetProperty<'addons'> = [
fileURLToPath(import.meta.resolve('@storybook/preset-react-webpack')),
fileURLToPath(import.meta.resolve('@storybook/react-webpack5/preset')),
];

export const core: PresetProperty<'core'> = async (config, options) => {
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/nextjs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
ReactOptions,
StorybookConfig as StorybookConfigBase,
TypescriptOptions as TypescriptOptionsReact,
} from '@storybook/preset-react-webpack';
} from '@storybook/react-webpack5';

import type * as NextImage from 'next/image';
import type { NextRouter } from 'next/router';
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/react-vite/src/plugins/docgen-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ReactDocgenResolveError extends Error {
/* The below code was copied from:
* https://github.com/reactjs/react-docgen/blob/df2daa8b6f0af693ecc3c4dc49f2246f60552bcb/packages/react-docgen/src/importer/makeFsImporter.ts#L14-L63
* because it wasn't exported from the react-docgen package.
* watch out: when updating this code, also update the code in code/presets/react-webpack/src/loaders/docgen-resolver.ts
* watch out: when updating this code, also update the code in code/frameworks/react-webpack5/src/loaders/docgen-resolver.ts
*/

// These extensions are sorted by priority
Expand Down
15 changes: 15 additions & 0 deletions code/frameworks/react-webpack5/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ const config: BuildEntries = {
entryPoint: './src/preset.ts',
dts: false,
},
{
exportEntries: ['./preset-cra'],
entryPoint: './src/preset-cra.ts',
dts: false,
},
{
exportEntries: ['./preset-react-docs'],
entryPoint: './src/preset-react-docs.ts',
dts: false,
},
{
exportEntries: ['./react-docgen-loader'],
entryPoint: './src/loaders/react-docgen-loader.ts',
dts: false,
},
{
exportEntries: ['./node'],
entryPoint: './src/node/index.ts',
Expand Down
18 changes: 15 additions & 3 deletions code/frameworks/react-webpack5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@
"default": "./dist/node/index.js"
},
"./package.json": "./package.json",
"./preset": "./dist/preset.js"
"./preset": "./dist/preset.js",
"./preset-cra": "./dist/preset-cra.js",
"./preset-react-docs": "./dist/preset-react-docs.js",
"./react-docgen-loader": "./dist/loaders/react-docgen-loader.js"
},
"files": [
"dist/**/*",
Expand All @@ -49,8 +52,17 @@
],
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preset-react-webpack": "workspace:*",
"@storybook/react": "workspace:*"
"@storybook/core-webpack": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0",
"@types/semver": "^7.7.1",
"empathic": "^2.0.0",
"magic-string": "^0.30.5",
"react-docgen": "^7.1.1",
"resolve": "^1.22.8",
"semver": "^7.7.3",
"tsconfig-paths": "^4.2.0",
"webpack": "5"
},
"devDependencies": {
"@types/node": "^22.19.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import fs from 'node:fs';
import * as fs from 'node:fs';
import { join, sep } from 'node:path';

import { beforeEach, describe, expect, it, vi } from 'vitest';

import { getReactScriptsPath } from './cra-config.ts';

vi.mock('fs', () => ({
vi.mock('node:fs', () => ({
realpathSync: vi.fn(() => '/test-project'),
readFileSync: vi.fn(),
existsSync: vi.fn(() => true),
Expand Down Expand Up @@ -44,8 +44,6 @@ describe('cra-config', () => {

describe('when used with a custom react-scripts package without symlinks in .bin folder', () => {
beforeEach(() => {
// In case of .bin/react-scripts is not symlink (like it happens on Windows),
// realpathSync() method does not translate the path.
vi.mocked(fs.realpathSync).mockImplementationOnce((filePath) => filePath.toString());

vi.mocked(fs.readFileSync).mockImplementationOnce(
Expand Down
67 changes: 67 additions & 0 deletions code/frameworks/react-webpack5/src/loaders/docgen-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { extname } from 'node:path';

import resolve from 'resolve';

export class ReactDocgenResolveError extends Error {
readonly code = 'MODULE_NOT_FOUND';

constructor(filename: string) {
super(`'${filename}' was ignored by react-docgen.`);
}
}

// These extensions are sorted by priority.
export const RESOLVE_EXTENSIONS = [
'.js',
'.cts',
'.mts',
'.ctsx',
'.mtsx',
'.ts',
'.tsx',
'.mjs',
'.cjs',
'.mts',
'.cts',
'.jsx',
];

export function defaultLookupModule(filename: string, basedir: string): string {
const resolveOptions = {
basedir,
extensions: RESOLVE_EXTENSIONS,
includeCoreModules: false,
};

try {
return resolve.sync(filename, resolveOptions);
} catch (error) {
const ext = extname(filename);
let newFilename: string;

switch (ext) {
case '.js':
case '.mjs':
case '.cjs': {
const base = filename.slice(0, -2);
try {
return resolve.sync(`${base}ts`, { ...resolveOptions, extensions: [] });
} catch {
newFilename = `${base}tsx`;
}
break;
}

case '.jsx':
newFilename = `${filename.slice(0, -3)}tsx`;
break;
default:
throw error;
}

return resolve.sync(newFilename, {
...resolveOptions,
extensions: [],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ const reactDocgenResolverMock = vi.hoisted(() => {
};
});

vi.mock('./docgen-resolver', async (importOriginal) => {
const actual = await importOriginal<typeof import('path')>();
vi.mock('./docgen-resolver.ts', async (importOriginal) => {
const actual = await importOriginal<typeof import('./docgen-resolver.ts')>();
return {
...actual,
defaultLookupModule: reactDocgenResolverMock.defaultLookupModule,
};
});

vi.mock('react-docgen', async (importOriginal) => {
const actual = await importOriginal<typeof import('path')>();
const actual = await importOriginal<typeof import('react-docgen')>();
return {
...actual,
makeFsImporter: reactDocgenMock.makeFsImporter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const actualNameHandler: Handler = function actualNameHandler(documentation, com

currentPath = currentPath.parentPath;
}
// Could not find an actual name
documentation.set('actualName', '');
}
};
Expand All @@ -76,7 +75,6 @@ export default async function reactDocgenLoader(
map: any
) {
const callback = this.async();
// get options
const options = this.getOptions() || {};
const { debug = false } = options;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (
test: /\.(cjs|mjs|tsx?|jsx?)$/,
enforce: 'pre',
loader: fileURLToPath(
import.meta.resolve('@storybook/preset-react-webpack/react-docgen-loader')
import.meta.resolve('@storybook/react-webpack5/react-docgen-loader')
),
options: {
debug,
Expand All @@ -52,7 +52,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (
test: /\.(cjs|mjs|jsx?)$/,
enforce: 'pre',
loader: fileURLToPath(
import.meta.resolve('@storybook/preset-react-webpack/react-docgen-loader')
import.meta.resolve('@storybook/react-webpack5/react-docgen-loader')
),
options: {
debug,
Expand All @@ -65,7 +65,6 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (
...(config.plugins || []),
new ReactDocgenTypeScriptPlugin({
...reactDocgenTypescriptOptions,
// We *need* this set so that RDT returns default values in the same format as react-docgen
savePropValueAsString: true,
}),
],
Expand Down
3 changes: 2 additions & 1 deletion code/frameworks/react-webpack5/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { WebpackDefinePlugin } from '@storybook/builder-webpack5';
import type { StorybookConfig } from './types.ts';

export const addons: PresetProperty<'addons'> = [
fileURLToPath(import.meta.resolve('@storybook/preset-react-webpack')),
fileURLToPath(import.meta.resolve('@storybook/react-webpack5/preset-cra')),
fileURLToPath(import.meta.resolve('@storybook/react-webpack5/preset-react-docs')),
];

export const core: PresetProperty<'core'> = {
Expand Down
54 changes: 46 additions & 8 deletions code/frameworks/react-webpack5/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,52 @@ import type { CompatibleString } from 'storybook/internal/types';

import type {
BuilderOptions,
StorybookConfigWebpack,
TypescriptOptions as TypescriptOptionsBuilder,
StorybookConfigWebpack,
} from '@storybook/builder-webpack5';
import type {
ReactOptions,
StorybookConfig as StorybookConfigBase,
TypescriptOptions as TypescriptOptionsReact,
} from '@storybook/preset-react-webpack';
TypescriptOptions as TypescriptOptionsBase,
WebpackConfiguration as WebpackConfigurationBase,
} from '@storybook/core-webpack';
import type { PluginOptions as ReactDocgenTypescriptOptions } from '@storybook/react-docgen-typescript-plugin';

export type { BuilderResult } from '@storybook/core-webpack';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Re-export the webpack5 BuilderResult to preserve stats.

Line 15 now exports the core result type, but @storybook/builder-webpack5 extends it with stats?: Stats. Since this framework uses the webpack5 builder, consumers importing BuilderResult from @storybook/react-webpack5 lose that webpack-specific field.

🐛 Proposed fix
-export type { BuilderResult } from '@storybook/core-webpack';
+export type { BuilderResult } from '@storybook/builder-webpack5';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type { BuilderResult } from '@storybook/core-webpack';
export type { BuilderResult } from '@storybook/builder-webpack5';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frameworks/react-webpack5/src/types.ts` at line 15, The current export
re-exports BuilderResult from '@storybook/core-webpack', which drops the
webpack-specific stats field; update the export so consumers of this framework
keep that field by re-exporting BuilderResult from '@storybook/builder-webpack5'
(or declare a local BuilderResult = CoreBuilderResult & { stats?:
import("webpack").Stats } and export it) — locate the export line exporting
BuilderResult in types.ts and replace it with the re-export or extended type
referencing '@storybook/builder-webpack5' (or import webpack's Stats) so the
stats?: Stats property is preserved.


export interface ReactOptions {
strictMode?: boolean;
/**
* Use React's legacy root API to mount components
*
* 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
*/
legacyRootApi?: boolean;
}

export type TypescriptOptions = TypescriptOptionsBase & {
/**
* Sets the type of Docgen when working with React and TypeScript
*
* @default `'react-docgen'`
*/
reactDocgen: 'react-docgen-typescript' | 'react-docgen' | false;
/**
* Configures `react-docgen-typescript-plugin`
*
* @default
* @see https://github.com/storybookjs/storybook/blob/next/code/builders/builder-webpack5/src/config/defaults.js#L4-L6
*/
reactDocgenTypescriptOptions: ReactDocgenTypescriptOptions;
};

type StorybookConfigBaseWithTypes<TWebpackConfiguration = WebpackConfigurationBase> =
StorybookConfigBase<TWebpackConfiguration> & {
typescript?: Partial<TypescriptOptions>;
};

type FrameworkName = CompatibleString<'@storybook/react-webpack5'>;
type BuilderName = CompatibleString<'@storybook/builder-webpack5'>;
Expand All @@ -25,21 +63,21 @@ type StorybookConfigFramework = {
name: FrameworkName;
options: FrameworkOptions;
};
core?: StorybookConfigBase['core'] & {
core?: StorybookConfigBaseWithTypes['core'] & {
builder?:
| BuilderName
| {
name: BuilderName;
options: BuilderOptions;
};
};
typescript?: Partial<TypescriptOptionsBuilder & TypescriptOptionsReact> &
StorybookConfigBase['typescript'];
typescript?: Partial<TypescriptOptionsBuilder & TypescriptOptions> &
StorybookConfigBaseWithTypes['typescript'];
};

/** The interface for Storybook configuration in `main.ts` files. */
export type StorybookConfig = Omit<
StorybookConfigBase,
StorybookConfigBaseWithTypes,
keyof StorybookConfigWebpack | keyof StorybookConfigFramework
> &
StorybookConfigWebpack &
Expand Down
5 changes: 5 additions & 0 deletions code/frameworks/server-webpack5/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const config: BuildEntries = {
entryPoint: './src/preset.ts',
dts: false,
},
{
exportEntries: ['./loader'],
entryPoint: './src/loader.ts',
dts: false,
},
{
exportEntries: ['./node'],
entryPoint: './src/node/index.ts',
Expand Down
8 changes: 6 additions & 2 deletions code/frameworks/server-webpack5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"code": "./src/index.ts",
"default": "./dist/index.js"
},
"./loader": "./dist/loader.js",
"./node": {
"types": "./dist/node/index.d.ts",
"code": "./src/node/index.ts",
Expand All @@ -47,8 +48,11 @@
],
"dependencies": {
"@storybook/builder-webpack5": "workspace:*",
"@storybook/preset-server-webpack": "workspace:*",
"@storybook/server": "workspace:*"
"@storybook/core-webpack": "workspace:*",
"@storybook/server": "workspace:*",
"safe-identifier": "^0.4.2",
"ts-dedent": "^2.0.0",
"yaml-loader": "^0.8.1"
},
"devDependencies": {
"@types/node": "^22.19.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { compileCsfModule } from './lib/compiler/index.ts';

export default (content: string) => {
try {
const after = compileCsfModule(JSON.parse(content));
return after;
return compileCsfModule(JSON.parse(content));
} catch (e) {
// for debugging
console.log(content, e);
}
return content;
Comment on lines 3 to 9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail the loader instead of swallowing compile errors.

If JSON.parse or compileCsfModule fails, returning the original source lets webpack continue with non-CSF content and dumps the full story file to logs. Let the error propagate so users get a real build failure, and avoid logging user content.

Proposed fix
 export default (content: string) => {
-  try {
-    return compileCsfModule(JSON.parse(content));
-  } catch (e) {
-    console.log(content, e);
-  }
-  return content;
+  return compileCsfModule(JSON.parse(content));
 };

As per coding guidelines, “Use Storybook loggers instead of raw console.* in normal code paths: use 'storybook/internal/node-logger' for server-side code and 'storybook/internal/client-logger' for client-side code”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default (content: string) => {
try {
const after = compileCsfModule(JSON.parse(content));
return after;
return compileCsfModule(JSON.parse(content));
} catch (e) {
// for debugging
console.log(content, e);
}
return content;
export default (content: string) => {
return compileCsfModule(JSON.parse(content));
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/frameworks/server-webpack5/src/loader.ts` around lines 3 - 9, The loader
currently swallows parse/compile errors and logs raw file content; change the
exported default function so that failures in JSON.parse or compileCsfModule do
not return the original content but instead propagate an error to fail the
build: remove the silent return-on-error or rethrow the caught error (throw e),
and replace the console.log call with the server logger from
'storybook/internal/node-logger' (use logger.error with a short contextual
message and the error, but do not log the full content). Ensure the catch
references the same symbols (JSON.parse, compileCsfModule) and that the function
either throws the error or calls the loader failure path so webpack fails
instead of continuing.

Expand Down
Loading
Loading