diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c4897afc1d..9e043a28f046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 10.1.3 + +- Angular: Honor --loglevel and --logfile in dev/build - [#33212](https://github.com/storybookjs/storybook/pull/33212), thanks @valentinpalkovic! +- Core: Minor UI fixes - [#33218](https://github.com/storybookjs/storybook/pull/33218), thanks @ghengeveld! +- Telemetry: Add playwright-prompt - [#33229](https://github.com/storybookjs/storybook/pull/33229), thanks @valentinpalkovic! + ## 10.1.2 - Checklist: Fix how state changes are reported and drop some completion restrictions - [#33217](https://github.com/storybookjs/storybook/pull/33217), thanks @ghengeveld! diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 72ba0cbea684..8fb0f8f52407 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,16 @@ +## 10.2.0-alpha.3 + +- Addon Docs: Skip `!autodocs` stories when computing primary story - [#32712](https://github.com/storybookjs/storybook/pull/32712), thanks @ia319! +- Angular: Honor --loglevel and --logfile in dev/build - [#33212](https://github.com/storybookjs/storybook/pull/33212), thanks @valentinpalkovic! +- CSF: Export type to prevent `type cannot be named`-errors - [#33216](https://github.com/storybookjs/storybook/pull/33216), thanks @unional! +- Chore: Upgrade Chromatic CLI - [#33176](https://github.com/storybookjs/storybook/pull/33176), thanks @ghengeveld! +- Core: Enhance getPrettier function to provide prettier interface - [#33260](https://github.com/storybookjs/storybook/pull/33260), thanks @valentinpalkovic! +- Core: Fix cwd handling for negated globs - [#33241](https://github.com/storybookjs/storybook/pull/33241), thanks @ia319! +- NextJS: Alias image to use fileURLToPath for better resolution - [#33256](https://github.com/storybookjs/storybook/pull/33256), thanks @ndelangen! +- Nextj.js: Support top-level weight/style in next/font/local with string src - [#32998](https://github.com/storybookjs/storybook/pull/32998), thanks @Chiman2937! +- Telemetry: Cache Storybook metadata by main config content hash - [#33247](https://github.com/storybookjs/storybook/pull/33247), thanks @valentinpalkovic! +- TypeScript: Fix summary undefined type issue - [#32585](https://github.com/storybookjs/storybook/pull/32585), thanks @afsalshamsudeen! + ## 10.2.0-alpha.2 - CLI: Remove any return type of getAbsolutePath - [#32977](https://github.com/storybookjs/storybook/pull/32977), thanks @nzws! diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 89d60281caab..1fc58c65300d 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -101,6 +101,7 @@ const PlayFnNotice = styled.div( padding: '3px 8px', fontSize: '10px', fontWeight: 'bold', + zIndex: 99, '> *': { display: 'block', }, diff --git a/code/.storybook/storybook.setup.ts b/code/.storybook/storybook.setup.ts index 20a8abe5380f..91cc5bcdd8fb 100644 --- a/code/.storybook/storybook.setup.ts +++ b/code/.storybook/storybook.setup.ts @@ -15,9 +15,10 @@ setProjectAnnotations([ // https://vitest.dev/guide/browser/interactivity-api.html loaders: async (context) => { if (globalThis.__vitest_browser__) { - const vitest = await import('@vitest/browser/context'); + const vitest = await import('vitest/browser'); const { userEvent: browserEvent } = vitest; - context.userEvent = browserEvent.setup(); + // Unfortunately the types of userEvent don't match so we cast it + context.userEvent = (browserEvent as unknown as typeof storybookEvent).setup(); context.expect = vitestExpect; } else { context.userEvent = storybookEvent.setup(); diff --git a/code/addons/a11y/src/index.ts b/code/addons/a11y/src/index.ts index 48ffbe5e2c06..5f447f93eb29 100644 --- a/code/addons/a11y/src/index.ts +++ b/code/addons/a11y/src/index.ts @@ -5,6 +5,6 @@ import type { A11yTypes } from './types'; export { PARAM_KEY } from './constants'; export * from './params'; -export type { A11yTypes } from './types'; +export type { A11yGlobals, A11yTypes } from './types'; export default () => definePreviewAddon(addonAnnotations); diff --git a/code/addons/docs/src/blocks/blocks/Controls.tsx b/code/addons/docs/src/blocks/blocks/Controls.tsx index da5036b969a0..5f7e9eee69fa 100644 --- a/code/addons/docs/src/blocks/blocks/Controls.tsx +++ b/code/addons/docs/src/blocks/blocks/Controls.tsx @@ -14,6 +14,7 @@ import { ArgsTableError, ArgsTable as PureArgsTable, TabbedArgsTable } from '../ import { DocsContext } from './DocsContext'; import { useArgs } from './useArgs'; import { useGlobals } from './useGlobals'; +import { usePrimaryStory } from './usePrimaryStory'; import { getComponentName } from './utils'; type ControlsParameters = { @@ -39,12 +40,15 @@ function extractComponentArgTypes( export const Controls: FC = (props) => { const { of } = props; - if ('of' in props && of === undefined) { - throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); + const context = useContext(DocsContext); + const primaryStory = usePrimaryStory(); + + const story = of ? context.resolveOf(of, ['story']).story : primaryStory; + + if (!story) { + return null; } - const context = useContext(DocsContext); - const { story } = context.resolveOf(of || 'story', ['story']); const { parameters, argTypes, component, subcomponents } = story; const controlsParameters = parameters.docs?.controls || ({} as ControlsParameters); diff --git a/code/addons/docs/src/blocks/blocks/Primary.stories.tsx b/code/addons/docs/src/blocks/blocks/Primary.stories.tsx index 39b9123ea726..66b1108984a9 100644 --- a/code/addons/docs/src/blocks/blocks/Primary.stories.tsx +++ b/code/addons/docs/src/blocks/blocks/Primary.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import * as DefaultButtonStories from '../examples/Button.stories'; +import * as ButtonNoAutodocsStories from '../examples/ButtonNoAutodocs.stories'; +import * as ButtonSomeAutodocsStories from '../examples/ButtonSomeAutodocs.stories'; import * as StoriesParametersStories from '../examples/StoriesParameters.stories'; import { Primary } from './Primary'; @@ -59,3 +61,23 @@ export const WithoutToolbarOfStringMetaAttached: Story = { }, parameters: { relativeCsfPaths: ['../examples/StoriesParameters.stories'] }, }; + +export const NoAutodocsExample: Story = { + name: 'Button (No Autodocs)', + args: { + of: ButtonNoAutodocsStories, + }, + parameters: { + relativeCsfPaths: ['../examples/ButtonNoAutodocs.stories'], + }, +}; + +export const SomeAutodocsExample: Story = { + name: 'Button (Some Autodocs)', + args: { + of: ButtonSomeAutodocsStories, + }, + parameters: { + relativeCsfPaths: ['../examples/ButtonSomeAutodocs.stories'], + }, +}; diff --git a/code/addons/docs/src/blocks/blocks/Primary.tsx b/code/addons/docs/src/blocks/blocks/Primary.tsx index 588e0428131b..4c22ad1eccd1 100644 --- a/code/addons/docs/src/blocks/blocks/Primary.tsx +++ b/code/addons/docs/src/blocks/blocks/Primary.tsx @@ -1,26 +1,11 @@ import type { FC } from 'react'; -import React, { useContext } from 'react'; +import React from 'react'; -import { DocsContext } from './DocsContext'; import { DocsStory } from './DocsStory'; -import type { Of } from './useOf'; -import { useOf } from './useOf'; +import { usePrimaryStory } from './usePrimaryStory'; -interface PrimaryProps { - /** Specify where to get the primary story from. */ - of?: Of; -} - -export const Primary: FC = (props) => { - const { of } = props; - if ('of' in props && of === undefined) { - throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); - } - - const { csfFile } = useOf(of || 'meta', ['meta']); - const context = useContext(DocsContext); - - const primaryStory = context.componentStoriesFromCSFFile(csfFile)[0]; +export const Primary: FC = () => { + const primaryStory = usePrimaryStory(); return primaryStory ? ( diff --git a/code/addons/docs/src/blocks/blocks/usePrimaryStory.test.tsx b/code/addons/docs/src/blocks/blocks/usePrimaryStory.test.tsx new file mode 100644 index 000000000000..aa92719b802a --- /dev/null +++ b/code/addons/docs/src/blocks/blocks/usePrimaryStory.test.tsx @@ -0,0 +1,66 @@ +// @vitest-environment happy-dom +import { renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import React from 'react'; +import type { FC, PropsWithChildren } from 'react'; + +import type { PreparedStory } from 'storybook/internal/types'; + +import type { DocsContextProps } from './DocsContext'; +import { DocsContext } from './DocsContext'; +import { usePrimaryStory } from './usePrimaryStory'; + +const stories: Record> = { + story1: { name: 'Story One', tags: ['!autodocs'] }, + story2: { name: 'Story Two', tags: ['autodocs'] }, + story3: { name: 'Story Three', tags: ['autodocs'] }, + story4: { name: 'Story Four', tags: [] }, +}; + +const createMockContext = (storyList: PreparedStory[]) => ({ + componentStories: vi.fn(() => storyList), +}); + +const Wrapper: FC }>> = ({ + children, + context, +}) => {children}; + +describe('usePrimaryStory', () => { + it('ignores !autodocs stories', () => { + const mockContext = createMockContext([ + stories.story1, + stories.story2, + stories.story3, + ] as PreparedStory[]); + const { result } = renderHook(() => usePrimaryStory(), { + wrapper: ({ children }) => {children}, + }); + expect(result.current?.name).toBe('Story Two'); + }); + + it('selects the first autodocs story', () => { + const mockContext = createMockContext([stories.story2, stories.story3] as PreparedStory[]); + const { result } = renderHook(() => usePrimaryStory(), { + wrapper: ({ children }) => {children}, + }); + expect(result.current?.name).toBe('Story Two'); + }); + + it('returns undefined if no story has "autodocs" tag', () => { + const mockContext = createMockContext([stories.story1, stories.story4] as PreparedStory[]); + const { result } = renderHook(() => usePrimaryStory(), { + wrapper: ({ children }) => {children}, + }); + expect(result.current).toBeUndefined(); + }); + + it('returns undefined for empty story list', () => { + const mockContext = createMockContext([]); + const { result } = renderHook(() => usePrimaryStory(), { + wrapper: ({ children }) => {children}, + }); + expect(result.current).toBeUndefined(); + }); +}); diff --git a/code/addons/docs/src/blocks/blocks/usePrimaryStory.ts b/code/addons/docs/src/blocks/blocks/usePrimaryStory.ts new file mode 100644 index 000000000000..fd177287023e --- /dev/null +++ b/code/addons/docs/src/blocks/blocks/usePrimaryStory.ts @@ -0,0 +1,15 @@ +import { useContext } from 'react'; + +import type { PreparedStory } from 'storybook/internal/types'; + +import { DocsContext } from './DocsContext'; + +/** + * A hook to get the primary story for the current component's doc page. It defines the primary + * story as the first story that includes the 'autodocs' tag + */ +export const usePrimaryStory = (): PreparedStory | undefined => { + const context = useContext(DocsContext); + const stories = context.componentStories(); + return stories.find((story) => story.tags.includes('autodocs')); +}; diff --git a/code/addons/docs/src/blocks/components/ArgsTable/types.ts b/code/addons/docs/src/blocks/components/ArgsTable/types.ts index 9a335f3bfeab..c81327d82215 100644 --- a/code/addons/docs/src/blocks/components/ArgsTable/types.ts +++ b/code/addons/docs/src/blocks/components/ArgsTable/types.ts @@ -46,11 +46,11 @@ export interface ArgType { disable?: boolean; subcategory?: string; defaultValue?: { - summary?: string; + summary?: string | undefined; detail?: string; }; type?: { - summary?: string; + summary?: string | undefined; detail?: string; }; readonly?: boolean; diff --git a/code/addons/docs/src/blocks/examples/StoriesParameters.stories.tsx b/code/addons/docs/src/blocks/examples/StoriesParameters.stories.tsx index 74b397ee4b69..b986320df76d 100644 --- a/code/addons/docs/src/blocks/examples/StoriesParameters.stories.tsx +++ b/code/addons/docs/src/blocks/examples/StoriesParameters.stories.tsx @@ -5,6 +5,7 @@ import { EmptyExample } from './EmptyExample'; const meta = { title: 'examples/Stories for the Stories and Primary Block', component: EmptyExample, + tags: ['autodocs'], } satisfies Meta; export default meta; diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index 38f83ca1d90b..ea17fbc1fb17 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -76,8 +76,8 @@ "@types/micromatch": "^4.0.0", "@types/node": "^22.0.0", "@types/semver": "^7", - "@vitest/browser-playwright": "^4.0.1", - "@vitest/runner": "^4.0.1", + "@vitest/browser-playwright": "^4.0.14", + "@vitest/runner": "^4.0.14", "empathic": "^2.0.0", "es-toolkit": "^1.36.0", "istanbul-lib-report": "^3.0.1", @@ -93,7 +93,7 @@ "tree-kill": "^1.2.2", "ts-dedent": "^2.2.0", "typescript": "^5.8.3", - "vitest": "^4.0.1" + "vitest": "^4.0.14" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index ff8cfa4e64df..0845f8dc8bd2 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -1,5 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; -import { createVitest as actualCreateVitest } from 'vitest/node'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Channel, type ChannelTransport } from 'storybook/internal/channels'; import { experimental_MockUniversalStore } from 'storybook/internal/core-server'; @@ -43,14 +42,21 @@ const vitest = vi.hoisted(() => ({ }, })); +const mockCreateVitest = vi.fn(); + vi.mock('vitest/node', async (importOriginal) => ({ ...(await importOriginal()), - createVitest: vi.fn(() => Promise.resolve(vitest)), + createVitest: mockCreateVitest, })); -const createVitest = vi.mocked(actualCreateVitest); +// Use the mock function directly +const createVitest = mockCreateVitest; const transport = { setHandler: vi.fn(), send: vi.fn() } satisfies ChannelTransport; + +beforeEach(() => { + createVitest.mockResolvedValue(vitest); +}); const mockChannel = new Channel({ transport }); const mockStore = new experimental_MockUniversalStore( { diff --git a/code/addons/vitest/src/node/test-manager.ts b/code/addons/vitest/src/node/test-manager.ts index ce25486b7910..f66270dfc091 100644 --- a/code/addons/vitest/src/node/test-manager.ts +++ b/code/addons/vitest/src/node/test-manager.ts @@ -1,4 +1,4 @@ -import type { TestResult, TestState } from 'vitest/dist/node.js'; +import type { TestResult, TestState } from 'vitest/node'; import type { experimental_UniversalStore } from 'storybook/internal/core-server'; import type { diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index c54b2a8b04a8..4d811de01228 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -8,7 +8,7 @@ import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/p import { setViewport } from './viewports'; -declare module '@vitest/browser/context' { +declare module 'vitest/browser' { interface BrowserCommands { getInitialGlobals: () => Promise>; } diff --git a/code/core/src/backgrounds/preview.ts b/code/core/src/backgrounds/preview.ts index f7f094531073..448d73c2ec1d 100644 --- a/code/core/src/backgrounds/preview.ts +++ b/code/core/src/backgrounds/preview.ts @@ -2,7 +2,12 @@ import { definePreviewAddon } from 'storybook/internal/csf'; import { PARAM_KEY } from './constants'; import { withBackgroundAndGrid } from './decorator'; -import type { BackgroundTypes, BackgroundsParameters, GlobalState } from './types'; +import type { + BackgroundTypes, + BackgroundsGlobals, + BackgroundsParameters, + GlobalState, +} from './types'; const decorators = globalThis.FEATURES?.backgrounds ? [withBackgroundAndGrid] : []; @@ -21,7 +26,7 @@ const initialGlobals: Record = { [PARAM_KEY]: { value: undefined, grid: false }, }; -export type { BackgroundTypes }; +export type { BackgroundTypes, BackgroundsGlobals }; export default () => definePreviewAddon({ diff --git a/code/core/src/bin/core.ts b/code/core/src/bin/core.ts index ee757dfd7356..280e1888efe2 100644 --- a/code/core/src/bin/core.ts +++ b/code/core/src/bin/core.ts @@ -27,8 +27,10 @@ addToGlobalContext('cliVersion', version); */ const handleCommandFailure = async (logFilePath: string | boolean): Promise => { - const logFile = await logTracker.writeToFile(logFilePath); - logger.log(`Debug logs are written to: ${logFile}`); + try { + const logFile = await logTracker.writeToFile(logFilePath); + logger.log(`Debug logs are written to: ${logFile}`); + } catch {} logger.outro('Storybook exited with an error'); process.exit(1); }; @@ -66,8 +68,10 @@ const command = (name: string) => }) .hook('postAction', async (command) => { if (logTracker.shouldWriteLogsToFile) { - const logFile = await logTracker.writeToFile(command.getOptionValue('logfile')); - logger.outro(`Debug logs are written to: ${logFile}`); + try { + const logFile = await logTracker.writeToFile(command.getOptionValue('logfile')); + logger.outro(`Debug logs are written to: ${logFile}`); + } catch {} } }); diff --git a/code/core/src/channels/index.test.ts b/code/core/src/channels/index.test.ts index 5bb7baf6babe..a55f537971c2 100644 --- a/code/core/src/channels/index.test.ts +++ b/code/core/src/channels/index.test.ts @@ -148,7 +148,6 @@ describe('Channel', () => { }); it('should use setImmediate if async is true', () => { - // @ts-expect-error no idea what's going on here! global.setImmediate = vi.fn(setImmediate); channel = new Channel({ async: true, transport }); diff --git a/code/core/src/common/js-package-manager/JsPackageManager.test.ts b/code/core/src/common/js-package-manager/JsPackageManager.test.ts index 3156f14cf1ec..8ca7d8ec4a8a 100644 --- a/code/core/src/common/js-package-manager/JsPackageManager.test.ts +++ b/code/core/src/common/js-package-manager/JsPackageManager.test.ts @@ -17,7 +17,6 @@ describe('JsPackageManager', () => { beforeEach(() => { // @ts-expect-error Ignore abstract class error jsPackageManager = new JsPackageManager(); - // @ts-expect-error latestVersion is a method that exists on the instance mockLatestVersion = vi.spyOn(jsPackageManager, 'latestVersion'); vi.clearAllMocks(); diff --git a/code/core/src/common/utils/formatter.ts b/code/core/src/common/utils/formatter.ts index 67114256ba7c..2461ef7b31e3 100644 --- a/code/core/src/common/utils/formatter.ts +++ b/code/core/src/common/utils/formatter.ts @@ -1,8 +1,46 @@ -export async function getPrettier() { - return import('prettier').catch((e) => ({ - resolveConfig: async () => null, - format: (content: string) => content, - })); +// Prettier interface definition +// Note: We want to avoid importing prettier directly to prevent bundling its type import +// because prettier is an optional peer dependency and might not be available +interface Prettier { + resolveConfig: (filePath: string, options?: { editorconfig?: boolean }) => Promise; + format: (content: string, options?: any) => Promise | string; + check: (content: string, options?: any) => Promise; + clearConfigCache: () => Promise; + formatWithCursor: ( + content: string, + options?: any + ) => Promise<{ formatted: string; cursorOffset: number }>; + getFileInfo: ( + filePath: string, + options?: any + ) => Promise<{ ignored: boolean; inferredParser: string | null }>; + getSupportInfo: () => Promise<{ languages: any[]; options: any[] }>; + resolveConfigFile: (filePath?: string) => Promise; + version: string; + AstPath: any; + doc: any; + util: any; +} + +export async function getPrettier(): Promise { + try { + return await import('prettier'); + } catch { + return { + AstPath: class {} as any, + doc: {} as any, + util: {} as any, + version: '0.0.0-fallback', + resolveConfig: async () => null, + format: (content: string) => Promise.resolve(content), + check: () => Promise.resolve(false), + clearConfigCache: () => Promise.resolve(undefined), + formatWithCursor: () => Promise.resolve({ formatted: '', cursorOffset: 0 }), + getFileInfo: async () => ({ ignored: false, inferredParser: null }), + getSupportInfo: () => Promise.resolve({ languages: [], options: [] }), + resolveConfigFile: async () => null, + }; + } } /** @@ -30,7 +68,7 @@ export async function formatFileContent(filePath: string, content: string): Prom } } -async function formatWithEditorConfig(filePath: string, content: string) { +async function formatWithEditorConfig(filePath: string, content: string): Promise { const { resolveConfig, format } = await getPrettier(); const config = await resolveConfig(filePath, { editorconfig: true }); diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 6cb2ae2ad8a6..168694a5659f 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -161,16 +161,20 @@ export class StoryIndexGenerator { const pathToSubIndex = {} as SpecifierStoriesCache; - const fullGlob = slash(join(specifier.directory, specifier.files)); + // Calculate a new CWD for each glob to handle paths that go above the workingDir. + const globCwd = slash(resolve(workingDir, specifier.directory)); + // Prepend ./ to patterns starting with ! to ensure they are treated as extglobs + const globPattern = specifier.files.startsWith('!') ? `./${specifier.files}` : specifier.files; // Dynamically import globby because it is a pure ESM module // eslint-disable-next-line depend/ban-dependencies const { globby } = await import('globby'); - const files = await globby(fullGlob, { + // Execute globby within the new CWD to ensure `ignore` patterns work correctly. + const files = await globby(globPattern, { absolute: true, - cwd: workingDir, - ...commonGlobOptions(fullGlob), + cwd: globCwd, + ...commonGlobOptions(globPattern), }); if (files.length === 0 && !ignoreWarnings) { diff --git a/code/core/src/core-server/withTelemetry.ts b/code/core/src/core-server/withTelemetry.ts index 2e0ce5726da8..c0d6deb37815 100644 --- a/code/core/src/core-server/withTelemetry.ts +++ b/code/core/src/core-server/withTelemetry.ts @@ -9,8 +9,6 @@ import { import type { EventType } from 'storybook/internal/telemetry'; import type { CLIOptions } from 'storybook/internal/types'; -import { dedent } from 'ts-dedent'; - import { StorybookError } from '../storybook-error'; type TelemetryOptions = { diff --git a/code/core/src/instrumenter/instrumenter.test.ts b/code/core/src/instrumenter/instrumenter.test.ts index 2d75669b6c1e..a3eea57e1da1 100644 --- a/code/core/src/instrumenter/instrumenter.test.ts +++ b/code/core/src/instrumenter/instrumenter.test.ts @@ -694,8 +694,13 @@ describe('Instrumenter', () => { }); it('steps through each interceptable function on "next"', async () => { - const fn = vi.fn(); - const { fn: instrumentedFn } = instrument({ fn }, { intercept: true }); + const fn = vi.fn(async () => {}); + const { fn: instrumentedFn } = instrument( + { + fn: async () => fn(), + }, + { intercept: true } + ); const mockedInstrumentedFn = vi.fn(instrumentedFn); const play = async () => { diff --git a/code/core/src/manager-api/tests/refs.test.ts b/code/core/src/manager-api/tests/refs.test.ts index 07eddf864e49..bc337d3a7497 100644 --- a/code/core/src/manager-api/tests/refs.test.ts +++ b/code/core/src/manager-api/tests/refs.test.ts @@ -32,12 +32,12 @@ vi.mock('@storybook/global', () => { ]; // global.location value after all edgecaseLocations are returned const lastLocation = { origin: 'https://storybook.js.org', pathname: '/storybook/' }; - Object.defineProperties(globalMock, { - location: { - get: edgecaseLocations - .reduce((mockFn, location) => mockFn.mockReturnValueOnce(location), vi.fn()) - .mockReturnValue(lastLocation), - }, + const locationMock = vi.fn(); + edgecaseLocations.forEach((location) => locationMock.mockReturnValueOnce(location)); + locationMock.mockReturnValue(lastLocation); + + Object.defineProperty(globalMock, 'location', { + get: locationMock, }); return { global: globalMock }; }); diff --git a/code/core/src/node-logger/index.ts b/code/core/src/node-logger/index.ts index 0184a017c2e9..ae2de0410ed1 100644 --- a/code/core/src/node-logger/index.ts +++ b/code/core/src/node-logger/index.ts @@ -11,6 +11,8 @@ export { protectUrls, createHyperlink } from './wrap-utils'; export { CLI_COLORS } from './logger/colors'; export { ConsoleLogger, StyledConsoleLogger } from './logger/console'; +export type { LogLevel } from './logger/logger'; + // The default is stderr, which can cause some tools (like rush.js) to think // there are issues with the build: https://github.com/storybookjs/storybook/issues/14621 npmLog.stream = process.stdout; diff --git a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts index 577160c56cf6..114278e06210 100644 --- a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts +++ b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts @@ -3905,8 +3905,8 @@ describe('PreviewWeb', () => { }, }, "docs": { - "container": [MockFunction spy], - "page": [MockFunction spy], + "container": [MockFunction], + "page": [MockFunction], "renderer": [Function], }, "fileName": "./src/ComponentOne.stories.js", @@ -3979,8 +3979,8 @@ describe('PreviewWeb', () => { }, }, "docs": { - "container": [MockFunction spy], - "page": [MockFunction spy], + "container": [MockFunction], + "page": [MockFunction], "renderer": [Function], }, "fileName": "./src/ComponentOne.stories.js", @@ -4031,7 +4031,7 @@ describe('PreviewWeb', () => { }, }, "docs": { - "page": [MockFunction spy], + "page": [MockFunction], "renderer": [Function], }, "fileName": "./src/ExtraComponentOne.stories.js", diff --git a/code/core/src/preview-api/modules/store/csf/prepareStory.test.ts b/code/core/src/preview-api/modules/store/csf/prepareStory.test.ts index 63b42fa8f36a..69aca0b7b902 100644 --- a/code/core/src/preview-api/modules/store/csf/prepareStory.test.ts +++ b/code/core/src/preview-api/modules/store/csf/prepareStory.test.ts @@ -581,7 +581,7 @@ describe('prepareStory', () => { moduleExport, }, { id, title }, - { render: vi.fn() } + { render: vi.fn() as any } ); const context = prepareContext({ args: { one: 1 }, globals: {}, ...story }); @@ -601,7 +601,7 @@ describe('prepareStory', () => { moduleExport, }, { id, title }, - { render: vi.fn() } + { render: vi.fn() as any } ); const context = prepareContext({ diff --git a/code/core/src/telemetry/sanitize.test.ts b/code/core/src/telemetry/sanitize.test.ts index d5cac7d16f5c..1f085782a328 100644 --- a/code/core/src/telemetry/sanitize.test.ts +++ b/code/core/src/telemetry/sanitize.test.ts @@ -20,23 +20,32 @@ describe(`Errors Helpers`, () => { }); it(`Sanitizes current path from error stacktraces`, () => { - const errorMessage = `this is a test`; - let e: any; - try { - throw new Error(errorMessage); - } catch (error) { - e = error; - } + const errorMessage = `Test error message`; + const mockCwd = `/Users/testuser/project`; + const mockCwdSpy = vi.spyOn(process, `cwd`).mockImplementation(() => mockCwd); + + const e = { + message: errorMessage, + stack: ` + Error: Test error message + at Object. (${mockCwd}/src/index.js:1:32) + at Object. (${mockCwd}/node_modules/some-lib/index.js:1:69) + at Module._compile (internal/modules/cjs/loader.js:736:30) + `, + name: 'Error', + }; + expect(e).toBeDefined(); expect(e.message).toEqual(errorMessage); - expect(e.stack).toEqual(expect.stringContaining(process.cwd())); + expect(e.stack).toEqual(expect.stringContaining(mockCwd)); - const sanitizedError = sanitizeError(e); + const sanitizedError = sanitizeError(e as Error, '/'); + + expect(sanitizedError.message).toEqual(errorMessage); + expect(sanitizedError.stack).toEqual(expect.not.stringContaining(mockCwd)); + expect(sanitizedError.stack).toEqual(expect.stringContaining('$SNIP')); - expect(sanitizedError.message).toEqual(expect.stringContaining(errorMessage)); - expect(sanitizedError.message).toEqual( - expect.not.stringContaining(process.cwd().replace(/\\/g, `\\\\`)) - ); + mockCwdSpy.mockRestore(); }); it(`Sanitizes a section of the current path from error stacktrace`, () => { diff --git a/code/core/src/telemetry/storybook-metadata.ts b/code/core/src/telemetry/storybook-metadata.ts index 5d420f2cabfd..705bf1270ccf 100644 --- a/code/core/src/telemetry/storybook-metadata.ts +++ b/code/core/src/telemetry/storybook-metadata.ts @@ -1,5 +1,7 @@ +import { createHash } from 'node:crypto'; +import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; -import { dirname } from 'node:path'; +import { dirname, resolve } from 'node:path'; import { getStorybookConfiguration, @@ -8,6 +10,7 @@ import { loadMainConfig, versions, } from 'storybook/internal/common'; +import { getInterpretedFile } from 'storybook/internal/common'; import { readConfig } from 'storybook/internal/csf-tools'; import type { PackageJson, StorybookConfig } from 'storybook/internal/types'; @@ -265,12 +268,26 @@ async function getPackageJsonDetails() { }; } -let cachedMetadata: StorybookMetadata; -export const getStorybookMetadata = async (_configDir?: string) => { - if (cachedMetadata) { - return cachedMetadata; +// Cache metadata keyed by a hash of the main config file to avoid caching +// empty/incorrect values during init flows when the configDir is created/updated. +const metadataCache = new Map(); + +async function hashMainConfig(configDir: string): Promise { + try { + const mainPath = getInterpretedFile(resolve(configDir, 'main')) as string | null; + + if (!mainPath || !existsSync(mainPath)) { + return 'missing'; + } + const content = await readFile(mainPath); + const hash = createHash('sha256').update(new Uint8Array(content)).digest('hex'); + return hash; + } catch { + return 'unknown'; } +} +export const getStorybookMetadata = async (_configDir?: string) => { const { packageJson, packageJsonPath } = await getPackageJsonDetails(); // TODO: improve the way configDir is extracted, as a "storybook" script might not be present // Scenarios: @@ -284,12 +301,21 @@ export const getStorybookMetadata = async (_configDir?: string) => { '--config-dir' ) as string)) ?? '.storybook'; + const contentHash = await hashMainConfig(configDir); + const cacheKey = `${configDir}::${contentHash}`; + const cached = metadataCache.get(cacheKey); + + if (cached) { + return cached; + } + const mainConfig = await loadMainConfig({ configDir }).catch(() => undefined); - cachedMetadata = await computeStorybookMetadata({ + const computed = await computeStorybookMetadata({ mainConfig, packageJson, packageJsonPath, configDir, }); - return cachedMetadata; + metadataCache.set(cacheKey, computed); + return computed; }; diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index 3523f60f7f0c..b8f347124a4d 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -2,6 +2,7 @@ import type { FileSystemCache } from 'storybook/internal/common'; import { type StoryIndexGenerator } from 'storybook/internal/core-server'; import { type CsfFile } from 'storybook/internal/csf-tools'; +import type { LogLevel } from 'storybook/internal/node-logger'; import type { Server as HttpServer, IncomingMessage, ServerResponse } from 'http'; import type { Server as NetServer } from 'net'; @@ -173,7 +174,8 @@ export interface CLIBaseOptions { disableTelemetry?: boolean; enableCrashReports?: boolean; configDir?: string; - loglevel?: string; + loglevel?: LogLevel; + logfile?: string | boolean; quiet?: boolean; } diff --git a/code/core/src/viewport/preview.ts b/code/core/src/viewport/preview.ts index 30ca59dadd2d..b92ee57c8c4d 100644 --- a/code/core/src/viewport/preview.ts +++ b/code/core/src/viewport/preview.ts @@ -1,13 +1,13 @@ import { definePreviewAddon } from 'storybook/internal/csf'; import { PARAM_KEY } from './constants'; -import type { GlobalState, ViewportTypes } from './types'; +import type { GlobalState, ViewportGlobals, ViewportTypes } from './types'; export const initialGlobals: Record = { [PARAM_KEY]: { value: undefined, isRotated: false }, }; -export type { ViewportTypes }; +export type { ViewportGlobals, ViewportTypes }; export default () => definePreviewAddon({ diff --git a/code/frameworks/angular/build-schema.json b/code/frameworks/angular/build-schema.json index 9753db540f2d..d3963edc7215 100644 --- a/code/frameworks/angular/build-schema.json +++ b/code/frameworks/angular/build-schema.json @@ -32,7 +32,11 @@ "loglevel": { "type": "string", "description": "Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent].", - "pattern": "(silly|verbose|info|warn|silent)" + "pattern": "(trace|debug|info|warn|error|silent)" + }, + "logfile": { + "type": "string", + "description": "If provided, the log output will be written to the specified file path." }, "debugWebpack": { "type": "boolean", diff --git a/code/frameworks/angular/src/builders/build-storybook/index.ts b/code/frameworks/angular/src/builders/build-storybook/index.ts index 191f93a16e31..2f34d7bc3aba 100644 --- a/code/frameworks/angular/src/builders/build-storybook/index.ts +++ b/code/frameworks/angular/src/builders/build-storybook/index.ts @@ -4,7 +4,7 @@ import { getEnvConfig, getProjectRoot, versions } from 'storybook/internal/commo import { buildStaticStandalone, withTelemetry } from 'storybook/internal/core-server'; import { addToGlobalContext } from 'storybook/internal/telemetry'; import type { CLIOptions } from 'storybook/internal/types'; -import { logger } from 'storybook/internal/node-logger'; +import { logger, logTracker } from 'storybook/internal/node-logger'; import type { BuilderContext, @@ -60,6 +60,7 @@ export type StorybookBuilderOptions = JsonObject & { | 'statsJson' | 'disableTelemetry' | 'debugWebpack' + | 'logfile' | 'previewUrl' >; @@ -71,6 +72,14 @@ const commandBuilder: BuilderHandlerFn = async ( options, context ): Promise => { + // Apply logger configuration from builder options + if (options.loglevel) { + logger.setLogLevel(options.loglevel); + } + if (options.logfile) { + logTracker.enableLogWriting(); + } + logger.intro('Building Storybook'); const { tsConfig } = await setup(options, context); @@ -147,6 +156,10 @@ const commandBuilder: BuilderHandlerFn = async ( }; await runInstance({ ...standaloneOptions, mode: 'static' }); + if (logTracker.shouldWriteLogsToFile) { + const logFile = await logTracker.writeToFile(options.logfile as any); + logger.info(`Debug logs are written to: ${logFile}`); + } logger.outro('Storybook build completed successfully'); return { success: true } as BuilderOutput; }; diff --git a/code/frameworks/angular/src/builders/start-storybook/index.ts b/code/frameworks/angular/src/builders/start-storybook/index.ts index 9562ab66ba0c..977dd4cc717f 100644 --- a/code/frameworks/angular/src/builders/start-storybook/index.ts +++ b/code/frameworks/angular/src/builders/start-storybook/index.ts @@ -4,7 +4,7 @@ import { getEnvConfig, getProjectRoot, versions } from 'storybook/internal/commo import { buildDevStandalone, withTelemetry } from 'storybook/internal/core-server'; import { addToGlobalContext } from 'storybook/internal/telemetry'; import type { CLIOptions } from 'storybook/internal/types'; -import { logger } from 'storybook/internal/node-logger'; +import { logger, logTracker } from 'storybook/internal/node-logger'; import type { BuilderContext, @@ -65,6 +65,7 @@ export type StorybookBuilderOptions = JsonObject & { | 'open' | 'docs' | 'debugWebpack' + | 'logfile' | 'webpackStatsJson' | 'statsJson' | 'loglevel' @@ -80,6 +81,14 @@ const commandBuilder: BuilderHandlerFn = ( return new Observable((observer) => { (async () => { try { + // Apply logger configuration from builder options + if (options.loglevel) { + logger.setLogLevel(options.loglevel); + } + if (options.logfile) { + logTracker.enableLogWriting(); + } + logger.intro('Starting Storybook'); const { tsConfig } = await setup(options, context); @@ -187,6 +196,15 @@ const commandBuilder: BuilderHandlerFn = ( // so the dev server continues running. Architect will keep subscribing // until the Observable completes, which allows watch mode to work. } catch (error) { + // Write logs to file on failure when enabled + try { + if (logTracker.shouldWriteLogsToFile) { + try { + const logFile = await logTracker.writeToFile(options.logfile as any); + logger.outro(`Debug logs are written to: ${logFile}`); + } catch {} + } + } catch {} observer.error(error); } })(); diff --git a/code/frameworks/angular/src/node/index.ts b/code/frameworks/angular/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/angular/src/node/index.ts +++ b/code/frameworks/angular/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/angular/start-schema.json b/code/frameworks/angular/start-schema.json index 84d6bd80861b..ac81c01d6ab0 100644 --- a/code/frameworks/angular/start-schema.json +++ b/code/frameworks/angular/start-schema.json @@ -148,7 +148,11 @@ "loglevel": { "type": "string", "description": "Controls level of logging during build. Can be one of: [silly, verbose, info (default), warn, error, silent].", - "pattern": "(silly|verbose|info|warn|silent)" + "pattern": "(trace|debug|info|warn|error|silent)" + }, + "logfile": { + "type": "string", + "description": "If provided, the log output will be written to the specified file path." }, "sourceMap": { "type": ["boolean", "object"], diff --git a/code/frameworks/ember/src/node/index.ts b/code/frameworks/ember/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/ember/src/node/index.ts +++ b/code/frameworks/ember/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/html-vite/src/node/index.ts b/code/frameworks/html-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/html-vite/src/node/index.ts +++ b/code/frameworks/html-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts b/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts index daf37993926f..c3db70d9e705 100644 --- a/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts +++ b/code/frameworks/nextjs/src/font/webpack/loader/local/get-font-face-declarations.ts @@ -46,6 +46,8 @@ export async function getFontFaceDeclarations( return `@font-face { font-family: ${id}; src: url(.${localFontPath}); + ${weight ? `font-weight: ${weight};` : ''} + ${style ? `font-style: ${style};` : ''} ${fontDeclarations} }`; } diff --git a/code/frameworks/nextjs/src/font/webpack/loader/utils/get-css-meta.ts b/code/frameworks/nextjs/src/font/webpack/loader/utils/get-css-meta.ts index 0fe376d7e152..f12fb1df9a45 100644 --- a/code/frameworks/nextjs/src/font/webpack/loader/utils/get-css-meta.ts +++ b/code/frameworks/nextjs/src/font/webpack/loader/utils/get-css-meta.ts @@ -15,7 +15,11 @@ export function getCSSMeta(options: Options) { .${className} { font-family: ${options.fontFamily}; ${isNextCSSPropertyValid(options.styles) ? `font-style: ${options.styles[0]};` : ''} - ${isNextCSSPropertyValid(options.weights) ? `font-weight: ${options.weights[0]};` : ''} + ${ + isNextCSSPropertyValid(options.weights) && !options.weights[0]?.includes(' ') + ? `font-weight: ${options.weights[0]};` + : '' + } } ${ @@ -39,8 +43,7 @@ export function getCSSMeta(options: Options) { function getClassName({ styles, weights, fontFamily }: Options) { const font = fontFamily.replaceAll(' ', '-').toLowerCase(); const style = isNextCSSPropertyValid(styles) ? styles[0] : null; - const weight = isNextCSSPropertyValid(weights) ? weights[0] : null; - + const weight = isNextCSSPropertyValid(weights) ? weights[0]?.replaceAll(' ', '-') : null; return `${font}${style ? `-${style}` : ''}${weight ? `-${weight}` : ''}`; } diff --git a/code/frameworks/nextjs/src/images/webpack.ts b/code/frameworks/nextjs/src/images/webpack.ts index 6141fe04da69..adcff3dd9d0f 100644 --- a/code/frameworks/nextjs/src/images/webpack.ts +++ b/code/frameworks/nextjs/src/images/webpack.ts @@ -19,14 +19,16 @@ const configureImageDefaults = (baseConfig: WebpackConfig): void => { resolve.alias = { ...resolve.alias, 'sb-original/next/image': fileURLToPath(import.meta.resolve('next/image')), - 'next/image': '@storybook/nextjs/images/next-image', + 'next/image': fileURLToPath(import.meta.resolve('@storybook/nextjs/images/next-image')), }; if (semver.satisfies(version, '>=13.0.0')) { resolve.alias = { ...resolve.alias, 'sb-original/next/legacy/image': fileURLToPath(import.meta.resolve('next/legacy/image')), - 'next/legacy/image': '@storybook/nextjs/images/next-legacy-image', + 'next/legacy/image': fileURLToPath( + import.meta.resolve('@storybook/nextjs/images/next-legacy-image') + ), }; } }; diff --git a/code/frameworks/nextjs/src/node/index.ts b/code/frameworks/nextjs/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/nextjs/src/node/index.ts +++ b/code/frameworks/nextjs/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/preact-vite/src/node/index.ts b/code/frameworks/preact-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/preact-vite/src/node/index.ts +++ b/code/frameworks/preact-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/react-native-web-vite/src/node/index.ts b/code/frameworks/react-native-web-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/react-native-web-vite/src/node/index.ts +++ b/code/frameworks/react-native-web-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/react-vite/src/node/index.ts b/code/frameworks/react-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/react-vite/src/node/index.ts +++ b/code/frameworks/react-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/react-webpack5/src/node/index.ts b/code/frameworks/react-webpack5/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/react-webpack5/src/node/index.ts +++ b/code/frameworks/react-webpack5/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/server-webpack5/src/node/index.ts b/code/frameworks/server-webpack5/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/server-webpack5/src/node/index.ts +++ b/code/frameworks/server-webpack5/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/svelte-vite/src/node/index.ts b/code/frameworks/svelte-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/svelte-vite/src/node/index.ts +++ b/code/frameworks/svelte-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/sveltekit/src/node/index.ts b/code/frameworks/sveltekit/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/sveltekit/src/node/index.ts +++ b/code/frameworks/sveltekit/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/vue3-vite/src/node/index.ts b/code/frameworks/vue3-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/vue3-vite/src/node/index.ts +++ b/code/frameworks/vue3-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/frameworks/web-components-vite/src/node/index.ts b/code/frameworks/web-components-vite/src/node/index.ts index bbfba66cc964..31a74ec3447c 100644 --- a/code/frameworks/web-components-vite/src/node/index.ts +++ b/code/frameworks/web-components-vite/src/node/index.ts @@ -3,3 +3,5 @@ import type { StorybookConfig } from '../types'; export function defineMain(config: StorybookConfig) { return config; } + +export type { StorybookConfig }; diff --git a/code/lib/cli-storybook/src/bin/run.ts b/code/lib/cli-storybook/src/bin/run.ts index f0cb4dc9591f..cab65aa2eedf 100644 --- a/code/lib/cli-storybook/src/bin/run.ts +++ b/code/lib/cli-storybook/src/bin/run.ts @@ -36,8 +36,10 @@ const handleCommandFailure = logger.error(String(error)); } - const logFile = await logTracker.writeToFile(logFilePath); - logger.log(`Debug logs are written to: ${logFile}`); + try { + const logFile = await logTracker.writeToFile(logFilePath); + logger.log(`Debug logs are written to: ${logFile}`); + } catch {} logger.outro(''); process.exit(1); }; @@ -79,8 +81,10 @@ const command = (name: string) => }) .hook('postAction', async (command) => { if (logTracker.shouldWriteLogsToFile) { - const logFile = await logTracker.writeToFile(command.getOptionValue('logfile')); - logger.log(`Debug logs are written to: ${logFile}`); + try { + const logFile = await logTracker.writeToFile(command.getOptionValue('logfile')); + logger.log(`Debug logs are written to: ${logFile}`); + } catch {} logger.outro(CLI_COLORS.success('Done!')); } }); diff --git a/code/lib/cli-storybook/src/upgrade.ts b/code/lib/cli-storybook/src/upgrade.ts index 74a9c5d4e404..914f96193620 100644 --- a/code/lib/cli-storybook/src/upgrade.ts +++ b/code/lib/cli-storybook/src/upgrade.ts @@ -7,6 +7,7 @@ import { logger, prompt, } from 'storybook/internal/node-logger'; +import type { LogLevel } from 'storybook/internal/node-logger'; import { UpgradeStorybookToLowerVersionError, UpgradeStorybookUnknownCurrentVersionError, @@ -123,6 +124,7 @@ export type UpgradeOptions = { configDir?: string[]; fixId?: string; skipInstall?: boolean; + loglevel?: LogLevel; logfile?: string | boolean; }; diff --git a/code/lib/create-storybook/src/bin/run.ts b/code/lib/create-storybook/src/bin/run.ts index 977ce2adbeba..322c6370987b 100644 --- a/code/lib/create-storybook/src/bin/run.ts +++ b/code/lib/create-storybook/src/bin/run.ts @@ -104,7 +104,9 @@ const createStorybookProgram = program }) .hook('postAction', async (command) => { if (logTracker.shouldWriteLogsToFile) { - await logTracker.writeToFile(command.getOptionValue('logfile')); + try { + await logTracker.writeToFile(command.getOptionValue('logfile')); + } catch {} } }); diff --git a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts index e4ad291db47d..f852a85fa2e7 100644 --- a/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts +++ b/code/lib/create-storybook/src/commands/AddonConfigurationCommand.test.ts @@ -1,22 +1,17 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { type JsPackageManager, PackageManagerName } from 'storybook/internal/common'; +import { type JsPackageManager } from 'storybook/internal/common'; import { logger, prompt } from 'storybook/internal/node-logger'; +import { TelemetryService } from '../services/TelemetryService'; +import { VersionService } from '../services/VersionService'; import { AddonConfigurationCommand } from './AddonConfigurationCommand'; +vi.mock('storybook/internal/cli', { spy: true }); vi.mock('storybook/internal/node-logger', { spy: true }); - -vi.mock('storybook/internal/cli', async (actualImport) => ({ - ...(await actualImport()), - AddonVitestService: vi.fn().mockImplementation(() => ({ - installPlaywright: vi.fn().mockResolvedValue({ errors: [] }), - })), -})); - -vi.mock('../../../cli-storybook/src/postinstallAddon', () => ({ - postinstallAddon: vi.fn(), -})); +vi.mock('../../../cli-storybook/src/postinstallAddon', { spy: true }); +vi.mock('../services/TelemetryService', { spy: true }); +vi.mock('../services/VersionService', { spy: true }); describe('AddonConfigurationCommand', () => { let command: AddonConfigurationCommand; @@ -41,16 +36,37 @@ describe('AddonConfigurationCommand', () => { // Mock the AddonVitestService const { AddonVitestService } = await import('storybook/internal/cli'); - mockAddonVitestService = vi.mocked(AddonVitestService as any); + mockAddonVitestService = vi.mocked(AddonVitestService); const mockInstance = { installPlaywright: vi.fn().mockResolvedValue({ errors: [] }), }; mockAddonVitestService.mockImplementation(() => mockInstance as any); - command = new AddonConfigurationCommand(mockPackageManager, { - yes: true, - disableTelemetry: true, - } as any); + vi.mocked(VersionService).mockImplementation(() => ({})); + + vi.mocked(TelemetryService).mockImplementation((disableTelemetry: boolean = false) => { + return { + disableTelemetry, + versionService: new VersionService(), + }; + }); + + const mockAddonVitestServiceInstance = { + installPlaywright: vi.fn().mockResolvedValue({ errors: [] }), + }; + const mockTelemetryServiceInstance = { + trackPlaywrightPromptDecision: vi.fn(), + }; + + command = new AddonConfigurationCommand( + mockPackageManager, + { + yes: true, + disableTelemetry: true, + } as any, + mockAddonVitestServiceInstance as any, + mockTelemetryServiceInstance as any + ); mockTask = { success: vi.fn(), @@ -59,7 +75,7 @@ describe('AddonConfigurationCommand', () => { group: vi.fn(), }; - vi.mocked(prompt.taskLog).mockReturnValue(mockTask); + vi.mocked(prompt.taskLog).mockReturnValue(mockTask as any); vi.mocked(logger.log).mockImplementation(() => {}); vi.clearAllMocks(); diff --git a/code/lib/create-storybook/src/commands/FinalizationCommand.ts b/code/lib/create-storybook/src/commands/FinalizationCommand.ts index 7d3f3aac832c..887851ada2f6 100644 --- a/code/lib/create-storybook/src/commands/FinalizationCommand.ts +++ b/code/lib/create-storybook/src/commands/FinalizationCommand.ts @@ -68,8 +68,10 @@ export class FinalizationCommand { ); this.printNextSteps(storybookCommand); - const logFile = await logTracker.writeToFile(this.logfile); - logger.warn(`Debug logs are written to: ${logFile}`); + try { + const logFile = await logTracker.writeToFile(this.logfile); + logger.warn(`Debug logs are written to: ${logFile}`); + } catch {} } /** Print success message with feature summary */ diff --git a/code/lib/create-storybook/src/commands/GeneratorExecutionCommand.test.ts b/code/lib/create-storybook/src/commands/GeneratorExecutionCommand.test.ts index e3dceeda0120..ce76e45966e1 100644 --- a/code/lib/create-storybook/src/commands/GeneratorExecutionCommand.test.ts +++ b/code/lib/create-storybook/src/commands/GeneratorExecutionCommand.test.ts @@ -43,9 +43,9 @@ describe('GeneratorExecutionCommand', () => { mockAddonService = { getAddonsForFeatures: vi.fn().mockReturnValue([]), }; - vi.mocked(AddonService).mockImplementation( - () => mockAddonService as unknown as InstanceType - ); + vi.mocked(AddonService).mockImplementation(function () { + return mockAddonService; + }); mockPackageManager = { getRunCommand: vi.fn().mockReturnValue('npm run storybook'), } as unknown as JsPackageManager; @@ -71,7 +71,7 @@ describe('GeneratorExecutionCommand', () => { }), }; - vi.mocked(generatorRegistry.get).mockReturnValue(mockGenerator); + vi.mocked(generatorRegistry.get).mockReturnValue(mockGenerator as any); vi.mocked(logger.warn).mockImplementation(() => {}); vi.mocked(baseGenerator).mockResolvedValue({ configDir: '.storybook', diff --git a/code/lib/create-storybook/src/commands/ProjectDetectionCommand.test.ts b/code/lib/create-storybook/src/commands/ProjectDetectionCommand.test.ts index 3bc07e5a2325..6c5ae59f0955 100644 --- a/code/lib/create-storybook/src/commands/ProjectDetectionCommand.test.ts +++ b/code/lib/create-storybook/src/commands/ProjectDetectionCommand.test.ts @@ -11,14 +11,7 @@ import type { CommandOptions } from '../generators/types'; import { ProjectTypeService } from '../services/ProjectTypeService'; import { ProjectDetectionCommand } from './ProjectDetectionCommand'; -vi.mock('storybook/internal/common', async () => { - const actual = await vi.importActual('storybook/internal/common'); - return { - ...actual, - HandledError: class HandledError extends Error {}, - }; -}); - +vi.mock('storybook/internal/common', { spy: true }); vi.mock('storybook/internal/node-logger', { spy: true }); vi.mock('../services/ProjectTypeService', { spy: true }); @@ -45,9 +38,9 @@ describe('ProjectDetectionCommand', () => { detectLanguage: vi.fn().mockResolvedValue(SupportedLanguage.JAVASCRIPT), }; - vi.mocked(ProjectTypeService).mockImplementation( - () => mockProjectTypeService as unknown as InstanceType - ); + vi.mocked(ProjectTypeService).mockImplementation(function () { + return mockProjectTypeService; + }); options = { packageManager: PackageManagerName.NPM, @@ -56,6 +49,16 @@ describe('ProjectDetectionCommand', () => { command = new ProjectDetectionCommand(options, mockPackageManager); + // Mock HandledError constructor + vi.mocked(HandledError).mockImplementation( + class MockHandledError extends Error { + constructor(message: string) { + super(message); + this.name = 'HandledError'; + } + } as any + ); + vi.mocked(logger.step).mockImplementation(() => {}); vi.mocked(logger.error).mockImplementation(() => {}); vi.mocked(logger.debug).mockImplementation(() => {}); diff --git a/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts b/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts index a7ca74bd211f..e69ab8de5924 100644 --- a/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts +++ b/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { ProjectType, globalSettings } from 'storybook/internal/cli'; +import { AddonVitestService, ProjectType, globalSettings } from 'storybook/internal/cli'; import type { JsPackageManager } from 'storybook/internal/common'; import { PackageManagerName, isCI } from 'storybook/internal/common'; import { logger, prompt } from 'storybook/internal/node-logger'; @@ -8,20 +8,15 @@ import type { SupportedBuilder } from 'storybook/internal/types'; import { Feature } from 'storybook/internal/types'; import type { CommandOptions } from '../generators/types'; +import { FeatureCompatibilityService } from '../services/FeatureCompatibilityService'; +import { TelemetryService } from '../services/TelemetryService'; import { UserPreferencesCommand } from './UserPreferencesCommand'; -vi.mock('storybook/internal/cli', async () => { - const actual = await vi.importActual('storybook/internal/cli'); - return { - ...actual, - AddonVitestService: vi.fn().mockImplementation(() => ({ - validateCompatibility: vi.fn(), - })), - globalSettings: vi.fn(), - }; -}); +vi.mock('storybook/internal/cli', { spy: true }); vi.mock('storybook/internal/common', { spy: true }); vi.mock('storybook/internal/node-logger', { spy: true }); +vi.mock('../services/FeatureCompatibilityService', { spy: true }); +vi.mock('../services/TelemetryService', { spy: true }); interface CommandWithPrivates { telemetryService: { @@ -44,6 +39,27 @@ describe('UserPreferencesCommand', () => { command = new UserPreferencesCommand(commandOptions, mockPackageManager); + // Mock AddonVitestService + const mockAddonVitestService = vi.fn().mockImplementation(() => ({ + validateCompatibility: vi.fn().mockResolvedValue({ compatible: true }), + })); + vi.mocked(AddonVitestService).mockImplementation(mockAddonVitestService); + + // Mock FeatureCompatibilityService + vi.mocked(FeatureCompatibilityService).mockImplementation(function () { + return { + validateTestFeatureCompatibility: vi.fn().mockResolvedValue({ compatible: true }), + }; + }); + + // Mock TelemetryService + vi.mocked(TelemetryService).mockImplementation(function () { + return { + trackNewUserCheck: vi.fn(), + trackInstallType: vi.fn(), + }; + }); + // Mock globalSettings const mockSettings = { value: { init: {} }, @@ -61,7 +77,7 @@ describe('UserPreferencesCommand', () => { }; const mockFeatureService = { - validateTestFeatureCompatibility: vi.fn(), + validateTestFeatureCompatibility: vi.fn().mockResolvedValue({ compatible: true }), }; // Inject mocked services @@ -75,12 +91,6 @@ describe('UserPreferencesCommand', () => { vi.mocked(logger.log).mockImplementation(() => {}); vi.mocked(isCI).mockReturnValue(false); - // Default feature validation (compatible) - const featureService = (command as unknown as CommandWithPrivates).featureService; - vi.mocked(featureService.validateTestFeatureCompatibility).mockResolvedValue({ - compatible: true, - }); - vi.clearAllMocks(); }); diff --git a/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts b/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts index 664dffc2ac8d..0301e4966fd7 100644 --- a/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts +++ b/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts @@ -6,24 +6,25 @@ import { SupportedBuilder, SupportedFramework } from 'storybook/internal/types'; import { FeatureCompatibilityService } from './FeatureCompatibilityService'; -vi.mock('storybook/internal/cli', async () => { - const actual = await vi.importActual('storybook/internal/cli'); - return { - ...actual, - AddonVitestService: vi.fn().mockImplementation(() => ({ - validateCompatibility: vi.fn(), - })), - }; -}); +vi.mock('storybook/internal/cli', { spy: true }); describe('FeatureCompatibilityService', () => { let service: FeatureCompatibilityService; const mockPackageManager = { getInstalledVersion: vi.fn(), } as Partial as JsPackageManager; - const mockAddonVitestService = new AddonVitestService(mockPackageManager); + let mockAddonVitestService: AddonVitestService; beforeEach(() => { + // Mock AddonVitestService constructor and methods + const mockValidateCompatibility = vi.fn().mockResolvedValue({ compatible: true }); + vi.mocked(AddonVitestService).mockImplementation(function (this: any) { + return { + validateCompatibility: mockValidateCompatibility, + }; + }); + + mockAddonVitestService = new AddonVitestService(mockPackageManager); service = new FeatureCompatibilityService(mockPackageManager, mockAddonVitestService); }); diff --git a/code/lib/eslint-plugin/vitest-setup.ts b/code/lib/eslint-plugin/vitest-setup.ts new file mode 100644 index 000000000000..293e8d7b4e12 --- /dev/null +++ b/code/lib/eslint-plugin/vitest-setup.ts @@ -0,0 +1,9 @@ +import * as vitest from 'vitest'; + +import { RuleTester } from '@typescript-eslint/rule-tester'; + +// https://typescript-eslint.io/packages/rule-tester/#vitest +RuleTester.afterAll = vitest.afterAll; +RuleTester.it = vitest.it; +RuleTester.itOnly = vitest.it.only; +RuleTester.describe = vitest.describe; diff --git a/code/lib/eslint-plugin/vitest.config.ts b/code/lib/eslint-plugin/vitest.config.ts index 98f25a30aae9..e1acdd2a43e9 100644 --- a/code/lib/eslint-plugin/vitest.config.ts +++ b/code/lib/eslint-plugin/vitest.config.ts @@ -1,15 +1,12 @@ -import { defineConfig } from 'vitest/config'; +import { defineConfig, mergeConfig } from 'vitest/config'; -export default defineConfig({ - test: { - name: 'eslint-plugin-storybook', - include: ['**/*.test.ts'], - environment: 'node', - globals: true, - coverage: { - provider: 'v8', - reporter: ['text', 'lcov'], - exclude: ['dist/**', 'tools/**'], +import { vitestCommonConfig } from '../../vitest.shared'; + +export default mergeConfig( + vitestCommonConfig, + defineConfig({ + test: { + setupFiles: ['./vitest-setup.ts'], }, - }, -}); + }) +); diff --git a/code/package.json b/code/package.json index 3ac071ed38b1..5dc65b2f78cf 100644 --- a/code/package.json +++ b/code/package.json @@ -127,9 +127,10 @@ "@typescript-eslint/parser": "^8.8.1", "@vitejs/plugin-react": "^4.3.2", "@vitejs/plugin-vue": "^4.4.0", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-istanbul": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/browser": "^4.0.14", + "@vitest/browser-playwright": "^4.0.14", + "@vitest/coverage-istanbul": "^4.0.14", + "@vitest/coverage-v8": "^4.0.14", "create-storybook": "workspace:*", "cross-env": "^7.0.3", "danger": "^13.0.4", @@ -182,7 +183,7 @@ "uuid": "^11.1.0", "vite": "^7.0.4", "vite-plugin-inspect": "^11.0.0", - "vitest": "^3.2.4", + "vitest": "^4.0.14", "wait-on": "^8.0.3" }, "dependenciesMeta": { @@ -247,5 +248,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "10.2.0-alpha.3" } diff --git a/code/renderers/react/vitest.setup.ts b/code/renderers/react/vitest.setup.ts index fa0f0cca3da2..162814d94360 100644 --- a/code/renderers/react/vitest.setup.ts +++ b/code/renderers/react/vitest.setup.ts @@ -1,5 +1,3 @@ -import { register } from 'node:module'; - import { beforeEach, vi } from 'vitest'; import { type JsPackageManager, JsPackageManagerFactory } from 'storybook/internal/common'; @@ -18,8 +16,6 @@ vi.mock('node:fs', async () => { return { default: fs, ...fs }; }); -vi.mock('node:module', { spy: true }); - vi.mock(import('./src/componentManifest/utils'), { spy: true }); vi.mock('storybook/internal/common', { spy: true }); vi.mock('empathic/find', { spy: true }); @@ -52,5 +48,4 @@ beforeEach(() => { } throw new Error(`Unable to resolve ${id}`); }); - vi.mocked(register).mockImplementation(() => {}); }); diff --git a/code/vitest-setup.ts b/code/vitest-setup.ts index 8a34809c9e5f..daef00cd6a07 100644 --- a/code/vitest-setup.ts +++ b/code/vitest-setup.ts @@ -32,6 +32,12 @@ const ignoreList = [ (error: any) => error.message.includes(' child must forward its ref to a DOM element.'), (error: any) => error.message.includes('Please ensure the tabIndex prop is passed through.'), + // Vitest only warns about this if the import comes from a file outside of `node_modules`. + // This only occurs locally for us and is safe to ignore. + // It will stop once we start importing from `vitest/browser` instead (not a Vitest 3 compatible change). + // TODO: can be removed in SB11 (when/if we remove Vitest 3 support) + (error: any) => + error.message.includes('tries to load a deprecated "@vitest/browser/context" module.'), ]; const throwMessage = (type: any, message: any) => { diff --git a/code/vitest-storybook.config.mts b/code/vitest-storybook.config.mts deleted file mode 100644 index 2ede258444db..000000000000 --- a/code/vitest-storybook.config.mts +++ /dev/null @@ -1,61 +0,0 @@ -import { defaultExclude, defineProject, mergeConfig } from 'vitest/config'; - -import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; - -import Inspect from 'vite-plugin-inspect'; - -import { vitestCommonConfig } from './vitest.shared'; - -const extraPlugins: any[] = []; -if (process.env.INSPECT === 'true') { - // this plugin assists in inspecting the Storybook Vitest plugin's transformation and sourcemaps - extraPlugins.push( - Inspect({ - outputDir: '.vite-inspect', - build: true, - open: true, - include: ['**/*.stories.*'], - }) - ); -} - -export default mergeConfig( - vitestCommonConfig, - // @ts-expect-error added this because of testNamePattern below - defineProject({ - plugins: [ - storybookTest({ - tags: { - include: ['vitest'], - }, - }), - ...extraPlugins, - ], - test: { - name: 'storybook-ui', - exclude: [ - ...defaultExclude, - 'node_modules/**', - '**/__mockdata__/**', - '**/Zoom.stories.tsx', // expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED - './addons/docs/src/blocks/**', // won't work because of https://github.com/storybookjs/storybook/issues/29783 - ], - // TODO: bring this back once portable stories support storybook/preview-api hooks - // @ts-expect-error this type does not exist but the property does! - testNamePattern: /^(?!.*(UseState)).*$/, - browser: { - enabled: true, - provider: 'playwright', - instances: [ - { - browser: 'chromium', - }, - ], - headless: true, - screenshotFailures: false, - }, - setupFiles: ['./.storybook/storybook.setup.ts'], - environment: 'happy-dom', - }, - }) -); diff --git a/code/vitest.config.storybook.ts b/code/vitest.config.storybook.ts new file mode 100644 index 000000000000..2f4bcdcd3215 --- /dev/null +++ b/code/vitest.config.storybook.ts @@ -0,0 +1,55 @@ +import { defaultExclude, defineProject } from 'vitest/config'; + +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + +import { playwright } from '@vitest/browser-playwright'; +import Inspect from 'vite-plugin-inspect'; + +const extraPlugins: any[] = []; +if (process.env.INSPECT === 'true') { + // this plugin assists in inspecting the Storybook Vitest plugin's transformation and sourcemaps + extraPlugins.push( + Inspect({ + outputDir: '.vite-inspect', + build: true, + open: true, + include: ['**/*.stories.*'], + }) + ); +} + +export default defineProject({ + plugins: [ + storybookTest({ + tags: { + include: ['vitest'], + }, + }), + ...extraPlugins, + ], + test: { + name: 'storybook-ui', + exclude: [ + ...defaultExclude, + 'node_modules/**', + '**/__mockdata__/**', + '**/Zoom.stories.tsx', // expected to fail in Vitest because of fetching /iframe.html to cause ECONNREFUSED + './addons/docs/src/blocks/**', // won't work because of https://github.com/storybookjs/storybook/issues/29783 + ], + // TODO: bring this back once portable stories support storybook/preview-api hooks + testNamePattern: /^(?!.*(UseState)).*$/, + browser: { + enabled: true, + provider: playwright({}), + instances: [ + { + browser: 'chromium', + }, + ], + headless: true, + screenshotFailures: false, + }, + setupFiles: ['./.storybook/storybook.setup.ts'], + environment: 'happy-dom', + }, +}); diff --git a/code/vitest.config.ts b/code/vitest.config.ts index 584e12d93b81..06602a33a802 100644 --- a/code/vitest.config.ts +++ b/code/vitest.config.ts @@ -1,13 +1,26 @@ import { coverageConfigDefaults, defineConfig } from 'vitest/config'; +/** + * CircleCI reports the wrong number of threads to Node.js, so we need to set it manually. Unit + * tests are running with the xlarge resource class, which has 8 vCPUs. + * + * @see https://jahed.dev/2022/11/20/fixing-node-js-multi-threading-on-circleci/ + * @see https://vitest.dev/config/maxworkers.html#maxworkers + * @see https://circleci.com/docs/configuration-reference/#x86 + * @see .circleci/config.yml#L187 + */ +const threadCount = process.env.CI ? (process.platform === 'win32' ? 4 : 7) : undefined; + export default defineConfig({ test: { env: { NODE_ENV: 'test', }, + pool: 'threads', + maxWorkers: threadCount, projects: [ - 'vitest-storybook.config.mts', + 'vitest.config.storybook.ts', 'addons/*/vitest.config.ts', 'frameworks/*/vitest.config.ts', 'lib/*/vitest.config.ts', @@ -18,7 +31,6 @@ export default defineConfig({ ], coverage: { - all: false, provider: 'istanbul', exclude: [ ...coverageConfigDefaults.exclude, diff --git a/code/vitest.shared.ts b/code/vitest.shared.ts index c2709d10593b..f511d3446d3f 100644 --- a/code/vitest.shared.ts +++ b/code/vitest.shared.ts @@ -2,26 +2,8 @@ import { resolve } from 'node:path'; import { defineConfig } from 'vitest/config'; -/** - * CircleCI reports the wrong number of threads to Node.js, so we need to set it manually. Unit - * tests are running with the xlarge resource class, which has 8 vCPUs. - * - * @see https://jahed.dev/2022/11/20/fixing-node-js-multi-threading-on-circleci/ - * @see https://vitest.dev/config/#pooloptions-threads-maxthreads - * @see https://circleci.com/docs/configuration-reference/#x86 - * @see .circleci/config.yml#L214 - */ -const threadCount = process.env.CI ? (process.platform === 'win32' ? 4 : 7) : undefined; - export const vitestCommonConfig = defineConfig({ test: { - pool: 'threads', - poolOptions: { - threads: { - minThreads: threadCount, - maxThreads: threadCount, - }, - }, passWithNoTests: true, clearMocks: true, setupFiles: [resolve(__dirname, './vitest-setup.ts')], diff --git a/docs/get-started/frameworks/angular.mdx b/docs/get-started/frameworks/angular.mdx index 79e5dac188c2..a25d38bd46f8 100644 --- a/docs/get-started/frameworks/angular.mdx +++ b/docs/get-started/frameworks/angular.mdx @@ -6,102 +6,31 @@ sidebar: title: Angular --- -Storybook for Angular is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Angular](https://angular.io/) applications. It includes: +Storybook for Angular is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Angular](https://angular.io/) applications. It uses Angular builders and integrates with [Compodoc](https://compodoc.app/) to provide automatic documentation generation. -* 🧱 Uses Angular builders -* 🎛️ Compodoc integration -* 💫 and more! +## Install -## Requirements +To install Storybook in an existing Angular project, run this command in your project's root directory: -* Angular ≥ 18.0 \< 21.0 -* Webpack ≥ 5.0 + -## Getting started +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -### In a project without Storybook +### Requirements -Follow the prompts after running this command in your Angular project's root directory: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -[More on getting started with Storybook.](../install.mdx) - -### In a project with Storybook - -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -#### Automatic migration - -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/angular`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. - -#### Manual migration - -First, install the framework: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Then, update your `.storybook/main.js|ts` to change the framework property: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Finally, update your `angular.json` to include the Storybook builder: - -```jsonc title="angular.json" -{ - "projects": { - "your-project": { - "architect": { - "storybook": { - "builder": "@storybook/angular:start-storybook", - "options": { - // The path to the storybook config directory - "configDir": ".storybook", - // The build target of your project - "browserTarget": "your-project:build", - // The port you want to start Storybook on - "port": 6006 - // More options available, documented here: - // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json - } - }, - "build-storybook": { - "builder": "@storybook/angular:build-storybook", - "options": { - "configDir": ".storybook", - "browserTarget": "your-project:build", - "outputDir": "dist/storybook/your-project" - // More options available, documented here: - // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json - } - } - } - } - } -} -``` + ## Run Storybook -To run Storybook for a particular project, please run the following: +To run Storybook for a particular project, run the following: ```sh ng run :storybook @@ -115,15 +44,19 @@ ng run :build-storybook You will find the output in the configured `outputDir` (default is `dist/storybook/`). -## Setup Compodoc +## Configure + +To make the most out of Storybook in your Angular project, you can set up Compodoc integration and Storybook [decorators](/docs/writing-stories/decorators/) based on your project needs. + +### Compodoc You can include JSDoc comments above components, directives, and other parts of your Angular code to include documentation for those elements. Compodoc uses these comments to [generate documentation](../../writing-docs/autodocs.mdx) for your application. In Storybook, it is useful to add explanatory comments above `@Inputs` and `@Outputs`, since these are the main elements that Storybook displays in its user interface. The `@Inputs` and `@Outputs` are elements you can interact with in Storybook, such as [controls](../../essentials/controls.mdx). -### Automatic setup +#### Automatic setup When installing Storybook via `npx storybook@latest init`, you can set up Compodoc automatically. -### Manual setup +#### Manual setup If you have already installed Storybook, you can set up Compodoc manually. @@ -187,7 +120,7 @@ const preview: Preview = {}; export default preview; ``` -## `applicationConfig` decorator +### Application-wide providers If your component relies on application-wide providers, like the ones defined by [`BrowserAnimationsModule`](https://angular.dev/api/platform-browser/animations/BrowserAnimationsModule) or any other modules that use the forRoot pattern to provide a [`ModuleWithProviders`](https://angular.dev/api/core/ModuleWithProviders), you can apply the `applicationConfig` [decorator](../../writing-stories/decorators.mdx) to all stories for that component. This will provide them with the [bootstrapApplication function](https://angular.io/guide/standalone-components#configuring-dependency-injection), used to bootstrap the component in Storybook. @@ -230,7 +163,7 @@ export const WithCustomApplicationProvider: Story = { } ``` -## `moduleMetadata` decorator +### Angular dependencies If your component has dependencies on other Angular directives and modules, these can be supplied using the `moduleMetadata` [decorator](../../writing-stories/decorators.mdx) either for all stories of a component or for individual stories. @@ -270,66 +203,104 @@ export const WithCustomProvider: Story = { }; ``` + ## FAQ -### How do I migrate to an Angular Storybook builder? +### How do I manually install the Angular framework? -The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. +The easiest way to install the Angular framework is to run the upgrade command, but you can also set it up manually. First, install the framework: -You can run `npx storybook@latest automigrate` to try letting Storybook detect and automatically fix your configuration. Otherwise, you can follow the next steps to adjust your configuration manually. +{/* prettier-ignore-start */} -#### Do you have only one Angular project in your workspace? + -First, go to your `angular.json` and add `storybook` and `build-storybook` entries in your project's `architect` section, as shown [above](#manual-setup). +{/* prettier-ignore-end */} -Second, adjust your `package.json` script section. Usually, it will look like this: +Then, update your `.storybook/main.js|ts` to change the framework property: -```jsonc title="package.json" -{ - "scripts": { - "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` - "build-storybook": "build-storybook" // or `storybook build` - } -} -``` +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} -Now, you can run Storybook with `ng run :storybook` and build it with `ng run :build-storybook`. Adjust the scripts in your `package.json` accordingly. +Finally, update your `angular.json` to include the Storybook builder: -```json title="package.json" +```jsonc title="angular.json" { - "scripts": { - "storybook": "ng run :storybook", - "build-storybook": "ng run :build-storybook" + "projects": { + "your-project": { + "architect": { + "storybook": { + "builder": "@storybook/angular:start-storybook", + "options": { + // The path to the storybook config directory + "configDir": ".storybook", + // The build target of your project + "browserTarget": "your-project:build", + // The port you want to start Storybook on + "port": 6006 + // More options available, documented here: + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/start-storybook/schema.json + } + }, + "build-storybook": { + "builder": "@storybook/angular:build-storybook", + "options": { + "configDir": ".storybook", + "browserTarget": "your-project:build", + "outputDir": "dist/storybook/your-project" + // More options available, documented here: + // https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular/src/builders/build-storybook/schema.json + } + } + } + } } } ``` - Also, `compodoc` is now built into `@storybook/angular`; you don't have to call it explicitly. If we're running `compodoc` in your `package.json` scripts like this: +### How do I migrate to an Angular Storybook builder? -```json title="package.json" +The Storybook [Angular builder](https://angular.io/guide/glossary#builder) is a way to run Storybook in an Angular workspace. It is a drop-in replacement for running `storybook dev` and `storybook build` directly. + +You can run `npx storybook@latest automigrate` to let Storybook detect and automatically fix your configuration. Otherwise, you can follow the next steps to adjust your configuration manually. + +#### With a single project in your Angular workspace + +Go to your `angular.json` and add `storybook` and `build-storybook` entries in your project's `architect` section, as shown [above](#manual-setup). + +Then, adjust your `package.json` script section, to replace the existing Storybook scripts with the Angular CLI commands: + +```diff title="package.json" { "scripts": { - "docs:json": "compodoc -p tsconfig.json -e json -d ./documentation", - "storybook": "npm run docs:json && start-storybook -p 6006", - "build-storybook": "npm run docs:json && build-storybook" +- "storybook": "start-storybook -p 6006", // or `storybook dev -p 6006` +- "build-storybook": "build-storybook" // or `storybook build` ++ "storybook": "ng run :storybook", - "build-storybook": "ng run :build-storybook" +- "docs:json": "compodoc -p tsconfig.json -e json -d ./documentation", +- "storybook": "npm run docs:json && start-storybook -p 6006", +- "build-storybook": "npm run docs:json && build-storybook" ++ "storybook": "ng run \ No newline at end of file + +## Supported frameworks + + + +## Community-maintained frameworks + +Storybook includes an active community that supports additional frameworks and libraries. These community-maintained frameworks are actively developed and maintained by community contributors. + + \ No newline at end of file diff --git a/docs/get-started/frameworks/nextjs-vite.mdx b/docs/get-started/frameworks/nextjs-vite.mdx index 2d6f8073c518..dfbdf9ac47d7 100644 --- a/docs/get-started/frameworks/nextjs-vite.mdx +++ b/docs/get-started/frameworks/nextjs-vite.mdx @@ -6,137 +6,77 @@ sidebar: title: Next.js (Vite) --- -Storybook for Next.js (Vite) is the **recommended** [framework](../../contribute/framework.mdx) for developing and testing UI components in isolation for [Next.js](https://nextjs.org/) applications. It uses [Vite](https://vitejs.dev/) for faster builds and better performance. It includes: +Storybook for Next.js (Vite) is the **recommended** [framework](../../contribute/framework.mdx) for developing and testing UI components in isolation for [Next.js](https://nextjs.org/) applications. It uses [Vite](https://vitejs.dev/) for faster builds, better performance and [Storybook Testing](https://storybook.js.org/docs/writing-tests) support. -* 🔀 Routing -* 🖼 Image optimization -* ⤵️ Absolute imports -* 🎨 Styling -* ⚡ Vite-powered builds -* 💫 and more! +## Install -This Vite-based framework offers several advantages over the Webpack-based [`@storybook/nextjs`](./nextjs.mdx) framework: +To install Storybook in an existing Next.js project, run this command in your project's root directory: -* ⚡ **Faster builds** - Vite's build system is significantly faster than Webpack -* 🔧 **Modern tooling** - Uses the latest build tools and optimizations -* 🧪 **Better test support** - Full support for the [Vitest addon](../../writing-tests/integrations/vitest-addon/index.mdx) and other testing features -* 📦 **Simpler configuration** - No need for Babel or complex Webpack configurations -* 🎯 **Better development experience** - Faster HMR (Hot Module Replacement) and dev server startup - -## Requirements + -* Next.js ≥ 14.1 +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -## Getting started -### In a project without Storybook +### Requirements -When you run `storybook init` in your Next.js project, Storybook will automatically detect your project and select the `@storybook/nextjs-vite` framework **unless** your project has custom Webpack or Babel configurations that may be incompatible with Vite. + -Follow the prompts after running this command in your Next.js project's root directory: +## Choose between Vite and Webpack -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -[More on getting started with Storybook.](../install.mdx) - - - -If your project has a custom `webpack.config.js` or `.babelrc` file, `storybook init` will prompt you to choose between: - -- **`@storybook/nextjs-vite`** (recommended) - Faster, more modern, supports latest testing features -- **`@storybook/nextjs`** (Webpack 5) - Better compatibility with custom Webpack/Babel configurations - -Choose `nextjs-vite` if you're willing to migrate your custom configurations to Vite. Choose `nextjs` (Webpack 5) if you need to keep your existing Webpack/Babel setup. - - +This Vite-based framework offers several advantages over the Webpack-based [`@storybook/nextjs`](./nextjs.mdx) framework, and is the recommended option: -### In a project with Storybook - -This framework is designed to work with Storybook 10+. If you're not already using v10, upgrade with this command: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -#### Automatic migration - -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/nextjs-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. - -You can also use the [`nextjs-to-nextjs-vite` automigration](#migrating-from-webpack) to migrate from the Webpack-based `@storybook/nextjs` framework to this Vite-based framework. - -#### Manual migration - -First, install the framework: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Then, update your `.storybook/main.js|ts` to change the framework property: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - - - -If your Storybook configuration contains custom Webpack operations in [`webpackFinal`](../../api/main-config/main-config-webpack-final.mdx), you will likely need to create equivalents in [`viteFinal`](../../api/main-config/main-config-vite-final.mdx). - -For more information, see the [Vite builder documentation](../../builders/vite.mdx#migrating-from-webpack). - - - -Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: +* ⚡ **Faster builds** - Vite's build system is significantly faster than Webpack +* 🔧 **Modern tooling** - Uses the latest build tools and optimizations +* 🧪 **Better test support** - Full support for the [Vitest addon](../../writing-tests/integrations/vitest-addon/index.mdx) and other testing features +* 📦 **Simpler configuration** - No need for Babel or complex Webpack configurations +* 🎯 **Better development experience** - Faster Hot Module Replacement and dev server startup -{/* prettier-ignore-start */} +Storybook will automatically detect your project and select the `nextjs-vite` framework **unless** your project has custom Webpack or Babel configurations. If you have custom configurations, Storybook will ask you which framework to install. - +Choose `nextjs-vite` if you're willing to migrate existing Babel or Webpack configurations to Vite. Choose `nextjs` (Webpack 5) if you need to keep your existing Webpack/Babel setup. -{/* prettier-ignore-end */} -#### Migrating from Webpack +## Run Storybook -Storybook provides a migration tool for migrating to this framework from the Webpack-based Next.js framework, `@storybook/nextjs`. To migrate, run this command: +To run Storybook for a particular project, run the following: -```bash -npx storybook automigrate nextjs-to-nextjs-vite -``` + -This automigration tool performs the following actions: +To build Storybook, run: -1. Updates `package.json` files to replace `@storybook/nextjs` with `@storybook/nextjs-vite` -2. Updates `.storybook/main.js|ts` to change the framework property -3. Scans and updates import statements in your story files and configuration files + - +You will find the output in the configured `outputDir` (default is `storybook-static`). -If your project has custom Webpack configurations in `.storybook/main.js|ts` (via `webpackFinal`), you'll need to migrate those to Vite configuration (via `viteFinal`) after running this automigration. See the [Vite builder documentation](../builders/vite.mdx#migrating-from-webpack) for more information. - -## Run the Setup Wizard -If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../../essentials/controls.mdx). +## Configure -![Storybook onboarding](../../_assets/get-started/example-onboarding-wizard.png) +Storybook for Next.js with Vite supports many Next.js features including: -If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. +* 🖼 [Image optimization](#nextjss-image-component) +* 🔤 [Font optimization](#nextjs-font-optimization) +* 🔀 [Routing and navigation](#nextjs-routing) +* 🌐 [`next/head`](#nextjs-head) +* ⤵️ [Absolute imports](#imports) +* 🎨 [Styling](#styling) +* 🎭 [Module mocking](#mocking-modules) +* ☁️ [React Server Component (experimental)](#react-server-components-rsc) -## Next.js's Image component +### Next.js's Image component This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/pages/api-reference/components/image) with no configuration. -### Local images +#### Local images [Local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) are supported. @@ -162,7 +102,7 @@ function Home() { } ``` -### Remote images +#### Remote images [Remote images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#remote-images) are also supported. @@ -180,15 +120,15 @@ export default function Home() { } ``` -## Next.js font optimization +### Next.js font optimization [next/font](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) is partially supported in Storybook. The packages `next/font/google` and `next/font/local` are supported. -### `next/font/google` +#### `next/font/google` You don't have to do anything. `next/font/google` is supported out of the box. -### `next/font/local` +#### `next/font/local` For local fonts you have to define the [src](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) property. The path is relative to the directory where the font loader function is called. @@ -203,7 +143,7 @@ const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' }); The Vite-based framework automatically handles font path mapping, so you don't need to configure `staticDirs` for fonts like you would with the Webpack-based framework. -### Not supported features of `next/font` +#### Not supported features of `next/font` The following features are not supported (yet). Support for these features might be planned for the future: @@ -213,7 +153,7 @@ The following features are not supported (yet). Support for these features might * [preload](https://nextjs.org/docs/pages/api-reference/components/font#preload) option gets ignored. Storybook handles Font loading its own way. * [display](https://nextjs.org/docs/pages/api-reference/components/font#display) option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly. -### Mocking fonts during testing +#### Mocking fonts during testing Occasionally fetching fonts from Google may fail as part of your Storybook build step. It is highly recommended to mock these requests, as those failures can cause your pipeline to fail as well. Next.js [supports mocking fonts](https://github.com/vercel/next.js/blob/725ddc7371f80cca273779d37f961c3e20356f95/packages/font/src/google/fetch-css-from-google-fonts.ts#L36) via a JavaScript module located where the env var `NEXT_FONT_GOOGLE_MOCKED_RESPONSES` references. @@ -257,7 +197,7 @@ module.exports = { }; ``` -## Next.js routing +### Next.js routing [Next.js's router](https://nextjs.org/docs/pages/building-your-application/routing) is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the [Actions panel](../../essentials/actions.mdx). @@ -267,7 +207,7 @@ You should only use `next/router` in the `pages` directory. In the `app` directo -### Overriding defaults +#### Overriding defaults Per-story overrides can be done by adding a `nextjs.router` property onto the story [parameters](../../writing-stories/parameters.mdx). The framework will shallowly merge whatever you put here into the router. @@ -283,7 +223,7 @@ These overrides can also be applied to [all stories for a component](../../api/p -### Default router +#### Default router The default values on the stubbed router are as follows (see [globals](../../essentials/toolbars-and-globals.mdx#globals) for more details on how globals work). @@ -332,7 +272,7 @@ const preview: Preview = { }; ``` -## Next.js navigation +### Next.js navigation @@ -340,7 +280,7 @@ Please note that [`next/navigation`](https://nextjs.org/docs/app/building-your-a -### Set `nextjs.appDirectory` to `true` +#### Set `nextjs.appDirectory` to `true` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true` in for that component's stories: @@ -358,7 +298,7 @@ If your Next.js project uses the `app` directory for every page (in other words, {/* prettier-ignore-end */} -### Overriding defaults +#### Overriding defaults Per-story overrides can be done by adding a `nextjs.navigation` property onto the story [parameters](../../writing-stories/parameters.mdx). The framework will shallowly merge whatever you put here into the router. @@ -374,7 +314,7 @@ These overrides can also be applied to [all stories for a component](../../api/p -### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks +#### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks The `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks are supported in Storybook. You have to set the `nextjs.navigation.segments` parameter to return the segments or the params you want to use. @@ -426,7 +366,7 @@ These overrides can also be applied to [a single story](../../api/parameters.mdx The default value of `nextjs.navigation.segments` is `[]` if not set. -### Default navigation context +#### Default navigation context The default values on the stubbed navigation context are as follows: @@ -466,11 +406,13 @@ const preview: Preview = { }; ``` -## Next.js Head +### Next.js Head [`next/head`](https://nextjs.org/docs/pages/api-reference/components/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head `children` are placed into the head element of the iframe that Storybook uses to render your stories. -## Sass/Scss +### Styling + +#### Sass/Scss [Global Sass/Scss stylesheets](https://nextjs.org/docs/pages/building-your-application/styling/sass) are supported without any additional configuration as well. Just import them into [`.storybook/preview.js|ts`](../../configure/index.mdx#configure-story-rendering) @@ -491,7 +433,7 @@ export default { }; ``` -## CSS/Sass/Scss Modules +#### CSS/Sass/Scss Modules [CSS modules](https://nextjs.org/docs/pages/building-your-application/styling/css-modules) work as expected. @@ -511,13 +453,14 @@ export function Button() { } ``` -## PostCSS +#### PostCSS Next.js lets you [customize PostCSS config](https://nextjs.org/docs/pages/building-your-application/configuring/post-css). Thus this framework will automatically handle your PostCSS config for you. This allows for cool things like zero-config Tailwind! (See [Next.js' example](https://github.com/vercel/next.js/tree/canary/packages/create-next-app/templates/default-tw)) -## Absolute imports +### Imports +#### Absolute imports [Absolute imports](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases#absolute-imports) from the root directory are supported. @@ -551,7 +494,7 @@ Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules -## Module aliases +#### Module aliases [Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported. @@ -571,7 +514,7 @@ export default function HomePage() { } ``` -## Subpath imports +#### Subpath imports As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules). @@ -607,11 +550,11 @@ export default function HomePage() { } ``` -## Mocking modules +### Mocking modules Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx) to control and assert their behavior. -### Built-in mocked modules +#### Built-in mocked modules This framework provides mocks for many of Next.js' internal modules: @@ -620,11 +563,11 @@ This framework provides mocks for many of Next.js' internal modules: 3. [`@storybook/nextjs-vite/navigation.mock`](#storybooknextjs-vitenavigationmock) 4. [`@storybook/nextjs-vite/router.mock`](#storybooknextjs-viteroutermock) -### Mocking other modules +#### Mocking other modules To mock other modules, use [automocking](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx#automocking) or one of the [alternative methods](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx#alternative-methods) documented in the mocking modules guide. -## Runtime config +### Runtime config Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime. @@ -658,13 +601,13 @@ Calls to `getConfig` would return the following object when called within Storyb } ``` -## Custom Vite configuration +### Custom Vite configuration You can customize the [Vite configuration](../../builders/vite.mdx#configuration) used by Storybook in your `.storybook/main.js|ts` file. By default, Storybook's configuration extends the Vite configuration used by your project, but you can configure it to not do so. Not all Vite modifications are copy/paste-able between `next.config.js` and `.storybook/main.js|ts`. It is recommended to do your research on how to properly make your modification to Storybook's Vite config and on how [Vite works](https://vitejs.dev/guide/). -## Typescript +### Typescript Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box. @@ -679,7 +622,7 @@ Storybook handles most [Typescript](https://www.typescriptlang.org/) configurati } ``` -## React Server Components (RSC) +### React Server Components (RSC) (⚠️ **Experimental**) @@ -711,6 +654,65 @@ In the future we will provide better mocking support in Storybook and support fo ## FAQ + +### How do I migrate from the `nextjs` (Webpack 5) addon? + +#### Automatic migration + +Storybook provides a migration tool for migrating to this framework from the Webpack-based Next.js framework, [`@storybook/nextjs`](./nextjs.mdx). To migrate, run this command: + +```bash +npx storybook automigrate nextjs-to-nextjs-vite +``` + +This automigration tool performs the following actions: + +1. Updates `package.json` files to replace `@storybook/nextjs` with `@storybook/nextjs-vite` +2. Updates `.storybook/main.js|ts` to change the framework property +3. Scans and updates import statements in your story files and configuration files + + + +If your project has custom Webpack configurations in `.storybook/main.js|ts` (via `webpackFinal`), you'll need to migrate those to Vite configuration (via `viteFinal`) after running this automigration. See the [Vite builder documentation](../builders/vite.mdx#migrating-from-webpack) for more information. + + + +#### Manual migration + +First, install the framework: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Then, update your `.storybook/main.js|ts` to change the framework property: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + + + +If your Storybook configuration contains custom Webpack operations in [`webpackFinal`](../../api/main-config/main-config-webpack-final.mdx), you will likely need to create equivalents in [`viteFinal`](../../api/main-config/main-config-vite-final.mdx). + +For more information, see the [Vite builder documentation](../../builders/vite.mdx#migrating-from-webpack). + + + +Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + + + ### Stories for pages/components which fetch data Next.js pages can fetch data directly within server components in the `app` directory, which often include module imports that only run in a node environment. This does not (currently) work within Storybook, because if you import from a Next.js page file containing those node module imports in your stories, your Storybook's Vite build will crash because those modules will not run in a browser. To get around this, you can extract the component in your page file into a separate file and import that pure component in your stories. Or, if that's not feasible for some reason, you can [configure Vite to handle those modules](https://vitejs.dev/config/dep-optimization-options.html#optimizedeps-exclude) in your Storybook's [`viteFinal` configuration](../../builders/vite.mdx#configuration). diff --git a/docs/get-started/frameworks/nextjs.mdx b/docs/get-started/frameworks/nextjs.mdx index 01e583d9b668..78e4ed7aeb6d 100644 --- a/docs/get-started/frameworks/nextjs.mdx +++ b/docs/get-started/frameworks/nextjs.mdx @@ -6,14 +6,8 @@ sidebar: title: Next.js (Webpack) --- -Storybook for Next.js (Webpack) is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Next.js](https://nextjs.org/) applications using Webpack 5. It includes: +Storybook for Next.js (Webpack) is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Next.js](https://nextjs.org/) applications using [Webpack 5](https://webpack.js.org/). -* 🔀 Routing -* 🖼 Image optimization -* ⤵️ Absolute imports -* 🎨 Styling -* 🎛 Webpack & Babel config -* 💫 and more! @@ -23,99 +17,74 @@ Use this Webpack-based framework (`@storybook/nextjs`) only if: - Your project has custom Webpack configurations that are incompatible with Vite - Your project has custom Babel configurations that require Webpack - You need specific Webpack features not available in Vite - -## Requirements - -* Next.js ≥ 14.1 +## Install -## Getting started +To install Storybook in an existing Next.js project, run this command in your project's root directory: -### In a project without Storybook + -When you run `storybook init` in your Next.js project, Storybook will automatically detect your project and select the [`@storybook/nextjs-vite`](./nextjs-vite.mdx) framework (which we recommend) unless your project has custom Webpack or Babel configurations that may be incompatible with Vite. +The command will prompt you to choose between this framework and [`@storybook/nextjs-vite`](./nextjs-vite.mdx). We recommend the Vite-based framework ([learn why](./nextjs-vite.mdx#choose-between-vite-and-webpack)). +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -
-If your project has custom Webpack/Babel configurations -If `storybook init` detects a custom Webpack configuration in your Next.js config file or `.babelrc` file, it will prompt you to choose between: +### Requirements -- **`@storybook/nextjs-vite`** (recommended) - Faster, more modern, supports latest testing features, but may be incompatible with your custom Webpack/Babel config -- **`@storybook/nextjs`** (Webpack 5) - Better compatibility with custom Webpack/Babel configurations + -Choose `@storybook/nextjs` (Webpack 5) if you need to keep your existing Webpack/Babel setup and cannot migrate to Vite. +## Run Storybook -
+To run Storybook for a particular project, run the following: -Follow the prompts after running this command in your Next.js project's root directory: - -{/* prettier-ignore-start */} + - +To build Storybook, run: -{/* prettier-ignore-end */} + -[More on getting started with Storybook.](../install.mdx) +You will find the output in the configured `outputDir` (default is `storybook-static`). -### In a project with Storybook -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: -{/* prettier-ignore-start */} - +## Configure -{/* prettier-ignore-end */} +Storybook for Next.js with Vite supports many Next.js features including: -#### Automatic migration +* 🖼 [Image optimization](#nextjss-image-component) +* 🔤 [Font optimization](#nextjs-font-optimization) +* 🔀 [Routing and navigation](#nextjs-routing) +* 🌐 [`next/head`](#nextjs-head) +* ⤵️ [Absolute imports](#imports) +* 🎨 [Styling](#styling) +* 🎭 [Module mocking](#mocking-modules) +* ☁️ [React Server Component (experimental)](#react-server-components-rsc) -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/nextjs`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. -#### Manual migration -First, install the framework: -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Then, update your `.storybook/main.js|ts` to change the framework property: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} -#### Migrating to Vite -Please refer to the [migration instructions for `@storybook/nextjs-vite`](./nextjs-vite.mdx#migrating-from-webpack). -## Run the Setup Wizard -If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../../essentials/controls.mdx). -![Storybook onboarding](../../_assets/get-started/example-onboarding-wizard.png) -If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. -## Next.js's Image component +### Next.js's Image component This framework allows you to use Next.js's [next/image](https://nextjs.org/docs/pages/api-reference/components/image) with no configuration. -### Local images +#### Local images [Local images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#local-images) are supported. @@ -141,7 +110,7 @@ function Home() { } ``` -### Remote images +#### Remote images [Remote images](https://nextjs.org/docs/pages/building-your-application/optimizing/images#remote-images) are also supported. @@ -159,15 +128,15 @@ export default function Home() { } ``` -## Next.js font optimization +### Next.js font optimization [next/font](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) is partially supported in Storybook. The packages `next/font/google` and `next/font/local` are supported. -### `next/font/google` +#### `next/font/google` You don't have to do anything. `next/font/google` is supported out of the box. -### `next/font/local` +#### `next/font/local` For local fonts you have to define the [src](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#local-fonts) property. The path is relative to the directory where the font loader function is called. @@ -180,7 +149,7 @@ import localFont from 'next/font/local'; const localRubikStorm = localFont({ src: './fonts/RubikStorm-Regular.ttf' }); ``` -#### `staticDir` mapping +##### `staticDir` mapping You have to tell Storybook where the `fonts` directory is located, via the [`staticDirs` configuration](../../api/main-config/main-config-static-dirs.mdx#with-configuration-objects). The `from` value is relative to the `.storybook` directory. The `to` value is relative to the execution context of Storybook. Very likely it is the root of your project. @@ -190,7 +159,7 @@ You have to tell Storybook where the `fonts` directory is located, via the [`sta {/* prettier-ignore-end */} -### Not supported features of `next/font` +#### Not supported features of `next/font` The following features are not supported (yet). Support for these features might be planned for the future: @@ -200,7 +169,7 @@ The following features are not supported (yet). Support for these features might * [preload](https://nextjs.org/docs/pages/api-reference/components/font#preload) option gets ignored. Storybook handles Font loading its own way. * [display](https://nextjs.org/docs/pages/api-reference/components/font#display) option gets ignored. All fonts are loaded with display set to "block" to make Storybook load the font properly. -### Mocking fonts during testing +#### Mocking fonts during testing Occasionally fetching fonts from Google may fail as part of your Storybook build step. It is highly recommended to mock these requests, as those failures can cause your pipeline to fail as well. Next.js [supports mocking fonts](https://github.com/vercel/next.js/blob/725ddc7371f80cca273779d37f961c3e20356f95/packages/font/src/google/fetch-css-from-google-fonts.ts#L36) via a JavaScript module located where the env var `NEXT_FONT_GOOGLE_MOCKED_RESPONSES` references. @@ -244,7 +213,7 @@ module.exports = { }; ``` -## Next.js routing +### Next.js routing [Next.js's router](https://nextjs.org/docs/pages/building-your-application/routing) is automatically stubbed for you so that when the router is interacted with, all of its interactions are automatically logged to the [Actions panel](../../essentials/actions.mdx). @@ -252,7 +221,7 @@ module.exports = { You should only use `next/router` in the `pages` directory. In the `app` directory, it is necessary to use `next/navigation`. -### Overriding defaults +#### Overriding defaults Per-story overrides can be done by adding a `nextjs.router` property onto the story [parameters](../../writing-stories/parameters.mdx). The framework will shallowly merge whatever you put here into the router. @@ -266,7 +235,7 @@ Per-story overrides can be done by adding a `nextjs.router` property onto the st These overrides can also be applied to [all stories for a component](../../api/parameters.mdx#meta-parameters) or [all stories in your project](../../api/parameters.mdx#project-parameters). Standard [parameter inheritance](../../api/parameters.mdx#parameter-inheritance) rules apply. -### Default router +#### Default router The default values on the stubbed router are as follows (see [globals](../../essentials/toolbars-and-globals.mdx#globals) for more details on how globals work). @@ -315,13 +284,13 @@ const preview: Preview = { }; ``` -## Next.js navigation +### Next.js navigation Please note that [`next/navigation`](https://nextjs.org/docs/app/building-your-application/routing) can only be used in components/pages in the `app` directory. -### Set `nextjs.appDirectory` to `true` +#### Set `nextjs.appDirectory` to `true` If your story imports components that use `next/navigation`, you need to set the parameter `nextjs.appDirectory` to `true` in for that component's stories: @@ -339,7 +308,7 @@ If your Next.js project uses the `app` directory for every page (in other words, {/* prettier-ignore-end */} -### Overriding defaults +#### Overriding defaults Per-story overrides can be done by adding a `nextjs.navigation` property onto the story [parameters](../../writing-stories/parameters.mdx). The framework will shallowly merge whatever you put here into the router. @@ -353,7 +322,7 @@ Per-story overrides can be done by adding a `nextjs.navigation` property onto th These overrides can also be applied to [all stories for a component](../../api/parameters.mdx#meta-parameters) or [all stories in your project](../../api/parameters.mdx#project-parameters). Standard [parameter inheritance](../../api/parameters.mdx#parameter-inheritance) rules apply. -### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks +#### `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks The `useSelectedLayoutSegment`, `useSelectedLayoutSegments`, and `useParams` hooks are supported in Storybook. You have to set the `nextjs.navigation.segments` parameter to return the segments or the params you want to use. @@ -403,7 +372,7 @@ export default function ParamsBasedComponent() { The default value of `nextjs.navigation.segments` is `[]` if not set. -### Default navigation context +#### Default navigation context The default values on the stubbed navigation context are as follows: @@ -443,11 +412,13 @@ const preview: Preview = { }; ``` -## Next.js Head +### Next.js Head [`next/head`](https://nextjs.org/docs/pages/api-reference/components/head) is supported out of the box. You can use it in your stories like you would in your Next.js application. Please keep in mind, that the Head `children` are placed into the head element of the iframe that Storybook uses to render your stories. -## Sass/Scss +### Styling + +#### Sass/Scss [Global Sass/Scss stylesheets](https://nextjs.org/docs/pages/building-your-application/styling/sass) are supported without any additional configuration as well. Just import them into [`.storybook/preview.js|ts`](../../configure/index.mdx#configure-story-rendering) @@ -468,7 +439,7 @@ export default { }; ``` -## CSS/Sass/Scss Modules +#### CSS/Sass/Scss Modules [CSS modules](https://nextjs.org/docs/pages/building-your-application/styling/css-modules) work as expected. @@ -488,7 +459,7 @@ export function Button() { } ``` -## Styled JSX +#### Styled JSX The built in CSS-in-JS solution for Next.js is [styled-jsx](https://nextjs.org/docs/pages/building-your-application/styling/css-in-js), and this framework supports that out of the box too, zero config. @@ -542,13 +513,14 @@ You can use your own babel config too. This is an example of how you can customi } ``` -## PostCSS +#### PostCSS Next.js lets you [customize PostCSS config](https://nextjs.org/docs/pages/building-your-application/configuring/post-css). Thus this framework will automatically handle your PostCSS config for you. This allows for cool things like zero-config Tailwind! (See [Next.js' example](https://github.com/vercel/next.js/tree/canary/packages/create-next-app/templates/default-tw)) -## Absolute imports +### Imports +#### Absolute imports [Absolute imports](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases#absolute-imports) from the root directory are supported. @@ -580,7 +552,7 @@ import 'styles/globals.scss'; Absolute imports **cannot** be mocked in stories/tests. See the [Mocking modules](#mocking-modules) section for more information. -## Module aliases +#### Module aliases [Module aliases](https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases#module-aliases) are also supported. @@ -600,7 +572,7 @@ export default function HomePage() { } ``` -## Subpath imports +#### Subpath imports As an alternative to [module aliases](#module-aliases), you can use [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) to import modules. This follows Node package standards and has benefits when [mocking modules](#mocking-modules). @@ -634,11 +606,11 @@ export default function HomePage() { } ``` -## Mocking modules +### Mocking modules Components often depend on modules that are imported into the component file. These can be from external packages or internal to your project. When rendering those components in Storybook or testing them, you may want to [mock those modules](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx) to control and assert their behavior. -### Built-in mocked modules +#### Built-in mocked modules This framework provides mocks for many of Next.js' internal modules: @@ -647,11 +619,11 @@ This framework provides mocks for many of Next.js' internal modules: 3. [`@storybook/nextjs/navigation.mock`](#storybooknextjsnavigationmock) 4. [`@storybook/nextjs/router.mock`](#storybooknextjsroutermock) -### Mocking other modules +#### Mocking other modules To mock other modules, use [automocking](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx#automocking) or one of the [alternative methods](../../writing-stories/mocking-data-and-modules/mocking-modules.mdx#alternative-methods) documented in the mocking modules guide. -## Runtime config +### Runtime config Next.js allows for [Runtime Configuration](https://nextjs.org/docs/pages/api-reference/next-config-js/runtime-configuration) which lets you import a handy `getConfig` function to get certain configuration defined in your `next.config.js` file at runtime. @@ -685,7 +657,7 @@ Calls to `getConfig` would return the following object when called within Storyb } ``` -## Custom Webpack config +### Custom Webpack config Next.js comes with a lot of things for free out of the box like Sass support, but sometimes you add [custom Webpack config modifications to Next.js](https://nextjs.org/docs/pages/api-reference/next-config-js/webpack). This framework takes care of most of the Webpack modifications you would want to add. If Next.js supports a feature out of the box, then that feature will work out of the box in Storybook. If Next.js doesn't support something out of the box, but makes it easy to configure, then this framework will do the same for that thing for Storybook. @@ -701,7 +673,7 @@ Below is an example of how to add SVGR support to Storybook with this framework. {/* prettier-ignore-end */} -## Typescript +### Typescript Storybook handles most [Typescript](https://www.typescriptlang.org/) configurations, but this framework adds additional support for Next.js's support for [Absolute Imports and Module path aliases](https://nextjs.org/docs/pages/building-your-application/configuring/absolute-imports-and-module-aliases). In short, it takes into account your `tsconfig.json`'s [baseUrl](https://www.typescriptlang.org/tsconfig#baseUrl) and [paths](https://www.typescriptlang.org/tsconfig#paths). Thus, a `tsconfig.json` like the one below would work out of the box. @@ -716,7 +688,7 @@ Storybook handles most [Typescript](https://www.typescriptlang.org/) configurati } ``` -## React Server Components (RSC) +### React Server Components (RSC) (⚠️ **Experimental**) @@ -765,6 +737,36 @@ This is because those versions of Yarn have different package resolution rules t ## FAQ +### How do I manually install the Next.js framework? + +First, install the framework: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Then, update your `.storybook/main.js|ts` to change the framework property: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Finally, if you were using Storybook plugins to integrate with Next.js, those are no longer necessary when using this framework and can be removed: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +### How do I migrate to the Next.js Vite framework? + +Please refer to the [migration instructions for `@storybook/nextjs-vite`](./nextjs-vite.mdx#how-do-i-migrate-from-the-nextjs-webpack-5-addon). + ### Stories for pages/components which fetch data Next.js pages can fetch data directly within server components in the `app` directory, which often include module imports that only run in a node environment. This does not (currently) work within Storybook, because if you import from a Next.js page file containing those node module imports in your stories, your Storybook's Webpack will crash because those modules will not run in a browser. To get around this, you can extract the component in your page file into a separate file and import that pure component in your stories. Or, if that's not feasible for some reason, you can [polyfill those modules](https://webpack.js.org/configuration/node/) in your Storybook's [`webpackFinal` configuration](../../builders/webpack.mdx#extending-storybooks-webpack-config). diff --git a/docs/get-started/frameworks/preact-vite.mdx b/docs/get-started/frameworks/preact-vite.mdx index 0446cb48d8d0..7f9e21a53db2 100644 --- a/docs/get-started/frameworks/preact-vite.mdx +++ b/docs/get-started/frameworks/preact-vite.mdx @@ -6,46 +6,44 @@ sidebar: title: Preact (Vite) --- -Storybook for Preact & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Preact](https://preactjs.com/) applications built with [Vite](https://vitejs.dev/). It includes: +Storybook for Preact & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Preact](https://preactjs.com/) applications built with [Vite](https://vitejs.dev/). -* 🏎️ Pre-bundled for performance -* 🪄 Zero config -* 💫 and more! +## Install -## Requirements +To install Storybook in an existing Preact project, run this command in your project's root directory: -* Preact 8.x || 10.x -* Vite ≥ 5.0 + -## Getting started +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -### In a project without Storybook +### Requirements -Follow the prompts after running this command in your Preact project's root directory: + -{/* prettier-ignore-start */} - - -{/* prettier-ignore-end */} +## Run Storybook -[More on getting started with Storybook.](../install.mdx) +To run Storybook for a particular project, run the following: -### In a project with Storybook + -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: +To build Storybook, run: -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} + -#### Automatic migration +You will find the output in the configured `outputDir` (default is `storybook-static`). -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/preact-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. -#### Manual migration +## FAQ +### How do I manually install the Preact framework? First, install the framework: diff --git a/docs/get-started/frameworks/react-native-web-vite.mdx b/docs/get-started/frameworks/react-native-web-vite.mdx index 8d1acd47b1ca..7017c450e94f 100644 --- a/docs/get-started/frameworks/react-native-web-vite.mdx +++ b/docs/get-started/frameworks/react-native-web-vite.mdx @@ -6,101 +6,55 @@ sidebar: title: React Native Web --- -Storybook for React Native Web is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [React Native](https://reactnative.dev/) applications. It uses [Vite](https://vitejs.dev/) to build your components for web browsers. The framework includes: - -* ⚛️ React Native components -* 🧑‍💻 Shareable on the web -* 🪄 Zero config -* 💫 and more! +Storybook for React Native Web is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [React Native](https://reactnative.dev/). It uses [Vite](https://vitejs.dev/) to build your components for web browsers. In addition to React Native Web, Storybook supports on-device [React Native](https://github.com/storybookjs/react-native) development. If you're unsure what's right for you, read our [comparison](#react-native-vs-react-native-web). -## Requirements -* React-Native ≥ 0.72 -* React-Native-Web ≥ 0.19 -* Vite ≥ 5.0 -## Getting started -### In a project without Storybook -When you run `storybook init` in your React Native project, Storybook will detect your project and present you with a prompt to choose which type of Storybook you want to install: +## Install -**React Native options:** +To install Storybook in an existing React Native project, run this command in your project's root directory: -- **"React Native"**: Storybook on your device/simulator - - High-fidelity, runs in your actual React Native application - - Limited feature set compared to web version - - Best for projects that rely on native modules or device-specific features + -- **"React Native Web"**: Storybook on web for docs, test, and sharing - - Feature-rich web-based Storybook - - Full support for documentation, testing, and accessibility features - - Best for projects that want web-based sharing and testing +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -- **"Both"**: Add both native and web Storybooks - - Installs and configures both environments - - Allows you to run Storybook for both native and web in the same project - - Best for projects that want both native fidelity and web features -Follow the prompts after running this command in your React Native project's root directory: +### Requirements -{/* prettier-ignore-start */} + - -{/* prettier-ignore-end */} +## Run Storybook -[More on getting started with Storybook.](../install.mdx) +To run Storybook for a particular project, run the following: -### In a project with Storybook `addon-react-native-web` + -The [React Native Web addon](https://github.com/storybookjs/addon-react-native-web) was a Webpack-based precursor to the React Native Web Vite framework (i.e., `@storybook/react-native-web-vite`). If you're using the addon, you should migrate to the framework, which is faster, more stable, maintained, and better documented. To do so, follow the steps below. +To build Storybook, run: -Run the following command to upgrade Storybook to the latest version: + -{/* prettier-ignore-start */} +You will find the output in the configured `outputDir` (default is `storybook-static`). - -{/* prettier-ignore-end */} - - - This framework is designed to work with Storybook 8.5 and above for the best experience. We won't be able to provide support if you're using an older Storybook version. - - -Install the framework and its peer dependencies: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Update your `.storybook/main.js|ts` to change the framework property and remove the `@storybook/addon-react-native-web` addon: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Finally, remove the addon and similar packages (i.e., `@storybook/react-webpack5` and `@storybook/addon-react-native-web`) from your project. - -### In a project with Storybook `react-native` - -[Storybook for React Native](https://github.com/storybookjs/react-native) is a framework that runs in a simulator or on your mobile device. It's possible to run React Native Web alongside React Native, but we are still working on a seamless integration. In the meantime, we recommend running one or the other. If you need help figuring out what's right for you, read our [comparison](#react-native-vs-react-native-web). - -## Run the Setup Wizard - -If all goes well, you should see a setup wizard that will help you get started with Storybook. The wizard will introduce you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../../essentials/controls.mdx). - -![Storybook onboarding](../../_assets/get-started/example-onboarding-wizard.png) - -If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. ## React Native vs React Native Web @@ -133,7 +87,8 @@ So, which option is right for you? **Both.** It’s also possible to use both options together. This increases Storybook’s install footprint but is a good option if you want native fidelity in addition to all of the web features. Learn more below. -## Using both React Native and React Native Web + +### Using both React Native and React Native Web The easiest way to use React Native and React Native Web is to select the **"Both"** option when installing Storybook. This will install and create configurations for both environments, allowing you to run Storybook for both in the same project. @@ -148,6 +103,44 @@ After installation, you'll see instructions for both environments: However, you can install them separately if one version is installed. You can add a React Native Web Storybook alongside an existing React Native Storybook by running the install command and selecting "React Native Web" in the setup wizard, and vice versa. + +## FAQ + +### How do I migrate from the React Native Web addon? + +The [React Native Web addon](https://github.com/storybookjs/addon-react-native-web) was a Webpack-based precursor to the React Native Web Vite framework (i.e., `@storybook/react-native-web-vite`). If you're using the addon, you should migrate to the framework, which is faster, more stable, maintained, and better documented. To do so, follow the steps below. + +Run the following command to upgrade Storybook to the latest version: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + + + This framework is designed to work with Storybook 8.5 and above for the best experience. We won't be able to provide support if you're using an older Storybook version. + + +Install the framework and its peer dependencies: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Update your `.storybook/main.js|ts` to change the framework property and remove the `@storybook/addon-react-native-web` addon: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Finally, remove the addon and similar packages (i.e., `@storybook/react-webpack5` and `@storybook/addon-react-native-web`) from your project. + + ## API ### Options diff --git a/docs/get-started/frameworks/react-vite.mdx b/docs/get-started/frameworks/react-vite.mdx index 0f57a8115432..a020acb172bf 100644 --- a/docs/get-started/frameworks/react-vite.mdx +++ b/docs/get-started/frameworks/react-vite.mdx @@ -6,34 +6,47 @@ sidebar: title: React (Vite) --- -Storybook for React & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Vite](https://vitejs.dev/). It includes: +Storybook for React & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Vite](https://vitejs.dev/). -* 🏎️ Pre-bundled for performance -* 🪄 Zero config -* 💫 and more! +## Install -## Requirements +To install Storybook in an existing React project, run this command in your project's root directory: -* React ≥ 16.8 -* Vite ≥ 5.0 + -## Getting started +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -### In a project without Storybook -Follow the prompts after running this command in your React project's root directory: +### Requirements -{/* prettier-ignore-start */} + - -{/* prettier-ignore-end */} +## Run Storybook + +To run Storybook for a particular project, run the following: + + + +To build Storybook, run: + + -[More on getting started with Storybook.](../install.mdx) +You will find the output in the configured `outputDir` (default is `storybook-static`). -### In a project with Storybook -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: +## FAQ +### How do I migrate from the React Webpack framework? + +The `upgrade` command should prompt you to migrate to `@storybook/react-vite` when you run it: {/* prettier-ignore-start */} @@ -41,11 +54,9 @@ This framework is designed to work with Storybook 7+. If you’re not already us {/* prettier-ignore-end */} -#### Automatic migration - -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/react-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. +In case that auto-migration does not work for your project, refer to the manual installation instructions below. -#### Manual migration +### How do I manually install the React framework? First, install the framework: @@ -63,13 +74,6 @@ Then, update your `.storybook/main.js|ts` to change the framework property: {/* prettier-ignore-end */} -## Run the Setup Wizard - -If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../../essentials/controls.mdx). - -![Storybook onboarding](../../_assets/get-started/example-onboarding-wizard.png) - -If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. ## API diff --git a/docs/get-started/frameworks/react-webpack5.mdx b/docs/get-started/frameworks/react-webpack5.mdx index 90c9792f5f5a..9a839863ca1f 100644 --- a/docs/get-started/frameworks/react-webpack5.mdx +++ b/docs/get-started/frameworks/react-webpack5.mdx @@ -8,40 +8,63 @@ sidebar: Storybook for React & Webpack is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [React](https://react.dev/) applications built with [Webpack](https://webpack.js.org/). -## Requirements + -* React ≥ 16.8 -* Webpack ≥ 5.0 +**We recommend using [`@storybook/react-vite`](./react-vite.mdx)** for most React projects. The Vite-based framework is faster, more modern, and offers better support for testing features. -## Getting started +Use this Webpack-based framework (`@storybook/react-webpack5`) only if you need specific Webpack features not available in Vite. + -### In a project without Storybook +## Install -Follow the prompts after running this command in your React project's root directory: +To install Storybook in an existing React project, run this command in your project's root directory: -{/* prettier-ignore-start */} + - +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -{/* prettier-ignore-end */} +### Requirements -[More on getting started with Storybook.](../install.mdx) + -### In a project with Storybook -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: +## Run Storybook -{/* prettier-ignore-start */} +To run Storybook for a particular project, run the following: - + -{/* prettier-ignore-end */} +To build Storybook, run: + + -#### Automatic migration +You will find the output in the configured `outputDir` (default is `storybook-static`). -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/react-webpack5`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. -#### Manual migration +## Configure + +### Create React App (CRA) + +Support for [Create React App](https://create-react-app.dev/) is handled by [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app). + +This preset enables support for all CRA features, including Sass/SCSS and TypeScript. + +### Manually initialized apps + +If you're working on an app that was initialized manually (i.e., without the use of CRA), ensure that your app has [react-dom](https://www.npmjs.com/package/react-dom) included as a dependency. Failing to do so can lead to unforeseen issues with Storybook and your project. + + +## FAQ + +### How do I manually install the React Webpack framework? First, install the framework: @@ -81,21 +104,9 @@ Finally, update your `.storybook/main.js|ts` to change the framework property: {/* prettier-ignore-end */} -## Run the Setup Wizard - -If all goes well, you should see a setup wizard that will help you get started with Storybook introducing you to the main concepts and features, including how the UI is organized, how to write your first story, and how to test your components' response to various inputs utilizing [controls](../../essentials/controls.mdx). - -![Storybook onboarding](../../_assets/get-started/example-onboarding-wizard.png) - -If you skipped the wizard, you can always run it again by adding the `?path=/onboarding` query parameter to the URL of your Storybook instance, provided that the example stories are still available. +### How do I migrate to the React Vite framework? -## Create React App (CRA) - -Support for [Create React App](https://create-react-app.dev/) is handled by [`@storybook/preset-create-react-app`](https://github.com/storybookjs/presets/tree/master/packages/preset-create-react-app). - -This preset enables support for all CRA features, including Sass/SCSS and TypeScript. - -If you're working on an app that was initialized manually (i.e., without the use of CRA), ensure that your app has [react-dom](https://www.npmjs.com/package/react-dom) included as a dependency. Failing to do so can lead to unforeseen issues with Storybook and your project. +Please refer to the [migration instructions for `@storybook/react-vite`](./react-vite.mdx#how-do-i-migrate-from-the-react-webpack-framework). ## API diff --git a/docs/get-started/frameworks/svelte-vite.mdx b/docs/get-started/frameworks/svelte-vite.mdx index d02e46d9feae..3e3980b79d40 100644 --- a/docs/get-started/frameworks/svelte-vite.mdx +++ b/docs/get-started/frameworks/svelte-vite.mdx @@ -8,56 +8,40 @@ sidebar: Storybook for Svelte & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for applications using [Svelte](https://svelte.dev/) built with [Vite](https://vitejs.dev/). -## Requirements -* Svelte ≥ 5.0 -* Vite ≥ 5.0 +## Install -## Getting started +To install Storybook in an existing Svelte project, run this command in your project's root directory: -### In a project without Storybook + -Follow the prompts after running this command in your Svelte project's root directory: +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} +### Requirements -[More on getting started with Storybook.](../install.mdx) + -### In a project with Storybook -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: +## Run Storybook -{/* prettier-ignore-start */} +To run Storybook for a particular project, run the following: - + -{/* prettier-ignore-end */} +To build Storybook, run: -#### Automatic migration + -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/svelte-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. +You will find the output in the configured `outputDir` (default is `storybook-static`). -#### Manual migration - -First, install the framework: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -Then, update your `.storybook/main.js|ts` to change the framework property: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} ## Writing native Svelte stories @@ -175,6 +159,27 @@ If you enabled automatic documentation generation with the `autodocs` story prop {/* prettier-ignore-end */} + +## FAQ +### How do I manually install the Svelte framework? + +First, install the framework: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Then, update your `.storybook/main.js|ts` to change the framework property: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + + ## API ### Options diff --git a/docs/get-started/frameworks/sveltekit.mdx b/docs/get-started/frameworks/sveltekit.mdx index 47c459b16b4a..b80f25e0c47e 100644 --- a/docs/get-started/frameworks/sveltekit.mdx +++ b/docs/get-started/frameworks/sveltekit.mdx @@ -6,70 +6,47 @@ sidebar: title: SvelteKit --- -Storybook for SvelteKit is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [SvelteKit](https://kit.svelte.dev/) applications. It includes: +Storybook for SvelteKit is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [SvelteKit](https://kit.svelte.dev/) applications. -* 🪄 Zero config -* 🧩 Easily mock many SvelteKit modules -* 🔗 Automatic link handling -* 💫 and more! +## Install -## Requirements +To install Storybook in an existing SvelteKit project, run this command in your project's root directory: -* SvelteKit ≥ 1.0 + -## Getting started +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -### In a project without Storybook +### Requirements -Follow the prompts after running this command in your Sveltekit project's root directory: + -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -[More on getting started with Storybook.](../install.mdx) - -### In a project with Storybook - -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: - -{/* prettier-ignore-start */} - - - -{/* prettier-ignore-end */} - -#### Automatic migration - -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/sveltekit`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. - -#### Manual migration -First, install the framework: +## Run Storybook -{/* prettier-ignore-start */} +To run Storybook for a particular project, run the following: - + -{/* prettier-ignore-end */} +To build Storybook, run: -Then, update your `.storybook/main.js|ts` to change the framework property: + -{/* prettier-ignore-start */} +You will find the output in the configured `outputDir` (default is `storybook-static`). - -{/* prettier-ignore-end */} +## Configure -Finally, these packages are now either obsolete or part of `@storybook/sveltekit`, so you no longer need to depend on them directly. You can remove them (`npm uninstall`, `yarn remove`, `pnpm remove`) from your project: +This section covers SvelteKit support and configuration options. -* `@storybook/svelte-vite` -* `storybook-builder-vite` -* `@storybook/builder-vite` - -## Supported features +### Supported features All Svelte language features are supported out of the box, as the Storybook framework uses the Svelte compiler directly. However, SvelteKit has some [Kit-specific modules](https://kit.svelte.dev/docs/modules) that aren't supported. Here's a breakdown of what will and will not work within Storybook: @@ -90,7 +67,7 @@ However, SvelteKit has some [Kit-specific modules](https://kit.svelte.dev/docs/m | [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private) | ⛔ Not supported | This is a server-side feature, and Storybook renders all components on the client. | | [`$service-worker`](https://svelte.dev/docs/kit/$service-worker) | ⛔ Not supported | This is a service worker feature, which does not apply to Storybook. | -## How to mock +### How to mock To mock a SvelteKit import you can define it within `parameters.sveltekit_experimental`: @@ -102,7 +79,7 @@ To mock a SvelteKit import you can define it within `parameters.sveltekit_experi The [available parameters](#parameters) are documented in the API section, below. -### Mocking links +#### Mocking links The default link-handling behavior (e.g., when clicking an `` element) is to log an action to the [Actions panel](../../essentials/actions.mdx). @@ -232,6 +209,33 @@ If you enabled automatic documentation generation with the `autodocs` story prop {/* prettier-ignore-end */} +## FAQ + +### How do I manually install the SvelteKit framework? + +First, install the framework: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Then, update your `.storybook/main.js|ts` to change the framework property: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Finally, these packages are now either obsolete or part of `@storybook/sveltekit`, so you no longer need to depend on them directly. You can remove them (`npm uninstall`, `yarn remove`, `pnpm remove`) from your project: + +* `@storybook/svelte-vite` +* `storybook-builder-vite` +* `@storybook/builder-vite` + + ## API ### Parameters @@ -381,15 +385,3 @@ Enables or disables automatic documentation generation for component properties. #### When to disable docgen Disabling docgen can improve build performance for large projects, but [argTypes won't be inferred automatically](../../api/arg-types.mdx#automatic-argtype-inference), which will prevent features like [Controls](../../essentials/controls.mdx) and [docs](../../writing-docs/autodocs.mdx) from working as expected. To use those features, you will need to [define `argTypes` manually](../../api/arg-types.mdx#manually-specifying-argtypes). - -## Troubleshooting - -### Error when starting Storybook - -When starting Storybook after upgrading to v7.0, it may quit with the following error: - -```sh -ERR! SyntaxError: Identifier '__esbuild_register_import_meta_url__' has already been declared -``` - -This can occur when manually upgrading from 6.5 to 7.0. To resolve it, you'll need to remove the `svelteOptions` property in `.storybook/main.js`, as that is not supported (and no longer necessary) in Storybook 7+ with SvelteKit. diff --git a/docs/get-started/frameworks/vue3-vite.mdx b/docs/get-started/frameworks/vue3-vite.mdx index 85e864bfcc0e..141e2734c4f4 100644 --- a/docs/get-started/frameworks/vue3-vite.mdx +++ b/docs/get-started/frameworks/vue3-vite.mdx @@ -6,65 +6,55 @@ sidebar: title: Vue (Vite) --- -Storybook for Vue & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Vue](https://vuejs.org/) applications built with [Vite](https://vitejs.dev/). It includes: +Storybook for Vue & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for [Vue](https://vuejs.org/) applications built with [Vite](https://vitejs.dev/). -* 🏎️ Pre-bundled for performance -* 🪄 Zero config -* 🧠 Comprehensive docgen -* 💫 and more! +## Install -## Requirements +To install Storybook in an existing Vue project, run this command in your project's root directory: -* Vue ≥ 3 -* Vite ≥ 5.0 + -## Getting started +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -### In a project without Storybook +### Requirements -Follow the prompts after running this command in your Vue project's root directory: + -{/* prettier-ignore-start */} +## Run Storybook - +To run Storybook for a particular project, run the following: -{/* prettier-ignore-end */} + -[More on getting started with Storybook.](../install.mdx) +To build Storybook, run: -### In a project with Storybook + -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: +You will find the output in the configured `outputDir` (default is `storybook-static`). -{/* prettier-ignore-start */} - -{/* prettier-ignore-end */} -#### Automatic migration -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/vue3-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. -#### Manual migration -First, install the framework: - -{/* prettier-ignore-start */} - - -{/* prettier-ignore-end */} -Then, update your `.storybook/main.js|ts` to change the framework property: -{/* prettier-ignore-start */} - +## Configure -{/* prettier-ignore-end */} +Storybook for Vue 3 with Vite is designed to work out of the box with minimal configuration. This section covers configuration options for the framework. -## Extending the Vue application +### Extending the Vue application Storybook creates a [Vue 3 application](https://vuejs.org/api/application.html#application-api) for your component preview. When using global custom components (`app.component`), directives (`app.directive`), extensions (`app.use`), or other application methods, you will need to configure those in the `./storybook/preview.js|ts` file. @@ -82,7 +72,7 @@ setup((app) => { }); ``` -## Using `vue-component-meta` +### Using `vue-component-meta` `vue-component-meta` is only available in Storybook ≥ 8. It is currently an opt-in, but it will become the default in a future version of Storybook. @@ -109,13 +99,13 @@ export default config; `vue-component-meta` comes with many benefits and enables more documentation features, such as: -### Support for multiple component types +#### Support for multiple component types `vue-component-meta` supports all types of Vue components (including SFC, functional, composition/options API components) from `.vue`, `.ts`, `.tsx`, `.js`, and `.jsx` files. It also supports both default and named component exports. -### Prop description and JSDoc tag annotations +#### Prop description and JSDoc tag annotations To describe a prop, including tags, you can use JSDoc comments in your component's props definition: @@ -142,7 +132,7 @@ The props definition above will generate the following controls: ![Controls generated from props](../../_assets/get-started/vue-component-meta-prop-types-controls.png) -### Events types extraction +#### Events types extraction To provide a type for an emitted event, you can use TypeScript types (including JSDoc comments) in your component's `defineEmits` call: @@ -167,7 +157,7 @@ Which will generate the following controls: ![Controls generated from events](../../_assets/get-started/vue-component-meta-event-types-controls.png) -### Slots types extraction +#### Slots types extraction The slot types are automatically extracted from your component definition and displayed in the controls panel. @@ -204,7 +194,7 @@ The definition above will generate the following controls: ![Controls generated from slots](../../_assets/get-started/vue-component-meta-slot-types-controls.png) -### Exposed properties and methods +#### Exposed properties and methods The properties and methods exposed by your component are automatically extracted and displayed in the [Controls](../../essentials/controls.mdx) panel. @@ -228,7 +218,7 @@ The definition above will generate the following controls: ![Controls generated from exposed properties and methods](../../_assets/get-started/vue-component-meta-exposed-types-controls.png) -### Override the default configuration +#### Override the default configuration If you're working with a project that relies on [`tsconfig references`](https://www.typescriptlang.org/docs/handbook/project-references.html) to link to other existing configuration files (e.g., `tsconfig.app.json`, `tsconfig.node.json`), we recommend that you update your [`.storybook/main.js|ts`](../../configure/index.mdx) configuration file and add the following: @@ -256,7 +246,27 @@ export default config; Otherwise, you might face missing component types/descriptions or unresolvable import aliases like `@/some/import`. -## Troubleshooting + + +## FAQ + +### How do I manually install the Vue framework? + +First, install the framework: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +Then, update your `.storybook/main.js|ts` to change the framework property: + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} ### Storybook doesn't work with my Vue 2 project @@ -268,6 +278,9 @@ Otherwise, you might face missing component types/descriptions or unresolvable i {/* prettier-ignore-end */} + + + ## API ### Options diff --git a/docs/get-started/frameworks/web-components-vite.mdx b/docs/get-started/frameworks/web-components-vite.mdx index 91705283d339..59d52d127055 100644 --- a/docs/get-started/frameworks/web-components-vite.mdx +++ b/docs/get-started/frameworks/web-components-vite.mdx @@ -8,39 +8,39 @@ sidebar: Storybook for Web components & Vite is a [framework](../../contribute/framework.mdx) that makes it easy to develop and test UI components in isolation for applications using [Web components](https://www.webcomponents.org/introduction) built with [Vite](https://vitejs.dev/). -## Requirements -* Vite ≥ 5.0 +## Install -## Getting started +To install Storybook in an existing project, run this command in your project's root directory: -### In a project without Storybook + -Follow the prompts after running this command in your Web components project's root directory: +You can then get started [writing stories](/docs/get-started/whats-a-story/), [running tests](/docs/writing-tests/) and [documenting your components](/docs/writing-docs/). For more control over the installation process, refer to the [installation guide](/docs/install/). -{/* prettier-ignore-start */} +### Requirements - + -{/* prettier-ignore-end */} -[More on getting started with Storybook.](../install.mdx) +## Run Storybook -### In a project with Storybook +To run Storybook for a particular project, run the following: -This framework is designed to work with Storybook 7+. If you’re not already using v7, upgrade with this command: + -{/* prettier-ignore-start */} +To build Storybook, run: - - -{/* prettier-ignore-end */} + -#### Automatic migration +You will find the output in the configured `outputDir` (default is `storybook-static`). -When running the `upgrade` command above, you should get a prompt asking you to migrate to `@storybook/web-components-vite`, which should handle everything for you. In case that auto-migration does not work for your project, refer to the manual migration below. -#### Manual migration +## FAQ +### How do I manually install the Web Components framework? First, install the framework: diff --git a/docs/index.mdx b/docs/index.mdx index 574da0d6086b..6c1b33ea4d59 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -3,17 +3,13 @@ title: Get started with Storybook hideRendererSelector: true --- -Welcome to Storybook's documentation ✦ Learn how to get started with Storybook in your project. Then, explore Storybook's main concepts and discover additional resources to help you grow and maintain your Storybook. - -## What is Storybook? - Storybook is a frontend workshop for building UI components and pages in isolation. It helps you develop and share hard-to-reach states and edge cases without needing to run your whole app. Thousands of teams use it for UI development, testing, and documentation. It's open source and free. ## Install Storybook -Storybook is a standalone tool that runs alongside your app. It's a zero-config environment that works with any modern frontend framework. You can install Storybook into an existing project or create a new one from scratch. +Run this command to install Storybook into an existing project or create a new one from scratch: - + Want to know more about installing Storybook? Check out the [installation guide](./get-started/install.mdx). diff --git a/docs/versions/next.json b/docs/versions/next.json index c03e0cffcc85..ea0d4bacc08c 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.2.0-alpha.2","info":{"plain":"- CLI: Remove any return type of getAbsolutePath - [#32977](https://github.com/storybookjs/storybook/pull/32977), thanks @nzws!\n- Checklist: Fix how state changes are reported and drop some completion restrictions - [#33217](https://github.com/storybookjs/storybook/pull/33217), thanks @ghengeveld!\n- Core: Avoid late layout shift and improve ChecklistWidget perceived performance - [#33184](https://github.com/storybookjs/storybook/pull/33184), thanks @ghengeveld!\n- Core: Minor UI fixes - [#33218](https://github.com/storybookjs/storybook/pull/33218), thanks @ghengeveld!\n- Preview: Prevent error in RN due to `navigator?.clipboard` - [#33219](https://github.com/storybookjs/storybook/pull/33219), thanks @ndelangen!\n- Solid: Add Solid to the list of supported frameworks for addon-vitest - [#33084](https://github.com/storybookjs/storybook/pull/33084), thanks @valentinpalkovic!\n- Telemetry: Add playwright-prompt - [#33229](https://github.com/storybookjs/storybook/pull/33229), thanks @valentinpalkovic!\n- UI: Fix excessive height in TabbedArgsTable - [#33205](https://github.com/storybookjs/storybook/pull/33205), thanks @Sidnioulz!"}} \ No newline at end of file +{"version":"10.2.0-alpha.3","info":{"plain":"- Addon Docs: Skip `!autodocs` stories when computing primary story - [#32712](https://github.com/storybookjs/storybook/pull/32712), thanks @ia319!\n- Angular: Honor --loglevel and --logfile in dev/build - [#33212](https://github.com/storybookjs/storybook/pull/33212), thanks @valentinpalkovic!\n- CSF: Export type to prevent `type cannot be named`-errors - [#33216](https://github.com/storybookjs/storybook/pull/33216), thanks @unional!\n- Chore: Upgrade Chromatic CLI - [#33176](https://github.com/storybookjs/storybook/pull/33176), thanks @ghengeveld!\n- Core: Enhance getPrettier function to provide prettier interface - [#33260](https://github.com/storybookjs/storybook/pull/33260), thanks @valentinpalkovic!\n- Core: Fix cwd handling for negated globs - [#33241](https://github.com/storybookjs/storybook/pull/33241), thanks @ia319!\n- NextJS: Alias image to use fileURLToPath for better resolution - [#33256](https://github.com/storybookjs/storybook/pull/33256), thanks @ndelangen!\n- Nextj.js: Support top-level weight/style in next/font/local with string src - [#32998](https://github.com/storybookjs/storybook/pull/32998), thanks @Chiman2937!\n- Telemetry: Cache Storybook metadata by main config content hash - [#33247](https://github.com/storybookjs/storybook/pull/33247), thanks @valentinpalkovic!\n- TypeScript: Fix summary undefined type issue - [#32585](https://github.com/storybookjs/storybook/pull/32585), thanks @afsalshamsudeen!"}} \ No newline at end of file diff --git a/scripts/build/utils/modify-core-theme-types.ts b/scripts/build/utils/modify-core-theme-types.ts index 5613444fc457..a543c350344c 100644 --- a/scripts/build/utils/modify-core-theme-types.ts +++ b/scripts/build/utils/modify-core-theme-types.ts @@ -1,6 +1,6 @@ import { readFile, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; +import { join } from 'pathe'; import { dedent } from 'ts-dedent'; import { CODE_DIRECTORY } from '../../utils/constants'; diff --git a/scripts/package.json b/scripts/package.json index 4f1b0f093862..7c8103890315 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -91,9 +91,9 @@ "@typescript-eslint/eslint-plugin": "^8.31.0", "@typescript-eslint/experimental-utils": "^5.62.0", "@typescript-eslint/parser": "^8.31.0", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.14", "ansi-regex": "^6.0.1", - "chromatic": "^11.28.2", + "chromatic": "^13.3.4", "codecov": "^3.8.1", "commander": "^12.1.0", "cross-env": "^7.0.3", @@ -175,7 +175,7 @@ "type-fest": "~2.19", "typescript": "^5.9.3", "uuid": "^9.0.1", - "vitest": "^3.2.4", + "vitest": "^4.0.14", "wait-on": "^8.0.3", "window-size": "^1.1.1", "yaml": "^2.7.0", diff --git a/scripts/vitest.config.ts b/scripts/vitest.config.ts index d0a3dff0b354..e0a86e20674e 100644 --- a/scripts/vitest.config.ts +++ b/scripts/vitest.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from 'vitest/config'; * tests are running with the small resource class, which has 1 vCPU. * * @see https://jahed.dev/2022/11/20/fixing-node-js-multi-threading-on-circleci/ - * @see https://vitest.dev/config/#pooloptions-threads-maxthreads + * @see https://vitest.dev/config/maxworkers.html#maxworkers * @see https://circleci.com/docs/configuration-reference/#x86 * @see .circleci/config.yml#L187 */ @@ -14,11 +14,7 @@ const threadCount = process.env.CI ? 1 : undefined; export default defineConfig({ test: { clearMocks: true, - poolOptions: { - threads: { - minThreads: threadCount, - maxThreads: threadCount, - }, - }, + pool: 'threads', + maxWorkers: threadCount, }, }); diff --git a/test-storybooks/yarn-pnp/package.json b/test-storybooks/yarn-pnp/package.json index cb9924ed904d..3a9cc3a59ca3 100644 --- a/test-storybooks/yarn-pnp/package.json +++ b/test-storybooks/yarn-pnp/package.json @@ -73,7 +73,7 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.34.1", "vite": "^7.1.7", - "vitest": "^4.0.7", + "vitest": "^4.0.14", "vue-tsc": "^3.1.0" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f0bd67bca8fe..0c21fb00352c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3832,7 +3832,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": version: 0.3.31 resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: @@ -7963,8 +7963,8 @@ __metadata: "@types/micromatch": "npm:^4.0.0" "@types/node": "npm:^22.0.0" "@types/semver": "npm:^7" - "@vitest/browser-playwright": "npm:^4.0.1" - "@vitest/runner": "npm:^4.0.1" + "@vitest/browser-playwright": "npm:^4.0.14" + "@vitest/runner": "npm:^4.0.14" empathic: "npm:^2.0.0" es-toolkit: "npm:^1.36.0" istanbul-lib-report: "npm:^3.0.1" @@ -7980,7 +7980,7 @@ __metadata: tree-kill: "npm:^1.2.2" ts-dedent: "npm:^2.2.0" typescript: "npm:^5.8.3" - vitest: "npm:^4.0.1" + vitest: "npm:^4.0.14" peerDependencies: "@vitest/browser": ^3.0.0 || ^4.0.0 "@vitest/browser-playwright": ^4.0.0 @@ -8219,9 +8219,10 @@ __metadata: "@typescript-eslint/parser": "npm:^8.8.1" "@vitejs/plugin-react": "npm:^4.3.2" "@vitejs/plugin-vue": "npm:^4.4.0" - "@vitest/browser": "npm:^3.2.4" - "@vitest/coverage-istanbul": "npm:^3.2.4" - "@vitest/coverage-v8": "npm:^3.2.4" + "@vitest/browser": "npm:^4.0.14" + "@vitest/browser-playwright": "npm:^4.0.14" + "@vitest/coverage-istanbul": "npm:^4.0.14" + "@vitest/coverage-v8": "npm:^4.0.14" create-storybook: "workspace:*" cross-env: "npm:^7.0.3" danger: "npm:^13.0.4" @@ -8274,7 +8275,7 @@ __metadata: uuid: "npm:^11.1.0" vite: "npm:^7.0.4" vite-plugin-inspect: "npm:^11.0.0" - vitest: "npm:^3.2.4" + vitest: "npm:^4.0.14" wait-on: "npm:^8.0.3" dependenciesMeta: ejs: @@ -8781,9 +8782,9 @@ __metadata: "@typescript-eslint/experimental-utils": "npm:^5.62.0" "@typescript-eslint/parser": "npm:^8.31.0" "@verdaccio/types": "npm:^10.8.0" - "@vitest/coverage-v8": "npm:^3.2.4" + "@vitest/coverage-v8": "npm:^4.0.14" ansi-regex: "npm:^6.0.1" - chromatic: "npm:^11.28.2" + chromatic: "npm:^13.3.4" codecov: "npm:^3.8.1" commander: "npm:^12.1.0" cross-env: "npm:^7.0.3" @@ -8867,7 +8868,7 @@ __metadata: uuid: "npm:^9.0.1" verdaccio: "npm:^5.31.1" verdaccio-auth-memory: "npm:^10.2.2" - vitest: "npm:^3.2.4" + vitest: "npm:^4.0.14" wait-on: "npm:^8.0.3" window-size: "npm:^1.1.1" yaml: "npm:^2.7.0" @@ -10916,29 +10917,29 @@ __metadata: languageName: node linkType: hard -"@vitest/browser-playwright@npm:^4.0.1": - version: 4.0.10 - resolution: "@vitest/browser-playwright@npm:4.0.10" +"@vitest/browser-playwright@npm:^4.0.14": + version: 4.0.14 + resolution: "@vitest/browser-playwright@npm:4.0.14" dependencies: - "@vitest/browser": "npm:4.0.10" - "@vitest/mocker": "npm:4.0.10" + "@vitest/browser": "npm:4.0.14" + "@vitest/mocker": "npm:4.0.14" tinyrainbow: "npm:^3.0.3" peerDependencies: playwright: "*" - vitest: 4.0.10 + vitest: 4.0.14 peerDependenciesMeta: playwright: optional: false - checksum: 10c0/914228451e98d507b1b13e8bb465a1c345f88517a9d914d61c8732ecd1e147d6896db305d038f4b267f969ae270a14ea710834ee95a9d07ab42149fb0b84bf99 + checksum: 10c0/4f781884357f14e543a90d8426a579b23372ba29dda77af6290f1d3535646889ed833e4e4166d89a1c095b555427f3d7f83841a58bfe7ef8e26d6942a0d490fd languageName: node linkType: hard -"@vitest/browser@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/browser@npm:4.0.10" +"@vitest/browser@npm:4.0.14, @vitest/browser@npm:^4.0.14": + version: 4.0.14 + resolution: "@vitest/browser@npm:4.0.14" dependencies: - "@vitest/mocker": "npm:4.0.10" - "@vitest/utils": "npm:4.0.10" + "@vitest/mocker": "npm:4.0.14" + "@vitest/utils": "npm:4.0.14" magic-string: "npm:^0.30.21" pixelmatch: "npm:7.1.0" pngjs: "npm:^7.0.0" @@ -10946,55 +10947,27 @@ __metadata: tinyrainbow: "npm:^3.0.3" ws: "npm:^8.18.3" peerDependencies: - vitest: 4.0.10 - checksum: 10c0/cfb3bf85970b96c1addf745fc8e9a7b8944cb640081a8bffc2b2b98669a038cf92be9cf04547fbbe27294c87f2abe7657bfaf99ad60a27a18134aa8dbcee4558 - languageName: node - linkType: hard - -"@vitest/browser@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/browser@npm:3.2.4" - dependencies: - "@testing-library/dom": "npm:^10.4.0" - "@testing-library/user-event": "npm:^14.6.1" - "@vitest/mocker": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - magic-string: "npm:^0.30.17" - sirv: "npm:^3.0.1" - tinyrainbow: "npm:^2.0.0" - ws: "npm:^8.18.2" - peerDependencies: - playwright: "*" - vitest: 3.2.4 - webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0 - peerDependenciesMeta: - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true - checksum: 10c0/0db39daad675aad187eff27d5a7f17a9f533d7abc7476ee1a0b83a9c62a7227b24395f4814e034ecb2ebe39f1a2dec0a8c6a7f79b8d5680c3ac79e408727d742 + vitest: 4.0.14 + checksum: 10c0/6f741fbe1173b0620f27730c74eec2e92bcd584bf1d9075633563497196b747f5c7a21059f21ec4fbc37a4979aca4dd3b4572dbdebe0482b473fb55609b5af37 languageName: node linkType: hard -"@vitest/coverage-istanbul@npm:^3.2.4": - version: 3.2.4 - resolution: "@vitest/coverage-istanbul@npm:3.2.4" +"@vitest/coverage-istanbul@npm:^4.0.14": + version: 4.0.14 + resolution: "@vitest/coverage-istanbul@npm:4.0.14" dependencies: "@istanbuljs/schema": "npm:^0.1.3" - debug: "npm:^4.4.1" istanbul-lib-coverage: "npm:^3.2.2" istanbul-lib-instrument: "npm:^6.0.3" istanbul-lib-report: "npm:^3.0.1" istanbul-lib-source-maps: "npm:^5.0.6" - istanbul-reports: "npm:^3.1.7" - magicast: "npm:^0.3.5" - test-exclude: "npm:^7.0.1" - tinyrainbow: "npm:^2.0.0" + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.1" + obug: "npm:^2.1.1" + tinyrainbow: "npm:^3.0.3" peerDependencies: - vitest: 3.2.4 - checksum: 10c0/3727a389143f03fa96815f40db25772de4b13abbeeb0cec737dbfe37b429253ab0cbe9f89d6430f86267fdc0c3c76e302c9dd094ed7ce9c91eac33de731cb198 + vitest: 4.0.14 + checksum: 10c0/baeb4b239b4e4b48e76c41df86ba20c99366cd1a3a6f53637f489b7fd7c12371389312f853a3ac7e0867e577100e35456a3e8038783fccd32fe4e631e2e6d218 languageName: node linkType: hard @@ -11025,6 +10998,31 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:^4.0.14": + version: 4.0.14 + resolution: "@vitest/coverage-v8@npm:4.0.14" + dependencies: + "@bcoe/v8-coverage": "npm:^1.0.2" + "@vitest/utils": "npm:4.0.14" + ast-v8-to-istanbul: "npm:^0.3.8" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.1" + obug: "npm:^2.1.1" + std-env: "npm:^3.10.0" + tinyrainbow: "npm:^3.0.3" + peerDependencies: + "@vitest/browser": 4.0.14 + vitest: 4.0.14 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/ae4f7c0b187167bb679c6eee9b6dc6d036e15b629506cb2a462675de7ebd7bcf43ed07c2ade9d9737c351cf9f8fcd614f42b028a26cd4e44fce56ec05a79d6ca + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -11038,17 +11036,17 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/expect@npm:4.0.10" +"@vitest/expect@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/expect@npm:4.0.14" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.10" - "@vitest/utils": "npm:4.0.10" + "@vitest/spy": "npm:4.0.14" + "@vitest/utils": "npm:4.0.14" chai: "npm:^6.2.1" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/b8d2f1872d32e2288861b4ae0a530671460b5bed15ffb0544e4efd56e824445bd99aa364fd10332a0dd045fdd4314f8c5a3ab184eb55a43ea8c7f4e8991c8500 + checksum: 10c0/cb82f16c0e7bd82743d91bc99a0c2a0906a2d5760d0bd80d68964e4d4d5fd99097b154de2315014a857ce86d66ecb7bda81c6ba4b9b3a3a5dc5c16fcc4187bde languageName: node linkType: hard @@ -11084,11 +11082,11 @@ __metadata: languageName: node linkType: hard -"@vitest/mocker@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/mocker@npm:4.0.10" +"@vitest/mocker@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/mocker@npm:4.0.14" dependencies: - "@vitest/spy": "npm:4.0.10" + "@vitest/spy": "npm:4.0.14" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.21" peerDependencies: @@ -11099,11 +11097,11 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/be72c1f1aa8b22f7f55e91f8e42ec39eb19e46a0d85b9f36745d14b831d370136ca4e20433934a0f0a076a07a1c9e54e309a3994e31a4f91115de8dc91615c8c + checksum: 10c0/fba7366b26a7fe1222bb576ec807297270a2ad55d9db0d4849b4011364b182545326a8e9522a386e89d52afefa3bafbf456c57792ba9fa2fab4d84772e8c02ae languageName: node linkType: hard -"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": +"@vitest/pretty-format@npm:3.2.4": version: 3.2.4 resolution: "@vitest/pretty-format@npm:3.2.4" dependencies: @@ -11112,55 +11110,33 @@ __metadata: languageName: node linkType: hard -"@vitest/pretty-format@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/pretty-format@npm:4.0.10" +"@vitest/pretty-format@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/pretty-format@npm:4.0.14" dependencies: tinyrainbow: "npm:^3.0.3" - checksum: 10c0/7a7d44aad921cad8b9049cb94be3e9ba695678489bfd1b2e049bb661d70f722c7657c95589ed0094976eeca878ddfa7e3f8dbc7c9de3ef9b281bcc77d0f01b0d - languageName: node - linkType: hard - -"@vitest/runner@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/runner@npm:3.2.4" - dependencies: - "@vitest/utils": "npm:3.2.4" - pathe: "npm:^2.0.3" - strip-literal: "npm:^3.0.0" - checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a - languageName: node - linkType: hard - -"@vitest/runner@npm:4.0.10, @vitest/runner@npm:^4.0.1": - version: 4.0.10 - resolution: "@vitest/runner@npm:4.0.10" - dependencies: - "@vitest/utils": "npm:4.0.10" - pathe: "npm:^2.0.3" - checksum: 10c0/bbd1bfabae5efb8e3b528b96312334a9be9af1a4ff792b1aa710209c2694ee2198498c654319e32215433fa7152a056ec22fa0766545a94fd77050ca6a0f5e2d + checksum: 10c0/ca03cbad86053a05eb3164b1794ada25767215e94f76fe069c0a0431629500a53b221610b186917bfbdebf6a28ac7d3945f78e1e18875230ea6dda685c6a18f3 languageName: node linkType: hard -"@vitest/snapshot@npm:3.2.4": - version: 3.2.4 - resolution: "@vitest/snapshot@npm:3.2.4" +"@vitest/runner@npm:4.0.14, @vitest/runner@npm:^4.0.14": + version: 4.0.14 + resolution: "@vitest/runner@npm:4.0.14" dependencies: - "@vitest/pretty-format": "npm:3.2.4" - magic-string: "npm:^0.30.17" + "@vitest/utils": "npm:4.0.14" pathe: "npm:^2.0.3" - checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc + checksum: 10c0/97e49a99772fdc0b798d1ba5e8eabc76fa8846a7b5e41c7ac8a43cb0455d333fa37987b88bcbe344d7af51c967f06016c54fef70ded3a212479c71cd4d892d78 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/snapshot@npm:4.0.10" +"@vitest/snapshot@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/snapshot@npm:4.0.14" dependencies: - "@vitest/pretty-format": "npm:4.0.10" + "@vitest/pretty-format": "npm:4.0.14" magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/21ca6098468175b8f766033ef7eb701027321c6c249b95ff0f96566b5d394e12d69a6b17049dec5b6af08f59a3e4ecf46cdb00e365dc26745099da086119db22 + checksum: 10c0/6b187b08751fbacb32baa2e970d6f2fa90e9de1bc76c97f64bb5370c2341ff18af63af571dd11fa94cbd5ddba00de6b5280cbab948bca738d80f57d8f662035a languageName: node linkType: hard @@ -11173,10 +11149,10 @@ __metadata: languageName: node linkType: hard -"@vitest/spy@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/spy@npm:4.0.10" - checksum: 10c0/e6950fea42e5886e7ae6a991647060c84bcd1817262a1c0a73435e66bbbcc4f2078e29079822ccc2a884396fcfd0759036c14d2e23e062e52cb66b7c4cc7e347 +"@vitest/spy@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/spy@npm:4.0.14" + checksum: 10c0/46917fab9c9aaa3c4f815300ec8e21631a7f9cd4d74aac06bad29bb750d9e7a726cd26149c29ea16b1dc5197995faceff3efdcc41c49f402e9da8916dd410be3 languageName: node linkType: hard @@ -11191,13 +11167,13 @@ __metadata: languageName: node linkType: hard -"@vitest/utils@npm:4.0.10": - version: 4.0.10 - resolution: "@vitest/utils@npm:4.0.10" +"@vitest/utils@npm:4.0.14": + version: 4.0.14 + resolution: "@vitest/utils@npm:4.0.14" dependencies: - "@vitest/pretty-format": "npm:4.0.10" + "@vitest/pretty-format": "npm:4.0.14" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/cfca5d33ac7b609e6ada34c17dfbe02322eb6c5016d17a9dc8dd1b6db3d7962bd39ca4f9e40f127cd3c5bb8a6193df8b7c99a3c3f30b3ae051e52fbe6bedd3e4 + checksum: 10c0/be5432b4445bdb1b41d1ad1bffe9e2a297b7d1d9addef3cbf3782d66da4e80ec8a14e2396638172572e5a6e3527f34bae7f1b98cee00cbe1175b099a28073ecd languageName: node linkType: hard @@ -12381,14 +12357,14 @@ __metadata: languageName: node linkType: hard -"ast-v8-to-istanbul@npm:^0.3.3": - version: 0.3.3 - resolution: "ast-v8-to-istanbul@npm:0.3.3" +"ast-v8-to-istanbul@npm:^0.3.3, ast-v8-to-istanbul@npm:^0.3.8": + version: 0.3.8 + resolution: "ast-v8-to-istanbul@npm:0.3.8" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" + "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^9.0.1" - checksum: 10c0/ffc39bc3ab4b8c1f7aea945960ce6b1e518bab3da7c800277eab2da07d397eeae4a2cb8a5a5f817225646c8ea495c1e4434fbe082c84bae8042abddef53f50b2 + checksum: 10c0/6f7d74fc36011699af6d4ad88ecd8efc7d74bd90b8e8dbb1c69d43c8f4bec0ed361fb62a5b5bd98bbee02ee87c62cd8bcc25a39634964e45476bf5489dfa327f languageName: node linkType: hard @@ -13510,13 +13486,6 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.7.14": - version: 6.7.14 - resolution: "cac@npm:6.7.14" - checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 - languageName: node - linkType: hard - "cacache@npm:^16.1.0": version: 16.1.3 resolution: "cacache@npm:16.1.3" @@ -13846,28 +13815,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^11.28.2": - version: 11.28.2 - resolution: "chromatic@npm:11.28.2" - peerDependencies: - "@chromatic-com/cypress": ^0.*.* || ^1.0.0 - "@chromatic-com/playwright": ^0.*.* || ^1.0.0 - peerDependenciesMeta: - "@chromatic-com/cypress": - optional: true - "@chromatic-com/playwright": - optional: true - bin: - chroma: dist/bin.js - chromatic: dist/bin.js - chromatic-cli: dist/bin.js - checksum: 10c0/0e30c01adae08708b871c5e27462af14a8426e0fe443d0c42afb2477326703504b02fa96a02e64c858072f535d149b96f207c43b147ba1492de83bb5f505e59e - languageName: node - linkType: hard - -"chromatic@npm:^13.3.3": - version: 13.3.3 - resolution: "chromatic@npm:13.3.3" +"chromatic@npm:^13.3.3, chromatic@npm:^13.3.4": + version: 13.3.4 + resolution: "chromatic@npm:13.3.4" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -13880,7 +13830,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/6fc54df030113d91ef00a2050f5cb13ca182b355dae2c29cdd326fac6cf21d8ddc2cd93dc3f5db04379b7769d4df8e3ea5f18c3642e9e3a48545565f992a838c + checksum: 10c0/1800c1640dbc168b621daeca5895698cb5a0a1def50b9d1ada5ea99ce242bf1f70d15065460948b168eedea1f56422553184f4cce1d01a7816f32c60054d704d languageName: node linkType: hard @@ -15017,7 +14967,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -17483,7 +17433,7 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.1, expect-type@npm:^1.2.2": +"expect-type@npm:^1.2.2": version: 1.2.2 resolution: "expect-type@npm:1.2.2" checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b @@ -20883,7 +20833,7 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.1.4, istanbul-reports@npm:^3.1.7": +"istanbul-reports@npm:^3.1.4, istanbul-reports@npm:^3.1.7, istanbul-reports@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: @@ -22344,6 +22294,17 @@ __metadata: languageName: node linkType: hard +"magicast@npm:^0.5.1": + version: 0.5.1 + resolution: "magicast@npm:0.5.1" + dependencies: + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + source-map-js: "npm:^1.2.1" + checksum: 10c0/a00bbf3688b9b3e83c10b3bfe3f106cc2ccbf20c4f2dc1c9020a10556dfe0a6a6605a445ee8e86a6e2b484ec519a657b5e405532684f72678c62e4c0d32f962c + languageName: node + linkType: hard + "make-dir@npm:^1.0.0": version: 1.3.0 resolution: "make-dir@npm:1.3.0" @@ -24341,6 +24302,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + "ohash@npm:^2.0.11": version: 2.0.11 resolution: "ohash@npm:2.0.11" @@ -29656,15 +29624,6 @@ __metadata: languageName: node linkType: hard -"strip-literal@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-literal@npm:3.0.0" - dependencies: - js-tokens: "npm:^9.0.1" - checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 - languageName: node - linkType: hard - "stubs@npm:^3.0.0": version: 3.0.0 resolution: "stubs@npm:3.0.0" @@ -30185,7 +30144,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": +"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -30195,13 +30154,6 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.1.1": - version: 1.1.1 - resolution: "tinypool@npm:1.1.1" - checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b - languageName: node - linkType: hard - "tinyrainbow@npm:^2.0.0": version: 2.0.0 resolution: "tinyrainbow@npm:2.0.0" @@ -31490,21 +31442,6 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:3.2.4": - version: 3.2.4 - resolution: "vite-node@npm:3.2.4" - dependencies: - cac: "npm:^6.7.14" - debug: "npm:^4.4.1" - es-module-lexer: "npm:^1.7.0" - pathe: "npm:^2.0.3" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - bin: - vite-node: vite-node.mjs - checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b - languageName: node - linkType: hard - "vite-plugin-commonjs@npm:^0.10.4": version: 0.10.4 resolution: "vite-plugin-commonjs@npm:0.10.4" @@ -31656,7 +31593,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.0.4": +"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.0.4": version: 7.2.2 resolution: "vite@npm:7.2.2" dependencies: @@ -31739,77 +31676,21 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^3.2.4": - version: 3.2.4 - resolution: "vitest@npm:3.2.4" +"vitest@npm:^4.0.14": + version: 4.0.14 + resolution: "vitest@npm:4.0.14" dependencies: - "@types/chai": "npm:^5.2.2" - "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" - "@vitest/pretty-format": "npm:^3.2.4" - "@vitest/runner": "npm:3.2.4" - "@vitest/snapshot": "npm:3.2.4" - "@vitest/spy": "npm:3.2.4" - "@vitest/utils": "npm:3.2.4" - chai: "npm:^5.2.0" - debug: "npm:^4.4.1" - expect-type: "npm:^1.2.1" - magic-string: "npm:^0.30.17" - pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.2" - std-env: "npm:^3.9.0" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^0.3.2" - tinyglobby: "npm:^0.2.14" - tinypool: "npm:^1.1.1" - tinyrainbow: "npm:^2.0.0" - vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - vite-node: "npm:3.2.4" - why-is-node-running: "npm:^2.3.0" - peerDependencies: - "@edge-runtime/vm": "*" - "@types/debug": ^4.1.12 - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.2.4 - "@vitest/ui": 3.2.4 - happy-dom: "*" - jsdom: "*" - peerDependenciesMeta: - "@edge-runtime/vm": - optional: true - "@types/debug": - optional: true - "@types/node": - optional: true - "@vitest/browser": - optional: true - "@vitest/ui": - optional: true - happy-dom: - optional: true - jsdom: - optional: true - bin: - vitest: vitest.mjs - checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb - languageName: node - linkType: hard - -"vitest@npm:^4.0.1": - version: 4.0.10 - resolution: "vitest@npm:4.0.10" - dependencies: - "@vitest/expect": "npm:4.0.10" - "@vitest/mocker": "npm:4.0.10" - "@vitest/pretty-format": "npm:4.0.10" - "@vitest/runner": "npm:4.0.10" - "@vitest/snapshot": "npm:4.0.10" - "@vitest/spy": "npm:4.0.10" - "@vitest/utils": "npm:4.0.10" - debug: "npm:^4.4.3" + "@vitest/expect": "npm:4.0.14" + "@vitest/mocker": "npm:4.0.14" + "@vitest/pretty-format": "npm:4.0.14" + "@vitest/runner": "npm:4.0.14" + "@vitest/snapshot": "npm:4.0.14" + "@vitest/spy": "npm:4.0.14" + "@vitest/utils": "npm:4.0.14" es-module-lexer: "npm:^1.7.0" expect-type: "npm:^1.2.2" magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" pathe: "npm:^2.0.3" picomatch: "npm:^4.0.3" std-env: "npm:^3.10.0" @@ -31821,18 +31702,18 @@ __metadata: why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" - "@types/debug": ^4.1.12 + "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.10 - "@vitest/browser-preview": 4.0.10 - "@vitest/browser-webdriverio": 4.0.10 - "@vitest/ui": 4.0.10 + "@vitest/browser-playwright": 4.0.14 + "@vitest/browser-preview": 4.0.14 + "@vitest/browser-webdriverio": 4.0.14 + "@vitest/ui": 4.0.14 happy-dom: "*" jsdom: "*" peerDependenciesMeta: "@edge-runtime/vm": optional: true - "@types/debug": + "@opentelemetry/api": optional: true "@types/node": optional: true @@ -31850,7 +31731,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/5da26cc0c2db1a905615e89eecb0bcc6da376088db77de41c2bf8a6037488671f3e03674659bde1ee806c8dcd822a1e327f7e74413313bdf1f2fff64ecd3b993 + checksum: 10c0/97e05dabe5be18ecc72e4fa2f45be7353f828c35ad2d8957772027be52aa1f60d5f2609d166c85369d5888b9f664968dce2b918a7fffbcc91fbac29f1fdddabe languageName: node linkType: hard @@ -32677,7 +32558,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3": +"ws@npm:^8.18.0, ws@npm:^8.18.3": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: