# Setup Storybook

Squide provides helpers to set up a Storybook story file for rendering components using Squide. This guide assumes that you already have a working Storybook environment.

# Install the packages

To set up Storybook, first, open a terminal at the root of the Storybook application and install the following packages:

pnpm add msw msw-storybook-addon

# Register the MSW addon

Then, update the standard .storybook/preview.tsx file and register the Mock Service Worker (MSW) addon:

import { initialize as initializeMsw, mswLoader } from "msw-storybook-addon";
import { Suspense } from "react";
import type { Preview } from "storybook-react-rsbuild";

initializeMsw({
    onUnhandledRequest: "bypass"
});

const preview: Preview = {
    decorators: [
        Story => {
            return (
                <Suspense fallback="UNHANDLED SUSPENSE BOUNDARY, should be handled in your components...">
                    <Story />
                </Suspense>
            );
        }
    ],
    loaders: [mswLoader]
};

export default preview;

Then, update the standard .storybook/main.ts file and set the staticDirs option to ["public"]:

import { createRequire } from "node:module";
import { dirname, join } from "node:path";
import type { StorybookConfig } from "storybook-react-rsbuild";

const require = createRequire(import.meta.url);

const storybookConfig: StorybookConfig = {
    framework: getAbsolutePath("storybook-react-rsbuild"),
    staticDirs: ["public"]
};

export default storybookConfig;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getAbsolutePath(value: string): any {
    return dirname(require.resolve(join(value, "package.json")));
}

# Initialize MSW

Finally, ensure that MSW is correctly initialized. Confirm that a mockServiceWorker.js file exists in the /public folder. If it's missing, open a terminal at the root of the Storybook application and execute the following command:

pnpm dlx msw init

# Configure a project

# Install the packages

To set up a project, first, open a terminal at the project root and install the following packages:

pnpm add @squide/firefly-rsbuild-storybook

# Create a runtime instance

Then, update the story files to create a runtime instance using the initializeFireflyForStorybook function:

import { initializeFireflyForStorybook } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule]
});

const meta = {
    title: "Page",
    component: Page
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

# Setup a decorator

Then, set up a decorator using the withFireflyDecorator function:

import { initializeFireflyForStorybook, withFireflyDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule]
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        withFireflyDecorator(runtime)
    ]
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

Or embed the FireflyDecorator component in an existing decorator:

import { initializeFireflyForStorybook, FireflyDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule]
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        story => (
            <FireflyDecorator runtime={runtime}>
                {story()}
            </FireflyDecorator>
        )
    ]
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

# Setup MSW

Next, forward the MSW request handlers registered by the modules to the Storybook addon:

import { initializeFireflyForStorybook, FireflyDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule]
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        story => (
            <FireflyDecorator runtime={runtime}>
                {story()}
            </FireflyDecorator>
        )
    ],
    parameters: {
        msw: {
            handlers: runtime.requestHandlers
        }
    }
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

# Setup environment variables

Then, if the components included in the stories rely on environment variables, mock the environment variables using the initializeFireflyForStorybook function:

import { initializeFireflyForStorybook, withFireflyDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule],
    environmentVariables: {
        apiBaseUrl: "https://my-api.com"
    }
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        withFireflyDecorator(runtime)
    ]
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

# Setup feature flags

Finally, if the components included in the stories rely on feature flags, mock the feature flags using the initializeFireflyForStorybook function:

import { initializeFireflyForStorybook, withFireflyDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

const featureFlags = new Map([
    ["render-summary", true]
] as const);

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule],
    featureFlags
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        withFireflyDecorator(runtime)
    ]
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {} satisfies Story;

To test different variations of a feature flag, use the withFeatureFlagsOverrideDecorator hook:

import { initializeFireflyForStorybook, withFireflyDecorator, withFeatureFlagsOverrideDecorator } from "@squide/firefly-rsbuild-storybook";
import type { Decorator, Meta, StoryObj } from "storybook-react-rsbuild";
import { Page } from "./Page.tsx";
import { registerModule } from "./registerModule.tsx";

// This syntax with the nested arrays and "as const" is super important to get type safety with
// the "withFeatureFlagsOverrideDecorator" decorator.
const featureFlags = new Map([
    ["render-summary", true]
] as const);

const runtime = await initializeFireflyForStorybook({
    localModules: [registerModule],
    featureFlags
});

const meta = {
    title: "Page",
    component: Page,
    decorators: [
        withFireflyDecorator(runtime)
    ]
} satisfies Meta<typeof Page>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default = {
    decorators: [
        withFeatureFlagsOverrideDecorator(featureFlags, { "render-summary": false })
    ]
} satisfies Story;

# Try it 🚀

Start the Storybook application using the development script. Then open a story that uses Squide components. It should render without errors. Make a change to the story and confirm that it re-renders correctly.

# Troubleshoot issues

If you are experiencing issues with this guide: