Storybook Vite Plugin Integration#
Storybook's framework-specific Vite plugins intercept and mock module imports so stories can run in isolation without a live server. The @storybook/tanstack-react framework demonstrates this pattern: it registers a moduleInterceptionPlugin as a pre-ordered Vite plugin whose resolveId hook β also forced to run in pre order β short-circuits resolution of specific module IDs and redirects them to Storybook-owned mock files.
How moduleInterceptionPlugin Works#
The plugin, defined in module-interception.ts, intercepts three categories of imports:
| Import ID | Redirected to |
|---|---|
@tanstack/react-router (and sub-paths) | routerMockPath |
@tanstack/react-start, @tanstack/react-start/server, @tanstack/react-start-server, @tanstack/start-server-core, @tanstack/react-start | startMockPath |
@tanstack/start-storage-context | startStorageContextMockPath |
Any ID containing virtual:cloudflare, server-entry, or worker-entry | startMockPath |
The matching for the last row is a substring includes() check over the INTERCEPTED_PATTERNS constant defined at the top of the file . Resolution delegates back to Vite via this.resolve(mockPath, importer, { skipSelf: true }), falling back to the raw mock path if Vite can't resolve it .
To avoid circular aliases, the router interception is skipped when the importer is itself inside export-mocks/ .
The plugin is installed in preset.ts after filtering out TanStack Start Vite plugins from the user's config . It also excludes several TanStack packages from Vite's optimizeDeps .
The tests in module-interception.test.ts confirm the interception behavior for each category and demonstrate the circular alias guard.
Conflict with @cloudflare/vite-plugin#
The virtual:cloudflare and worker-entry patterns in INTERCEPTED_PATTERNS were added to prevent TanStack Start's server/worker entry points from being bundled into Storybook's client-only preview. However, because matching uses id.includes(pattern), any module ID that happens to contain those substrings is intercepted β including virtual:cloudflare/worker-entry from @cloudflare/vite-plugin.
When a TanStack Start app targets Cloudflare Workers, the Cloudflare Vite plugin registers virtual:cloudflare/worker-entry. Storybook's interception plugin captures this request and resolves it to startMockPath instead. The mock imports storybook/test, which is unavailable in the Cloudflare Worker runtime, producing:
ReferenceError: __STORYBOOK_MODULE_TEST__ is not defined
A minimal reproduction is available at ssj9685/storybook-cloudflare-vite-repro. The issue is tracked in storybookjs/storybook#35704 .
Workarounds (Until Fixed)#
-
Use
@storybook/react-viteinstead of@storybook/tanstack-react. It has no TanStack-specific module interception and will not conflict with@cloudflare/vite-plugin. -
Strip
@cloudflare/vite-pluginfrom the Storybook Vite config viaviteFinalin.storybook/main.ts:// .storybook/main.ts viteFinal(config) { return { ...config, plugins: config.plugins?.filter( (p: any) => p?.name !== 'cloudflare:...' ), }; }This follows the same pattern that
preset.tsalready uses to strip TanStack Start plugins .
Key Source Files#
| File | Purpose |
|---|---|
plugins/module-interception.ts | moduleInterceptionPlugin β resolveId-based module interceptor |
plugins/module-interception.test.ts | Unit tests covering all interception scenarios |
preset.ts | Assembles the plugin pipeline; filters TanStack Start plugins |