Skip to content

Addon-Vitest: Refactor Vitest setup to eliminate the need for a dedicated setup file#34025

Merged
valentinpalkovic merged 11 commits intonextfrom
valentin/remove-need-for-setup-file
Mar 6, 2026
Merged

Addon-Vitest: Refactor Vitest setup to eliminate the need for a dedicated setup file#34025
valentinpalkovic merged 11 commits intonextfrom
valentin/remove-need-for-setup-file

Conversation

@valentinpalkovic
Copy link
Copy Markdown
Contributor

@valentinpalkovic valentinpalkovic commented Mar 5, 2026

Closes #31700

What I did

Simplified the setup experience for @storybook/addon-vitest by removing the requirement for users to manually create and maintain a vitest.setup file. The addon now automatically provides project annotations when needed.

Key Changes:

  1. Automatic Project Annotations: Created a new internal setup file (setup-file-with-project-annotations.ts) that loads project annotations from a virtual module provided by the Storybook Vite plugin
  2. Smart Detection: Added requiresProjectAnnotations() utility that intelligently detects when project annotations are needed based on:
    • Existence of user-created setup files with setProjectAnnotations
    • CSF4 format detection
    • Vitest configuration
  3. Backward Compatibility: Existing setup files are still respected with a deprecation warning informing users they can safely remove them
  4. Simplified Postinstall: Removed code that generates vitest.setup files during installation
  5. Updated Templates: Removed SETUP_FILE references from all Vitest config templates

Benefits:

  • Better DX: Users no longer need to create or maintain setup files
  • Fewer errors: Eliminates common setup mistakes
  • Cleaner configs: Vitest configurations are simpler
  • Automatic updates: Project annotations stay in sync without manual intervention

Migration Path:

For users with existing setup files containing setProjectAnnotations, the addon will:

  • Detect the existing setup and skip automatic injection
  • Display a one-time warning that the file can be safely removed
  • Continue working without breaking changes

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Testing steps:

  1. Generate a fresh sandbox: yarn task sandbox --template react-vite/default-ts --start-from auto
  2. Navigate to the sandbox: cd ../storybook-sandboxes/react-vite-default-ts
  3. Verify no vitest.setup.ts file exists in .storybook/
  4. Run vitest tests to ensure they pass without manual setup file
  5. Test backward compatibility by creating a custom setup file with setProjectAnnotations and verify the warning appears
  6. Test CSF4 detection with appropriate templates

Additional testing:

  • Test with existing sandboxes that have setup files to verify backward compatibility
  • Verify the deprecation warning displays correctly
  • Test both TypeScript and JavaScript configurations
  • Verify the vitest.config templates no longer reference setup files

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

Suggested label: feature request (improves DX by removing manual setup requirement)

🦋 Canary release

This pull request has been released as version 0.0.0-pr-34025-sha-73af6b3a. Try it out in a new sandbox by running npx storybook@0.0.0-pr-34025-sha-73af6b3a sandbox or in an existing project with npx storybook@0.0.0-pr-34025-sha-73af6b3a upgrade.

More information
Published version 0.0.0-pr-34025-sha-73af6b3a
Triggered by @valentinpalkovic
Repository storybookjs/storybook
Branch valentin/remove-need-for-setup-file
Commit 73af6b3a
Datetime Fri Mar 6 09:23:45 UTC 2026 (1772789025)
Workflow run 22757241671

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=34025

Summary by CodeRabbit

  • New Features

    • Vitest now conditionally injects Storybook project annotations into test runs.
  • Improvements

    • Manual Vitest setup files are no longer required; setup happens automatically when needed.
    • Test runs include preview APIs for more reliable story testing.
    • Sandbox/test scaffolding simplified to reduce per-framework setup.
  • Documentation

    • Docs updated to state no additional Vitest configuration is needed.
  • Chores

    • CI/publish workflow and build tooling updated.

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Mar 5, 2026

View your CI Pipeline Execution ↗ for commit 5dbf094

Command Status Duration Result
nx run-many -t compile -c production --parallel=1 ✅ Succeeded 4m 52s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-06 11:04:07 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a plugin-managed internal Vitest setup file that injects Storybook project annotations via a virtual builder module, exposes it in the Vitest addon build exports, removes prior postinstall/sandbox-generated setup-file logic and template setupFiles, and extends per-entry browser externals in the bundler.

Changes

Cohort / File(s) Summary
Vitest addon build & exports
code/addons/vitest/build-config.ts, code/addons/vitest/package.json
Adds a browser build entry for setup-file-with-project-annotations and exposes it in package exports (./internal/setup-file-with-project-annotations).
Vitest plugin & runtime helpers
code/addons/vitest/src/vitest-plugin/index.ts, code/addons/vitest/src/vitest-plugin/setup-file-with-project-annotations.ts, code/addons/vitest/src/vitest-plugin/utils.ts
Plugin now computes internal setup files, conditionally injects setup-file-with-project-annotations (calls setProjectAnnotations(getProjectAnnotations())), adds requiresProjectAnnotations detection, and updates includes/optDeps.
Postinstall / update / tests
code/addons/vitest/src/postinstall.ts, code/addons/vitest/src/postinstall.test.ts, code/addons/vitest/src/updateVitestFile.test.ts
Removed logic that detected/created explicit vitest.setup files; tests updated to reflect removal of setupFiles injection.
Templates & sandbox
code/addons/vitest/templates/..., scripts/tasks/sandbox-parts.ts, test-storybooks/.../.storybook/vitest.setup.ts
Removed template setupFiles entries and sandbox/framework-specific vitest.setup generation; example/test setup files simplified (project-annotations wiring removed).
Build tooling
scripts/build/utils/entry-utils.ts, scripts/build/utils/generate-bundle.ts
Added optional external?: string[] to BuildEntry and merged per-entry browser externals into bundle generation.
Docs & snippets
docs/writing-tests/accessibility-testing.mdx, docs/_snippets/storybook-addon-a11y-test-setup.md, docs/writing-tests/integrations/vitest-addon/index.mdx
Removed manual Vitest a11y setup examples and replaced guidance with “No additional configuration is needed.”
CLI automigrate tweak
code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts
Tightened error reporting: only report inability to transform setup when a setup file path exists and transformation returned null.
Misc tests/examples
test-storybooks/*, code/addons/vitest/templates/*
Removed references to injecting SETUP_FILE into templates and removed setup file generation/writes tied to postinstall/sandbox flows.

Sequence Diagram(s)

sequenceDiagram
    participant Plugin as Vitest Plugin
    participant Builder as virtual:/@storybook/builder-vite
    participant Vitest as Vitest Runner
    participant FS as Filesystem

    Plugin->>Vitest: read test config (including setupFiles)
    Plugin->>FS: resolve existing setupFiles paths
    Plugin->>Plugin: call requiresProjectAnnotations(testConfig, options, isCSF4)
    Plugin->>Builder: import virtual:/@storybook/builder-vite/project-annotations.js
    Builder-->>Plugin: provide getProjectAnnotations()
    alt needs annotations
        Plugin->>FS: include internal setup file (setup-file-with-project-annotations)
        Plugin->>Vitest: inject internal setup file into final setupFiles
        Vitest->>FS: load & execute setup-file-with-project-annotations
        Note right of Vitest: setup file calls setProjectAnnotations(getProjectAnnotations())
    else no annotations needed
        Plugin->>Vitest: leave setupFiles unchanged (or preserve existing entries)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/addons/vitest/src/vitest-plugin/index.ts (1)

329-334: ⚠️ Potential issue | 🟠 Major

Preserve array-form test.setupFiles when merging configs.

The current logic only checks for setupFiles as a string and filters to false for array form, silently dropping user-provided setup files when configured as an array. Vitest accepts both string and string[], so both forms must be preserved.

The suggested fix correctly handles both types by normalizing them to an array before merging:

Suggested implementation
+const existingSetupFiles = Array.isArray(nonMutableInputConfig.test?.setupFiles)
+  ? nonMutableInputConfig.test.setupFiles
+  : typeof nonMutableInputConfig.test?.setupFiles === 'string'
+    ? [nonMutableInputConfig.test.setupFiles]
+    : [];
+
 const baseConfig: Omit<ViteUserConfig, 'plugins'> = {
   cacheDir: resolvePathInStorybookCache('sb-vitest', projectId),
   test: {
     expect: { requireAssertions: false },
     setupFiles: [
       ...internalSetupFiles,
-      // if the existing setupFiles is a string, we have to include it otherwise we're overwriting it
-      typeof nonMutableInputConfig.test?.setupFiles === 'string' &&
-        nonMutableInputConfig.test?.setupFiles,
-    ].filter(Boolean) as string[],
+      ...existingSetupFiles,
+    ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/addons/vitest/src/vitest-plugin/index.ts` around lines 329 - 334, The
merge currently only preserves nonMutableInputConfig.test?.setupFiles when it's
a string, dropping array-form values; update the merge for setupFiles in the
vitest plugin to normalize nonMutableInputConfig.test?.setupFiles to an array
(using Array.isArray or similar) before spreading so both string and string[]
are preserved and concatenated with internalSetupFiles; locate the setupFiles
merge expression (referencing setupFiles, internalSetupFiles, and
nonMutableInputConfig.test?.setupFiles) and replace the conditional with a
normalized array merge.
🧹 Nitpick comments (1)
scripts/build/utils/generate-bundle.ts (1)

210-213: Use explicit null-coalescing in flatMap instead of relying on filter(Boolean) for type safety in strict mode.

In TypeScript strict mode, filter(Boolean) does not narrow union types because Boolean is a regular function, not a type predicate. The array remains (string | undefined)[] after filtering, causing type inconsistency. The refactor using flatMap((entry) => entry.external ?? []) properly narrows to string[] and is clearer under strict typing.

♻️ Suggested refactor
         external: [
           ...(sharedOptions.external as string[]),
-          ...(entries.browser.flatMap((entry) => entry.external) ?? []),
-        ].filter(Boolean),
+          ...entries.browser.flatMap((entry) => entry.external ?? []),
+        ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build/utils/generate-bundle.ts` around lines 210 - 213, The current
construction uses entries.browser.flatMap((entry) => entry.external) and then
.filter(Boolean), which doesn't narrow types under TS strict mode; change the
flatMap to explicitly coalesce undefined: use entries.browser.flatMap((entry) =>
entry.external ?? []) so the result is string[] (alongside
sharedOptions.external) and then you can remove the reliance on filter(Boolean);
update the external array assembly (the spread of sharedOptions.external and the
entries.flatMap) so the union is explicitly narrowed to string[].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@code/addons/vitest/src/vitest-plugin/index.ts`:
- Around line 329-334: The merge currently only preserves
nonMutableInputConfig.test?.setupFiles when it's a string, dropping array-form
values; update the merge for setupFiles in the vitest plugin to normalize
nonMutableInputConfig.test?.setupFiles to an array (using Array.isArray or
similar) before spreading so both string and string[] are preserved and
concatenated with internalSetupFiles; locate the setupFiles merge expression
(referencing setupFiles, internalSetupFiles, and
nonMutableInputConfig.test?.setupFiles) and replace the conditional with a
normalized array merge.

---

Nitpick comments:
In `@scripts/build/utils/generate-bundle.ts`:
- Around line 210-213: The current construction uses
entries.browser.flatMap((entry) => entry.external) and then .filter(Boolean),
which doesn't narrow types under TS strict mode; change the flatMap to
explicitly coalesce undefined: use entries.browser.flatMap((entry) =>
entry.external ?? []) so the result is string[] (alongside
sharedOptions.external) and then you can remove the reliance on filter(Boolean);
update the external array assembly (the spread of sharedOptions.external and the
entries.flatMap) so the union is explicitly narrowed to string[].

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 429cc422-1f74-4eba-ab41-07f14d6a60f2

📥 Commits

Reviewing files that changed from the base of the PR and between 670866d and 4bdbd87.

📒 Files selected for processing (15)
  • code/addons/vitest/build-config.ts
  • code/addons/vitest/package.json
  • code/addons/vitest/src/postinstall.test.ts
  • code/addons/vitest/src/postinstall.ts
  • code/addons/vitest/src/updateVitestFile.test.ts
  • code/addons/vitest/src/vitest-plugin/index.ts
  • code/addons/vitest/src/vitest-plugin/setup-file-with-project-annotations.ts
  • code/addons/vitest/src/vitest-plugin/utils.ts
  • code/addons/vitest/templates/vitest.config.3.2.template.ts
  • code/addons/vitest/templates/vitest.config.4.template.ts
  • code/addons/vitest/templates/vitest.config.template.ts
  • code/addons/vitest/templates/vitest.workspace.template.ts
  • scripts/build/utils/entry-utils.ts
  • scripts/build/utils/generate-bundle.ts
  • scripts/tasks/sandbox-parts.ts
💤 Files with no reviewable changes (6)
  • code/addons/vitest/templates/vitest.config.3.2.template.ts
  • code/addons/vitest/templates/vitest.config.4.template.ts
  • code/addons/vitest/templates/vitest.workspace.template.ts
  • code/addons/vitest/templates/vitest.config.template.ts
  • code/addons/vitest/src/postinstall.test.ts
  • code/addons/vitest/src/postinstall.ts

@valentinpalkovic valentinpalkovic marked this pull request as draft March 5, 2026 11:30
…e and streamline project annotations handling
@valentinpalkovic valentinpalkovic force-pushed the valentin/remove-need-for-setup-file branch from 4bdbd87 to 4137924 Compare March 5, 2026 11:35
@valentinpalkovic valentinpalkovic marked this pull request as ready for review March 5, 2026 14:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/addons/vitest/src/vitest-plugin/index.ts`:
- Around line 316-324: The code passes previewOrConfigFile (an absolute
filesystem path) into import.meta.resolve inside the internalSetupFiles
construction, which fails because import.meta.resolve expects a module specifier
or file: URL; update the logic in the internalSetupFiles mapping to detect if
previewOrConfigFile is an absolute path and, for that case, convert it to a
file: URL (e.g., via pathToFileURL) and then call fileURLToPath, or bypass
import.meta.resolve and directly use
fileURLToPath(pathToFileURL(previewOrConfigFile)); keep using
import.meta.resolve for bare/relative specifiers and ensure the symbols
internalSetupFiles, previewOrConfigFile, import.meta.resolve, and fileURLToPath
are the points of change.

In `@code/addons/vitest/src/vitest-plugin/utils.ts`:
- Around line 42-52: The unguarded readFileSync inside the
hasStorybookAnnotations computation can throw if a setup file is
missing/unreadable; wrap the readFileSync call in a try/catch (or check
existence) so that failures simply return false for that entry instead of
propagating an exception. Update the hasStorybookAnnotations logic (referencing
userSetupFiles, hasStorybookAnnotations, readFileSync, setupFileContent,
finalOptions.configDir) so any read or access error for a setupFile is caught
and treated as “no annotations” (return false) to allow graceful config
detection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ffd87e1e-337f-43fe-980a-a0155692cb14

📥 Commits

Reviewing files that changed from the base of the PR and between 4bdbd87 and 016e90d.

📒 Files selected for processing (22)
  • code/addons/vitest/build-config.ts
  • code/addons/vitest/package.json
  • code/addons/vitest/src/postinstall.test.ts
  • code/addons/vitest/src/postinstall.ts
  • code/addons/vitest/src/updateVitestFile.test.ts
  • code/addons/vitest/src/vitest-plugin/index.ts
  • code/addons/vitest/src/vitest-plugin/setup-file-with-project-annotations.ts
  • code/addons/vitest/src/vitest-plugin/utils.ts
  • code/addons/vitest/templates/vitest.config.3.2.template.ts
  • code/addons/vitest/templates/vitest.config.4.template.ts
  • code/addons/vitest/templates/vitest.config.template.ts
  • code/addons/vitest/templates/vitest.workspace.template.ts
  • code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts
  • docs/_snippets/storybook-addon-a11y-test-setup.md
  • docs/writing-tests/accessibility-testing.mdx
  • docs/writing-tests/integrations/vitest-addon/index.mdx
  • scripts/build/utils/entry-utils.ts
  • scripts/build/utils/generate-bundle.ts
  • scripts/tasks/sandbox-parts.ts
  • test-storybooks/portable-stories-kitchen-sink/react-vitest-3/.storybook/vitest.setup.ts
  • test-storybooks/portable-stories-kitchen-sink/react/.storybook/vitest.setup.ts
  • test-storybooks/yarn-pnp/.storybook/vitest.setup.ts
💤 Files with no reviewable changes (9)
  • docs/writing-tests/integrations/vitest-addon/index.mdx
  • test-storybooks/yarn-pnp/.storybook/vitest.setup.ts
  • code/addons/vitest/src/postinstall.test.ts
  • code/addons/vitest/templates/vitest.config.template.ts
  • code/addons/vitest/src/postinstall.ts
  • code/addons/vitest/templates/vitest.workspace.template.ts
  • code/addons/vitest/templates/vitest.config.4.template.ts
  • docs/_snippets/storybook-addon-a11y-test-setup.md
  • code/addons/vitest/templates/vitest.config.3.2.template.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • scripts/build/utils/entry-utils.ts
  • scripts/build/utils/generate-bundle.ts
  • code/addons/vitest/src/vitest-plugin/setup-file-with-project-annotations.ts

Comment on lines +316 to +324
const internalSetupFiles = (
[
'@storybook/addon-vitest/internal/setup-file',
areProjectAnnotationRequired &&
'@storybook/addon-vitest/internal/setup-file-with-project-annotations',
isCSF4 && previewOrConfigFile,
].filter(Boolean) as string[]
).map((filePath) => fileURLToPath(import.meta.resolve(filePath)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Node.js import.meta.resolve absolute filesystem path Windows behavior

💡 Result:

import.meta.resolve() in Node.js returns an absolute URL string, not a native filesystem path. On Windows, local files resolve to file:///C:/... (forward slashes, file: scheme). Node documents this explicitly: ESM “are resolved and cached as URLs”, and import.meta.resolve(specifier) “returns the absolute URL string”. [1]

So if you need an absolute Windows path like C:\dir\file.js, convert the returned file URL with fileURLToPath():

import { fileURLToPath } from "node:url";

const url = import.meta.resolve("./dep.js");   // e.g. "file:///C:/proj/dep.js" on Windows
const path = fileURLToPath(url);              // "C:\\proj\\dep.js"

Avoid new URL(...).pathname on Windows, because it produces paths like /C:/Users/... (leading slash), which are not valid Win32 paths; fileURLToPath() handles this correctly. [2]

Sources: Node.js ESM docs (import.meta.resolve, URL-based resolution) [1], Windows path conversion caveat and fileURLToPath() example [2].

[1] (nodejs.org)
[2] (bero.dev)

Citations:


🌐 Web query:

Node.js ESM import.meta.resolve documentation specification

💡 Result:

import.meta.resolve is not part of the ECMAScript (TC39) language spec; TC39 only standardizes the import.meta syntax and leaves its properties up to the host environment [4], [5]. In practice, the closest “specification” is split between:

  • WHATWG HTML Standard (the web platform / browsers): defines the underlying “resolve a module specifier” algorithm (including import maps) that host resolution APIs build on [2]. MDN notes that the HTML standard defines import.meta.resolve using this module specifier resolution machinery [3].
  • Node.js documentation (Node’s host behavior): Node documents import.meta.resolve(specifier) as returning the resolved absolute URL string using Node’s full ESM resolution rules (including package resolution/exports), and documents caveats and a Node-only extra parameter behind a flag [1]. Node also publishes its ESM resolution algorithm specification (e.g., ESM_RESOLVE) in the same ESM docs page [1].

Primary references

  1. Node.js ESM docs (import.meta.resolve + “Resolution algorithm specification”) [1]
  2. WHATWG HTML Standard: “resolve a module specifier” algorithm [2]
  3. MDN: import.meta.resolve() (and its relationship to HTML / not being in ECMAScript) [3]
  4. MDN: import.meta (host-defined properties) [4]
  5. TC39 import.meta proposal/spec text (syntax only) [5]

Sources: [1] (nodejs.org) [2] (html.spec.whatwg.org) [3] (developer.mozilla.org) [4] (developer.mozilla.org) [5] (tc39.es)

Citations:


🌐 Web query:

import.meta.resolve accept absolute filesystem path input fails

💡 Result:

import.meta.resolve() resolves module specifiers, not raw OS filesystem paths. An absolute path like /Users/me/app/foo.js (or C:\...) is not a valid module specifier/URL, so resolution fails. import.meta.resolve() accepts relative specifiers (./foo.js), bare specifiers (pkg), or an absolute URL (not an absolute filesystem path). [1][2]

Fix (Node.js): convert the path to a file: URL first:

import { pathToFileURL, fileURLToPath } from "node:url";

const fsPath = "/Users/me/app/foo.js";       // or "C:\\Users\\me\\app\\foo.js"
const url = pathToFileURL(fsPath).href;      // "file:///Users/me/app/foo.js"

const resolvedUrl = import.meta.resolve(url);
const resolvedPath = fileURLToPath(resolvedUrl);

If you meant “resolve relative to this module”, pass a relative specifier:

const resolvedUrl = import.meta.resolve("./foo.js");

[2]

Sources: MDN import.meta.resolve() (accepted inputs) [1]; Node.js ESM docs for import.meta.resolve() (returns an absolute URL string; module-relative resolution) [2].

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve
[2] https://nodejs.org/download/release/v23.11.1/docs/api/esm.html#importmetaresolvespecifier


🌐 Web query:

Node.js import.meta.resolve expects module specifier not file path

💡 Result:

import.meta.resolve() takes a module specifier (the same kind of string you’d put in an import ... from "..."), not an OS file path like /tmp/x.js or C:\x.js. In Node.js, valid specifiers are typically relative ('./dep.js'), bare ('some-package'), or absolute URL specifiers such as a file: URL ('file:///opt/app/dep.js'). [1]

If you currently have a filesystem path, convert it to a file: URL first

import { pathToFileURL } from 'node:url';

const filePath = '/tmp/dep.js';
const specifier = pathToFileURL(filePath).href;

const resolved = import.meta.resolve(specifier);

Node explicitly recommends using url.pathToFileURL() when importing/resolving from a path (because ESM resolution is URL-based and involves encoding rules). [1]

If you meant “resolve a file relative to this module”

const resolved = import.meta.resolve('./dep.js');

Relative specifiers must include the file extension in Node’s ESM. [1]

Sources: Node.js ESM docs on specifier types, URL-based resolution, and import.meta.resolve(). [1]
[1] https://nodejs.org/download/nightly/v26.0.0-nightly2026022776215dc993/docs/api/esm.html


Passing absolute filesystem paths to import.meta.resolve() will fail.

import.meta.resolve() expects module specifiers (relative like './foo', bare like 'pkg', or file: URLs), not raw filesystem paths. The previewOrConfigFile value is an absolute path, so it cannot be passed directly to import.meta.resolve(). When CSF4 is enabled and a preview/config file is present, this will cause a runtime failure.

💡 Proposed fix
-      const internalSetupFiles = (
-        [
-          '@storybook/addon-vitest/internal/setup-file',
-          areProjectAnnotationRequired &&
-            '@storybook/addon-vitest/internal/setup-file-with-project-annotations',
-          isCSF4 && previewOrConfigFile,
-        ].filter(Boolean) as string[]
-      ).map((filePath) => fileURLToPath(import.meta.resolve(filePath)));
+      const internalSetupFiles = [
+        '@storybook/addon-vitest/internal/setup-file',
+        areProjectAnnotationRequired &&
+          '@storybook/addon-vitest/internal/setup-file-with-project-annotations',
+        isCSF4 && previewOrConfigFile,
+      ]
+        .filter((filePath): filePath is string => Boolean(filePath))
+        .map((filePath) =>
+          path.isAbsolute(filePath) ? filePath : fileURLToPath(import.meta.resolve(filePath))
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/addons/vitest/src/vitest-plugin/index.ts` around lines 316 - 324, The
code passes previewOrConfigFile (an absolute filesystem path) into
import.meta.resolve inside the internalSetupFiles construction, which fails
because import.meta.resolve expects a module specifier or file: URL; update the
logic in the internalSetupFiles mapping to detect if previewOrConfigFile is an
absolute path and, for that case, convert it to a file: URL (e.g., via
pathToFileURL) and then call fileURLToPath, or bypass import.meta.resolve and
directly use fileURLToPath(pathToFileURL(previewOrConfigFile)); keep using
import.meta.resolve for bare/relative specifiers and ensure the symbols
internalSetupFiles, previewOrConfigFile, import.meta.resolve, and fileURLToPath
are the points of change.

Comment on lines +42 to +52
const hasStorybookAnnotations = userSetupFiles.find((setupFile) => {
const hasStorybookSetupFileName = dirname(setupFile) === finalOptions.configDir;

if (!hasStorybookSetupFileName) {
return false;
}

// Check if the file contains setProjectAnnotations
const setupFileContent = readFileSync(setupFile, 'utf-8');
return setupFileContent.includes('setProjectAnnotations');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard setup-file reads to avoid hard failure during config detection.

Line 50 performs an unguarded readFileSync. If a configured setup file is missing or unreadable, this path throws and can fail initialization instead of gracefully skipping detection.

💡 Proposed fix
   const hasStorybookAnnotations = userSetupFiles.find((setupFile) => {
     const hasStorybookSetupFileName = dirname(setupFile) === finalOptions.configDir;
 
     if (!hasStorybookSetupFileName) {
       return false;
     }
 
     // Check if the file contains setProjectAnnotations
-    const setupFileContent = readFileSync(setupFile, 'utf-8');
-    return setupFileContent.includes('setProjectAnnotations');
+    try {
+      const setupFileContent = readFileSync(setupFile, 'utf-8');
+      return setupFileContent.includes('setProjectAnnotations');
+    } catch {
+      return false;
+    }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/addons/vitest/src/vitest-plugin/utils.ts` around lines 42 - 52, The
unguarded readFileSync inside the hasStorybookAnnotations computation can throw
if a setup file is missing/unreadable; wrap the readFileSync call in a try/catch
(or check existence) so that failures simply return false for that entry instead
of propagating an exception. Update the hasStorybookAnnotations logic
(referencing userSetupFiles, hasStorybookAnnotations, readFileSync,
setupFileContent, finalOptions.configDir) so any read or access error for a
setupFile is caught and treated as “no annotations” (return false) to allow
graceful config detection.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot bot commented Mar 6, 2026

Package Benchmarks

Commit: 5dbf094, ran on 6 March 2026 at 11:01:23 UTC

No significant changes detected, all good. 👏

@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22754954776

1 similar comment
@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22754954776

@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22756123941

@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22756222703

@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22756334816

@storybook-bot
Copy link
Copy Markdown
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/22756820074

@valentinpalkovic valentinpalkovic force-pushed the valentin/remove-need-for-setup-file branch from e73ac71 to 73af6b3 Compare March 6, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Docs]: It's unclear that addon-a11y + addon-vitest requires additional setup in vitest.setup.ts

4 participants