Skip to content

Angular: share one component-meta analyzer between docgen and story-docs - #35841

Closed
valentinpalkovic wants to merge 2 commits into
storybookjs:valentin/angular-story-docs-snippetsfrom
valentinpalkovic:valentin/angular-story-docs-shared-manager
Closed

valentinpalkovic wants to merge 2 commits into
storybookjs:valentin/angular-story-docs-snippetsfrom
valentinpalkovic:valentin/angular-story-docs-shared-manager

Conversation

@valentinpalkovic

@valentinpalkovic valentinpalkovic commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #

What I did

Follow-up on #35807. story-docs-preset.ts built its own AngularComponentMetaManager right next to the one docgen-worker.ts already owns, so the dev server ends up running two warm TypeScript language services over the same files, per default, whenever experimentalDocgenServer is on.

The docgen worker already runs one persistent node:worker_threads Worker and imports each DocgenProviderDescriptor's module by moduleSpecifier into it. Node caches ES module evaluation per resolved URL within a thread, so colocating both exports in the same worker-entry module gets them the same manager instance for free. No second worker, no new descriptor system, just a third message kind on a protocol that's already there:

                              ┌─ docgen-worker.ts (angular-vite) ─┐
                              │                                    │
init (moduleSpecifier import) │  let managerPromise (module scope) │
        │                     │        │           │               │
        ▼                     │        ▼           ▼               │
 core worker thread ──────────┤  createDocgenProvider  queryComponentMeta
        │                     └────────┬───────────┬───────────────┘
        │                              │           │
   'extract' (docgen)             (existing)   'query' (new)
        │                              │           │
        ▼                              ▼           ▼
 core/docgen payload            DocgenPayload   AngularComponentMetaResult
                                                  (selector, enum table -
                                                   fields DocgenPayload
                                                   never carried)

story-docs-preset.ts now proxies through options.docgenWorker.query(...) instead of constructing its own analyzer:

// before
const createManager = async () => new AngularComponentMetaManager(typescript.default ?? typescript);
// ...
const manager = await (managerPromise ??= createManager());

// after
const manager: AngularComponentMetaQuerySource | undefined = docgenWorker && {
  extractComponentMeta: async (componentPath, names) =>
    (await docgenWorker.query({ componentPath, ...names })) as AngularComponentMetaResult | undefined,
};

options.docgenWorker is threaded in from common-preset.ts, which now resolves the docgen descriptors and builds the worker client before composing experimental_storyDocsProvider, passing the client through presets.apply's args parameter (merged into the callee's options):

const storyDocsProvider = await options.presets.apply<StoryDocsProvider>(
  'experimental_storyDocsProvider',
  async () => undefined,
  { docgenWorker }
);

One wrinkle worth flagging: since buildStoryDocsPayload's manager call now crosses a postMessage boundary, AngularComponentMetaQuerySource.extractComponentMeta returns a Promise (a real AngularComponentMetaManager still resolves synchronously, and awaiting a non-Promise value is a no-op), so buildStoryDocsPayload itself is now async. I would argue that's a fair trade for one shared, watched analyzer instead of two.

One manager, one startWatching(), one recycleIfHeapPressured(). Docgen and story-docs now invalidate together instead of story-docs going stale on its own after edits, and the dev server holds one warm TypeScript program for Angular analysis instead of two.

What a bad run looks like

Reverting docgen-worker.ts's module-scoped managerPromise back to a per-call local variable (so queryComponentMeta builds its own manager again) breaks the new coverage in docgen-worker.test.ts:

$ yarn test docgen-worker

 FAIL  |@storybook/angular-vite| src/docgen/docgen-worker.test.ts > createDocgenProvider > shares the same analyzer between the docgen middleware and queryComponentMeta
AssertionError: expected 2 to be 1 // Object.is equality

- Expected
+ Received

- 1
+ 2

 ❯ src/docgen/docgen-worker.test.ts:199:31

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

  1. Check out this branch and run yarn && yarn nx run-many -t compile.
  2. Run yarn task sandbox --template angular-vite/docgen-server-ts --start-from auto (the sandbox with experimentalDocgenServer and componentsManifest on). Open a story's Docs page - argTypes and the Source block snippet should render exactly as they did before this PR.
  3. Without restarting the dev server, edit the story's component (e.g. add an @Input()). Both the argTypes table and the Source block snippet should pick up the change on the next load - previously only argTypes updated live.
  4. yarn test docgen-worker story-docs-build angular-story-docs-snippets build-story-docs common-preset docgen should all pass.

Documentation

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

Internal worker plumbing behind an experimental flag, so nothing user-facing to document here.

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>

story-docs built its own AngularComponentMetaManager alongside docgen's, so the
dev server ran two warm TypeScript language services for the same files and
story-docs's copy never called startWatching(), so it went stale after edits
while docgen's stayed live.

Extends the existing docgen worker protocol with a query message so a
DocgenWorkerModule can expose queryComponentMeta alongside createDocgenProvider.
angular-vite's worker-entry module now shares one module-scoped manager between
both exports, and story-docs-preset.ts proxies through options.docgenWorker
instead of constructing its own analyzer.
@github-actions

github-actions Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor
Fails
🚫

PR is not labeled with one of: ["cleanup","BREAKING CHANGE","feature request","bug","documentation","maintenance","build","dependencies"]

🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

🚫

PR is not labeled with one of: ["qa:needed","qa:skip","qa:success"]

🚫 PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter Good examples: - "Docs: Describe Canvas Doc Block" - "Svelte: Support Svelte v4" Bad examples: - "add new api docs" - "fix: Svelte 4 support" - "Vue: improve docs"
🚫 This PR needs an approving review from a Storybook Core or Developer Experience team member before it can be merged.
Warnings
⚠️

This PR targets valentin/angular-story-docs-snippets. The default branch for contributions is next. Please make sure you are targeting the correct branch.

Generated by 🚫 dangerJS against fd2e758

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0359c06a-c162-4461-8eb7-db5937fc7cc2

📥 Commits

Reviewing files that changed from the base of the PR and between 1c5d61d and fd2e758.

📒 Files selected for processing (2)
  • code/core/src/shared/open-service/services/docgen/types.ts
  • code/core/src/shared/open-service/services/story-docs/types.ts
💤 Files with no reviewable changes (1)
  • code/core/src/shared/open-service/services/story-docs/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/shared/open-service/services/docgen/types.ts

Walkthrough

The docgen worker now supports component metadata queries. Angular Story Docs receives the shared worker, uses asynchronous metadata lookup, and reuses the worker’s analyzer manager.

Changes

Docgen and Story Docs integration

Layer / File(s) Summary
Query contracts and provider options
code/core/src/shared/open-service/services/docgen/types.ts, code/core/src/shared/open-service/services/docgen/worker/protocol.ts, code/core/src/shared/open-service/services/story-docs/types.ts, code/core/src/types/modules/core-common.ts
The shared types define component metadata queries, query request and response variants, worker-backed Story Docs options, and public type exports.
Worker query transport and dispatch
code/core/src/shared/open-service/services/docgen/worker/*
The client forwards query requests. The worker invokes registered metadata handlers in order and returns results or serialized errors.
Angular analyzer and Story Docs integration
code/frameworks/angular-vite/src/docgen/docgen-worker.ts, code/frameworks/angular-vite/src/docgen/story-docs-build.ts, code/frameworks/angular-vite/src/docgen/story-docs-preset.ts, code/core/src/core-server/presets/common-preset.ts
The Angular worker shares its analyzer manager across provider calls. Story Docs forwards metadata requests through the worker and awaits payload generation. The common preset creates the docgen worker before resolving the Story Docs provider.
Analyzer lifecycle and asynchronous payload validation
code/frameworks/angular-vite/src/docgen/*.test.ts, code/lib/docgen-harness/src/angular/**/*.test.ts
Tests isolate module-scoped analyzer state, verify analyzer reuse, and update payload construction for asynchronous metadata sources.

Sequence Diagram(s)

sequenceDiagram
  participant CommonPreset
  participant StoryDocsProvider
  participant DocgenWorkerClient
  participant AngularDocgenWorker
  participant AngularAnalyzer
  CommonPreset->>DocgenWorkerClient: create docgen worker
  CommonPreset->>StoryDocsProvider: pass docgenWorker in options
  StoryDocsProvider->>DocgenWorkerClient: query component metadata
  DocgenWorkerClient->>AngularDocgenWorker: forward query request
  AngularDocgenWorker->>AngularAnalyzer: extract component metadata
  AngularAnalyzer-->>AngularDocgenWorker: metadata result
  AngularDocgenWorker-->>DocgenWorkerClient: query response
  DocgenWorkerClient-->>StoryDocsProvider: metadata result
  StoryDocsProvider-->>CommonPreset: asynchronous Story Docs payload
Loading

Possibly related PRs


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.

🧹 Nitpick comments (1)
code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts (1)

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

Keep Vitest mock behavior in setup hooks.

The changed tests configure mock behavior inside test cases. Move each setup into an applicable beforeEach block.

  • code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts#L133-L133: move the buildDocgenPayload return setup into test setup.
  • code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts#L213-L213: move the new test's buildDocgenPayload setup into test setup.
  • code/frameworks/angular-vite/src/docgen/story-docs-build.test.ts#L86-L90: configure the rejecting query-source mock in setup.

As per coding guidelines, Vitest mock behaviors must be implemented in beforeEach blocks, and inline mock implementations must not be used in test cases.

🤖 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/frameworks/angular-vite/src/docgen/docgen-worker.test.ts` at line 133,
Move the Vitest mock behavior into applicable beforeEach hooks: in
code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts at lines 133-133
and 213-213, configure buildDocgenPayload’s return values during test setup; in
code/frameworks/angular-vite/src/docgen/story-docs-build.test.ts at lines 86-90,
configure the rejecting query-source mock in setup. Remove inline mock
implementations from the test cases while preserving each test’s 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.

Nitpick comments:
In `@code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts`:
- Line 133: Move the Vitest mock behavior into applicable beforeEach hooks: in
code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts at lines 133-133
and 213-213, configure buildDocgenPayload’s return values during test setup; in
code/frameworks/angular-vite/src/docgen/story-docs-build.test.ts at lines 86-90,
configure the rejecting query-source mock in setup. Remove inline mock
implementations from the test cases while preserving each test’s behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a179f406-9ba0-43a0-898e-0d506d1f983e

📥 Commits

Reviewing files that changed from the base of the PR and between 18ef96c and 1c5d61d.

📒 Files selected for processing (14)
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/shared/open-service/services/docgen/types.ts
  • code/core/src/shared/open-service/services/docgen/worker/docgen-worker-client.ts
  • code/core/src/shared/open-service/services/docgen/worker/docgen-worker.ts
  • code/core/src/shared/open-service/services/docgen/worker/protocol.ts
  • code/core/src/shared/open-service/services/story-docs/types.ts
  • code/core/src/types/modules/core-common.ts
  • code/frameworks/angular-vite/src/docgen/docgen-worker.test.ts
  • code/frameworks/angular-vite/src/docgen/docgen-worker.ts
  • code/frameworks/angular-vite/src/docgen/story-docs-build.test.ts
  • code/frameworks/angular-vite/src/docgen/story-docs-build.ts
  • code/frameworks/angular-vite/src/docgen/story-docs-preset.ts
  • code/lib/docgen-harness/src/angular/angular-story-docs-snippets.test.ts
  • code/lib/docgen-harness/src/angular/story-docs/build-story-docs.test.ts

Comment thread code/core/src/shared/open-service/services/docgen/types.ts
Comment thread code/core/src/shared/open-service/services/story-docs/types.ts Outdated
Co-authored-by: Valentin Palkovic <dev@valentinpalkovic.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant