Skip to content

Maintenance: Replace resolve and resolve.exports with oxc-resolver#34692

Merged
valentinpalkovic merged 11 commits into
nextfrom
valentin/replace-resolve-with-oxc-resolver
May 20, 2026
Merged

Maintenance: Replace resolve and resolve.exports with oxc-resolver#34692
valentinpalkovic merged 11 commits into
nextfrom
valentin/replace-resolve-with-oxc-resolver

Conversation

@valentinpalkovic
Copy link
Copy Markdown
Contributor

@valentinpalkovic valentinpalkovic commented May 3, 2026

Closes #

What I did

Stacked on top of #34625 (which introduces oxc-resolver and oxc-parser as runtime deps of code/core).

Now that oxc-resolver is available, the two remaining call sites of the legacy resolve and resolve.exports npm packages in code/core can be migrated. This PR removes both packages.

Why now

The change-detection PR (#34625) added oxc-resolver to code/core's runtime deps for its dependency-graph builder, but did not migrate the two pre-existing call sites that were still on the older resolvers. This PR closes that loop and lets us drop two transitive deps.

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

The existing GitDiffProvider.test.ts continues to pass (13/13). No new tests are added because the assertion would be "execa was called with a specific env" which over-specifies an implementation detail; the user-visible behavior is "concurrent commits no longer fail," which is verified manually.

Manual testing

  1. cd code && yarn storybook:ui (or run any sandbox dev server in a repo).
  2. In another terminal, repeatedly run touch code/core/src/foo.ts && git add code/core/src/foo.ts && git commit -m wip while the dev server is running.
  3. Before this fix: occasionally fails with fatal: Unable to create '.git/index.lock': File exists.
  4. After this fix: commits always succeed regardless of dev server activity.

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 publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Refactor
    • Switched to a new resolver that prefers ESM/browser entrypoints and uses a browser-first resolution order, with improved retry/fallback remapping for various JS/TS file extensions to reduce import resolution failures.
  • Chores
    • Removed unused development dependencies from the workspace setup.

Review Change Stack

Now that `oxc-resolver` is a runtime dependency of `code/core` (added in
the change-detection PR), the two remaining call sites of the legacy
`resolve` and `resolve.exports` packages can be migrated:

- `common/utils/interpret-files.ts`: `resolve.sync` + `packageFilter`
  (prefer `module` over `main`) → `ResolverFactory.sync` with
  `mainFields: ['module', 'main']`.
- `mocking-utils/resolve.ts`: `resolve.exports({ browser: true })` on a
  manually-fetched `package.json` → `ResolverFactory.sync` with
  `conditionNames: ['browser', 'import', 'module', 'default']`,
  `mainFields: ['browser', 'module', 'main']`, and
  `aliasFields: [['browser']]`. The `require.resolve` fallback for
  legacy package layouts is preserved.

`resolve` and `resolve.exports` are removed from `code/core`'s
dependencies.

The public `resolveImport(id, options)` signature changes from
`resolve.SyncOpts` to `{ basedir: string }`. Both internal callers
(`StoryIndexGenerator.resolveComponentPath`, `cachedResolveImport` in
the React renderer's component manifest) only pass `basedir`, so this
is a safe narrowing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic added maintenance User-facing maintenance tasks ci:normal labels May 3, 2026
@valentinpalkovic valentinpalkovic self-assigned this May 3, 2026
@valentinpalkovic valentinpalkovic moved this to Empathy Queue (prioritized) in Core Team Projects May 3, 2026
@valentinpalkovic valentinpalkovic moved this from Empathy Queue (prioritized) to In Progress in Core Team Projects May 3, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 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

Replaced resolve/resolve.exports with oxc-resolver and updated resolution logic in two modules to use resolver factories (ESM-preferring mainFields and browser-first conditions), preserved JS→TS/JSX→TSX fallback remapping, and added a require.resolve fallback for external modules.

Changes

Dependency & Resolver Migration to oxc-resolver

Layer / File(s) Summary
Dependency Removal
code/core/package.json
Removed resolve and resolve.exports from devDependencies.
Resolver Factory / Config
code/core/src/common/utils/interpret-files.ts, code/core/src/mocking-utils/resolve.ts
Imported ResolverFactory from oxc-resolver and created resolver instances with extensions: [...] and mainFields: ['module','main']; mocking-utils/resolve.ts uses browser-first conditionNames and aliasFields for legacy browser.
API Surface
code/core/src/common/utils/interpret-files.ts
Added export interface ResolveImportOptions { basedir: string }; changed resolveImport to accept ResolveImportOptions and introduced an internal resolveSync helper that throws a descriptive error on failure.
Core Resolution Logic
code/core/src/common/utils/interpret-files.ts
Replaced prior resolve usage with oxc-resolver factory; removed packageFilter approach and kept the fallback that remaps .js/.mjs/.cjs.ts and .jsx.tsx before retrying resolution.
External Module Resolution
code/core/src/mocking-utils/resolve.ts
Rewrote resolveExternalModule to use externalResolver.sync(root, path) and return result.path or fall back to require.resolve(..., { paths: [root] }); removed manual package.json/resolve.exports flow.
Tests / Documentation
(none changed)
No tests or docs updated in this PR.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 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.

Actionable comments posted: 1

🤖 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/core/src/common/utils/interpret-files.ts`:
- Around line 30-33: The ResolverFactory instantiated as importResolver lacks
conditionNames so packages using conditional exports won't resolve; update the
ResolverFactory call that creates importResolver (the ResolverFactory
instantiation near supportedExtensions/mainFields) to include conditionNames set
for server-side ESM (e.g., ['node','import']) so ESM conditional exports are
honored when resolving imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dabe5719-4118-433f-a4a6-b6a25956f9fc

📥 Commits

Reviewing files that changed from the base of the PR and between 4a248d8 and 72e6893.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • code/core/package.json
  • code/core/src/common/utils/interpret-files.ts
  • code/core/src/mocking-utils/resolve.ts
💤 Files with no reviewable changes (1)
  • code/core/package.json

Comment thread code/core/src/common/utils/interpret-files.ts
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented May 3, 2026

Package Benchmarks

Commit: 553f2d1, ran on 20 May 2026 at 12:15:33 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 72 72 0
Self size 20.30 MB 20.25 MB 🎉 -53 KB 🎉
Dependency size 36.17 MB 36.17 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 203 203 0
Self size 908 KB 908 KB 0 B
Dependency size 87.59 MB 87.54 MB 🎉 -53 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 0 B
Dependency size 86.08 MB 86.03 MB 🎉 -53 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 0 B
Dependency size 56.48 MB 56.43 MB 🎉 -53 KB 🎉
Bundle Size Analyzer node node

Base automatically changed from valentin/change-detection-perf to next May 5, 2026 14:14
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/core/src/common/utils/interpret-files.ts (1)

39-59: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Try all matching TypeScript source extensions in the fallback path.

This retry only remaps to one target (.ts, or .tsx for .jsx), so valid files like foo.tsx, foo.mts, or foo.cts still fail even though they are listed in supportedExtensions. That turns legitimate ESM-style imports into false negatives.

Suggested fix
 export function resolveImport(id: string, options: ResolveImportOptions): string {
   try {
     return resolveSync(id, options.basedir);
   } catch (error) {
     const ext = extname(id);
@@
-    const newId = ['.js', '.mjs', '.cjs'].includes(ext)
-      ? `${id.slice(0, -2)}ts`
-      : ext === '.jsx'
-        ? `${id.slice(0, -3)}tsx`
-        : null;
-
-    if (!newId) {
-      throw error;
-    }
-    return resolveSync(newId, options.basedir);
+    const candidates =
+      ext === '.js'
+        ? [`${id.slice(0, -2)}ts`, `${id.slice(0, -2)}tsx`]
+        : ext === '.mjs'
+          ? [`${id.slice(0, -3)}mts`, `${id.slice(0, -3)}mtsx`]
+          : ext === '.cjs'
+            ? [`${id.slice(0, -3)}cts`, `${id.slice(0, -3)}ctsx`]
+            : ext === '.jsx'
+              ? [`${id.slice(0, -3)}tsx`]
+              : [];
+
+    for (const candidate of candidates) {
+      try {
+        return resolveSync(candidate, options.basedir);
+      } catch {
+        // Try the next interpreted extension.
+      }
+    }
+
+    throw error;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/common/utils/interpret-files.ts` around lines 39 - 59, In
resolveImport, replace the single remap (newId) logic with trying all
TypeScript-related candidate extensions instead of one; when extname(id) is one
of '.js', '.mjs', '.cjs' or '.jsx' build a list of candidate replacements (e.g.
map '.js' family to ['.ts','.mts','.cts'] and '.jsx' to ['.tsx','.mts','.cts']
or use the module's supportedExtensions list) and iterate calling
resolveSync(candidateId, options.basedir) until one succeeds, returning the
resolved path; if none succeed rethrow the original caught error—use the
existing symbols resolveImport, resolveSync, extname and preserve the original
error behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@code/core/src/common/utils/interpret-files.ts`:
- Around line 39-59: In resolveImport, replace the single remap (newId) logic
with trying all TypeScript-related candidate extensions instead of one; when
extname(id) is one of '.js', '.mjs', '.cjs' or '.jsx' build a list of candidate
replacements (e.g. map '.js' family to ['.ts','.mts','.cts'] and '.jsx' to
['.tsx','.mts','.cts'] or use the module's supportedExtensions list) and iterate
calling resolveSync(candidateId, options.basedir) until one succeeds, returning
the resolved path; if none succeed rethrow the original caught error—use the
existing symbols resolveImport, resolveSync, extname and preserve the original
error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82dc3e88-53cf-469f-8662-22291378004e

📥 Commits

Reviewing files that changed from the base of the PR and between a1393f1 and c63e535.

📒 Files selected for processing (2)
  • code/core/src/common/utils/interpret-files.ts
  • code/core/src/mocking-utils/resolve.ts

Comment thread code/core/src/mocking-utils/resolve.ts Outdated
@github-actions github-actions Bot added the Stale label May 18, 2026
@valentinpalkovic valentinpalkovic added ci:normal maintenance User-facing maintenance tasks and removed maintenance User-facing maintenance tasks Stale ci:normal labels May 19, 2026
Comment thread code/core/src/common/utils/interpret-files.ts Outdated
Comment thread code/core/src/mocking-utils/resolve.ts Outdated
Comment thread code/core/src/mocking-utils/resolve.ts Outdated
Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

LGTM, but check the comment on extensions. We might need to do more to support community frameworks.

Comment thread code/core/src/mocking-utils/resolve.ts Outdated
Comment thread code/core/src/mocking-utils/resolve.ts Outdated
valentinpalkovic and others added 2 commits May 20, 2026 09:34
Apply suggestion from @Sidnioulz — there's no naming conflict with the
other ResolverFactory consumer (interpret-files.ts is a separate module),
so the `as OxcResolverFactory` alias is unnecessary noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address @Sidnioulz's review feedback. The two extension lists in
resolve.ts both feed sb.mock() resolution but had drifted (`.json` and
`.jsx` were missing from the require.resolve trial loop). Extract a
single `moduleExtensions` constant used by both `externalResolver` and
`resolveWithExtensions`.

Side effect: `resolveWithExtensions` now also tries `.json` and `.jsx`
— pure improvement, no previously successful resolution changes target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic merged commit 44a9a82 into next May 20, 2026
140 checks passed
@valentinpalkovic valentinpalkovic deleted the valentin/replace-resolve-with-oxc-resolver branch May 20, 2026 13:04
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects May 20, 2026
Sidnioulz pushed a commit that referenced this pull request May 21, 2026
Address @Sidnioulz's review feedback (PR #34692). Inline extension lists
were scattered across code/core with subtle drift between similar tasks.

Introduces `code/core/src/shared/constants/extensions.ts` with four
task-named constants:
- `jsModuleExtensions`         — .mjs/.js/.cjs only
- `jsTsSourceExtensions`       — JS/TS family incl. JSX/TSX
- `storybookConfigExtensions`  — main/preview/manager config files
- `userModuleExtensions`       — user code incl. JSON, .vue, .svelte

Migrates five consumers:
- common/utils/interpret-files.ts  → storybookConfigExtensions
  (keeps the public `supportedExtensions` re-export used by the
   react renderer's component manifest)
- mocking-utils/resolve.ts         → userModuleExtensions
- common/utils/validate-config.ts  → jsModuleExtensions
- shared/utils/module.ts           → jsModuleExtensions
- core-server/change-detection/parser-registry/builtins.ts
                                   → jsTsSourceExtensions

Left intentionally inline (different task / subtle behavior):
- bin/loader.ts                 — runtime ESM/CJS loader, narrower set
- builder-manager/index.ts      — esbuild bundler config
- telemetry/get-application-file-count.ts — git ls-files glob (no dots)

No behavioral changes; pure deduplication.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request May 22, 2026
25 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal maintenance User-facing maintenance tasks

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants