Skip to content

fix(trace): resolve package import wildcard trailers - #66

Merged
pi0 merged 2 commits into
unjs:mainfrom
cmpadden:fix/nft-package-import-wildcard-trailers
Jul 27, 2026
Merged

fix(trace): resolve package import wildcard trailers#66
pi0 merged 2 commits into
unjs:mainfrom
cmpadden:fix/nft-package-import-wildcard-trailers

Conversation

@cmpadden

@cmpadden cmpadden commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Note

Edited by @pi0x (claude) in 8ae0920: swapped the @vercel/nft patch for a fallback resolver (see Update below). Reviewed and implemented with AI assistance (Claude Code); all changes were verified locally.

Summary

  • resolve package import/export wildcard patterns with trailers such as #*.js, which nft does not match
  • match both the pattern prefix and trailer and substitute only the captured wildcard
  • use Node-compatible pattern precedence
  • add an nf3 output-level regression that verifies the internal target is copied and remains importable

Context

NFT currently handles wildcard keys only when they end in *. A valid package import map such as:

{
  "imports": {
    "#*.js": "./runtime/*.js"
  }
}

therefore fails to trace #internal/marker.js. nf3 copies the package entry and manifest but omits the internal target, resulting in ERR_MODULE_NOT_FOUND at runtime.

Update

The original approach patched the bundled @vercel/nft@1.10.2 via pnpm.patchedDependencies. That patch has been replaced with a fallback resolver in src/trace.ts:

nftOptions.resolve ??= async (id, parent, job, cjsResolve) => {
  try {
    return await nftResolve(id, parent, job, cjsResolve);
  } catch (error) {
    const resolved = resolveModulePath(id, {
      from: parent,
      conditions: [...(nftOptions.conditions || []), cjsResolve ? "require" : "import"],
      try: true,
    });
    if (!resolved) throw error;
    return job.realpath(resolved, parent);
  }
};

nft exports its own resolver (exports.resolve = resolveDependency), so it stays the primary resolver and exsolve — which implements the Node resolution algorithm — is only consulted for specifiers nft throws on.

Why this over the patch:

  • No patch maintenance. An exact-version patchedDependencies key hard-fails installs with ERR_PNPM_UNUSED_PATCH the moment the nft version moves, which would break every renovate PR (@vercel/nft* is in minimumReleaseAgeExclude, so those land quickly).
  • Purely additive. The fallback only runs on nft's throw path, so nothing nft already resolves changes behaviour — it only turns a "Failed to resolve dependency" warning into a resolved file. It becomes a no-op once fix: resolve package import wildcard trailers vercel/nft#604 lands, and can be dropped then.
  • Wider coverage. Wildcard imports whose target is another package ("#utils/*": "@fixture/nitro-utils/*") failed even with the patch applied, because nft's wildcard branch only handles targets starting with ./ while its exact-match branch handles bare specifiers. exsolve handles both.

exsolve had the same defect in its imports matching (key.slice(0, -1) instead of key.slice(0, patternIndex)); fixed in exsolve 1.1.1, which this PR bumps to. exsolve* was added to minimumReleaseAgeExclude so the release installs.

Regression testing

The @fixture/imports-wildcard package exports an entry that imports #internal/marker.js through a #*.js mapping, and #utils/extra through a "#utils/*": "@fixture/nitro-utils/*" mapping to another package. The nf3 test traces the package, asserts both targets exist in the emitted node_modules, and imports the copied entry.

Without the fallback the test fails with ENOENT … runtime/internal/marker.js; with it, both cases resolve.

Validation

  • pnpm vitest run — 43 tests passed
  • pnpm test:types
  • pnpm lint
  • pnpm build — bundled dist size check passed (547 kB / 155 files)

Summary by CodeRabbit

  • Bug Fixes

    • Improved module tracing for wildcard import and export patterns.
    • Added fallback resolution for cases where certain package paths cannot be resolved automatically.
    • Preserved correct default and named exports when tracing package imports.
  • Tests

    • Added coverage for wildcard imports, external targets, and runtime marker generation.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

traceNodeModules now falls back from @vercel/nft resolution to resolveModulePath for unresolved wildcard import targets. The exsolve dependency and release-age exclusions are updated, and a fixture test verifies traced files and exports.

Changes

Wildcard import tracing

Layer / File(s) Summary
Resolver fallback integration
src/trace.ts, package.json, pnpm-workspace.yaml
traceNodeModules retries failed nft resolutions through resolveModulePath with the active import or require condition; exsolve is upgraded and excluded from the minimum release-age rule.
Trace integration and fixture validation
test/fixture/package-imports.mjs, test/trace.test.ts
The fixture re-exports wildcard-import values, and the test verifies the traced marker file plus the default and named exports.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • unjs/exsolve#56 — Covers the exsolve wildcard imports trailer resolution bug addressed by the dependency upgrade and fallback test.

Possibly related PRs

  • unjs/nf3#35 — Also changes src/trace.ts to use resolveModulePath during node module tracing.
  • unjs/nf3#58 — Also adjusts resolver conditions in the traceNodeModules resolution flow.
  • unjs/nf3#61 — Also adds resolveModulePath-based fallback logic to tracing resolution.

Suggested reviewers: pi0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing trace resolution for package import wildcard trailers.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Replace the `@vercel/nft` pnpm patch with a fallback resolver, so the fix
does not have to be re-applied on every nft release (an exact-version
`patchedDependencies` key hard-fails installs with ERR_PNPM_UNUSED_PATCH
as soon as the version moves).

nft exports its own resolver, so `nft.resolve` can wrap it and only fall
back to exsolve — which implements the Node resolution algorithm — for
specifiers nft throws on. exsolve had the same defect in its `imports`
matching and fixes it in 1.1.1 (unjs/exsolve#56).

This is purely additive: nft stays the primary resolver, so the fallback
only ever turns a "Failed to resolve dependency" warning into a resolved
file, and becomes a no-op once vercel/nft#604 lands.

It also covers a case the patch did not: wildcard imports whose target is
another package (`"#utils/*": "@fixture/nitro-utils/*"`). nft's wildcard
branch only handles targets starting with `./`, so those stayed
unresolved even with the patch applied. The fixture and test now assert
both that and the original `"#*.js": "./runtime/*.js"` trailer case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pnpm-workspace.yaml`:
- Line 10: Update the minimumReleaseAgeExclude entry in pnpm-workspace.yaml from
the broad exsolve* glob to the exact exsolve package name, preserving the
release-age bypass only for the declared dependency.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc42f7b3-4344-49a7-b1e1-d4e830234cfa

📥 Commits

Reviewing files that changed from the base of the PR and between ec3b15c and 8ae0920.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • test/fixture/node_modules/@fixture/imports-wildcard/index.mjs is excluded by !**/node_modules/**
  • test/fixture/node_modules/@fixture/imports-wildcard/package.json is excluded by !**/node_modules/**
📒 Files selected for processing (5)
  • package.json
  • pnpm-workspace.yaml
  • src/trace.ts
  • test/fixture/package-imports.mjs
  • test/trace.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/fixture/package-imports.mjs
  • test/trace.test.ts

Comment thread pnpm-workspace.yaml
@pi0
pi0 merged commit 87ad236 into unjs:main Jul 27, 2026
4 checks passed
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.70%. Comparing base (a81bf79) to head (8ae0920).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #66      +/-   ##
==========================================
+ Coverage   80.44%   80.70%   +0.26%     
==========================================
  Files           5        5              
  Lines         588      596       +8     
  Branches      170      172       +2     
==========================================
+ Hits          473      481       +8     
  Misses         84       84              
  Partials       31       31              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants