Skip to content

Test: Consider exports map#32157

Merged
yannbf merged 2 commits into
nextfrom
valentin/mocking-consider-exports-map
Jul 31, 2025
Merged

Test: Consider exports map#32157
yannbf merged 2 commits into
nextfrom
valentin/mocking-consider-exports-map

Conversation

@valentinpalkovic

@valentinpalkovic valentinpalkovic commented Jul 30, 2025

Copy link
Copy Markdown
Contributor

Closes #32151

What I did

Why

This change addresses a critical bug in our module mocking logic that occurred during production builds (vite build or webpack build). The previous implementation used require.resolve to determine the absolute path of mocked node_modules packages. However, require.resolve follows legacy CJS resolution rules and often fails to respect the "exports" map in a modern package's package.json.

This created a mismatch:

  • Bundler (Vite/Webpack): Correctly resolved a package like uuid to its modern ESM entry point (e.g., .../esm-browser/index.js).
  • Our Mocking Plugin: Incorrectly resolved the same package to its CJS entry point (e.g., .../cjs/index.js).

Because the absolute paths did not match, our mocking logic failed to identify and replace the module during the build process, causing mocks for packages like uuid to be ignored in the final bundle.

What

This PR updates the resolveMock utility to correctly and reliably resolve modern ESM packages by mimicking the bundler's own resolution strategy.

Prioritizes "exports" Map: The logic now first checks if a package's package.json contains an "exports" map.

Uses resolve.exports: If an "exports" map is present, it uses the resolve.exports package to parse it and find the correct ESM entry point, respecting conditions like "import" and "browser". This guarantees we resolve to the exact same file as Vite and Webpack.

Graceful Fallback: If no "exports" map is found, the resolver gracefully falls back to using the classic require.resolve. This ensures continued compatibility with older CJS-style packages.

This change makes our module resolution logic robust and consistent with modern bundler behavior, ensuring that mocks for third-party packages are applied correctly in all environments.

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

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

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.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

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

Greptile Summary

This PR fixes a critical bug in Storybook's module mocking system that caused third-party package mocks to fail during production builds (vite build or webpack build). The core issue was a resolution mismatch between modern bundlers and Storybook's mocking plugin:

  • Bundlers (Vite/Webpack): Correctly resolved packages like uuid to their modern ESM entry points using the "exports" map in package.json
  • Storybook's Mocking Plugin: Used require.resolve which follows legacy CJS resolution rules, resolving to different (CJS) entry points

This path mismatch prevented the mocking system from identifying and replacing modules during builds, causing mocks to be ignored in production.

The fix updates the resolveMock utility in code/core/src/core-server/mocking-utils/resolve.ts to:

  1. Prioritize exports maps: First checks if a package has an "exports" field in its package.json
  2. Use resolve.exports: Parses exports maps with proper ESM conditions ("import", "browser") to match bundler behavior
  3. Graceful fallback: Falls back to standard require.resolve for older CJS packages
  4. Handle scoped packages: Properly resolves @scope/package style imports

The changes also include comprehensive test coverage using the uuid package as a test case (which has modern exports maps), mock files, test stories, and sandbox configurations to validate the fix across different environments. The Vite plugin logic was also refined to ensure consistent path matching between development and production builds.

Confidence score: 4/5

• This PR addresses a well-defined bug with a targeted solution that maintains backward compatibility
• The core logic changes are sound but involve complex module resolution that could have edge cases
• The resolve.ts file needs careful attention as it handles the critical path resolution logic that could break mocking entirely if incorrect

@valentinpalkovic valentinpalkovic self-assigned this Jul 30, 2025
@valentinpalkovic valentinpalkovic added bug ci:normal Run our default set of CI jobs (choose this for most PRs). test utilities labels Jul 30, 2025

@greptile-apps greptile-apps Bot left a comment

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.

11 files reviewed, 1 comment

Edit Code Review Bot Settings | Greptile

const parts = path.split('/');
// For scoped packages like `@foo/bar`, the package name is the first two parts.
const packageName = path.startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0];
const entry = `.${path.slice(packageName.length)}`; // e.g., './add' from 'lodash-es/add'

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.

logic: Entry path calculation will produce incorrect results for root package imports. When path equals packageName (e.g., 'uuid'), path.slice(packageName.length) returns empty string, making entry just '.', but it should be '.' for root imports.

Suggested change
const entry = `.${path.slice(packageName.length)}`; // e.g., './add' from 'lodash-es/add'
const entry = path === packageName ? '.' : `.${path.slice(packageName.length)}`; // e.g., './add' from 'lodash-es/add'

@nx-cloud

nx-cloud Bot commented Jul 30, 2025

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 139af72

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 1m 14s View ↗

☁️ Nx Cloud last updated this comment at 2025-07-31 06:28:23 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal Run our default set of CI jobs (choose this for most PRs). test utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 9.1 beta - Vite - Third party module mocks with __mocks__ directory are not registered in built Storybooks

2 participants