@akfm/vite-plugin-storybook-mock-server-functions
v0.0.0
Published
Vite plugin that replaces Next.js Server Actions ("use server") with Storybook fn() mocks.
Maintainers
Readme
@akfm/vite-plugin-storybook-mock-server-functions
English | 日本語
A Vite plugin that lets Next.js App Router Server Actions ("use server") run standalone inside Storybook.
Next.js Server Actions rely on Next.js's own bundler transform and server runtime at execution time, so loading them as-is in Storybook (which is Vite-based) results in build or runtime errors. This plugin detects functions carrying a "use server" directive and replaces them with storybook/test's fn()-based mock functions, so components that use Server Actions can be rendered and tested in Storybook without a real server.
Features
- Detects both module-level and function-level
"use server"directives — no need to restructure existing action files. - Replaces only the directive-bearing functions with
fn()mocks; everything else in the file (including non-action exports) is left untouched. - Lets each story inject its own mock implementation via
parameters.serverFunctions, with mocks automatically reset between stories. - Ships as a single Vite plugin + one decorator — no extra Storybook addon to install.
Supported patterns
- Module-level directive:
"use server"at the top of a file. Every exported function in the file is treated as a Server Action. - Function-level (inline) directive:
"use server"at the top of an individual function body, with no module-level directive required. This works on a standalone function declaration/expression just as well as on a property/method of an object or a field/method of a class — so a single directive-bearing function among otherwise-plain exports (e.g. onlyapi.createis a Server Action, whileapi.formatLabelstays a regular function) is detected correctly either way.
// actions/todo.ts (module-level — every export in the file is a Server Action)
"use server";
export async function createTodo(formData: FormData) {
/* real server-side work */
}
// actions/delete-todo.ts (function-level, no module-level directive — only this
// function is a Server Action)
export async function deleteTodo(id: string) {
"use server";
/* real server-side work */
}
// actions/api.ts (function-level, only part of the object is a Server Action)
export const api = {
create: async (formData: FormData) => {
"use server";
/* real server-side work */
},
formatLabel: (todo: Todo) => todo.title.toUpperCase(), // regular function, left untouched
};Either way, at Storybook build time these functions are replaced with mock functions created via storybook/test's fn().
Installation
npm install -D @akfm/vite-plugin-storybook-mock-server-functionspnpm add -D @akfm/vite-plugin-storybook-mock-server-functionsyarn add -D @akfm/vite-plugin-storybook-mock-server-functionsRequires vite and storybook as peer dependencies.
Setup
1. Register the plugin
Add the plugin to .storybook/main.ts's viteFinal.
import { storybookMockServerFunctions } from "@akfm/vite-plugin-storybook-mock-server-functions";
const config = {
framework: "@storybook/nextjs-vite",
// ...
viteFinal: async (viteConfig) => {
viteConfig.plugins ??= [];
viteConfig.plugins.push(storybookMockServerFunctions());
return viteConfig;
},
};
export default config;2. Register the decorator
Add the decorator to .storybook/preview.ts. This resets all mocks before every story runs.
import { withServerFunctionMocks } from "@akfm/vite-plugin-storybook-mock-server-functions/runtime";
const preview = {
decorators: [withServerFunctionMocks],
};
export default preview;Injecting mock implementations from a story
Pass a Map<mock function, implementation function> via parameters.serverFunctions to inject any return value or behavior on a per-story basis. Since Vite's module graph is a singleton, the functions a story imports are the exact mock functions the plugin generated, so they can be used directly as Map keys.
import { createTodo } from "../actions/todo";
import { api } from "../actions/api";
export const Success: Story = {
parameters: {
serverFunctions: new Map([
[createTodo, async () => ({ ok: true, id: "1" })],
[api.create, async () => ({ ok: true })],
]),
},
};If parameters.serverFunctions is not set, each mock defaults to async () => undefined.
Non-Server-Action functions like api.formatLabel are left untransformed and can be imported and used with their original implementation.
Options
storybookMockServerFunctions({
include?: FilterPattern; // default: [/\.[jt]sx?$/]
exclude?: FilterPattern; // default: [/node_modules/]
})include / exclude are passed straight through to @rollup/pluginutils's createFilter.
Example
example/ contains a verification Storybook app built with @storybook/nextjs-vite.
pnpm install
pnpm build
pnpm --filter example storybookKnown limitations
- Re-exports of the form
export { x } from './y'are not supported. - The function-level directive is only recognized on functions with a
BlockStatementbody (a single-expression arrow function like() => "use server"can't hold a directive in its body, matching Next.js's own constraint). parameters.serverFunctionsis designed around passing aMapof functions directly. If you rely on story serialization (e.g. Storybook's composed stories feature), be aware that functions cannot be serialized.oxc-parserdepends on a platform-specific native (napi) binary. This is normally resolved automatically via npm/pnpm optionalDependencies for your OS/architecture, but it may not work in unsupported runtime environments.
License
MIT
