Skip to content

Core: Extract ComponentMetaManager to Core - #35657

Merged
huang-julien merged 5 commits into
nextfrom
julien/extract-component-meta-manager
Aug 3, 2026
Merged

huang-julien merged 5 commits into
nextfrom
julien/extract-component-meta-manager

Conversation

@huang-julien

@huang-julien huang-julien commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #

What I did

This PR extracts ComponentMetaManager from React renderer to Core for reusability across different renderer intergrzeation.

  • ComponentMetaProjectBase is the interface that the manager needs from every project
  • ComponentMetaProjectFactory truns tsconfigs into projects -> owns parseCommandLine and createInferredProject (fallback for file that no config covers)
  • ComponentM%etaManager is just the remaining implementation from react moved into Core

For React:

  • ComponentMetaPorject builds its own TS languager service

For Vue:

  • ComponentMetaProject would wrap a vueComponentMeta.createChecker() per tsconfig

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Check that nothing broke with react-component-meta

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features
    • Improved component metadata handling across multiple TypeScript projects, including inferred project support when no match is found.
    • Added a shared component-metadata manager API (including file-change broadcasting and TypeScript command-line abstractions) for reuse across renderers.
  • Bug Fixes
    • Improved detection and propagation of tsconfig and file changes (including save/rename edge cases), keeping metadata extraction up to date.
    • Reduced memory pressure during long sessions via automatic project recycling.

@huang-julien huang-julien added feature request react core ci:normal Run our default set of CI jobs (choose this for most PRs). refactoring qa:needed Pull Requests that will need manual QA prior to release. labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared ComponentMetaManager with TypeScript project resolution, filesystem watching, configuration-change handling, heap recycling, and exported contracts. The React renderer now delegates to this manager through a React-specific project factory, and the core package publishes the new internal entry.

Changes

Component Metadata Management

Layer / File(s) Summary
Component metadata contracts and exports
code/core/src/component-meta/types.ts, code/core/src/component-meta/index.ts, code/core/build-config.ts, code/core/package.json
Defines project and file-change interfaces, re-exports the shared API, and adds the storybook/internal/component-meta package entry.
Project resolution and lifecycle
code/core/src/component-meta/ComponentMetaManager.ts
Resolves files through configured or inferred TypeScript projects, follows project references, caches projects, recycles them under heap pressure, and disposes resources.
File watching and configuration updates
code/core/src/component-meta/ComponentMetaManager.ts
Adds recursive watcher management, event debouncing and classification, configuration-change handling, update broadcasting, and path/config matching helpers.
React project factory integration
code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts
Refactors the React manager to extend the shared implementation and create React-specific configured and inferred projects with a shared snapshot cache.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ReactComponentMetaManager
  participant BaseComponentMetaManager
  participant ComponentMetaProject
  Caller->>ReactComponentMetaManager: request component metadata project
  ReactComponentMetaManager->>BaseComponentMetaManager: resolve project for file
  BaseComponentMetaManager->>ComponentMetaProject: create or retrieve configured project
  ComponentMetaProject-->>BaseComponentMetaManager: resolved project
  BaseComponentMetaManager-->>ReactComponentMetaManager: return project
  ReactComponentMetaManager-->>Caller: extract component props
Loading

Possibly related PRs

  • storybookjs/storybook#35468: Further changes React component metadata project wiring by sharing a TypeScript DocumentRegistry during project creation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
code/core/src/component-meta/types.ts (1)

11-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the broken @link target.

ComponentMetaManager.ts is a sibling in this directory, so ../ComponentMetaManager resolves outside component-meta/ and won't link.

📝 Proposed fix
- * Contract a per-tsconfig project must satisfy for {`@link` ../ComponentMetaManager} to manage it.
+ * Contract a per-tsconfig project must satisfy for `ComponentMetaManager` to manage it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/component-meta/types.ts` around lines 11 - 18, Fix the JSDoc
link in the project contract comment to reference the sibling
ComponentMetaManager module within the component-meta directory, using the
correct relative target and leaving the surrounding documentation unchanged.
code/core/src/component-meta/ComponentMetaManager.ts (1)

292-313: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow the try to parsing/creation.

The watcher registration at Lines 303-306 is inside the same try. If watchProgramSourceDirs throws (it does sync sys.fileExists walks), the catch returns null even though the project is already in configProjects — the caller then silently falls back to the inferred project while the configured one stays cached and undisposed.

♻️ Proposed fix
     let project = this.configProjects.get(tsconfig);
     if (!project) {
       try {
         const getCommandLine = () => this.factory.parseCommandLine(tsconfig);
         project = this.factory.createConfiguredProject(getCommandLine(), tsconfig, getCommandLine);
-        this.configProjects.set(tsconfig, project);
-
-        // Auto-watch the project's directories if watching is active.
-        // This covers projects discovered after initial startWatching().
-        if (this.watching) {
-          this.watchDirectory(path.dirname(tsconfig));
-          this.watchProgramSourceDirs(project);
-        }
       } catch (err) {
         logger.debug(`[component-meta] Failed to parse tsconfig ${tsconfig}: ${err}`);
         return null;
       }
+      this.configProjects.set(tsconfig, project);
+
+      // Auto-watch the project's directories if watching is active.
+      // This covers projects discovered after initial startWatching().
+      if (this.watching) {
+        this.watchDirectory(path.dirname(tsconfig));
+        this.watchProgramSourceDirs(project);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/component-meta/ComponentMetaManager.ts` around lines 292 - 313,
Restrict the try/catch in getOrCreateConfiguredProject to parseCommandLine and
createConfiguredProject, storing the successfully created project before leaving
the guarded section. Move the watching block, including watchDirectory and
watchProgramSourceDirs, outside the catch so watcher errors are not converted
into a null result after the project has been cached.
code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts (1)

24-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the compatibility re-export and import the helpers directly in the test.

code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.test.ts is the only non-helper consumer, so let it import from storybook/internal/component-meta instead of keeping a re-export shim.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts`
around lines 24 - 25, Remove the isFileInDir and sortTSConfigs compatibility
re-export from ComponentMetaManager.ts, then update ComponentMetaManager.test.ts
to import those helpers directly from storybook/internal/component-meta.
Preserve all existing helper usage and behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/component-meta/ComponentMetaManager.ts`:
- Around line 320-366: Update onConfigChanged so a deleted configPath is always
removed from rootTsConfigs, regardless of whether configProjects contains an
instantiated project; retain project disposal and removal for instantiated
projects, and continue clearing searchedDirs after the config change.

---

Nitpick comments:
In `@code/core/src/component-meta/ComponentMetaManager.ts`:
- Around line 292-313: Restrict the try/catch in getOrCreateConfiguredProject to
parseCommandLine and createConfiguredProject, storing the successfully created
project before leaving the guarded section. Move the watching block, including
watchDirectory and watchProgramSourceDirs, outside the catch so watcher errors
are not converted into a null result after the project has been cached.

In `@code/core/src/component-meta/types.ts`:
- Around line 11-18: Fix the JSDoc link in the project contract comment to
reference the sibling ComponentMetaManager module within the component-meta
directory, using the correct relative target and leaving the surrounding
documentation unchanged.

In
`@code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts`:
- Around line 24-25: Remove the isFileInDir and sortTSConfigs compatibility
re-export from ComponentMetaManager.ts, then update ComponentMetaManager.test.ts
to import those helpers directly from storybook/internal/component-meta.
Preserve all existing helper usage and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bac8cfc7-18ff-40e8-9728-a8aa13bd76e1

📥 Commits

Reviewing files that changed from the base of the PR and between 20b8c54 and 3ce8951.

📒 Files selected for processing (6)
  • code/core/build-config.ts
  • code/core/package.json
  • code/core/src/component-meta/ComponentMetaManager.ts
  • code/core/src/component-meta/index.ts
  • code/core/src/component-meta/types.ts
  • code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts

Comment thread code/core/src/component-meta/ComponentMetaManager.ts
Comment thread code/core/src/component-meta/ComponentMetaManager.ts Outdated
@storybook-app-bot

storybook-app-bot Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Package Benchmarks

Commit: 4017976, ran on 3 August 2026 at 09:53:56 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 73 73 0
Self size 21.40 MB 21.42 MB 🚨 +25 KB 🚨
Dependency size 31.22 MB 31.22 MB 0 B
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 531 531 0
Self size 641 KB 641 KB 0 B
Dependency size 62.42 MB 62.40 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 94 94 0
Self size 1.42 MB 1.42 MB 🚨 +48 B 🚨
Dependency size 23.91 MB 23.90 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 125 125 0
Self size 29 KB 29 KB 🚨 +36 B 🚨
Dependency size 25.90 MB 25.89 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 83 83 0
Self size 32 KB 32 KB 0 B
Dependency size 21.23 MB 21.21 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 272 272 0
Self size 23 KB 23 KB 🚨 +12 B 🚨
Dependency size 48.20 MB 48.18 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/tanstack-react

Before After Difference
Dependency count 84 84 0
Self size 114 KB 114 KB 0 B
Dependency size 21.26 MB 21.24 MB 🎉 -15 KB 🎉
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 205 205 0
Self size 829 KB 829 KB 🚨 +84 B 🚨
Dependency size 86.08 MB 86.11 MB 🚨 +25 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 198 198 0
Self size 32 KB 32 KB 0 B
Dependency size 84.56 MB 84.58 MB 🚨 +25 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 74 74 0
Self size 1.09 MB 1.09 MB 🚨 +66 B 🚨
Dependency size 52.61 MB 52.64 MB 🚨 +25 KB 🚨
Bundle Size Analyzer node node

@storybook/react

Before After Difference
Dependency count 59 59 0
Self size 1.48 MB 1.46 MB 🎉 -15 KB 🎉
Dependency size 12.30 MB 12.30 MB 0 B
Bundle Size Analyzer Link Link

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
code/core/src/component-meta/ComponentMetaManager.ts (2)

549-565: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate cached projects for atomic tsconfig saves.

A rename event for an atomic save is classified as created when the file exists afterward. This code only reclassifies tracked source files, not tracked tsconfigs, so onConfigChanged(filePath, 'created') merely adds the path and leaves the existing cached project using stale compiler options and file membership.

Reclassify tracked config paths as changed, or make the created branch dispose an existing configured project.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/component-meta/ComponentMetaManager.ts` around lines 549 - 565,
Update the reclassification logic around onConfigChanged so atomic tsconfig
saves are treated as changed when the path belongs to an existing configured
project, rather than remaining created. Reuse the configured-project tracking
symbols to detect tracked config paths before invoking onConfigChanged, ensuring
cached projects are invalidated and refreshed with the new compiler options and
file membership.

477-524: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make watcher replacement atomic and handle watcher lifetime errors.

Narrower watchers are closed before watch() is successful, so a failed creation leaves those subdirectories unwatched. Also attach an asynchronous error listener to the created watcher and clean it up on failure; the surrounding synchronous try/catch cannot cover events emitted after creation, and an errored fs.watch watcher is unusable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/component-meta/ComponentMetaManager.ts` around lines 477 - 524,
Update the watcher setup around the recursive watch creation so replacement is
atomic: create and validate the new watcher before closing/removing narrower
watchers, preserving them if creation fails. Attach an asynchronous error
listener to the new watcher that removes it from watchersByDir, closes it
safely, and performs the required cleanup when fs.watch reports failure; do not
rely on the surrounding synchronous try/catch for emitted errors.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts`:
- Around line 42-43: Remove provenance references, external URLs, cross-file
line references, and historical wording such as “without the fix” from comments
in ComponentMetaManager. Rewrite only comments near the affected logic to state
the current maintenance rationale or behavior they clarify; remove comments that
provide no ongoing value.

---

Outside diff comments:
In `@code/core/src/component-meta/ComponentMetaManager.ts`:
- Around line 549-565: Update the reclassification logic around onConfigChanged
so atomic tsconfig saves are treated as changed when the path belongs to an
existing configured project, rather than remaining created. Reuse the
configured-project tracking symbols to detect tracked config paths before
invoking onConfigChanged, ensuring cached projects are invalidated and refreshed
with the new compiler options and file membership.
- Around line 477-524: Update the watcher setup around the recursive watch
creation so replacement is atomic: create and validate the new watcher before
closing/removing narrower watchers, preserving them if creation fails. Attach an
asynchronous error listener to the new watcher that removes it from
watchersByDir, closes it safely, and performs the required cleanup when fs.watch
reports failure; do not rely on the surrounding synchronous try/catch for
emitted errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9287d6b2-e04c-4868-ae27-4d7a1264b35f

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce8951 and 3960882.

📒 Files selected for processing (4)
  • code/core/src/component-meta/ComponentMetaManager.ts
  • code/core/src/component-meta/index.ts
  • code/core/src/component-meta/types.ts
  • code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/component-meta/index.ts

Comment thread code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts Outdated
@huang-julien
huang-julien force-pushed the julien/extract-component-meta-manager branch from 3960882 to 8c44c6a Compare July 30, 2026 09:37
@huang-julien
huang-julien force-pushed the julien/extract-component-meta-manager branch from 8c44c6a to 106df70 Compare July 30, 2026 13:26
Comment thread code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts Outdated
Comment thread code/core/src/component-meta/ComponentMetaManager.ts Outdated

@valentinpalkovic valentinpalkovic left a comment

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.

Second review pass. For transparency: I paired this with an AI code-quality review (the Cursor "thermo-nuclear code quality review" skill), then filtered it down to only the findings that are genuinely new on top of my earlier comments. A couple are deliberately low-confidence questions rather than blockers - flagged inline. 🙂

Comment thread code/core/src/component-meta/index.ts Outdated
@huang-julien
huang-julien force-pushed the julien/extract-component-meta-manager branch 2 times, most recently from 9f82a37 to 52937cd Compare July 31, 2026 09:37
@huang-julien
huang-julien force-pushed the julien/extract-component-meta-manager branch 2 times, most recently from b0fdb21 to 9be383b Compare July 31, 2026 15:53
huang-julien and others added 4 commits August 3, 2026 11:44
The extracted manager put typescript-coupled types on core's public
surface: `typeof ts` parameters and `ts.ParsedCommandLine` in the project
contract. Consumers resolve their own typescript copy (vue3-vite even pins
one as a runtime dependency), and across two copies `typeof ts` is not
assignable (TS2345) while comparing `ts.ParsedCommandLine` structurally
walks the whole compiler-API graph and overflows the checker (TS2321) —
exactly the CI failure on this branch.

Core's component-meta module now imports nothing from typescript:

- `ProjectCommandLine` (fileNames + projectReferences paths) replaces
  `ts.ParsedCommandLine` in the contract; a `CL` generic on the factory and
  manager lets renderers keep full command-line fidelity internally.
- The constructor takes a structural `ComponentMetaFileSystem` host
  (`sys.fileExists`/`sys.directoryExists` — all the manager uses); a real
  `typeof ts` still satisfies it.
- `parseTsconfigCommandLine` moves into the React renderer, its only
  consumer, deleting the last cross-boundary `typeof ts`.

Reproduced and verified with a dual-installation probe (typescript 6.0.3
on the contract side, 5.9.3 on the consumer side): the previous contract
fails with the CI error verbatim; this contract compiles clean. React's
public manager surface is unchanged; all 279 renderer tests pass as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@huang-julien
huang-julien force-pushed the julien/extract-component-meta-manager branch from 9be383b to 4017976 Compare August 3, 2026 09:44
@huang-julien
huang-julien merged commit 21ab396 into next Aug 3, 2026
122 of 127 checks passed
@huang-julien
huang-julien deleted the julien/extract-component-meta-manager branch August 3, 2026 12:37
@github-actions github-actions Bot mentioned this pull request Aug 3, 2026
2 tasks done
@JReinhold JReinhold added qa:skip Pull Requests that do not need any QA. (e.g. documentation) and removed qa:needed Pull Requests that will need manual QA prior to release. labels Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal Run our default set of CI jobs (choose this for most PRs). core feature request qa:skip Pull Requests that do not need any QA. (e.g. documentation) react refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants