Skip to content
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
67d5780
generate code snippets from csf file
kasperpeulen Oct 9, 2025
5efe183
Lot more cases
kasperpeulen Oct 10, 2025
446abe5
Cleanup code
kasperpeulen Oct 10, 2025
ef96eff
Inline args in JSX
kasperpeulen Oct 10, 2025
2d86537
More test examples
kasperpeulen Oct 10, 2025
3738a60
Add extra tests
kasperpeulen Oct 10, 2025
2442574
Fix type error
kasperpeulen Oct 10, 2025
18aaaf4
Template.bind expressions
kasperpeulen Oct 10, 2025
50ea146
Add componentManifestGenerator preset and implement for react
kasperpeulen Oct 15, 2025
addd229
Merge remote-tracking branch 'origin/10.1' into kasper/code-snippets
kasperpeulen Oct 15, 2025
4ed3856
Fix types
kasperpeulen Oct 15, 2025
a449847
Update code/core/src/core-server/dev-server.ts
kasperpeulen Oct 16, 2025
4ce972a
Update code/core/src/core-server/dev-server.ts
kasperpeulen Oct 16, 2025
1599fac
Update code/core/src/core-server/build-static.ts
kasperpeulen Oct 16, 2025
1bcd00b
Update code/core/src/core-server/build-static.ts
kasperpeulen Oct 16, 2025
bf9a49a
Improve dev server logic
kasperpeulen Oct 16, 2025
f2e3ddb
Merge remote-tracking branch 'origin/kasper/code-snippets' into kaspe…
kasperpeulen Oct 16, 2025
c0df22c
Add type
kasperpeulen Oct 16, 2025
25b197e
Fix lint
kasperpeulen Oct 16, 2025
18c671e
Use node logger
kasperpeulen Oct 16, 2025
11ac914
Fix
kasperpeulen Oct 16, 2025
98f121b
Add component name and tests
kasperpeulen Oct 16, 2025
bd91ab6
Fix typos
kasperpeulen Oct 16, 2025
d3f1940
Merge branch 'kasper/code-snippets' into kasper/manifest-component-name
kasperpeulen Oct 16, 2025
b865df9
Add component description to manifest
kasperpeulen Oct 16, 2025
51cdfc7
Extract jsdoc tags
kasperpeulen Oct 16, 2025
04cdc63
Make begin with props
kasperpeulen Oct 16, 2025
1e84547
Remove redundant features fetch
kasperpeulen Oct 16, 2025
a6f6673
Apply feedback
kasperpeulen Oct 16, 2025
fd80c9d
Implement last feedback
kasperpeulen Oct 17, 2025
e2213c8
Restructure
kasperpeulen Oct 17, 2025
55c37e5
Merge branch 'kasper/code-snippets' into kasper/props
kasperpeulen Oct 17, 2025
c7a1572
Fix type
kasperpeulen Oct 17, 2025
46c5a6a
Fix name
kasperpeulen Oct 17, 2025
d9f9a3b
Fix check
kasperpeulen Oct 17, 2025
0b83e62
Add export named handler and add tests for react-docgen
kasperpeulen Oct 17, 2025
146a644
Fix
kasperpeulen Oct 17, 2025
1e6dc13
Improve types
kasperpeulen Oct 17, 2025
8224f40
Fix filename
kasperpeulen Oct 17, 2025
0ed2b8c
Simplify memfs mock
kasperpeulen Oct 17, 2025
22d12cc
Fix snapshot
kasperpeulen Oct 17, 2025
9b18ad0
Fix format, use version number
kasperpeulen Oct 17, 2025
f9dd32e
Fix regex
kasperpeulen Oct 17, 2025
f6b154f
Fix type
kasperpeulen Oct 17, 2025
36854cb
Fix lint
kasperpeulen Oct 17, 2025
f926862
Add error handling for build
kasperpeulen Oct 17, 2025
ca19b25
Typo
kasperpeulen Oct 17, 2025
455fe3f
Fix children
kasperpeulen Oct 17, 2025
d1c6324
Fix unit tests
kasperpeulen Oct 17, 2025
c5311ef
Update code/renderers/react/src/componentManifest/generator.ts
kasperpeulen Oct 20, 2025
80ea6a6
Fix unit tests
kasperpeulen Oct 20, 2025
3825193
Add comment
kasperpeulen Oct 20, 2025
e4f7a57
Enable in UI
kasperpeulen Oct 20, 2025
917db04
Adress review
kasperpeulen Oct 20, 2025
6b39ffd
Trim the description
kasperpeulen Oct 20, 2025
46a5714
Better error handling
kasperpeulen Oct 20, 2025
4dfd30b
Address feedback
kasperpeulen Oct 22, 2025
1181213
Filter out when we can not find a component name
kasperpeulen Oct 22, 2025
0fd167e
Add a todo
kasperpeulen Oct 22, 2025
99ff2b8
Make react-docgen dev dep
kasperpeulen Oct 22, 2025
91d3927
Merge remote-tracking branch 'origin/10.1' into kasper/props
kasperpeulen Oct 22, 2025
ad233cb
Fix null and undefined values
kasperpeulen Oct 22, 2025
787206f
Fix examples
kasperpeulen Oct 22, 2025
39c7a71
Merge branch '10.1' into kasper/props
JReinhold Oct 23, 2025
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
1 change: 1 addition & 0 deletions code/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const config = defineMain({
features: {
developmentModeForBuild: true,
experimentalTestSyntax: true,
experimentalComponentsManifest: true,
},
staticDirs: [{ from: './bench/bundle-analyzer', to: '/bundle-analyzer' }],
viteFinal: async (viteConfig, { configType }) => {
Expand Down
25 changes: 24 additions & 1 deletion code/core/src/core-server/build-static.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cp, mkdir } from 'node:fs/promises';
import { cp, mkdir, writeFile } from 'node:fs/promises';
import { rm } from 'node:fs/promises';
import { join, relative, resolve } from 'node:path';

Expand All @@ -12,6 +12,7 @@ import {
import { logger } from 'storybook/internal/node-logger';
import { getPrecedingUpgrade, telemetry } from 'storybook/internal/telemetry';
import type { BuilderOptions, CLIOptions, LoadOptions, Options } from 'storybook/internal/types';
import { type ComponentManifestGenerator } from 'storybook/internal/types';

import { global } from '@storybook/global';

Expand Down Expand Up @@ -163,6 +164,28 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption
initializedStoryIndexGenerator as Promise<StoryIndexGenerator>
)
);

if (features?.experimentalComponentsManifest) {
const componentManifestGenerator: ComponentManifestGenerator = await presets.apply(
'experimental_componentManifestGenerator'
);
const indexGenerator = await initializedStoryIndexGenerator;
if (componentManifestGenerator && indexGenerator) {
try {
const manifests = await componentManifestGenerator(
indexGenerator as unknown as import('storybook/internal/core-server').StoryIndexGenerator
);
await mkdir(join(options.outputDir, 'manifests'), { recursive: true });
await writeFile(
join(options.outputDir, 'manifests', 'components.json'),
JSON.stringify(manifests)
);
} catch (e) {
logger.error('Failed to generate manifests/components.json');
logger.error(e instanceof Error ? e : String(e));
}
}
}
}

if (!core?.disableProjectJson) {
Expand Down
32 changes: 30 additions & 2 deletions code/core/src/core-server/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { logConfig } from 'storybook/internal/common';
import { logger } from 'storybook/internal/node-logger';
import { MissingBuilderError } from 'storybook/internal/server-errors';
import type { Options } from 'storybook/internal/types';
import { type ComponentManifestGenerator } from 'storybook/internal/types';

import compression from '@polka/compression';
import polka from 'polka';
import invariant from 'tiny-invariant';

import { telemetry } from '../telemetry';
import type { StoryIndexGenerator } from './utils/StoryIndexGenerator';
import { type StoryIndexGenerator } from './utils/StoryIndexGenerator';
import { doTelemetry } from './utils/doTelemetry';
import { getManagerBuilder, getPreviewBuilder } from './utils/get-builders';
import { getCachingMiddleware } from './utils/get-caching-middleware';
Expand Down Expand Up @@ -135,8 +136,35 @@ export async function storybookDevServer(options: Options) {
throw indexError;
}

const features = await options.presets.apply('features');
if (features?.experimentalComponentsManifest) {
app.use('/manifests/components.json', async (req, res) => {
try {
const componentManifestGenerator: ComponentManifestGenerator = await options.presets.apply(
'experimental_componentManifestGenerator'
);
const indexGenerator = await initializedStoryIndexGenerator;
if (componentManifestGenerator && indexGenerator) {
const manifest = await componentManifestGenerator(
indexGenerator as unknown as import('storybook/internal/core-server').StoryIndexGenerator
);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(manifest));
return;
}
res.statusCode = 400;
res.end('No component manifest generator configured.');
return;
} catch (e) {
logger.error(e instanceof Error ? e : String(e));
res.statusCode = 500;
res.end(e instanceof Error ? e.toString() : String(e));
return;
}
});
}
// Now the preview has successfully started, we can count this as a 'dev' event.
doTelemetry(app, core, initializedStoryIndexGenerator, options);
doTelemetry(app, core, initializedStoryIndexGenerator as Promise<StoryIndexGenerator>, options);

async function cancelTelemetry() {
const payload = { eventType: 'dev' };
Expand Down
15 changes: 13 additions & 2 deletions code/core/src/csf-tools/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ export class CsfFile {

_rawComponentPath?: string;

_componentImportSpecifier?: t.ImportSpecifier | t.ImportDefaultSpecifier;

_meta?: StaticMeta;

_stories: Record<string, StaticStory> = {};
Expand All @@ -295,7 +297,7 @@ export class CsfFile {

_metaStatement: t.Statement | undefined;

_metaNode: t.Expression | undefined;
_metaNode: t.ObjectExpression | undefined;

_metaPath: NodePath<t.ExportDefaultDeclaration> | undefined;

Expand Down Expand Up @@ -364,9 +366,18 @@ export class CsfFile {
stmt.specifiers.find((spec) => spec.local.name === id)
) as t.ImportDeclaration;
if (importStmt) {
// Example: `import { ComponentImport } from './path-to-component'`
// const meta = { component: ComponentImport };
// Sets:
// - _rawComponentPath = './path-to-component'
// - _componentImportSpecifier = ComponentImport
const { source } = importStmt;
if (t.isStringLiteral(source)) {
const specifier = importStmt.specifiers.find((spec) => spec.local.name === id);
if (t.isStringLiteral(source) && specifier) {
this._rawComponentPath = source.value;
if (t.isImportSpecifier(specifier) || t.isImportDefaultSpecifier(specifier)) {
this._componentImportSpecifier = specifier;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
// Inspired by Vitest fixture implementation:
// https://github.com/vitest-dev/vitest/blob/200a4349a2f85686bc7005dce686d9d1b48b84d2/packages/runner/src/fixture.ts
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 {
return playFunction != null && getUsedProps(playFunction).includes('mount');
}
export function getUsedProps(fn: Function) {

export function getUsedProps(fn: (...args: any[]) => any) {
const match = fn.toString().match(/[^(]*\(([^)]*)/);

if (!match) {
Expand Down
28 changes: 24 additions & 4 deletions code/core/src/types/modules/core-common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// should be node:http, but that caused the ui/manager to fail to build, might be able to switch this back once ui/manager is in the core
import type { FileSystemCache } from 'storybook/internal/common';
import { type StoryIndexGenerator } from 'storybook/internal/core-server';

import type { Server as HttpServer, IncomingMessage, ServerResponse } from 'http';
import type { Server as NetServer } from 'net';
Expand Down Expand Up @@ -339,10 +340,26 @@ export interface TagOptions {

export type TagsOptions = Record<Tag, Partial<TagOptions>>;

/**
* The interface for Storybook configuration used internally in presets The difference is that these
* values are the raw values, AKA, not wrapped with `PresetValue<>`
*/
export interface ComponentManifest {
v: number;
components: Record<
string,
{
id: string;
name?: string;
description?: string;
import?: string;
summary?: string;
examples: { name: string; snippet: string }[];
jsDocTags: Record<string, string[]>;
}
>;
}

export type ComponentManifestGenerator = (
storyIndexGenerator: StoryIndexGenerator
) => Promise<ComponentManifest>;

export interface StorybookConfigRaw {
/**
* Sets the addons you want to use with Storybook.
Expand All @@ -356,6 +373,7 @@ export interface StorybookConfigRaw {
*/
addons?: Preset[];
core?: CoreConfig;
componentManifestGenerator?: ComponentManifestGenerator;
staticDirs?: (DirectoryMapping | string)[];
logLevel?: string;
features?: {
Expand Down Expand Up @@ -453,6 +471,8 @@ export interface StorybookConfigRaw {
developmentModeForBuild?: boolean;
/** Only show input controls in Angular */
angularFilterNonInputControls?: boolean;

experimentalComponentsManifest?: boolean;
};

build?: TestBuildConfig;
Expand Down
1 change: 0 additions & 1 deletion code/frameworks/react-vite/src/plugins/react-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export async function reactDocgen({
let matchPath: TsconfigPaths.MatchPath | undefined;

if (tsconfig.resultType === 'success') {
logger.info('Using tsconfig paths for react-docgen');
matchPath = TsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths, [
'browser',
'module',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export default async function reactDocgenLoader(
const tsconfig = TsconfigPaths.loadConfig(tsconfigPath);

if (tsconfig.resultType === 'success') {
logger.info('Using tsconfig paths for react-docgen');
matchPath = TsconfigPaths.createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths, [
'browser',
'module',
Expand Down
1 change: 1 addition & 0 deletions code/renderers/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"acorn-jsx": "^5.3.1",
"acorn-walk": "^7.2.0",
"babel-plugin-react-docgen": "^4.2.1",
"comment-parser": "^1.4.1",
"es-toolkit": "^1.36.0",
"escodegen": "^2.1.0",
"expect-type": "^0.15.0",
Expand Down
Loading