npm.io
0.4.0 • Published 6d ago

@chrishrb/storybook-addon-vue-manifest

Licence
MIT
Version
0.4.0
Deps
6
Size
122 kB
Vulns
0
Weekly
162

storybook-addon-vue-manifest

Generates a Storybook component manifest for Vue 3 projects using vue-component-meta (Vue language tools / Volar). The manifest contains structured component documentation — props, events, slots, descriptions, import statements and per-story Vue template snippets — and is the data source for the Storybook MCP server, enabling AI-assisted workflows for Vue projects.

Requirements

  • Storybook ≥ 10.5 (the experimental component manifest infrastructure lives in Storybook core)
  • @storybook/vue3-vite
  • Vue 3 with TypeScript

Usage

npm install --save-dev @chrishrb/storybook-addon-vue-manifest
// .storybook/main.ts
const config: StorybookConfig = {
  framework: '@storybook/vue3-vite',
  addons: [
    // ...
    '@chrishrb/storybook-addon-vue-manifest',
  ],
  features: {
    // required: enables the manifest routes and build output in Storybook core
    componentsManifest: true,
    // optional: also injects generated Vue snippets into docs source blocks
    experimentalCodeExamples: true,
  },
};
Generating the manifest into a file

Run a static build — Storybook core writes every manifest to disk:

storybook build
# -> storybook-static/manifests/components.json

During storybook dev the same manifest is served live at http://localhost:6006/manifests/components.json, plus an HTML debugger at /manifests/components.html.

With @storybook/addon-docs installed, unattached MDX docs additionally produce manifests/docs.json; attached MDX is embedded into components.json per component (both handled by addon-docs, not this addon).

Options

addons: [
  {
    name: '@chrishrb/storybook-addon-vue-manifest',
    options: {
      // tsconfig used by the vue-component-meta checker (relative to the project root).
      // Defaults to the vue3-vite framework `docgen.tsconfig` option, then 'tsconfig.json'.
      tsconfig: 'tsconfig.app.json',
    },
  },
],

Output shape

// manifests/components.json
{
  "v": 0,
  "components": {
    "example-button": {
      "id": "example-button",
      "name": "Button",
      "path": "./src/stories/Button.stories.ts",
      "import": "import Button from './Button.vue';",
      "description": "Primary UI component for user interaction",
      "summary": "A simple button",
      "jsDocTags": { "summary": ["A simple button"] },
      "stories": [
        {
          "id": "example-button--primary",
          "name": "Primary",
          "snippet": "<Button primary @click=\"onClick\" label=\"Button\" />",
          // verbatim render source for render-based stories (see "Story source" below)
          "source": "render: () => ({ /* setup(), components, template */ })"
        }
      ],
      // raw vue-component-meta data (props/events/slots/exposed, with resolved type schemas)
      "vueComponentMeta": { "props": [/* ... */], "events": [/* ... */] },
      // props projected into react-docgen shape so the stock MCP server renders them as `## Props`
      // (see "Works with the stock MCP server" below)
      "reactComponentMeta": { "props": { /* ... */ } }
    },
    // sub-components referenced by a story but lacking a story of their own get their own entry,
    // tagged with the component ids that reference them (see "Referenced components" below)
    "data-table-header-cell": {
      "id": "data-table-header-cell",
      "name": "DataTableHeaderCell",
      "path": "src/components/DataTableHeaderCell.vue",
      "stories": [],
      "import": "import DataTableHeaderCell from '@my-lib/ui';",
      "referencedBy": ["example-button"],
      "vueComponentMeta": { "props": [/* ... */] }
    }
  },
  "meta": { "docgen": "vue-component-meta", "durationMs": 875 }
}
Works with the stock MCP server

No fork or patch of @storybook/mcp is required. That server renders component APIs from React-shaped docgen fields and drops unknown manifest keys during validation, so a Vue-only vueComponentMeta field would never reach its formatter. To stay compatible with the unmodified published server, each entry also carries:

  • reactComponentMeta — props projected into the react-docgen shape the server reads, rendered as its ## Props section (enum/array/object schemas are expanded into TypeScript-like type strings).
  • the component description with the Vue-specific surface (events, slots, exposed) appended as ## Events / ## Slots / ## Exposed Markdown blocks, which the server renders verbatim.

The raw vueComponentMeta is retained for clients that consume it directly.

Story source

The snippet is the rendered Vue template. For stories written with a render function, the template alone references locals it does not define — e.g. <DataCollection :columns="columns" />, where columns is built in setup(). The source field carries the verbatim render function (its components/setup/data/template), preceded by the module-level declarations the render references (const columns = …, helpers, local types), resolved transitively so the snapshot is self-contained. Args-only stories (no render) omit source — the snippet already conveys everything.

Referenced components

Stories often render sub-components that have no story of their own (cell renderers used inside a table's column definitions, etc.). Each such component — registered in a components: {} map or passed to h(...) — is resolved, run through vue-component-meta, and emitted as its own manifest entry so it appears in component listings with full prop docs and a usable import. These entries carry a referencedBy array (the component ids whose stories use them) and an empty stories array, and are de-duplicated against components that already have a story-backed entry.

Type schema resolution

Each prop/event/slot/exposed entry carries a schema describing its resolved type. Complex types are expanded into a structured shape rather than left as a flat type string, so MCP clients and docs tooling can introspect them:

  • unions → { "kind": "enum", "schema": ["\"sm\"", "\"md\"", "\"lg\""] }
  • arrays → { "kind": "array", "schema": [/* element type */] }
  • objects → { "kind": "object", "schema": { "label": { /* nested prop */ } } }
  • callbacks → { "kind": "event", "schema": [/* parameter types */] }

To keep the manifest bounded, types declared in node_modules are left as opaque type strings instead of being expanded — the same approach Storybook applies to react-docgen-typescript (propFilter: !/node_modules/.test(parent.fileName)). This prevents large/circular DOM and library types (MouseEvent, HTMLElement, third-party generics) from exploding the output, while your own project types are fully resolved.

Generic components: a named object type parameterized by an unbound type parameter (e.g. a prop typed Foo<T> on a <script setup generic="T"> component) stays a type string and is not expanded. Unions, string-literal unions and arrays still resolve. This is a vue-component-meta limitation — the type parameter has no concrete binding to resolve against.

Notes & limitations

  • Only stories tagged manifest are included — Storybook adds that tag to every story by default, so usually everything is in.
  • Stories whose meta.component is defined inline (not imported from a file) produce an error entry instead of docgen data.
  • tsconfig project references are not supported by vue-component-meta (vuejs/language-tools#3896); the checker falls back to a referenceless project over the package root. Point the tsconfig option at a non-referencing config (e.g. tsconfig.app.json) for alias resolution.
  • The first manifest request in dev builds a TypeScript program and can take a few seconds; subsequent requests reuse the checker.
  • experimentalCodeExamples snippet injection works best on Storybook versions where the csf-plugin passes the story file path to enrichers; on older versions snippets fall back to heuristics (on* args treated as events) instead of vue-component-meta classification.

Keywords