Skip to content

MDX: Replace @storybook/docs-mdx with inline implementation#34611

Merged
JReinhold merged 12 commits into
nextfrom
copilot/move-docs-mdx-into-core
May 5, 2026
Merged

MDX: Replace @storybook/docs-mdx with inline implementation#34611
JReinhold merged 12 commits into
nextfrom
copilot/move-docs-mdx-into-core

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 22, 2026

Closes #

What I did

This PR inlines the @storybook/docs-mdx analyzer implementation into Storybook core and updates the core server to use that local implementation.

It also removes the external @storybook/docs-mdx dependency, adds the MDX parsing dependencies needed for the in-repo analyzer, and ports the upstream analyzer test coverage into core so behavior stays aligned with the original package.

The inline implementation is slightly different from the original one. Originally we would compile the MDX with a custom extraction plugin, that would get all the information out, but without using the compilation output at all. This was wasteful, so instead it now just parses the MDX to AST, that we then extract information on. This is both faster and smaller (-250kb).

AI summary on benchmarks on our internal MDX files:

Inline MDX implementation: ~57% faster, dramatically more stable

Replacing the external @storybook/docs-mdx dependency with an inline implementation cut MDX extraction time from an average of 64.5ms down to 27.6ms. More importantly, the "before" runs showed high variance (52–76ms), while the "after" runs are rock-solid consistent (27–28ms). Large files like csf4.mdx went from wildly unpredictable to reliably fast. All file sizes benefit, but the stability win is the real story here.

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

Caution

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

See the Docs2-based stories in the main Storybook, that they have correct names and titles. In the index.json, search for addons-docs-docs2 entries and see that tags, names and titles are correctly extracted.

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

  • 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

    • Enhanced MDX documentation parsing with improved extraction of metadata including titles, names, summaries, and template detection.
  • Chores

    • Updated development dependencies to modernize the MDX processing toolchain.

Copilot AI linked an issue Apr 22, 2026 that may be closed by this pull request
Copilot AI and others added 4 commits April 22, 2026 09:16
Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/9ae1b60f-09f9-449b-ab81-d6766abb62e2

Co-authored-by: JReinhold <5678122+JReinhold@users.noreply.github.com>
Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/9ae1b60f-09f9-449b-ab81-d6766abb62e2

Co-authored-by: JReinhold <5678122+JReinhold@users.noreply.github.com>
Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/9ae1b60f-09f9-449b-ab81-d6766abb62e2

Co-authored-by: JReinhold <5678122+JReinhold@users.noreply.github.com>
Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/9ae1b60f-09f9-449b-ab81-d6766abb62e2

Co-authored-by: JReinhold <5678122+JReinhold@users.noreply.github.com>
Copilot AI changed the title [WIP] Move @storybook/docs-mdx into core Move docs-mdx analysis into core-server and remove @storybook/docs-mdx Apr 22, 2026
Copilot AI requested a review from JReinhold April 22, 2026 09:39
Comment thread code/core/package.json Outdated
Comment thread code/core/src/core-server/utils/analyze-mdx.test.ts
Comment thread code/core/src/core-server/utils/docs-mdx.ts Outdated
@JReinhold JReinhold added maintenance User-facing maintenance tasks mdx ci:normal labels Apr 22, 2026
@JReinhold JReinhold changed the title Move docs-mdx analysis into core-server and remove @storybook/docs-mdx MDX: Replace @storybook/docs-mdx with inline implementation Apr 22, 2026
@JReinhold
Copy link
Copy Markdown
Contributor

@copilot replace the PR description with one that follows the official PR template at https://raw.githubusercontent.com/storybookjs/storybook/refs/heads/next/.github/PULL_REQUEST_TEMPLATE.md

Co-authored-by: Copilot <copilot@github.com>
@JReinhold JReinhold marked this pull request as ready for review April 22, 2026 19:17
@JReinhold JReinhold requested a review from shilman April 22, 2026 19:17
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Apr 22, 2026

Package Benchmarks

Commit: a237f33, ran on 4 May 2026 at 10:39:36 UTC

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

@storybook/addon-docs

Before After Difference
Dependency count 18 18 0
Self size 1.67 MB 1.25 MB 🎉 -414 KB 🎉
Dependency size 9.26 MB 9.26 MB 🎉 -12 B 🎉
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 50 50 0
Self size 20.58 MB 20.38 MB 🎉 -205 KB 🎉
Dependency size 16.56 MB 16.56 MB 0 B
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 536 536 0
Self size 651 KB 651 KB 🚨 +78 B 🚨
Dependency size 60.94 MB 60.97 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 93 93 0
Self size 1.12 MB 1.12 MB 🚨 +78 B 🚨
Dependency size 23.78 MB 23.81 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 122 122 0
Self size 30 KB 30 KB 🚨 +18 B 🚨
Dependency size 24.85 MB 24.88 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 83 83 0
Self size 36 KB 36 KB 🚨 +18 B 🚨
Dependency size 21.56 MB 21.59 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 273 273 0
Self size 23 KB 23 KB 🚨 +12 B 🚨
Dependency size 45.54 MB 45.56 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 184 184 0
Self size 839 KB 839 KB 0 B
Dependency size 68.27 MB 68.06 MB 🎉 -205 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 177 177 0
Self size 32 KB 32 KB 🎉 -36 B 🎉
Dependency size 66.79 MB 66.58 MB 🎉 -205 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 51 51 0
Self size 1.05 MB 1.05 MB 0 B
Dependency size 37.14 MB 36.93 MB 🎉 -205 KB 🎉
Bundle Size Analyzer node node

@storybook/react

Before After Difference
Dependency count 59 59 0
Self size 1.44 MB 1.47 MB 🚨 +28 KB 🚨
Dependency size 13.27 MB 13.27 MB 0 B
Bundle Size Analyzer Link Link

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

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: c7993412-bc52-43e3-bed2-daeabdbdbee1

📥 Commits

Reviewing files that changed from the base of the PR and between 542d76a and a237f33.

📒 Files selected for processing (2)
  • code/core/package.json
  • code/core/src/core-server/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/package.json

📝 Walkthrough

Walkthrough

Replaced the external Storybook MDX analyzer with a local implementation. Added MDX parsing/dev dependencies, implemented analyzeMdx (and extractImports) to parse MDX and extract <Meta> metadata and imports, wired the new analyzer into StoryIndexGenerator, added tests, and adjusted the build to alias acorn.

Changes

Local MDX Analyzer Implementation

Layer / File(s) Summary
Dependency Updates
code/core/package.json
Removed @storybook/docs-mdx devDependency; added acorn, acorn-jsx, hast-util-to-estree, mdast-util-from-markdown, mdast-util-mdx, mdast-util-mdx-jsx, and micromark-extension-mdxjs.
Core Implementation
code/core/src/core-server/utils/analyze-mdx.ts
New analyzer: parses MDX into AST, builds import identifier→module-path map, extracts single <Meta /> element, validates attributes (title, name, of, summary, isTemplate, tags), returns MdxAnalysisResult; exports MdxAnalysisResult, extractImports(), and analyzeMdx().
Tests / Validation
code/core/src/core-server/utils/analyze-mdx.test.ts
Added Vitest suite covering extractImports() and comprehensive analyzeMdx() behavior (parsing, import resolution, error cases, edge cases, snapshots).
Module Exports
code/core/src/core-server/index.ts
Re-export analyzeMdx from the new local ./utils/analyze-mdx.ts instead of @storybook/docs-mdx.
Integration / Wiring
code/core/src/core-server/utils/StoryIndexGenerator.ts
Switched imports and call site: use analyzeMdx(content) (local) in extractDocs() instead of external analyze(content); downstream handling of result unchanged.
Build Configuration
scripts/build/utils/generate-bundle.ts
Added esbuild alias for acorn to dist/acorn.mjs to ensure a single bundled acorn copy across CJS/ESM.

