Skip to content

fix(plugins): resolve optimized imports past local barrels#847

Open
llc1123 wants to merge 13 commits intocloudflare:mainfrom
llc1123:fix/issue-845-optimize-imports
Open

fix(plugins): resolve optimized imports past local barrels#847
llc1123 wants to merge 13 commits intocloudflare:mainfrom
llc1123:fix/issue-845-optimize-imports

Conversation

@llc1123
Copy link
Copy Markdown

@llc1123 llc1123 commented Apr 15, 2026

Summary

  • fix optimize-imports so optimized package imports keep following local re-export chains to the concrete leaf module instead of stopping at an intermediate barrel like antd/es/button/index.js
  • flatten local export * boundaries inside optimized packages before downstream graph analysis so @vitejs/plugin-rsc no longer sees the issue-845 client-boundary shape
  • add both a targeted transform regression and an App Router production-build regression covering the antd repro path

Validation

  • ./node_modules/.bin/vp test run tests/optimize-imports.test.ts -t "multi-hop antd barrel"
  • ./node_modules/.bin/vp test run tests/optimize-imports-build.test.ts

Issue

Fixes #845.

Current solution

The bug happened because vinext:optimize-imports used to stop at an intermediate local barrel (antd/es/button/index.js). That file is a "use client" boundary with export *, so @vitejs/plugin-rsc later failed with unsupported ExportAllDeclaration.

The fix keeps the rewrite generic rather than antd-specific:

  • recursively resolve local re-exports until the concrete implementation file is reached
  • safely flatten local wildcard re-exports inside optimized packages before plugin-rsc inspects them
  • fall back to a conservative direct-export map when parsing a JSX-bearing .js leaf fails, so the local-barrel walk can still reach the leaf module

This means the app import now rewrites to the concrete leaf module instead of leaking the intermediate export * barrel into the RSC build.

llc1123 and others added 2 commits April 15, 2026 10:25
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 15, 2026

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

The core approach is sound — recursively resolving past intermediate local barrels (like antd/es/button/index.js) to the concrete leaf module, and flattening export * boundaries before plugin-rsc inspects them. The fix is well-targeted to the issue-845 repro shape and the new test coverage is good.

However, there is one significant behavioral regression that needs to be addressed before merge.

Critical: enforce: "pre" breaks parseAst on JSX-containing user files

The old plugin comment said:

No enforce — runs after JSX transform so parseAst gets plain JS.

The switch to enforce: "pre" means the plugin now runs before the React JSX transform. Vite's parseAst does not handle JSX syntax by default:

parseAst('<div/>');                   // throws: Unexpected JSX expression
parseAst('<div/>', { lang: 'tsx' });  // works!

Any user .tsx/.jsx file containing JSX that also imports from an optimized package (the most common real-world case — e.g. import { Button } from "antd" in a component with return <Button />) will fail to parse, hit the catch on line 924, and return null — silently skipping the barrel import optimization for that file.

The wildcard flattening (the main fix in this PR) still works because it targets pre-compiled .js files inside node_modules, but the import rewriting for user code now silently degrades for any file containing JSX.

The fix is straightforward: detect the file extension and pass { lang: 'tsx' } to parseAst when the file is .tsx or .jsx.

The existing unit tests don't catch this because they all pass pure import statements (no JSX) as the code to transform — which doesn't reflect real usage.

Minor: duplicated candidate extensions list

The new localModuleCandidates() helper (lines 112-131) is a clean extraction, but the identical inline list at lines 632-652 in the existing export * wildcard resolution in buildExportMapFromFile was not updated to use it. This is a missed DRY opportunity that should be cleaned up since you're already extracting that helper in this PR.

Other observations

  1. The new client-side guard (lines 1181-1188) that skips rewrites with bare sub-package specifiers is a nice safety measure but isn't directly tested.

  2. buildSafeWildcardExportMap bailing on non-relative export * sources (line 222) is correct and conservative — a brief comment explaining why would help future readers.

  3. buildFallbackExportMap regex patterns are intentionally conservative (e.g. export default only matches identifier form, not export default function). This is fine as a fallback path.

  4. The build integration test only checks that output files exist — it doesn't verify the import was actually rewritten to the concrete leaf module. A stronger assertion would catch the JSX parsing regression.

Comment thread packages/vinext/src/plugins/optimize-imports.ts
Comment thread tests/optimize-imports.test.ts Outdated
Comment thread tests/optimize-imports-build.test.ts
@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 15, 2026

The review was submitted successfully. Here's a summary of what I flagged:

Critical issue: enforce: "pre" breaks parseAst on JSX-containing user files

The switch from no enforce to enforce: "pre" means the plugin runs before the React JSX transform. Vite's parseAst (Rolldown's parser) can't parse JSX by default, so any .tsx/.jsx user file containing JSX + a barrel import silently skips optimization. The fix is to pass { lang: 'tsx' } to parseAst when processing .tsx/.jsx files. I verified this works: parseAst(code, { lang: 'tsx' }) handles JSX correctly.

The wildcard flattening (the main fix) still works because it operates on pre-compiled .js files inside node_modules, but the import rewriting path for user code regresses.

Minor issues:

  • The new localModuleCandidates() helper duplicates an identical inline list in buildExportMapFromFile that wasn't updated to use it
  • The test code only passes pure imports (no JSX) so it doesn't exercise the real-world scenario
  • The build test only checks file existence, not that the import was actually rewritten

github run

llc1123 and others added 3 commits April 15, 2026 17:26
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 15, 2026

Addressed the review items in three focused commits:

  • fix(plugins): parse JSX before optimize-import rewriting
    • uses JSX-aware parseAst handling for .tsx/.jsx inputs while keeping the existing fallback behavior for unparseable .js leaf modules
    • deduplicates the inline local module candidate list via localModuleCandidates()
  • test: cover JSX-bearing optimize-import transforms
    • adds a real TSX user-file regression so optimize-import rewriting is exercised with JSX in the transformed source
  • test: strengthen optimize-imports build assertions
    • upgrades the build regression to read bundled output and assert the barrel shape no longer leaks into the build

Validation rerun:

  • ./node_modules/.bin/vp test run tests/optimize-imports.test.ts -t "issue-845 multi-hop antd barrel|keeps optimizing issue-845 imports from JSX-bearing TSX user files"
  • ./node_modules/.bin/vp test run tests/optimize-imports-build.test.ts

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

The follow-up commits address the critical enforce: "pre" / JSX parsing regression from the previous review. parseAstWithFileLang is a clean solution, the JSX-bearing TSX test exercises the real-world scenario, and the build test now checks actual bundle content instead of just file existence. The core fix (recursive resolution past intermediate barrels + wildcard flattening) is well-designed and correctly targeted.

A few items remain worth discussing — two functional concerns and one minor nit.

Comment thread packages/vinext/src/plugins/optimize-imports.ts Outdated
Comment thread packages/vinext/src/plugins/optimize-imports.ts
if (!nestedMap) return null;
}

return buildExportMapFromFile(filePath, readFile, cache, new Set<string>(), content);
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.

The validation loop (lines 228-243) recursively calls buildSafeWildcardExportMap on each export * target, checking the result is non-null, then discards those results. Line 245 calls buildExportMapFromFile which re-parses and re-walks the same files.

Correct and safe — the validation pass ensures early bail for unflattenable chains. The exportMapCache means the individual file parsing is cached, so the cost is mainly the extra traversal, not re-parsing. Not blocking, just noting for potential future optimization.

`${subPath}/index.mts`,
`${subPath}/index.cts`,
];
const candidates = localModuleCandidates(subPath);
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.

Good use of the extracted localModuleCandidates helper here (this was flagged as a DRY opportunity in the previous review).

One thing to note: the wildcard merge path here (lines 646-660, which copy sub-module entries into the parent export map) does not apply resolveConcreteLocalEntry to the merged entries. The ExportNamedDeclaration path (around line 685) does. This means if a wildcard sub-module itself re-exports through another intermediate barrel, the entries won't be chased to the leaf.

This works for the antd shape because the root barrel uses named re-exports (export { Button } from "./es/button/index.js"), but a package structured as export * from "./group" where group/index.js has export { Foo } from "./foo/index.js" could still stop at the intermediate. Worth tracking as a follow-up if more packages hit this shape.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 15, 2026

The review was submitted successfully. Here's a summary of what was posted:

Review summary: The PR is in good shape after the follow-up commits addressing the prior review's critical JSX parsing regression. The core fix — recursively resolving past intermediate barrels and flattening export * boundaries — is well-designed and correctly targeted to the issue-845 repro.

Comments posted (4):

  1. buildFallbackExportMap spurious "function" export (line 200) — The export default regex captures "function" as the identifier for export default function Foo() {}, recording a phantom entry. Suggested filtering out function/class keywords.

  2. buildSafeWildcardExportMap bail comment clarity (lines 228-233) — The return null on non-relative export * bails on the entire file, which is correct but could be made more explicit in the comment.

  3. Duplicated work in buildSafeWildcardExportMap (line 245) — The validation pass discards its results, then buildExportMapFromFile re-walks the same files. Noted as a potential optimization, not blocking (the exportMapCache mitigates re-parsing cost).

  4. resolveConcreteLocalEntry not applied to wildcard merges (line 643) — The wildcard export * merge path copies entries without chasing through intermediate barrels, unlike the ExportNamedDeclaration path. Works for the antd case but could miss deeper barrel chains via wildcard. Flagged as a follow-up.

github run

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 15, 2026

Follow-up fixes for the latest review:

  • fix(plugins): avoid phantom fallback exports
    • prevents buildFallbackExportMap from recording phantom "function" / "class" exports when matching export default function ... fallback cases
    • clarifies that buildSafeWildcardExportMap bails on the entire file when any wildcard target is external
    • adds a focused regression covering the fallback path with a JSX-bearing .js module so we exercise the exact parse-failure shape under discussion

Validation rerun:

  • ./node_modules/.bin/vp test run tests/optimize-imports.test.ts -t "does not record phantom function/class exports in fallback default-export parsing|issue-845 multi-hop antd barrel|keeps optimizing issue-845 imports from JSX-bearing TSX user files"
  • ./node_modules/.bin/vp test run tests/optimize-imports-build.test.ts

I left the extra traversal note and the wildcard-merge resolveConcreteLocalEntry gap as follow-up scope for now, since they were called out as non-blocking / future-shape concerns rather than regressions in the current issue-845 path.

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 15, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@847

commit: 277502c

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

This is a well-executed fix for issue-845. The core approach — recursively resolving through intermediate local barrels to the concrete leaf module, and flattening export * boundaries before plugin-rsc inspects them — is sound and correctly targeted. The iterative review cycle addressed the critical enforce: "pre" / JSX regression and the phantom "function" export issue. Test coverage is good across unit, transform, and build dimensions.

A few items worth flagging:

Functional concerns

  1. Client environment behavior change — The old code returned null early for env?.name === "client", skipping the entire transform. The new code lets client files proceed into wildcard flattening and into the import rewrite loop, only bailing at the per-import level (line 1175) for bare specifiers. This is intentional per the PR, but the existing test "skips transform on client environment" (line 785 in optimize-imports.test.ts) still passes only because lucide-react isn't installed so the barrel map is empty. The test name is now misleading — the client doesn't fully skip the transform anymore; it skips bare sub-package rewrites. Consider renaming the test or adding a case that verifies the client does get wildcard rewrites when the file is inside an optimized package.

  2. buildSafeWildcardExportMap circular reference handling — When visited.has(filePath) is true, it returns null, which propagates up as a bail signal causing the entire flattening to be abandoned. This is safe but conservative — a circular export * chain causes the entire file to be left untouched. The buildExportMapFromFile version handles cycles differently (returns empty map, which is non-fatal). The inconsistency is fine for correctness but could surprise future maintainers.

Minor / non-blocking

  1. The remaining items from prior reviews (wildcard-merge resolveConcreteLocalEntry gap at the export * merge path, duplicated traversal in buildSafeWildcardExportMap) were correctly deferred as non-blocking follow-ups.

Overall this is in good shape. The core fix is well-designed, the follow-up commits addressed all critical review items, and the test coverage exercises the real failure mode from issue-845.

const source = entry.source.startsWith("/")
? toRelativeModuleSpecifier(normalizedId, entry.source)
: entry.source;
if (!source) {
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.

toRelativeModuleSpecifier always returns a string (it either prepends ./ or returns the relative path as-is), so !source is always false here. This guard is dead code.

Not blocking — it's harmless as a defensive check, but worth noting for clarity.

if (!nestedMap) return null;
}

return buildExportMapFromFile(filePath, readFile, cache, new Set<string>(), content);
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.

Nit: buildSafeWildcardExportMap passes a fresh new Set<string>() to buildExportMapFromFile for visited, meaning the cycle-detection done by the outer visited set (line 214) is not shared with the inner call. This is correct — the outer visited guards the validation pass, and the inner visited guards the export-map-building pass — but it means the same files can be visited twice (once for validation, once for building). The exportMapCache mitigates re-parsing cost, so this is a performance nit, not a correctness issue.

`${subPath}/index.mts`,
`${subPath}/index.cts`,
];
const candidates = localModuleCandidates(subPath);
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.

Good use of the extracted localModuleCandidates helper (DRY cleanup from prior review).

As noted previously: the wildcard merge path here (lines 657-659, which copy sub-module entries into the parent export map) does not apply resolveConcreteLocalEntry to the merged entries, unlike the ExportNamedDeclaration path (around line 691). This means if a wildcard sub-module itself re-exports through another intermediate barrel, the entries won't be chased to the leaf. Works for the antd shape; flagging for visibility as a follow-up if more packages surface with deeply nested wildcard-only chains.

candidateContent,
);
const nextEntry = subMap.get(exportName);
if (!nextEntry) return entry;
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.

When buildExportMapFromFile returns a map but the target exportName isn't in it, we return the original entry without trying subsequent candidates from localModuleCandidates. This is fine for real-world package structures (the first matching file at a given path is the right one), but it's a subtlety worth being aware of — a barrel re-export pointing at a directory path like ./button where button.js exists but doesn't export Button (while button/index.js does) would stop at the wrong file.

The break-on-first-match semantic is inherited from the existing wildcard resolution and is reasonable.

Comment on lines +531 to +534
const subMap = await buildExportMapFromFile(
candidate,
readFile,
cache,
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.

Minor: passing new Set<string>() here means resolveConcreteLocalEntry won't benefit from the outer visited set used in buildExportMapFromFile. Since this is a different traversal (chasing through local re-exports to find the leaf, not building the original export map), a fresh set is correct. Just noting that if a deeply nested chain re-visits the same file via different paths, the cache hit in buildExportMapFromFile prevents re-parsing.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 15, 2026

The review was submitted successfully. Here's a summary of what was posted:

Review summary: The PR is in good shape. The core fix — recursively resolving past intermediate local barrels to the concrete leaf module, and flattening export * boundaries before plugin-rsc inspects them — is well-designed and correctly targeted to the issue-845 repro.

5 inline comments posted:

  1. Dead code guard at line 962toRelativeModuleSpecifier always returns a string, so the !source check is unreachable.

  2. Dual traversal in buildSafeWildcardExportMap (line 248) — The validation pass and the building pass use separate visited sets, meaning files can be visited twice. The exportMapCache mitigates re-parsing cost; this is a performance nit, not a correctness issue.

  3. Wildcard merge doesn't apply resolveConcreteLocalEntry (line 646) — The export * merge path copies entries without chasing through intermediate barrels, unlike the ExportNamedDeclaration path. Works for the antd shape; flagged as follow-up.

  4. resolveConcreteLocalEntry break-on-first-match (line 539) — If the first matching file doesn't export the target name, subsequent candidates aren't tried. Fine for real-world packages but worth being aware of.

  5. Fresh visited set in resolveConcreteLocalEntry (lines 531-534) — Correct since this is a different traversal (chasing to leaf) than the original export map build.

Top-level concerns:

  • The "skips transform on client environment" test is now misleadingly named since the client no longer early-returns — it now proceeds through the transform and only bails on bare sub-package specifiers.
  • buildSafeWildcardExportMap returns null (bail) on circular references, while buildExportMapFromFile returns an empty map (non-fatal). The inconsistency is safe but worth documenting.

github run

llc1123 and others added 4 commits April 16, 2026 00:45
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 15, 2026

Follow-up update for the CI/unit failure:

I tracked the unit failure down to the optimize-imports export-map path rather than a single broken assertion.

What changed:

  • fix(plugins): guard export-map reentry during barrel analysis
    • adds an inProgress guard in buildExportMapFromFile(...) so recursive barrel analysis does not re-enter the same file while its export map is still being built
  • test: split optimize-imports transform coverage
  • test: split optimize-imports export-map basics
  • test: split optimize-imports export-map recursion cases
    • splits the old tests/optimize-imports.test.ts coverage into smaller focused files so the unit worker no longer has to load the entire optimize-imports surface into one file

Also synced a couple of expectations to current behavior while splitting:

  • concrete local-file rewrites now assert explicit .js paths where that is the current normalized output
  • the client-environment transform test now reflects the current Next.js-aligned behavior: direct concrete-file rewrites are still allowed on the client side for optimized packages

Validation rerun:

  • ./node_modules/.bin/vp test run --project unit

Result:

  • full unit project passes locally again (94 test files / 2895 tests)

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

The core approach is sound — recursively resolving past intermediate local barrels and flattening export * boundaries before plugin-rsc inspects them is the right fix for issue-845. The iterative review cycle with bonk addressed the JSX parsing regression, phantom export bug, and reentry guard. The test split into focused files is a welcome improvement.

However, CI is currently failing on the unit test suite (tests/optimize-imports.test.ts, 1 failed), so this needs a fix before merge. There are also a couple of other items worth flagging.

Blocking: CI failure on client environment test

The test "rewrites direct barrel imports on client environment when they resolve to concrete files" (line 130-153) fails with expected null not to be null. The test creates a bare tmpDir without installing lucide-react, relying on the workspace's real node_modules. The old code early-returned null for the client environment, so this test path was never actually exercised. Now that client files proceed into the transform, the barrel resolution context is different and the test expectation doesn't hold.

This test needs to be fixed to either (a) set up a proper local barrel fixture for the client path (like the other tests do), or (b) be updated to reflect the new client-side behavior.

Non-blocking observations

  1. Source map removal — The change from s.generateMap({ hires: "boundary" }) to map: null is not explained in the commit messages. Other MagicString-based transforms in the codebase (fonts, instrumentation-client, index.ts) return proper source maps. This degrades dev-mode stack trace accuracy for files with rewritten barrel imports. Not blocking, but worth restoring or at least documenting the tradeoff.

  2. Dead !source guard (line 977) — toRelativeModuleSpecifier always returns a string, so the if (!source) check is unreachable dead code. Harmless but misleading.

  3. Follow-ups acknowledged — The wildcard-merge resolveConcreteLocalEntry gap and the dual-traversal in buildSafeWildcardExportMap were correctly deferred as non-blocking in prior reviews. Agree with that assessment.

"/app/page.tsx",
);
expect(result).toBeNull();
expect(result).not.toBeNull();
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.

This test is failing in CI: AssertionError: expected null not to be null.

The test creates a tmpDir without actually installing lucide-react in it — it relies on the workspace-level node_modules to resolve the barrel. The old code early-returned null for env?.name === "client", so this code path was never reached. Now that the client environment proceeds through the transform, barrel resolution fails (no lucide-react barrel in tmpDir/node_modules/) and returns null.

This needs to set up a proper local barrel fixture (like the other tests in this file do with setupTransform) or be updated to reflect the current client-side behavior.

return {
code: s.toString(),
map: s.generateMap({ hires: "boundary" }),
map: null,
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.

The switch from s.generateMap({ hires: "boundary" }) to map: null isn't mentioned in any commit message. Other MagicString-based transforms in this codebase (fonts.ts, instrumentation-client.ts, several in index.ts) return proper source maps for the same pattern (in-place rewrites of user source files via s.overwrite()).

Returning null means Vite can't compose source maps through this transform step, degrading dev-mode stack traces and browser devtools for files with rewritten barrel imports. The impact is small since changes are limited to import lines at the top of files, but it's a regression from the previous behavior.

Consider restoring:

Suggested change
map: null,
map: s.generateMap({ hires: "boundary" }),

const source = entry.source.startsWith("/")
? toRelativeModuleSpecifier(normalizedId, entry.source)
: entry.source;
if (!source) {
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.

Nit: toRelativeModuleSpecifier always returns a string (it either prepends ./ or returns the relative path as-is from path.relative), so !source is always false here. This guard is dead code.

Not blocking — it's harmless as a defensive check, but it could confuse readers into thinking toRelativeModuleSpecifier can return a falsy value.

Comment on lines +494 to +496
if (inProgress.has(filePath)) {
return cache.get(filePath) ?? new Map();
}
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.

Minor: The inProgress guard on line 494 checks cache.get(filePath) as a fallback, but cache.get(filePath) was already checked on line 491-492 and would have returned early. So this fallback always evaluates to new Map(). The intent is correct (prevent re-entrant calls from seeing a partially-built map), but the cache.get(filePath) ?? part is dead code since the cache lookup on line 491 already handles that case.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 15, 2026

Review submitted. Here's a summary of what I posted:

Review: REQUEST_CHANGES on PR #847

Blocking issue:

  • The unit test "rewrites direct barrel imports on client environment when they resolve to concrete files" (tests/optimize-imports.test.ts:150) is failing in CI with expected null not to be null. The test creates an empty tmpDir without a local lucide-react fixture, relying on the workspace's real node_modules. Since the old code early-returned null for the client environment, this path was never actually exercised. Now that client files proceed through the transform, barrel resolution fails and returns null. The test needs a proper local fixture or updated expectations.

Non-blocking comments (4 inline):

  1. Source map regression: map: null replaces s.generateMap({ hires: "boundary" }) without explanation; other MagicString transforms in the codebase return proper maps. Suggested restoring it.
  2. Dead !source guard at line 977 — toRelativeModuleSpecifier always returns a string.
  3. Dead cache.get(filePath) ?? fallback in the inProgress guard (lines 494-496) — the cache lookup already returned early on line 491-492.

github run

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 16, 2026

Follow-up fix for the remaining CI blocker:

  • test: add local fixture for client optimize-imports path
    • makes the client-environment lucide-react test self-contained instead of relying on an ambient workspace install
    • the test now creates a tiny local lucide-react barrel fixture under the temp app root and verifies the client transform rewrites to the concrete dist/esm/icons/sun.js leaf

This keeps the test aligned with the intended Next.js-style behavior we discussed:

  • client-side optimize-package rewrites are still allowed when they resolve directly to concrete local files
  • bare sub-package rewrites are still left alone for the client graph

Validation rerun:

  • ./node_modules/.bin/vp test run tests/optimize-imports.test.ts -t "rewrites direct barrel imports on client environment when they resolve to concrete files"
  • ./node_modules/.bin/vp test run --project unit

Result:

  • unit project passes locally again (94 test files / 2895 tests)

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

The core fix is well-designed: recursively resolving past intermediate local barrels to the concrete leaf module, and flattening export * boundaries before @vitejs/plugin-rsc inspects them, correctly addresses the issue-845 failure mode. The iterative review cycle addressed the critical issues (JSX parsing regression, phantom exports, reentry guard, client fixture). CI unit tests are now green.

The test split into focused files is a welcome improvement for maintainability.

A few items I'd flag:

Source map regression (actionable)

The map: null change replaces s.generateMap({ hires: "boundary" }) without explanation. Every other MagicString-based transform in this codebase returns proper source maps (fonts.ts, instrumentation-client.ts, index.ts). This degrades dev-mode stack traces for files with rewritten barrel imports. The impact is limited since changes are confined to import lines, but it's an unnecessary regression from the prior behavior.

Dead code (non-blocking)

  1. toRelativeModuleSpecifier always returns a string, so the !source guard at line 977 is unreachable.
  2. The cache.get(filePath) ?? fallback in the inProgress guard (line 495) is dead — the cache lookup at line 491-492 already returns early.

Both are harmless but slightly misleading to future readers.

Design observations (non-blocking, for awareness)

  • The wildcard merge path (line 669) doesn't apply resolveConcreteLocalEntry to merged entries, unlike the ExportNamedDeclaration path. This works for the antd shape but could miss deeper barrel chains via wildcard. The author acknowledged this as a follow-up.
  • buildSafeWildcardExportMap returns null (bail) on circular references while buildExportMapFromFile returns an empty map (non-fatal). The inconsistency is safe but worth documenting if this area gets more complex.

Overall this is in good shape. The fix is well-targeted, the test coverage exercises the real failure mode, and the iterative review addressed all critical items.

return {
code: s.toString(),
map: s.generateMap({ hires: "boundary" }),
map: null,
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.

This drops source maps without explanation. Every other MagicString transform in this codebase (fonts.ts, instrumentation-client.ts, index.ts) returns s.generateMap({ hires: "boundary" }). The impact is small (only import lines change), but it's an unnecessary regression from the prior behavior and breaks Vite's source map composition chain for these files.

Suggested change
map: null,
map: s.generateMap({ hires: "boundary" }),

const source = entry.source.startsWith("/")
? toRelativeModuleSpecifier(normalizedId, entry.source)
: entry.source;
if (!source) {
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.

Dead code: toRelativeModuleSpecifier always returns a string (it either prepends ./ or returns the path.relative result as-is), so !source is always false. This guard can never trigger.

Not blocking — harmless as a defensive check, but could confuse readers into thinking toRelativeModuleSpecifier can return a falsy value.

if (cached) return cached;

if (inProgress.has(filePath)) {
return cache.get(filePath) ?? new Map();
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.

Minor: the cache.get(filePath) ?? fallback here is dead code. If cache.get(filePath) returned a truthy value, the early return on line 491-492 would have already fired. At this point cache.get(filePath) is always undefined, so this always evaluates to new Map().

The intent (prevent re-entrant calls from seeing a partially-built map) is correct — just the fallback expression is misleading.

Comment thread tests/optimize-imports-build.test.ts Outdated
import vinext from "../packages/vinext/src/index.js";

async function withTempDir<T>(prefix: string, run: (tmpDir: string) => Promise<T>): Promise<T> {
const tempParent = path.resolve(import.meta.dirname, "../.sisyphus/tmp");
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.

Minor: the temp directory is placed under .sisyphus/tmp (relative to the test file's directory). Is .sisyphus/ gitignored? If not, this could leave artifacts in the working tree on test interruption (even though the finally block cleans up normally). Other tests in this PR use os.tmpdir() which is a safer default for temp files.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 16, 2026

The review was submitted successfully. Here's a summary of what was posted:

Review: COMMENT on PR #847

The review finds the PR in good shape overall. The core fix — recursively resolving past intermediate local barrels to the concrete leaf module and flattening export * boundaries before @vitejs/plugin-rsc inspects them — correctly addresses issue-845.

4 inline comments posted:

  1. Source map regression (line 1243)map: null replaces s.generateMap({ hires: "boundary" }) without explanation. Every other MagicString transform in the codebase returns proper source maps. Included a suggestion to restore it.

  2. Dead !source guard (line 977)toRelativeModuleSpecifier always returns a string, so this guard is unreachable.

  3. Dead cache.get(filePath) ?? fallback (line 495) — The cache lookup at line 491-492 already returns early, so cache.get(filePath) is always undefined at this point.

  4. Build test temp directory (line 9) — The temp directory is placed under .sisyphus/tmp instead of os.tmpdir() like the other tests. Flagged to confirm .sisyphus/ is gitignored.

Top-level observations:

  • The wildcard merge path doesn't apply resolveConcreteLocalEntry (acknowledged as follow-up by author)
  • buildSafeWildcardExportMap vs buildExportMapFromFile circular reference handling inconsistency (safe but worth documenting)

github run

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 16, 2026

Follow-up for the last four inline comments:

  • fix(plugins): restore optimize-imports sourcemaps
    • restores s.generateMap({ hires: "boundary" }) in the optimize-imports transform so it matches the repo's other MagicString-based transforms
    • removes the dead !source guard after toRelativeModuleSpecifier(...)
    • simplifies the reachable-but-redundant cache.get(filePath) ?? branch in the inProgress guard to new Map()
  • tests/optimize-imports-build.test.ts
    • moves the temp app back to os.tmpdir() like the rest of the test suite
    • keeps the test self-contained by symlinking the repo node_modules into the temp app, matching the other build-test pattern so JSX runtime resolution still works

Validation rerun:

  • ./node_modules/.bin/vp fmt --check packages/vinext/src/plugins/optimize-imports.ts tests/optimize-imports-build.test.ts
  • ./node_modules/.bin/vp test run tests/optimize-imports-build.test.ts
  • ./node_modules/.bin/vp test run --project unit

Result:

  • full unit project still passes locally (94 test files / 2895 tests)

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

The core fix is well-designed and correctly targeted at issue-845. The approach of recursively resolving past intermediate local barrels to the concrete leaf module, and flattening export * boundaries before @vitejs/plugin-rsc inspects them, is sound. The iterative review cycle addressed all critical issues (JSX parsing, phantom exports, reentry guard, client fixture, source maps). CI is green on the unit and Check jobs, with remaining integration jobs still pending at review time.

The test split into focused files is a welcome improvement for maintainability.

I have a few items to flag — one functional concern and several minor/non-blocking items.

Functional: canRewrite is always true

The canRewrite variable at line 971 is initialized to true and never set to false anywhere in the loop body. The guard if (!canRewrite || bySource.size === 0) continue on line 986 therefore only ever triggers on bySource.size === 0. This looks like leftover scaffolding from an earlier iteration where there was a bail condition. It's not a bug (it's equivalent to just checking bySource.size === 0), but it's misleading — it implies there's a code path that sets canRewrite = false when there isn't one.

Non-blocking items

  1. buildFallbackExportMap doesn't handle export default function() (anonymous) — The regex /^\s*export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/gm requires a named identifier after export default. Anonymous export default function() {} won't match. This is fine as a conservative fallback, but worth noting since it means the fallback path silently drops anonymous default exports. The AST-based path handles them correctly, so this only matters for .js files containing JSX that fail to parse.

  2. Build test withTempDir symlinks node_modules before writing fixture node_modules/antd — In optimize-imports-build.test.ts, withTempDir symlinks the repo's node_modules into the temp dir on line 11-15, then writeFixtureFile writes files under node_modules/antd/ on lines 130-168. Because the symlink makes node_modules point to the repo's real node_modules, the writeFixtureFile calls that write into node_modules/antd/ are writing into the actual repo's node_modules directory. This works if antd isn't installed in the repo (the directory is created fresh), but the finally block only removes the temp dir — it doesn't clean up files written through the symlink into the real node_modules. If the test is interrupted or the cleanup logic changes, stale antd files could be left in the workspace's node_modules.

  3. Acknowledged follow-ups — The wildcard-merge resolveConcreteLocalEntry gap and the dual-traversal in buildSafeWildcardExportMap were correctly deferred as non-blocking by the author. Agree with that assessment.

group.exports.push({ exported, originalName: entry.originalName });
}

if (!canRewrite || bySource.size === 0) continue;
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.

canRewrite is initialized to true on line 971 and never set to false anywhere in the loop body (lines 972-984). This guard is equivalent to just if (bySource.size === 0) continue.

Looks like leftover scaffolding from an earlier iteration. Either remove canRewrite entirely, or add a comment indicating what condition would set it to false if you plan to add one later.

Suggested change
if (!canRewrite || bySource.size === 0) continue;
if (bySource.size === 0) continue;

Comment thread tests/optimize-imports-build.test.ts Outdated
Comment on lines +9 to +15
async function withTempDir<T>(prefix: string, run: (tmpDir: string) => Promise<T>): Promise<T> {
const tmpDir = await mkdtemp(path.join(os.tmpdir(), prefix));
fs.symlinkSync(
path.resolve(import.meta.dirname, "../node_modules"),
path.join(tmpDir, "node_modules"),
"junction",
);
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.

This symlinks the repo's real node_modules into the temp dir. The test then calls writeFixtureFile to write into node_modules/antd/ (lines 130-168), which follows the symlink and writes into the actual repo's node_modules directory.

This works as long as antd isn't already installed in the workspace. But if the test is interrupted before the finally cleanup, or if antd ever gets added as a dev dependency, you'll have stale fixture files in the real node_modules.

Consider writing the antd fixture before the symlink and excluding it from the symlink target, or creating node_modules/antd as its own directory (not through the symlink) by removing the symlink for just that path. Alternatively, a brief comment explaining the constraint would help future maintainers.

}
}

for (const match of content.matchAll(/^\s*export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/gm)) {
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.

Minor: this regex only matches export default <identifier>, not export default function() {} (anonymous) or export default class {}. The AST path handles these correctly, so this only affects the fallback when .js files containing JSX fail to parse. The conservative behavior (silently dropping the anonymous default) is fine, but a brief comment noting this limitation would help readers understand the intentional gap.

Also: export default function Foo() {} will match "default" via recordDirectExport("default", "default") on line 198, but Foo is captured by the separate export function pattern on line 187 since the line also starts with export. This means Foo ends up in the map twice — once from line 187's declarationPatterns match and once potentially from line 200-202 (though the !exportMap.has(ident) guard on line 200 prevents the second write). This is correct but the interaction is subtle.

inProgress,
);
const nextEntry = subMap.get(exportName);
if (!nextEntry) return entry;
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.

When buildExportMapFromFile returns a map for candidate but the target exportName isn't in it, this returns the original entry without trying subsequent candidates from localModuleCandidates.

This means if ./button resolves to button.js (exists, but doesn't export Button) while button/index.js (also exists, does export Button) is a later candidate, we'd stop at the wrong file. This is an edge case unlikely to appear in real packages, but worth being aware of — the break-on-first-readable-file semantic in the wildcard resolution loop (line 674) has the same behavior, so at least it's consistent.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 16, 2026

Review submitted successfully. Here's a summary of what I posted:

Review: COMMENT on PR #847

The core fix is well-designed — recursively resolving past intermediate local barrels and flattening export * boundaries before @vitejs/plugin-rsc inspects them correctly addresses issue-845. The iterative review cycle addressed all prior critical issues. CI unit tests and Check are green.

4 inline comments posted:

  1. Dead canRewrite variable (line 986)canRewrite is initialized to true and never set to false. The guard is equivalent to just if (bySource.size === 0) continue. Included a suggestion to remove it.

  2. Build test symlink writes through to real node_modules (lines 9-15)withTempDir symlinks the repo's node_modules, then the test writes antd fixture files through the symlink into the real workspace. If interrupted, stale files could remain.

  3. buildFallbackExportMap regex interaction (line 197) — Anonymous export default function() {} won't match the fallback regex. Also noted the subtle but correct interaction between declarationPatterns and the export default regex for named functions.

  4. resolveConcreteLocalEntry break-on-first-match (line 550) — If the first readable candidate file doesn't export the target name, subsequent candidates aren't tried. Consistent with the wildcard resolution loop; unlikely to matter in practice.

github run

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 16, 2026

Follow-up for the latest review comments:

  • test: isolate optimize-imports build fixture writes
    • removes the dead canRewrite scaffolding in the optimize-imports transform loop
    • keeps the build test on os.tmpdir() but stops writing custom antd fixture files through a repo-wide node_modules symlink
    • the temp app now creates its own node_modules/antd fixture and only symlinks the workspace packages it actually needs for the build (react, react-dom, react-server-dom-webpack)

That keeps the reviewer-requested temp-dir cleanup while avoiding writes into the real workspace node_modules.

Validation rerun:

  • ./node_modules/.bin/vp fmt --check packages/vinext/src/plugins/optimize-imports.ts tests/optimize-imports-build.test.ts
  • ./node_modules/.bin/vp test run tests/optimize-imports-build.test.ts
  • ./node_modules/.bin/vp test run --project unit

Result:

  • full unit project still passes locally (94 test files / 2895 tests)

@llc1123
Copy link
Copy Markdown
Author

llc1123 commented Apr 16, 2026

One note on the two remaining non-blocking observations that I did not fold into this PR:

  1. buildFallbackExportMap and anonymous export default function() {}

    • This only affects the conservative fallback path, i.e. .js files that fail AST parsing and fall back to regex-based export recovery.
    • The normal AST path already handles anonymous default exports correctly, so this is not part of the main issue-845 path and it is not a known blocker.
    • Expanding the fallback semantics here would increase the regex / inference surface more than I want to for this PR, so I would prefer to keep that as a separate follow-up if needed.
  2. resolveConcreteLocalEntry breaking on the first readable candidate

    • This is the current resolution strategy: once a readable candidate file is found, we check that file for the target export instead of continuing across later candidates.
    • As noted in review, this seems unlikely to matter in practice, and it was not part of the issue-845 failure mode or any of the CI regressions we actually hit here.
    • If we want to broaden that behavior, I think it should be done as a separate follow-up, because it changes the candidate-resolution semantics and would deserve its own tests.

For this PR I tried to keep the scope tightly focused on:

  • the issue-845 correctness fix itself,
  • the concrete review items that affected correctness / CI / test isolation,
  • and not broadening into additional resolution-semantics changes unless they were directly required.

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review: fix(plugins): resolve optimized imports past local barrels

This is a well-executed fix. The core approach — recursively resolving past intermediate local barrels to the concrete leaf module, and flattening export * boundaries before @vitejs/plugin-rsc inspects them — is sound and correctly targeted to the issue-845 failure mode.

The iterative review cycle addressed all critical issues:

  • JSX parsing regression from enforce: "pre"parseAstWithFileLang
  • Phantom "function"/"class" exports in fallback path
  • Reentry guard for circular barrel analysis
  • Client environment fixture isolation
  • Source map restoration
  • Dead code cleanup (canRewrite, !source guard, cache.get fallback)
  • Build test temp directory isolation

The test split into focused files is a welcome improvement for maintainability. The new helpers (localModuleCandidates, toRelativeModuleSpecifier, findOptimizedPackageForFile, resolveLocalModuleFile, buildFallbackExportMap, buildSafeWildcardExportMap, resolveConcreteLocalEntry) are well-factored and appropriately scoped.

CI: Check and Vitest (unit) are green. Several E2E and integration jobs are still pending at review time but the passing unit and check jobs cover the changed code paths.

I have a few non-blocking observations below — none of them are merge blockers.

}
}

for (const match of content.matchAll(/^\s*export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/gm)) {
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.

Non-blocking observation: export default function() {} (anonymous default function) won't match this regex since it requires a named identifier. The AST-based path handles this correctly, so it only matters for .js files containing JSX that fail AST parsing and hit the fallback.

Also worth noting the subtle interaction: export default function Button() {} matches both this regex (capturing "Button" after filtering "function") and the declarationPatterns function regex on line 187 (which captures "Button" from the function declaration). The !exportMap.has(ident) guard on line 200 prevents the double-write, which is correct.

No action needed — just documenting for future readers.

if (!nestedMap) return null;
}

return buildExportMapFromFile(filePath, readFile, cache, new Set<string>(), content);
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.

Non-blocking: the validation pass (lines 228–245) and the building pass (this buildExportMapFromFile call) use separate visited sets, meaning files can be visited twice. The exportMapCache mitigates re-parsing cost, so the duplicate traversal is a performance nit rather than a correctness issue. Worth noting if this area gets more complex — the two passes could potentially share results.

candidateContent,
inProgress,
);
for (const [name, entry] of subMap) {
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.

Previously flagged and acknowledged by author: the wildcard merge path here copies entries from subMap without applying resolveConcreteLocalEntry. This means if a wildcard sub-module itself re-exports through another intermediate barrel (export * from "./group" where group/index.js has export { Foo } from "./foo/index.js"), the entries won't be chased to the leaf.

This works for the antd shape (root barrel uses named re-exports), and the author correctly scoped this as a follow-up. Just keeping the note visible for the merge reviewer.

inProgress,
);
const nextEntry = subMap.get(exportName);
if (!nextEntry) return entry;
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.

Minor: when the first readable candidate file has a valid export map but doesn't contain exportName, we return the original entry without trying later candidates. This is the break-on-first-readable-file semantic, consistent with the wildcard resolution loop at line 674. Unlikely to matter in practice — in real packages, the first readable file at a given path is the right one — but worth being aware of for any edge case where ./button.js exists but doesn't export Button while ./button/index.js does.

fs.writeFileSync(absPath, content);
}

function readTextFilesRecursive(root: string): string {
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.

Nit: readTextFilesRecursive follows symlinks (the react/react-dom symlinks created by symlinkWorkspacePackage) and would recursively read all .js files under the real react package if they end up in the dist/ tree. In practice this is fine because the build output won't contain symlinks to workspace packages in the output dir, but if the build ever emits a symlink (e.g., Vite preserves symlinks in some configurations), this could produce unexpectedly large buildOutput strings and slow assertions.

Not blocking — the current build test works correctly.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 16, 2026

Review submitted successfully. Here's a summary of what I posted:

Review: APPROVE on PR #847

The PR is in good shape after 6 rounds of iterative review that addressed all critical issues. The core fix — recursively resolving past intermediate local barrels to the concrete leaf module and flattening export * boundaries before @vitejs/plugin-rsc inspects them — correctly addresses issue-845.

All prior critical issues were resolved:

  • JSX parsing regression from enforce: "pre"
  • Phantom "function"/"class" exports in fallback path
  • Reentry guard for circular barrel analysis
  • Client environment fixture isolation
  • Source map restoration
  • Dead code cleanup

5 non-blocking inline comments for awareness/documentation:

  1. buildFallbackExportMap anonymous default export limitation (line 197) — only affects the regex fallback path
  2. Dual traversal in buildSafeWildcardExportMap (line 248) — performance nit, mitigated by cache
  3. Wildcard merge path doesn't apply resolveConcreteLocalEntry (line 669) — acknowledged follow-up
  4. resolveConcreteLocalEntry break-on-first-match (line 550) — unlikely edge case
  5. readTextFilesRecursive symlink traversal in build test (line 35) — works correctly in practice

github run

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

Labels

None yet

Projects

None yet

2 participants