Skip to content

ADR-006: Vitest Browser Mode + React 19 CJS Interop Strategy

ADR-006: Vitest Browser Mode + React 19 CJS Interop Strategy

Section titled “ADR-006: Vitest Browser Mode + React 19 CJS Interop Strategy”

Status: Accepted Date: 2026-06-20 Deciders: Development team

Our Storybook component testing uses @storybook/addon-vitest with Vitest’s browser mode (Playwright + Chromium). When running test:storybook, we encountered two cascading module resolution failures:

  1. use-sync-external-store/shim/with-selector.js does not provide an export named useSyncExternalStoreWithSelector
  2. react-dom/index.js does not provide an export named flushSync

Both failures stem from the same root cause: React 19 ships CJS-only npm packages. The index.js entry points use module.exports = require('./cjs/...') re-export chains. Vite’s CJS-to-ESM converter cannot follow these chains to detect named exports.

We adopted a two-part fix:

1. @vitejs/plugin-react (standard approach)

Section titled “1. @vitejs/plugin-react (standard approach)”

Added to vitest.config.ts plugins array. Per Vitest browser mode docs, this is required for React projects — it handles react-dom CJS interop automatically.

2. Alias for use-sync-external-store/shim/with-selector (targeted workaround)

Section titled “2. Alias for use-sync-external-store/shim/with-selector (targeted workaround)”

The use-sync-external-store shim uses module.exports = require(...) to re-export its CJS build. Vite cannot follow this chain. We alias the import to the underlying CJS file where named exports are detectable:

vitest.config.ts
resolve: {
alias: {
"use-sync-external-store/shim/with-selector": path.join(
uSesRoot,
"cjs/use-sync-external-store-shim/with-selector.development.js",
),
},
},

This is a known React packaging limitation (facebook/react#24590). React added proper exports fields in PR #25231, but the underlying CJS chain still trips Vite.

OptionVerdict
@storybook/test-runner (Jest + Playwright)Avoids CJS issues but is slower, requires running Storybook server separately, and Storybook themselves recommend Vitest for Vite projects
jsdom environment instead of browserNo real CSS rendering, no real user events — defeats the purpose of component tests
Do nothing (tests were already broken)Not acceptable — tests provide value and should run in CI
React 18 instead of React 19React 18 has the same CJS packaging; downgrading doesn’t help
  • Storybook Vitest tests now pass: pnpm test:storybook runs 55 test files in Chromium via Playwright
  • The alias is a single file path — well-documented with inline comments linking to the React issues
  • No runtime impact — the alias only affects the test environment