Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/vinext/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1960,6 +1960,12 @@ hydrate();
"react/jsx-dev-runtime",
],
},
// Exclude vinext from dependency optimization so esbuild doesn't
// scan dist files containing virtual module imports (virtual:vinext-*)
// that only resolve at Vite plugin time, not during pre-bundling.
optimizeDeps: {
exclude: ["vinext"],
},
// Enable JSX in .tsx/.jsx files
// Vite 7 uses `esbuild` for transforms, Vite 8+ uses `oxc`
...(getViteMajorVersion() >= 8
Expand Down Expand Up @@ -2004,6 +2010,7 @@ hydrate();
},
}),
optimizeDeps: {
exclude: ["vinext"],
entries: appEntries,
},
build: {
Expand All @@ -2015,6 +2022,7 @@ hydrate();
},
ssr: {
optimizeDeps: {
exclude: ["vinext"],
entries: appEntries,
},
build: {
Expand All @@ -2026,6 +2034,7 @@ hydrate();
},
client: {
optimizeDeps: {
exclude: ["vinext"],
// react and react-dom are framework dependencies used for
// hydration. They aren't crawled from app/ source files so
// must be pre-included to prevent late discovery and page
Expand Down
87 changes: 87 additions & 0 deletions tests/build-optimization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,93 @@ describe("clientManualChunks", () => {
});
});

// ─── optimizeDeps.exclude — prevents esbuild scanning virtual module imports ─

describe("optimizeDeps.exclude for vinext", () => {
it("excludes vinext at top level for Pages Router builds", async () => {
const vinext = (await import("../packages/vinext/src/index.js")).default;
const plugins = vinext();

const mainPlugin = plugins.find(
(p: any) => p.name === "vinext:config" && typeof p.config === "function",
);
expect(mainPlugin).toBeDefined();

const os = await import("node:os");
const fsp = await import("node:fs/promises");
const path = await import("node:path");

const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-ts-test-optdeps-"));
const rootNodeModules = path.resolve(import.meta.dirname, "../node_modules");
await fsp.symlink(rootNodeModules, path.join(tmpDir, "node_modules"), "junction");

await fsp.mkdir(path.join(tmpDir, "pages"), { recursive: true });
await fsp.writeFile(
path.join(tmpDir, "pages", "index.tsx"),
`export default function Home() { return <h1>Home</h1>; }`,
);
await fsp.writeFile(
path.join(tmpDir, "next.config.mjs"),
`export default {};`,
);

try {
const mockConfig = { root: tmpDir, build: {}, plugins: [] };
const result = await (mainPlugin as any).config(mockConfig, { command: "build" });

expect(result.optimizeDeps?.exclude).toContain("vinext");
} finally {
await fsp.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
}
}, 15000);

it("excludes vinext in all environments for App Router builds", async () => {
const vinext = (await import("../packages/vinext/src/index.js")).default;
const plugins = vinext();

const mainPlugin = plugins.find(
(p: any) => p.name === "vinext:config" && typeof p.config === "function",
);
expect(mainPlugin).toBeDefined();

const os = await import("node:os");
const fsp = await import("node:fs/promises");
const path = await import("node:path");

const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), "vinext-ts-test-optdeps-app-"));
const rootNodeModules = path.resolve(import.meta.dirname, "../node_modules");
await fsp.symlink(rootNodeModules, path.join(tmpDir, "node_modules"), "junction");

await fsp.mkdir(path.join(tmpDir, "app"), { recursive: true });
await fsp.writeFile(
path.join(tmpDir, "app", "layout.tsx"),
`export default function RootLayout({ children }: { children: React.ReactNode }) { return <html><body>{children}</body></html>; }`,
);
await fsp.writeFile(
path.join(tmpDir, "app", "page.tsx"),
`export default function Home() { return <h1>Home</h1>; }`,
);
await fsp.writeFile(
path.join(tmpDir, "next.config.mjs"),
`export default {};`,
);

try {
const mockConfig = { root: tmpDir, build: {}, plugins: [] };
const result = await (mainPlugin as any).config(mockConfig, { command: "build" });

// Top-level
expect(result.optimizeDeps?.exclude).toContain("vinext");
// Per-environment
expect(result.environments.rsc.optimizeDeps?.exclude).toContain("vinext");
expect(result.environments.ssr.optimizeDeps?.exclude).toContain("vinext");
expect(result.environments.client.optimizeDeps?.exclude).toContain("vinext");
} finally {
await fsp.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
}
}, 15000);
});

// ─── Treeshake config applied to Vite builds ──────────────────────────────────

describe("treeshake config integration", () => {
Expand Down
Loading