fix: merge top-level optimizeDeps with per-environment config#539
fix: merge top-level optimizeDeps with per-environment config#539james-elicx merged 8 commits intocloudflare:mainfrom
Conversation
When vinext's config() hook builds per-environment optimizeDeps, it now merges any exclude/include entries from config.optimizeDeps (populated by earlier plugins like @lingui/vite-plugin) instead of overwriting them. This ensures packages excluded by other plugins remain excluded across all three environments (rsc, ssr, client). Fixes cloudflare#538 https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb
When vinext's config() hook builds per-environment optimizeDeps, it now merges any exclude/include entries from config.optimizeDeps (populated by earlier plugins like @lingui/vite-plugin) instead of overwriting them. This ensures packages excluded by other plugins remain excluded across all three environments (rsc, ssr, client). Add test verifying top-level optimizeDeps entries are preserved in all per-environment configs after vinext's config hook runs. Fixes cloudflare#538 https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb
|
/bigbonk review |
commit: |
There was a problem hiding this comment.
Good fix for a real bug. The approach of capturing incoming optimizeDeps and merging with Set is clean and correct. A couple of observations below — one is a gap that affects Pages Router users with the same setup, the other is a minor test coverage note.
| const incomingExclude: string[] = | ||
| (config.optimizeDeps?.exclude as string[] | undefined) ?? []; | ||
| const incomingInclude: string[] = | ||
| (config.optimizeDeps?.include as string[] | undefined) ?? []; |
There was a problem hiding this comment.
This correctly handles the App Router environments, but the same class of bug exists at the top-level optimizeDeps (line ~1294) used for Pages Router builds:
optimizeDeps: {
exclude: ["vinext", "@vercel/og"],
},Vite's config() hook merging replaces arrays, so if @lingui/vite-plugin added entries to config.optimizeDeps.exclude, they get overwritten by this return value for Pages Router builds too.
This is pre-existing (not introduced by this PR), but since you're fixing this class of issue, it'd be worth applying the same merge pattern there as well. Something like:
optimizeDeps: {
exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])],
},You'd need to move the incomingExclude/incomingInclude capture above the if (hasAppDir) block so it's available in both code paths. Up to you whether to include it in this PR or a follow-up — the current fix is correct for the App Router case.
| const result = await (mainPlugin as any).config(mockConfig, { command: "build" }); | ||
|
|
||
| // All environments should contain the incoming excludes | ||
| for (const envName of ["rsc", "ssr", "client"]) { |
There was a problem hiding this comment.
Nice test coverage. One minor addition to consider: asserting that @vercel/og is still present in the excludes would confirm the merge didn't accidentally drop vinext's own entries beyond just "vinext". Currently only "vinext" is checked as the "vinext's own excludes" representative.
Also, it might be worth adding a negative assertion that there are no duplicates in the array, to validate the Set dedup is working:
| for (const envName of ["rsc", "ssr", "client"]) { | |
| for (const envName of ["rsc", "ssr", "client"]) { | |
| const envExclude = result.environments[envName].optimizeDeps?.exclude; | |
| expect(envExclude, `${envName} should contain @lingui/macro`).toContain("@lingui/macro"); | |
| expect(envExclude, `${envName} should contain @lingui/core/macro`).toContain( | |
| "@lingui/core/macro", | |
| ); | |
| // vinext's own excludes should still be present | |
| expect(envExclude, `${envName} should contain vinext`).toContain("vinext"); | |
| expect(envExclude, `${envName} should contain @vercel/og`).toContain("@vercel/og"); | |
| // No duplicates (Set dedup works) | |
| expect(new Set(envExclude).size, `${envName} should have no duplicate excludes`).toBe(envExclude.length); |
| root: tmpDir, | ||
| build: {}, | ||
| plugins: [], | ||
| optimizeDeps: { |
There was a problem hiding this comment.
Good idea to simulate entries that a real plugin like @lingui/vite-plugin would add. Consider also testing the dedup path: what happens when the incoming config contains an entry that vinext also adds (e.g. exclude: ["vinext", "@lingui/macro"])? The Set should dedup it, but an explicit test case would document that behavior.
|
Review posted successfully. Here's a summary of the feedback I left on PR #539: Overall: Good fix for a real bug. The approach of capturing incoming Three comments posted:
|
Address PR review feedback: - Move incomingExclude/incomingInclude capture above the hasAppDir branch so Pages Router builds also merge entries from earlier plugins (e.g. @lingui/vite-plugin) instead of overwriting them. - Add @vercel/og assertion to confirm vinext's own entries survive merging. - Add Set-based deduplication assertions to all environments. - Test overlapping entries (incoming "vinext" + vinext's own "vinext") to validate the Set dedup logic. https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb
…BbMW fix: merge optimizeDeps for Pages Router + improve test coverage
|
/bigbonk re-review |
There was a problem hiding this comment.
Good fix — the core approach is correct and addresses a real issue. The previous review's suggestions (dedup testing, @vercel/og assertions, Pages Router merge) have all been incorporated. Two remaining items: a CI failure and a minor gap in the top-level merge.
CI: Format check is failing on tests/build-optimization.test.ts. Run pnpm run fmt and push the fix.
| // Pages Router builds (which don't set per-environment configs) | ||
| // also preserve entries from earlier plugins. | ||
| viteConfig.optimizeDeps = { | ||
| exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])], |
There was a problem hiding this comment.
This replaces the entire optimizeDeps object with only exclude, which drops incomingInclude (and any other properties like entries or esbuildOptions) that earlier plugins may have set on config.optimizeDeps for Pages Router builds.
The App Router path correctly merges both incomingExclude and incomingInclude (the latter into the client environment). For consistency, the top-level config should preserve incoming include too:
| exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])], | |
| viteConfig.optimizeDeps = { | |
| exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])], | |
| ...(incomingInclude.length > 0 ? { include: incomingInclude } : {}), | |
| }; |
This way if a plugin like @lingui/vite-plugin sets both exclude and include at the top level, Pages Router builds preserve both.
|
Review posted. Here's a summary of the feedback: Overall: The fix is correct and well-tested. Previous review feedback has been fully incorporated. Two items remain:
|
Address upstream PR review feedback: - Spread incomingInclude into top-level optimizeDeps so Pages Router builds preserve include entries from upstream plugins (e.g. @lingui/vite-plugin) - Run pnpm run fmt to fix formatting in test file https://claude.ai/code/session_01T7kpjyEcsZTUG6wHscx3nb
…BbMW fix: preserve optimizeDeps.include for Pages Router + fix formatting
|
/bigbonk re-review |
There was a problem hiding this comment.
Good fix. The approach of capturing incoming optimizeDeps and merging via Set is correct and handles the core issue well. The previous review feedback (Pages Router merge, dedup testing, @vercel/og assertions, incomingInclude for Pages Router) has all been addressed.
CI is mostly green (Format, Lint, Typecheck all pass; remaining checks are still pending but the earlier failures have been resolved). Two minor observations below, neither blocking.
| viteConfig.optimizeDeps = { | ||
| exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])], | ||
| ...(incomingInclude.length > 0 ? { include: incomingInclude } : {}), | ||
| }; |
There was a problem hiding this comment.
Nit: this only preserves exclude and include from incoming config.optimizeDeps, silently dropping any other properties that earlier plugins might have set (e.g. entries, esbuildOptions, needsInterop). A more defensive approach would spread the entire incoming object:
| }; | |
| viteConfig.optimizeDeps = { | |
| ...config.optimizeDeps, | |
| exclude: [...new Set([...incomingExclude, "vinext", "@vercel/og"])], | |
| ...(incomingInclude.length > 0 ? { include: [...new Set([...incomingInclude])] } : {}), | |
| }; |
This is pre-existing (the original code also only set exclude), so not blocking — just something to consider for robustness.
| root: tmpDir, | ||
| build: {}, | ||
| plugins: [], | ||
| optimizeDeps: { exclude: ["@lingui/macro"] }, |
There was a problem hiding this comment.
Minor: the existing Pages Router test now passes optimizeDeps.exclude in the mock config but doesn't test include preservation for Pages Router. The implementation does conditionally spread incomingInclude for Pages Router (line 1340 of index.ts), but there's no test asserting it. Consider adding include: ["some-pages-lib"] to this mock config and verifying result.optimizeDeps?.include contains it — would give full coverage of the Pages Router merge path added at line 1338-1341.
|
Review posted. Here's a summary: Approved with two non-blocking observations:
The core fix is correct — it captures incoming |
Summary
Fixes #538
vinext's
config()hook was creating per-environmentoptimizeDepsobjects from scratch, discarding anyexcludeorincludeentries that other Vite plugins (e.g.@lingui/vite-plugin) had added to the top-levelconfig.optimizeDepsduring their ownconfighooks.This PR captures the incoming
config.optimizeDeps.excludeandconfig.optimizeDeps.includearrays before building the per-environment configs, then merges them (with deduplication viaSet) into each environment:excludenow includes incoming excludes +["vinext", "@vercel/og"]excludenow includes incoming excludes +["vinext", "@vercel/og"]excludenow includes incoming excludes +["vinext", "@vercel/og", ...serverExternalPackages];includenow includes incoming includes + React packagesChanges
packages/vinext/src/index.ts— Readconfig.optimizeDeps?.excludeandconfig.optimizeDeps?.includeat the top of the environments block, then spread them into each environment'soptimizeDepsusing[...new Set([...incoming, ...vinextOwn])]tests/build-optimization.test.ts— New test that passesoptimizeDeps.excludeandoptimizeDeps.includevia the mock config (simulating an earlier plugin like@lingui/vite-plugin) and verifies those entries appear in all three environments alongside vinext's own entriesTest plan
optimizeDeps.exclude(@lingui/macro,@lingui/core/macro) andoptimizeDeps.include(some-lib) via mock config, verifies they appear in rsc/ssr/client environments alongside vinext's own entriesoptimizeDeps.excludetests still pass (66/66 inbuild-optimization.test.ts)pnpm run fmt:check— passespnpm run lint— 0 warnings, 0 errorspnpm run typecheck— passes