Skip to content

fix(externals): force-trace named traceDeps to fix pnpm nested deps#4391

Open
pi0x wants to merge 4 commits into
mainfrom
fix/trace-include
Open

fix(externals): force-trace named traceDeps to fix pnpm nested deps#4391
pi0x wants to merge 4 commits into
mainfrom
fix/trace-include

Conversation

@pi0x

@pi0x pi0x commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Closes #4372

Problem

Under pnpm, transitive dependencies are not hoisted to the top-level node_modules — a native, non-bundleable dependency (e.g. sharp / bcrypt) that is only an indirect dependency lives exclusively under node_modules/.pnpm/.... nft can't statically detect such packages when they're loaded dynamically (native bindings), so they're dropped from the trace and the build fails / ships incomplete output. The known workaround was pnpm's publicHoistPattern.

Fix

resolveTraceDeps() now also returns traceInclude — the resolved named deps (builtins + user traceDeps, negations removed, RegExp entries excluded since they can't be resolved by name). The buildEnd handler forwards them to nf3's traceNodeModules via its new traceInclude option.

nf3 resolves each name from rootDir and from the roots of traced packages that declare it as a dependency — so a pnpm nested dep resolves from its dependent's real .pnpm location and gets force-traced (native binaries included).

Depends on

Draft until that nf3 change is released and the nf3 dependency here is bumped to a version that includes it (otherwise CI typecheck fails against the published types).

Tests

test/unit/trace-deps.test.ts — added coverage for the traceInclude output (builtins + user, RegExp excluded, negation handling). All unit tests pass.

Pass the resolved named `traceDeps` (builtins + user, RegExp excluded) to
nf3's `traceNodeModules` via the new `traceInclude` option. nft cannot
statically detect dynamically-loaded packages (e.g. native bindings), and
under pnpm such a nested dependency is not hoisted — it only resolves from
the dependent package's real `.pnpm` location, which nf3 now handles.

Requires nf3 with `traceInclude` support (unjs/nf3#50).

Closes #4372
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitro.build Ready Ready Preview, Comment Jun 29, 2026 7:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

resolveTraceDeps now computes a traceInclude list containing only non-RegExp resolved dependency names and returns it. The buildEnd hook in externals() forwards this traceInclude into the traceNodeModules call. Three unit tests are added to validate the filtering behavior.

Changes

traceInclude computation and forwarding

Layer / File(s) Summary
resolveTraceDeps and buildEnd wiring
src/build/plugins/externals.ts
resolveTraceDeps filters resolved deps to non-RegExp entries, returns them as traceInclude; buildEnd passes resolved?.traceInclude to traceNodeModules.
Unit tests for traceInclude
test/unit/trace-deps.test.ts
Three tests assert named deps appear in traceInclude, RegExp and negated entries are excluded, and traceInclude is undefined when all deps are negated.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

  • nitrojs/nitro#3923: Modifies the same externals tracing pipeline and traceNodeModules call in src/build/plugins/externals.ts.
  • nitrojs/nitro#4175: Directly modifies resolveTraceDeps() and the buildEnd trace configuration in the same file, adding fullTraceInclude and related trace-deps parsing logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commits and clearly matches the pnpm nested dependency tracing fix.
Description check ✅ Passed The description directly explains the traceDeps/nf3 fix and matches the code and tests in this PR.
Linked Issues check ✅ Passed The changes address #4372 by forwarding named traceDeps to traceNodeModules so pnpm nested deps can be traced.
Out of Scope Changes check ✅ Passed The diff stays focused on traceInclude plumbing and unit coverage, with no obvious unrelated changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/trace-include

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.

@pi0 pi0 marked this pull request as ready for review June 29, 2026 19:10
@pi0 pi0 self-requested a review as a code owner June 29, 2026 19:10

@coderabbitai coderabbitai 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.

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 `@test/unit/trace-deps.test.ts`:
- Around line 43-50: The trace deps test is too loose because it only checks
that traceInclude entries are strings, which would still pass if the RegExp
selector were incorrectly stringified. Tighten the assertion in
trace-deps.test.ts around resolveTraceDeps to verify the exact traceInclude
membership and count for the ["my-pkg", /my-.*-pkg/] case, using the existing
defaults fixture and the resolveTraceDeps result to ensure only the expected
builtins plus "my-pkg" are present.
🪄 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: d67ddb6c-1c92-46e6-a3e8-a797f95e462a

📥 Commits

Reviewing files that changed from the base of the PR and between 28fab16 and 848ff04.

📒 Files selected for processing (2)
  • src/build/plugins/externals.ts
  • test/unit/trace-deps.test.ts

Comment on lines +43 to +50
it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => {
const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults);
expect(result.traceInclude).toContain("sharp");
expect(result.traceInclude).toContain("canvas");
expect(result.traceInclude).toContain("my-pkg");
// RegExp entries cannot be resolved by name and must be excluded
expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true);
});

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the exact traceInclude membership.

This test doesn't really prove the RegExp selector is excluded. An implementation that accidentally stringifies the regex would still pass typeof d === "string". Please assert the final members/count instead.

Suggested test tightening
   it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => {
     const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults);
-    expect(result.traceInclude).toContain("sharp");
-    expect(result.traceInclude).toContain("canvas");
-    expect(result.traceInclude).toContain("my-pkg");
-    // RegExp entries cannot be resolved by name and must be excluded
-    expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true);
+    expect(result.traceInclude).toEqual(
+      expect.arrayContaining(["sharp", "canvas", "my-pkg"])
+    );
+    expect(result.traceInclude).toHaveLength(3);
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => {
const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults);
expect(result.traceInclude).toContain("sharp");
expect(result.traceInclude).toContain("canvas");
expect(result.traceInclude).toContain("my-pkg");
// RegExp entries cannot be resolved by name and must be excluded
expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true);
});
it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => {
const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults);
expect(result.traceInclude).toEqual(
expect.arrayContaining(["sharp", "canvas", "my-pkg"])
);
expect(result.traceInclude).toHaveLength(3);
});
🤖 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 `@test/unit/trace-deps.test.ts` around lines 43 - 50, The trace deps test is
too loose because it only checks that traceInclude entries are strings, which
would still pass if the RegExp selector were incorrectly stringified. Tighten
the assertion in trace-deps.test.ts around resolveTraceDeps to verify the exact
traceInclude membership and count for the ["my-pkg", /my-.*-pkg/] case, using
the existing defaults fixture and the resolveTraceDeps result to ensure only the
expected builtins plus "my-pkg" are present.

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4391

commit: f33f359

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.

traceDeps fails to trace nested dependencies in .pnpm folder

2 participants