Sequence Diagram(s)

(Skipped — changes are internal module replacement and parsing logic; no multi-actor runtime sequence requiring visualization.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Review rate limit: 3/5 reviews remaining, refill in 16 minutes and 51 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/core-server/utils/docs-mdx.ts`:
- Around line 156-159: The duplicate Meta detection in the JSX/MDX parsing
(inside the block that checks if (name === 'Meta')) only checks result.title,
result.name, and result.of; update that condition to also include result.summary
and result.isTemplate (i.e., if (result.title || result.name || result.of ||
result.summary || result.isTemplate)) so any prior Meta with summary or
isTemplate triggers the "Meta can only be declared once" error; update the check
in the Meta handling code path (where the Error is thrown) and add unit tests
covering sequences like <Meta isTemplate /> followed by <Meta summary="..."/>
and vice versa to assert the duplicate error is thrown.
🪄 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

Run ID: 3b031d95-d4f8-4e4f-be44-a2a0d9d249b0

📥 Commits

Reviewing files that changed from the base of the PR and between ff98f86 and 7df8ef7.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • code/core/package.json
  • code/core/src/core-server/index.ts
  • code/core/src/core-server/utils/StoryIndexGenerator.ts
  • code/core/src/core-server/utils/docs-mdx.test.ts
  • code/core/src/core-server/utils/docs-mdx.ts

Comment thread code/core/src/core-server/utils/docs-mdx.ts Outdated
@JReinhold
Copy link
Copy Markdown
Contributor

The size increase is suspicious, but real. I'll need to investigate why this is happening.

@ndelangen
Copy link
Copy Markdown
Member

It's indeed real, here's some insight @JReinhold

Before:
Screenshot 2026-04-28 at 17 47 51

After:
Screenshot 2026-04-28 at 17 47 44

@ndelangen
Copy link
Copy Markdown
Member

We can see that acorn is embedded multiple times, which is a red flag; we should address this, for sure.

But I suspect we're hitting that @storybook/docs-mdx was minified and compressed in node_modules and then bundled in.

In this new iteration (this PR), it's changed to the full graph, un-minified.

JReinhold and others added 3 commits May 1, 2026 23:34
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
code/core/src/core-server/utils/analyze-mdx.ts (1)

237-238: 💤 Low value

Consider removing unnecessary async wrapper.

parseMdx and analyzeParsedMdx are synchronous, so the async/Promise wrapper adds overhead without benefit. If the previous @storybook/docs-mdx API was async for a reason (e.g., future async parsing), this is fine to keep for compatibility. Otherwise, simplifying to a sync function would be cleaner.

♻️ Optional: Make synchronous if API compatibility isn't required
-export const analyzeMdx = async (code: string): Promise<MdxAnalysisResult> =>
-  analyzeParsedMdx(parseMdx(code));
+export const analyzeMdx = (code: string): MdxAnalysisResult =>
+  analyzeParsedMdx(parseMdx(code));

Note: This would require updating callers to remove await, so skip if maintaining the existing async API is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/utils/analyze-mdx.ts` around lines 237 - 238, The
analyzeMdx function currently wraps two synchronous calls (parseMdx and
analyzeParsedMdx) in an unnecessary async/Promise, so change analyzeMdx to be
synchronous by removing the async keyword and the Promise<MdxAnalysisResult>
return type and returning the result of analyzeParsedMdx(parseMdx(code))
directly; if you choose to make this change, update any callers that await
analyzeMdx to remove awaiting, otherwise keep the async signature for backward
compatibility.
code/core/src/core-server/utils/analyze-mdx.test.ts (1)

7-8: 💤 Low value

Tests use Babel parser while implementation uses Acorn.

The extractImports tests parse code with @babel/parser but the actual implementation uses acorn. Both produce ESTree-compatible ASTs, so this works, but subtle differences could cause test/production divergence. Consider using acorn directly for more accurate testing.

♻️ Optional: Use acorn parser for consistency
-import { parse } from '@babel/parser';
+import { Parser } from 'acorn';
+import acornJsx from 'acorn-jsx';

-const estreeParse = (code: string) =>
-  parse(code, { sourceType: 'module', plugins: ['jsx', 'estree'] }).program;
+const estreeParse = (code: string) =>
+  Parser.extend(acornJsx()).parse(code, {
+    ecmaVersion: 2024,
+    sourceType: 'module',
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/utils/analyze-mdx.test.ts` around lines 7 - 8,
Tests currently parse code with `@babel/parser` in the estreeParse helper while
the implementation uses acorn, risking subtle AST divergences; update the
estreeParse function used by tests to parse with acorn (and acorn-jsx if JSX is
needed) configured for module source type and ESTree-compatible output so the
test AST matches the runtime AST consumed by extractImports, ensuring you
replace the parse(...) call in estreeParse with an acorn-based parse invocation
(including the JSX plugin/extension if JSX is present).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/core/src/core-server/utils/analyze-mdx.test.ts`:
- Around line 7-8: Tests currently parse code with `@babel/parser` in the
estreeParse helper while the implementation uses acorn, risking subtle AST
divergences; update the estreeParse function used by tests to parse with acorn
(and acorn-jsx if JSX is needed) configured for module source type and
ESTree-compatible output so the test AST matches the runtime AST consumed by
extractImports, ensuring you replace the parse(...) call in estreeParse with an
acorn-based parse invocation (including the JSX plugin/extension if JSX is
present).

In `@code/core/src/core-server/utils/analyze-mdx.ts`:
- Around line 237-238: The analyzeMdx function currently wraps two synchronous
calls (parseMdx and analyzeParsedMdx) in an unnecessary async/Promise, so change
analyzeMdx to be synchronous by removing the async keyword and the
Promise<MdxAnalysisResult> return type and returning the result of
analyzeParsedMdx(parseMdx(code)) directly; if you choose to make this change,
update any callers that await analyzeMdx to remove awaiting, otherwise keep the
async signature for backward compatibility.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78e1984a-001d-4243-840d-f1c154bde535

📥 Commits

Reviewing files that changed from the base of the PR and between 7df8ef7 and 542d76a.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • code/core/package.json
  • code/core/src/core-server/index.ts
  • code/core/src/core-server/utils/StoryIndexGenerator.ts
  • code/core/src/core-server/utils/analyze-mdx.test.ts
  • code/core/src/core-server/utils/analyze-mdx.ts
  • scripts/build/utils/generate-bundle.ts
✅ Files skipped from review due to trivial changes (1)
  • code/core/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/core-server/index.ts
  • code/core/src/core-server/utils/StoryIndexGenerator.ts

@JReinhold
Copy link
Copy Markdown
Contributor

JReinhold commented May 2, 2026

I got completely nerd sniped here, and managed to both (theoretically) improve the speed of the MDX static analysis as well as remove 250kb from the existing solution (instead of increasing it by 500 kb)

I noticed a couple of things:

  1. We would compile MDX with a custom analysis plugin to extract the information we needed, but would throw away the compilation result. I replaced that with a parse-only run and then use the resulting MD AST as our basis for extraction. This should be faster, because we dont do the wasteful AST-to-JS bit anymore.

  2. acorn was included in the bundle twice via ESM and CJS. That was because we and mdx-js would import acorn directly, but acorn-jsx would also use it, however via an inline require. I fixed this with an esbuild alias forcing the use of the ESM version, and it seems to work fine.

It's hard to tell from the PR, but the unit tests stays exactly the same.

@JReinhold JReinhold merged commit 9893697 into next May 5, 2026
124 of 125 checks passed
@JReinhold JReinhold deleted the copilot/move-docs-mdx-into-core branch May 5, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal maintenance User-facing maintenance tasks mdx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move @storybook/docs-mdx into core

3 participants