@@ -2,6 +2,8 @@ import path from 'node:path';
22
33import { groupBy } from 'storybook/internal/common' ;
44
5+ import type { ComponentDoc , PropItem } from 'react-docgen-typescript' ;
6+
57import type { ComponentManifest , ComponentsManifest } from '../../../types' ;
68
79/** Minimal docs entry type for rendering in the manifest debugger */
@@ -56,6 +58,9 @@ export function renderComponentsManifest(
5658 docsWithError : unattachedDocsWithError + attachedDocsWithError ,
5759 } ;
5860
61+ const activeEngine = manifest ?. meta ?. docgen ?? 'react-docgen' ;
62+ const durationMs = manifest ?. meta ?. durationMs ?? 0 ;
63+
5964 // Top filters (clickable), no <b> tags; 1px active ring lives in CSS via :target
6065 const allPill = `<a class="filter-pill all" data-k="all" href="#filter-all">All</a>` ;
6166 const compErrorsPill =
@@ -695,6 +700,28 @@ export function renderComponentsManifest(
695700</header>
696701<main>
697702 <div class="wrap">
703+ ${
704+ activeEngine === 'react-docgen'
705+ ? `<div class="note info" style="margin-bottom: 16px;">
706+ <strong>Tip:</strong> You are using <code>react-docgen</code> (the default). Generation took <strong>${ ( durationMs / 1000 ) . toFixed ( 1 ) } s</strong>. For higher quality prop types, consider switching to <code>react-docgen-typescript</code> in your <code>main.ts</code>:
707+ <pre><code>typescript: {
708+ reactDocgen: 'react-docgen-typescript',
709+ }</code></pre>
710+ Note: <code>react-docgen-typescript</code> can be slower. If performance is acceptable for your project, it generally produces better results.
711+ <a href="https://storybook.js.org/docs/api/main-config/main-config-typescript#reactdocgen" target="_blank">Learn more</a>
712+ </div>`
713+ : activeEngine === 'react-docgen-typescript' && durationMs > 7500
714+ ? `<div class="note err" style="margin-bottom: 16px;">
715+ <strong>Performance warning:</strong> <code>react-docgen-typescript</code> took <strong>${ ( durationMs / 1000 ) . toFixed ( 1 ) } s</strong> to generate the manifest. This delay applies every time the manifest is used by an agent. Consider switching to the faster <code>react-docgen</code> in your <code>main.ts</code>:
716+ <pre><code>typescript: {
717+ reactDocgen: 'react-docgen',
718+ }</code></pre>
719+ <a href="https://storybook.js.org/docs/api/main-config/main-config-typescript#reactdocgen" target="_blank">Learn more</a>
720+ </div>`
721+ : `<div class="note ok" style="margin-bottom: 16px;">
722+ Using <code>${ activeEngine } </code>. Generation took <strong>${ ( durationMs / 1000 ) . toFixed ( 1 ) } s</strong>.
723+ </div>`
724+ }
698725 ${
699726 grid
700727 ? `<h2 class="section-title">Components</h2>
@@ -887,10 +914,27 @@ function renderComponentCard(key: string, c: ComponentManifestWithDocs, id: stri
887914 ? `<label for="${ slug } -docs" class="badge ${ a . docsErrors > 0 ? 'err' : 'ok' } as-toggle">${ a . docsErrors > 0 ? `${ a . docsErrors } /${ a . totalDocs } doc errors` : `${ a . totalDocs } ${ plural ( a . totalDocs , 'doc' ) } ` } </label>`
888915 : '' ;
889916
890- // When there is no prop type error, try to read prop types from reactDocgen if present
891- const reactDocgen : any = ! a . hasPropTypeError && 'reactDocgen' in c && c . reactDocgen ;
917+ // Determine which docgen engine produced results (they are now mutually exclusive)
918+ const reactDocgen =
919+ ! a . hasPropTypeError && 'reactDocgen' in c ? ( c . reactDocgen as DocgenDoc ) : undefined ;
920+ const reactDocgenTypescriptData =
921+ ! a . hasPropTypeError && 'reactDocgenTypescript' in c
922+ ? ( c . reactDocgenTypescript as RdtComponentDoc )
923+ : undefined ;
924+
892925 const parsedDocgen = reactDocgen ? parseReactDocgen ( reactDocgen ) : undefined ;
893- const propEntries = parsedDocgen ? Object . entries ( parsedDocgen . props ?? { } ) : [ ] ;
926+ const parsedReactDocgenTypescript = reactDocgenTypescriptData
927+ ? parseReactDocgenTypescript ( reactDocgenTypescriptData )
928+ : undefined ;
929+
930+ // Use whichever engine is active
931+ const activeParsed = parsedDocgen ?? parsedReactDocgenTypescript ;
932+ const cardEngine = parsedDocgen
933+ ? 'react-docgen'
934+ : parsedReactDocgenTypescript
935+ ? 'react-docgen-typescript'
936+ : '' ;
937+ const propEntries = activeParsed ? Object . entries ( activeParsed . props ?? { } ) : [ ] ;
894938 const propTypesBadge =
895939 ! a . hasPropTypeError && propEntries . length > 0
896940 ? `<label for="${ slug } -props" class="badge ok as-toggle">${ propEntries . length } ${ plural ( propEntries . length , 'prop type' ) } </label>`
@@ -927,7 +971,6 @@ function renderComponentCard(key: string, c: ComponentManifestWithDocs, id: stri
927971 . join ( '' )
928972 : '' ;
929973
930- esc ( c . error ?. message || 'Unknown error' ) ;
931974 return `
932975<article
933976 class="card
@@ -983,10 +1026,22 @@ function renderComponentCard(key: string, c: ComponentManifestWithDocs, id: stri
9831026 <div class="panel panel-props">
9841027 <div class="note ok">
9851028 <div class="row">
986- <span class="ex-name">Prop types</span>
1029+ <span class="ex-name">Prop types <small>( ${ cardEngine } )</small> </span>
9871030 <span class="badge ok">${ propEntries . length } ${ plural ( propEntries . length , 'prop type' ) } </span>
9881031 </div>
989- <pre><code>Component: ${ reactDocgen ?. definedInFile ? esc ( path . relative ( process . cwd ( ) , reactDocgen . definedInFile ) ) : '' } ${ reactDocgen ?. exportName ? '::' + esc ( reactDocgen ?. exportName ) : '' } </code></pre>
1032+ <pre><code>Component: ${
1033+ reactDocgen ?. definedInFile
1034+ ? esc ( path . relative ( process . cwd ( ) , reactDocgen . definedInFile ) )
1035+ : reactDocgenTypescriptData ?. filePath
1036+ ? esc ( path . relative ( process . cwd ( ) , reactDocgenTypescriptData . filePath ) )
1037+ : ''
1038+ } ${
1039+ reactDocgen ?. exportName
1040+ ? '::' + esc ( reactDocgen . exportName )
1041+ : reactDocgenTypescriptData ?. exportName
1042+ ? '::' + esc ( reactDocgenTypescriptData . exportName )
1043+ : ''
1044+ } </code></pre>
9901045 <pre><code>Props:</code></pre>
9911046 <pre><code>${ esc ( propsCode ) } </code></pre>
9921047 </div>
@@ -1081,20 +1136,70 @@ function renderComponentCard(key: string, c: ComponentManifestWithDocs, id: stri
10811136</article>` ;
10821137}
10831138
1139+ type ParsedProp = {
1140+ description ?: string ;
1141+ type ?: string ;
1142+ defaultValue ?: string ;
1143+ required ?: boolean ;
1144+ } ;
1145+
10841146type ParsedDocgen = {
1085- props : Record <
1086- string ,
1087- {
1088- description ?: string ;
1089- type ?: string ;
1090- defaultValue ?: string ;
1091- required ?: boolean ;
1092- }
1093- > ;
1147+ props : Record < string , ParsedProp > ;
1148+ } ;
1149+
1150+ type RdtComponentDoc = ComponentDoc & { exportName ?: string } ;
1151+
1152+ const parseReactDocgenTypescript = ( reactDocgenTypescript : RdtComponentDoc ) : ParsedDocgen => {
1153+ const props : Record < string , PropItem > = reactDocgenTypescript . props ?? { } ;
1154+ return {
1155+ props : Object . fromEntries (
1156+ Object . entries ( props ) . map ( ( [ propName , prop ] ) => [
1157+ propName ,
1158+ {
1159+ description : prop . description ,
1160+ // RDT uses prop.type.name as a flat string (e.g. "() => void", "{ id: string }")
1161+ // For enums, prefer prop.type.raw which has the full union
1162+ type : prop . type ?. raw ?? prop . type ?. name ,
1163+ defaultValue : prop . defaultValue ?. value ,
1164+ required : prop . required ,
1165+ } ,
1166+ ] )
1167+ ) ,
1168+ } ;
10941169} ;
10951170
1096- const parseReactDocgen = ( reactDocgen : any ) : ParsedDocgen => {
1097- const props : Record < string , any > = ( reactDocgen as any ) ?. props ?? { } ;
1171+ /** Shape of a react-docgen tsType node (recursive) */
1172+ interface DocgenTsType {
1173+ name ?: string ;
1174+ raw ?: string ;
1175+ value ?: string ;
1176+ elements ?: DocgenTsType [ ] ;
1177+ type ?: string ;
1178+ signature ?: {
1179+ arguments ?: { name : string ; type ?: DocgenTsType } [ ] ;
1180+ return ?: DocgenTsType ;
1181+ properties ?: { key : string ; value ?: DocgenTsType & { required ?: boolean } } [ ] ;
1182+ } ;
1183+ }
1184+
1185+ /** Shape of a single prop from react-docgen's Documentation.props */
1186+ interface DocgenPropItem {
1187+ description ?: string ;
1188+ tsType ?: DocgenTsType ;
1189+ type ?: DocgenTsType ;
1190+ defaultValue ?: { value ?: string } | null ;
1191+ required ?: boolean ;
1192+ }
1193+
1194+ /** Shape of react-docgen's Documentation (only fields we read) */
1195+ interface DocgenDoc {
1196+ props ?: Record < string , DocgenPropItem > ;
1197+ definedInFile ?: string ;
1198+ exportName ?: string ;
1199+ }
1200+
1201+ const parseReactDocgen = ( reactDocgen : DocgenDoc ) : ParsedDocgen => {
1202+ const props = reactDocgen . props ?? { } ;
10981203 return {
10991204 props : Object . fromEntries (
11001205 Object . entries ( props ) . map ( ( [ propName , prop ] ) => [
@@ -1111,54 +1216,53 @@ const parseReactDocgen = (reactDocgen: any): ParsedDocgen => {
11111216} ;
11121217
11131218// Serialize a react-docgen tsType into a TypeScript-like string when raw is not available
1114- function serializeTsType ( tsType : any ) : string | undefined {
1219+ function serializeTsType ( tsType : DocgenTsType | undefined ) : string | undefined {
11151220 if ( ! tsType ) {
11161221 return undefined ;
11171222 }
11181223 // Prefer raw if provided
1119- // Prefer raw if provided
1120- if ( 'raw' in tsType && typeof tsType . raw === 'string' && tsType . raw . trim ( ) . length > 0 ) {
1224+ if ( tsType . raw && tsType . raw . trim ( ) . length > 0 ) {
11211225 return tsType . raw ;
11221226 }
11231227
11241228 if ( ! tsType . name ) {
11251229 return undefined ;
11261230 }
11271231
1128- if ( 'elements' in tsType ) {
1232+ if ( tsType . elements ) {
11291233 if ( tsType . name === 'union' ) {
1130- const parts = ( tsType . elements ?? [ ] ) . map ( ( el : any ) => serializeTsType ( el ) ?? 'unknown' ) ;
1234+ const parts = tsType . elements . map ( ( el ) => serializeTsType ( el ) ?? 'unknown' ) ;
11311235 return parts . join ( ' | ' ) ;
11321236 }
11331237 if ( tsType . name === 'intersection' ) {
1134- const parts = ( tsType . elements ?? [ ] ) . map ( ( el : any ) => serializeTsType ( el ) ?? 'unknown' ) ;
1238+ const parts = tsType . elements . map ( ( el ) => serializeTsType ( el ) ?? 'unknown' ) ;
11351239 return parts . join ( ' & ' ) ;
11361240 }
11371241 if ( tsType . name === 'Array' ) {
11381242 // Prefer raw earlier; here build fallback
1139- const el = ( tsType . elements ?? [ ] ) [ 0 ] ;
1243+ const el = tsType . elements [ 0 ] ;
11401244 const inner = serializeTsType ( el ) ?? 'unknown' ;
11411245 return `${ inner } []` ;
11421246 }
11431247 if ( tsType . name === 'tuple' ) {
1144- const parts = ( tsType . elements ?? [ ] ) . map ( ( el : any ) => serializeTsType ( el ) ?? 'unknown' ) ;
1248+ const parts = tsType . elements . map ( ( el ) => serializeTsType ( el ) ?? 'unknown' ) ;
11451249 return `[${ parts . join ( ', ' ) } ]` ;
11461250 }
11471251 }
1148- if ( 'value' in tsType && tsType . name === 'literal' ) {
1252+ if ( tsType . value && tsType . name === 'literal' ) {
11491253 return tsType . value ;
11501254 }
1151- if ( 'signature' in tsType && tsType . name === 'signature' ) {
1255+ if ( tsType . signature && tsType . name === 'signature' ) {
11521256 if ( tsType . type === 'function' ) {
1153- const args = ( tsType . signature ? .arguments ?? [ ] ) . map ( ( a : any ) => {
1257+ const args = ( tsType . signature . arguments ?? [ ] ) . map ( ( a ) => {
11541258 const argType = serializeTsType ( a . type ) ?? 'any' ;
11551259 return `${ a . name } : ${ argType } ` ;
11561260 } ) ;
1157- const ret = serializeTsType ( tsType . signature ? .return ) ?? 'void' ;
1261+ const ret = serializeTsType ( tsType . signature . return ) ?? 'void' ;
11581262 return `(${ args . join ( ', ' ) } ) => ${ ret } ` ;
11591263 }
11601264 if ( tsType . type === 'object' ) {
1161- const props = ( tsType . signature ? .properties ?? [ ] ) . map ( ( p : any ) => {
1265+ const props = ( tsType . signature . properties ?? [ ] ) . map ( ( p ) => {
11621266 const req : boolean = Boolean ( p . value ?. required ) ;
11631267 const propType = serializeTsType ( p . value ) ?? 'any' ;
11641268 return `${ p . key } ${ req ? '' : '?' } : ${ propType } ` ;
@@ -1168,8 +1272,8 @@ function serializeTsType(tsType: any): string | undefined {
11681272 return 'unknown' ;
11691273 }
11701274 // Default case (Generic like Item<TMeta>)
1171- if ( 'elements' in tsType ) {
1172- const inner = ( tsType . elements ?? [ ] ) . map ( ( el : any ) => serializeTsType ( el ) ?? 'unknown' ) ;
1275+ if ( tsType . elements ) {
1276+ const inner = tsType . elements . map ( ( el ) => serializeTsType ( el ) ?? 'unknown' ) ;
11731277
11741278 if ( inner . length > 0 ) {
11751279 return `${ tsType . name } <${ inner . join ( ', ' ) } >` ;
0 commit comments