Skip to content

Vue3 Vite: Enable Schema Extraction for Vue Component Meta - #35565

Merged
valentinpalkovic merged 3 commits into
storybookjs:nextfrom
dmitriy-prokoshev:patch-2
Jul 24, 2026
Merged

valentinpalkovic merged 3 commits into
storybookjs:nextfrom
dmitriy-prokoshev:patch-2

Conversation

@dmitriy-prokoshev

@dmitriy-prokoshev dmitriy-prokoshev commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes #35552
Closes #35564

Summary

Enable schema: true in the default vue-component-meta checker options.

Motivation

After upgrading to vue-component-meta 3.x, metadata for some type aliases and unions may be returned as a string, for example:

"Color | undefined"

instead of an expanded schema object.

Enabling schema: true restores the expanded schema, allowing consumers to access enum and union metadata consistently.

Changes

  • Added schema: true to the default MetaCheckerOptions used by the Vue 3 Vite framework.

Testing

Tested with a Vue component using a type alias:

type Color =
  | 'theme'
  | 'blue'
  | 'green'
  | 'orange'
  | 'red'
  | 'pink'
  | 'brand';

defineProps<{
  color?: Color;
}>();

Before:

schema: "Color | undefined"

After:

schema: {
  kind: "enum",
  schema: [...]
}

Manual testing

  1. Create a Vue component with a prop using a string union type:

    type Color =
      | 'theme'
      | 'blue'
      | 'green'
      | 'orange'
      | 'red'
      | 'pink'
      | 'brand';
    
    defineProps<{
      color?: Color;
    }>();
  2. Enable vue-component-meta in Storybook.

  3. Verify that the generated metadata for the color prop contains a structured enum schema instead of a string representation.

Before:

schema: "Color | undefined"

After:

schema: {
  kind: "enum",
  schema: [...]
}

Related discussion: #35552
Fixes #35564

AI assistance

I used ChatGPT to help investigate the issue and prepare the initial patch. I reviewed and tested the final change myself.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Enabled schema support for Vue component metadata checking, improving compatibility with schema-aware tooling.
  • Bug Fixes
    • Improved prop type conversion for Vue docgen metadata so literal-union enum-like schemas convert to selectable enum controls, while enum reference schemas no longer mis-convert.
  • Tests
    • Added unit tests covering Vue prop schema conversion behavior for enum and enum-reference cases.

@dmitriy-prokoshev
dmitriy-prokoshev requested a review from a team July 23, 2026 13:04
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Vue component metadata now enables schema support, and Vue arg type conversion distinguishes literal union schemas from TypeScript enum member references. Tests cover enum conversion and fallback behavior.

Changes

Vue component schema support

Layer / File(s) Summary
Enable checker schema support
code/frameworks/vue3-vite/src/plugins/vue-component-meta.ts
The vue-component-meta checker options now set schema: true.
Handle enum schema conversion
code/renderers/vue3/src/extractArgTypes.ts, code/renderers/vue3/src/extractArgTypes.test.ts
Literal union schemas convert to Storybook enum types, while TypeScript enum member references retain generic types covered by new tests.

Estimated code review effort: 2 (Simple) | ~10 minutes


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.

@dmitriy-prokoshev dmitriy-prokoshev changed the title fix(vue3-vite): enable schema extraction for vue-component-meta Vue3 Vite: Enable Schema Extraction for Vue Component Meta Jul 23, 2026
@dmitriy-prokoshev

Copy link
Copy Markdown
Contributor Author

Could a maintainer please add the appropriate labels for this PR? Thanks!

@valentinpalkovic valentinpalkovic self-assigned this Jul 24, 2026
@valentinpalkovic valentinpalkovic moved this to In Progress in Core Team Projects Jul 24, 2026
@valentinpalkovic valentinpalkovic added bug patch:yes Bugfix & documentation PR that need to be picked to main branch ci:normal Run our default set of CI jobs (choose this for most PRs). labels Jul 24, 2026
@Sidnioulz Sidnioulz added the qa:needed Pull Requests that will need manual QA prior to release. label Jul 24, 2026
@valentinpalkovic valentinpalkovic removed the patch:yes Bugfix & documentation PR that need to be picked to main branch label Jul 24, 2026
@valentinpalkovic

valentinpalkovic commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Hey @dmitriy-prokoshev, thanks a lot for this PR! ❤️

I evaluated it against our new docgen baseline harness (25 Vue fixtures recorded through the vue-component-meta path, see #35574), and the results are pretty convincing: 15 of 25 fixtures improve. Literal unions become structured enum sbTypes again (so Controls shows a dropdown), imported union aliases unfold to their members, and the | undefined noise on optional props disappears. So we would like to move forward with this change.

One degradation showed up, though, and I would like to ask whether you are interested in fixing it as part of this PR (or a follow-up, whatever you prefer):

TS enums map to member names instead of runtime values. For

export enum Severity {
  Info = 'info',
  Warning = 'warning',
  Error = 'error',
}

convertVueComponentMetaProp in code/renderers/vue3/src/extractArgTypes.ts now produces

{ name: 'enum', value: ['Severity.Info', 'Severity.Warning', 'Severity.Error'] }

Since an enum sbType makes Controls infer a select, picking an option injects the literal string "Severity.Info" into the prop instead of 'info'. Numeric enums are affected as well ("Level.Low" instead of 0). To be fair: this is restored Storybook 8.x behavior, not something your PR introduces: schema: true just makes the code path reachable again. But it is the one spot where the change makes things worse instead of better, so I would love to close it in the same go.

The fix would live in the enum branch of convertVueComponentMetaProp: resolve enum members to their runtime values (or value/label pairs) instead of passing the EnumName.Member strings through. Our harness has a ready-made repro in code/lib/docgen-harness/src/vue3/__testfixtures__/props-ts-enum/.

@valentinpalkovic

valentinpalkovic commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

I looked into how a proper fix could look, documentation included, and argTypes already support everything we need. 🙂

The idea: keep the runtime values in options (that's what Controls passes to the component) and use control.labels purely for display. The docs table stays fully documented via table.type. For the Severity example:

severity: {
  // what controls actually pass to the component
  options: ['info', 'warning', 'error'],
  control: {
    type: 'select',
    // display-only: dropdown shows the enum member names
    labels: {
      info: 'Severity.Info',
      warning: 'Severity.Warning',
      error: 'Severity.Error',
    },
  },
  table: {
    type: {
      summary: 'Severity',
      detail: "Severity.Info = 'info'\nSeverity.Warning = 'warning'\nSeverity.Error = 'error'",
    },
  },
  type: { name: 'enum', value: ['info', 'warning', 'error'] },
}

The dropdown shows Severity.Info, the component receives 'info', and the props table documents the type name plus the member-to-value mapping on hover. control.labels is a documented API (works for select, multi-select, radio and the inline variants), and this also covers numeric enums (Level.Low → 0).

Two notes for the implementation:

  1. Control inference only derives options from an enum sbType: it never invents labels. Hence, extractFromVueComponentMeta needs to emit options + control.labels itself. It already returns a full StrictInputType, so nothing structurally blocks that.
  2. To be verified: whether vue-component-meta actually hands us the runtime values, or only the member reference strings ('Severity.Info'). If the value is not in the schema payload, the resolution has to happen at extraction time.

With schema extraction on, TS enum members arrive as reference strings
(Severity.Info) without their runtime values. An enum sbType makes
Controls infer a select whose options inject those name strings into
the component instead of the member values. Fall back to the readable
type string until the values are available; literal unions keep their
structured enum.

@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

🤖 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/vue3/src/extractArgTypes.ts`:
- Around line 217-220: Update the literal handling in isLiteralUnionSchema
within extractArgTypes to decode each schema literal rather than removing every
quote character, preserving escaped quotes and other valid literal content. Use
the decoded values for the enum result and add a regression case covering a
literal such as "say \"hi\"" so Controls receives the correct value.
🪄 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: e98c3578-d8eb-4162-aa72-76d981ff4b9e

📥 Commits

Reviewing files that changed from the base of the PR and between 3f56310 and 86a1ea5.

⛔ Files ignored due to path filters (1)
  • code/renderers/vue3/src/__snapshots__/extractArgTypes.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • code/renderers/vue3/src/extractArgTypes.test.ts
  • code/renderers/vue3/src/extractArgTypes.ts

Comment thread code/renderers/vue3/src/extractArgTypes.ts
@storybook-app-bot

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

Copy link
Copy Markdown
Contributor

Package Benchmarks

Commit: 86a1ea5, ran on 28 August 2026 at 08:33:19 UTC

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

storybook-addon-pseudo-states

Before After Difference
Dependency count 0 0 0
Self size 24 KB 21 KB 🎉 -3 KB 🎉
Dependency size 686 B 689 B 🚨 +3 B 🚨
Bundle Size Analyzer Link Link

@storybook/addon-vitest

Before After Difference
Dependency count 2 2 0
Self size 466 KB 427 KB 🎉 -38 KB 🎉
Dependency size 350 KB 350 KB 🚨 +3 B 🚨
Bundle Size Analyzer Link Link

@storybook/builder-vite

Before After Difference
Dependency count 1 11 🚨 +10 🚨
Self size 136 KB 130 KB 🎉 -6 KB 🎉
Dependency size 28 KB 1.33 MB 🚨 +1.30 MB 🚨
Bundle Size Analyzer Link Link

@storybook/builder-webpack5

Before After Difference
Dependency count 182 182 0
Self size 92 KB 79 KB 🎉 -13 KB 🎉
Dependency size 37.43 MB 37.81 MB 🚨 +386 KB 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 73 73 0
Self size 22.04 MB 21.31 MB 🎉 -736 KB 🎉
Dependency size 31.20 MB 30.98 MB 🎉 -218 KB 🎉
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 185 185 0
Self size 267 KB 160 KB 🎉 -107 KB 🎉
Dependency size 30.29 MB 30.66 MB 🚨 +374 KB 🚨
Bundle Size Analyzer Link Link

@storybook/angular-vite

Before After Difference
Dependency count 20 29 🚨 +9 🚨
Self size 23.08 MB 22.83 MB 🎉 -249 KB 🎉
Dependency size 11.49 MB 12.69 MB 🚨 +1.20 MB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 181 181 0
Self size 13 KB 13 KB 🚨 +3 B 🚨
Dependency size 32.79 MB 33.16 MB 🚨 +374 KB 🚨
Bundle Size Analyzer Link Link

@storybook/html-vite

Before After Difference
Dependency count 4 14 🚨 +10 🚨
Self size 22 KB 22 KB 🚨 +4 B 🚨
Dependency size 199 KB 1.49 MB 🚨 +1.29 MB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 536 527 🎉 -9 🎉
Self size 642 KB 640 KB 🎉 -2 KB 🎉
Dependency size 64.69 MB 64.54 MB 🎉 -151 KB 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 101 93 🎉 -8 🎉
Self size 1.42 MB 1.37 MB 🎉 -51 KB 🎉
Dependency size 23.32 MB 23.83 MB 🚨 +512 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preact-vite

Before After Difference
Dependency count 4 14 🚨 +10 🚨
Self size 12 KB 12 KB 🚨 +4 B 🚨
Dependency size 217 KB 1.51 MB 🚨 +1.29 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 122 122 0
Self size 29 KB 29 KB 🚨 +5 B 🚨
Dependency size 25.71 MB 24.93 MB 🎉 -784 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 79 83 🚨 +4 🚨
Self size 32 KB 32 KB 🎉 -237 B 🎉
Dependency size 20.44 MB 21.60 MB 🚨 +1.16 MB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 268 268 0
Self size 23 KB 23 KB 🚨 +17 B 🚨
Dependency size 49.88 MB 50.28 MB 🚨 +403 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 194 194 0
Self size 15 KB 15 KB 🎉 -7 B 🎉
Dependency size 38.70 MB 39.08 MB 🚨 +374 KB 🚨
Bundle Size Analyzer Link Link

@storybook/svelte-vite

Before After Difference
Dependency count 11 20 🚨 +9 🚨
Self size 54 KB 54 KB 🎉 -18 B 🎉
Dependency size 25.47 MB 27.05 MB 🚨 +1.58 MB 🚨
Bundle Size Analyzer Link Link

@storybook/sveltekit

Before After Difference
Dependency count 12 21 🚨 +9 🚨
Self size 56 KB 56 KB 🚨 +29 B 🚨
Dependency size 25.52 MB 27.11 MB 🚨 +1.58 MB 🚨
Bundle Size Analyzer Link Link

@storybook/tanstack-react

Before After Difference
Dependency count 80 84 🚨 +4 🚨
Self size 132 KB 112 KB 🎉 -20 KB 🎉
Dependency size 20.47 MB 21.64 MB 🚨 +1.16 MB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3-vite

Before After Difference
Dependency count 94 102 🚨 +8 🚨
Self size 32 KB 34 KB 🚨 +3 KB 🚨
Dependency size 18.54 MB 43.07 MB 🚨 +24.54 MB 🚨
Bundle Size Analyzer Link Link

@storybook/web-components-vite

Before After Difference
Dependency count 5 15 🚨 +10 🚨
Self size 19 KB 19 KB 🚨 +4 B 🚨
Dependency size 266 KB 1.56 MB 🚨 +1.29 MB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 205 205 0
Self size 869 KB 827 KB 🎉 -42 KB 🎉
Dependency size 87.61 MB 86.65 MB 🎉 -965 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 198 198 0
Self size 44 KB 32 KB 🎉 -12 KB 🎉
Dependency size 86.08 MB 85.12 MB 🎉 -954 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 74 74 0
Self size 1.09 MB 1.09 MB 🚨 +980 B 🚨
Dependency size 53.24 MB 52.29 MB 🎉 -954 KB 🎉
Bundle Size Analyzer node node

@storybook/csf-plugin

Before After Difference
Dependency count 0 9 🚨 +9 🚨
Self size 0 B 7 KB 🚨 +7 KB 🚨
Dependency size 0 B 1.29 MB 🚨 +1.29 MB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 157 157 0
Self size 18 KB 19 KB 🚨 +810 B 🚨
Dependency size 34.39 MB 34.78 MB 🚨 +386 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 59 59 0
Self size 1.46 MB 1.49 MB 🚨 +28 KB 🚨
Dependency size 12.28 MB 12.28 MB 🚨 +7 B 🚨
Bundle Size Analyzer Link Link

@storybook/vue3

Before After Difference
Dependency count 90 4 🎉 -86 🎉
Self size 184 KB 67 KB 🎉 -117 KB 🎉
Dependency size 18.11 MB 584 KB 🎉 -17.53 MB 🎉
Bundle Size Analyzer Link Link

@valentinpalkovic

valentinpalkovic commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Alright, question 2 from my last comment is answered — and it changed the plan a bit, so here's the full picture. 🙂

Verified: the runtime values are not in the schema payload. For the props-ts-enum fixture, vue-component-meta returns

{ kind: 'enum', type: 'Severity', schema: ['Severity.Info', 'Severity.Warning', 'Severity.Error'] }

member reference strings only. I traced it into Volar's resolveSchema (packages/component-meta/lib/schemaResolvers.ts): enum members fall through to a typeToString fallback, and the literal type's subtype.value ('info', 0) is available right there but gets discarded. Hence, the options + control.labels fix from my previous comment is impossible in extractArgTypes alone.

That reduces the fixable part in this PR to a small guard, so instead of asking you to do it, I pushed it directly to your branch (86a1ea5). I hope that's alright with you: TS enum member references now fall back to a documented other type (docs table keeps Severity, no select that would inject "Severity.Info" as a string), while literal unions keep their structured enum. The main win of your PR stays fully intact. Three unit tests for convertVueComponentMetaProp cover the split, and one existing snapshot was updated intentionally.

For the proper fix, I opened vuejs/language-tools#6131 upstream: it adds a literal schema node carrying the runtime value ({ kind: 'literal', type: 'Severity.Info', value: '"info"' }), scoped to enum members so literal unions keep their current string form. Once that ships in a vue-component-meta release, we can upgrade TS enums here to the full options + control. labels dropdown.

@valentinpalkovic
valentinpalkovic merged commit 7f6d1bd into storybookjs:next Jul 24, 2026
131 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects Jul 24, 2026
@github-actions github-actions Bot mentioned this pull request Jul 24, 2026
2 tasks done
@dmitriy-prokoshev

Copy link
Copy Markdown
Contributor Author

Hi @valentinpalkovic! I noticed that the patch:yes label was added and then removed before this PR was merged.

Since this fixes a regression introduced by the vue-component-meta 3.x upgrade in Storybook 10.5, would it be possible to backport this change to the 10.5.x line, for example in a potential 10.5.6 release?

The fix is relatively small, already covered by tests, and without it Vue users need to wait for Storybook 10.6.0 to restore structured union metadata and select controls.

No problem if there are compatibility or release-policy reasons for keeping it in 10.6 only — I would just like to understand whether a backport is still being considered.

valentinpalkovic pushed a commit that referenced this pull request Jul 31, 2026
#35565 landed schema: true in the vue3-vite checker options, so the recorder
now mirrors it. 17 of the 25 cm-argtypes baselines change - literal unions
become structured enum sbTypes, imported union aliases unfold to their
members, runtime [String, Number] and PropType casts become unions, and the
"| undefined" noise disappears from optional primitives. Every cm-snippet
baseline stays byte-identical.

The recorder also joins the comparator, matching what next established for
every other *-baselines.test.ts: it self-compares each committed baseline so
a checker or plugin change that loses extraction quality fails with named
violations rather than landing as an unremarkable snapshot diff.
@valentinpalkovic valentinpalkovic added the sev:S4 Low priority. Wait for community PRs. label Jul 31, 2026
pull Bot pushed a commit to Ballalhossaintalukder/storybook that referenced this pull request Jul 31, 2026
vue-component-meta stringifies a TS enum member to its qualified name
("Severity.Info"), which says nothing about what gets passed to the
component. storybookjs#35565 therefore had to keep enums out of Controls entirely:
an enum sbType would have made a select inject the literal string
"Severity.Info" instead of 'info'.

vuejs/language-tools#6131, released in vue-component-meta 3.3.9, adds a
"literal" schema node carrying the runtime value next to the member name.
Consume it: TS enums now resolve to an enum sbType of their runtime values,
with control.labels mapping each value back to the member name it is
written as. Picking "Severity.Info" in the dropdown passes 'info'; numeric
enums pass 0 and 1. table.type.summary keeps the enum name either way.

The dotted-string guard stays, narrowed to what it actually catches now:
unresolved qualified type names like "typeof Config.alpha", which
stringify with a dot but stand for no value.
@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
@github-actions

Copy link
Copy Markdown
Contributor
Fails
🚫

node failed.

Log

Details
Error:  Error: Could not find the Dangerfile at scripts/dangerfile.ts - if it is local, perhaps you have a typo? If it's using a remote file, it doesn't have a repo reference.
    at /usr/src/danger/dist/platforms/GitHub.js:161:27
    at step (/usr/src/danger/dist/platforms/GitHub.js:44:23)
    at Object.next (/usr/src/danger/dist/platforms/GitHub.js:25:53)
    at /usr/src/danger/dist/platforms/GitHub.js:19:71
    at new Promise (<anonymous>)
    at __awaiter (/usr/src/danger/dist/platforms/GitHub.js:15:12)
    at Object.executeRuntimeEnvironment (/usr/src/danger/dist/platforms/GitHub.js:144:88)
    at /usr/src/danger/dist/commands/danger-runner.js:101:47
    at step (/usr/src/danger/dist/commands/danger-runner.js:34:23)
    at Object.next (/usr/src/danger/dist/commands/danger-runner.js:15:53)
danger-results://tmp/danger-results-09943b11.json

Generated by 🚫 dangerJS against 86a1ea5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-scan:human bug ci:normal Run our default set of CI jobs (choose this for most PRs). qa:skip Pull Requests that do not need any QA. (e.g. documentation) sev:S4 Low priority. Wait for community PRs. vue3

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: @storybook/vue3-vite doesn't request schema from vue-component-meta 3.x, causing enum props to degrade to strings

4 participants