Skip to content

Commit 2e9f52a

Browse files
authored
Remove vite-node dependency in favor of Vite's native module runner APIs (#15104)
Use ESM path metadata in the absolute route typegen fixture, keep config loader resources alive through user `buildEnd` hooks, and avoid legacy SSR stack rewriting when using Vite runnable environments so native runner sourcemaps map correctly. Avoid overriding server `dev.createEnvironment` so integrations like `@cloudflare/vite-plugin` can provide their own environment implementations, while keeping native runner stack handling intact.
1 parent ddca616 commit 2e9f52a

8 files changed

Lines changed: 82 additions & 77 deletions

File tree

integration/typegen-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ test.describe("typegen", () => {
434434
import { type RouteConfig, route } from "@react-router/dev/routes";
435435
436436
export default [
437-
route("absolute/:id", path.resolve(__dirname, "routes/absolute.tsx")),
437+
route("absolute/:id", path.resolve(import.meta.dirname, "routes/absolute.tsx")),
438438
] satisfies RouteConfig;
439439
`,
440440
"app/routes/absolute.tsx": tsx`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Removed the `vite-node` dependency in favor of Vite's native module runner APIs

packages/react-router-dev/config/config.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from "node:fs";
22
import { execSync } from "node:child_process";
33
import { createRequire } from "node:module";
4-
import * as ViteNode from "../vite/vite-node";
4+
import * as ViteRunner from "../vite/vite-runner";
55
import type * as Vite from "vite";
66
import Path from "pathe";
77
import chokidar, {
@@ -434,13 +434,13 @@ function err<T>(error: string): Result<T> {
434434

435435
async function resolveConfig({
436436
root,
437-
viteNodeContext,
437+
viteRunnerContext,
438438
reactRouterConfigFile,
439439
skipRoutes,
440440
validateConfig,
441441
}: {
442442
root: string;
443-
viteNodeContext: ViteNode.Context;
443+
viteRunnerContext: ViteRunner.Context;
444444
reactRouterConfigFile?: string;
445445
skipRoutes?: boolean;
446446
validateConfig?: ValidateConfigFunction;
@@ -453,7 +453,7 @@ async function resolveConfig({
453453
return err(`${reactRouterConfigFile} no longer exists`);
454454
}
455455

456-
let configModule = await viteNodeContext.runner.executeFile(
456+
let configModule = await viteRunnerContext.runner.import(
457457
reactRouterConfigFile,
458458
);
459459

@@ -641,7 +641,7 @@ async function resolveConfig({
641641

642642
setAppDirectory(appDirectory);
643643
let routeConfigExport = (
644-
await viteNodeContext.runner.executeFile(
644+
await viteRunnerContext.runner.import(
645645
Path.join(appDirectory, routeConfigFile),
646646
)
647647
).default;
@@ -790,10 +790,10 @@ export async function createConfigLoader({
790790
root = Path.normalize(root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd());
791791

792792
let vite = await import("vite");
793-
let viteNodeContext = await ViteNode.createContext({
793+
let viteRunnerContext = await ViteRunner.createContext({
794794
root,
795795
mode,
796-
// Filter out any info level logs from vite-node
796+
// Filter out any info level logs from Vite's module runner
797797
customLogger: vite.createLogger("warn", {
798798
prefix: "[react-router]",
799799
}),
@@ -812,7 +812,7 @@ export async function createConfigLoader({
812812
let getConfig = () =>
813813
resolveConfig({
814814
root,
815-
viteNodeContext,
815+
viteRunnerContext,
816816
reactRouterConfigFile,
817817
skipRoutes,
818818
validateConfig,
@@ -878,16 +878,16 @@ export async function createConfigLoader({
878878
let moduleGraphChanged =
879879
configFileAddedOrRemoved ||
880880
Boolean(
881-
viteNodeContext.devServer?.moduleGraph.getModuleById(filepath),
881+
viteRunnerContext.environment.moduleGraph.getModuleById(filepath),
882882
);
883883

884884
// Bail out if no relevant changes detected
885885
if (!moduleGraphChanged && !appFileAddedOrRemoved) {
886886
return;
887887
}
888888

889-
viteNodeContext.devServer?.moduleGraph.invalidateAll();
890-
viteNodeContext.runner?.moduleCache.clear();
889+
viteRunnerContext.environment.moduleGraph.invalidateAll();
890+
viteRunnerContext.runner.clearCache();
891891

892892
let result = await getConfig();
893893

@@ -905,7 +905,7 @@ export async function createConfigLoader({
905905
configFileAddedOrRemoved ||
906906
(reactRouterConfigFile !== undefined &&
907907
isEntryFileDependency(
908-
viteNodeContext.devServer.moduleGraph,
908+
viteRunnerContext.environment.moduleGraph,
909909
reactRouterConfigFile,
910910
filepath,
911911
));
@@ -918,7 +918,7 @@ export async function createConfigLoader({
918918
let routeConfigCodeChanged =
919919
routeConfigFile !== undefined &&
920920
isEntryFileDependency(
921-
viteNodeContext.devServer.moduleGraph,
921+
viteRunnerContext.environment.moduleGraph,
922922
routeConfigFile,
923923
filepath,
924924
);
@@ -956,7 +956,7 @@ export async function createConfigLoader({
956956
},
957957
close: async () => {
958958
changeHandlers = [];
959-
await viteNodeContext.devServer.close();
959+
await viteRunnerContext.devServer.close();
960960
await fsWatcher?.close();
961961
},
962962
};
@@ -1145,7 +1145,7 @@ function findEntry(
11451145
}
11461146

11471147
function isEntryFileDependency(
1148-
moduleGraph: Vite.ModuleGraph,
1148+
moduleGraph: Vite.EnvironmentModuleGraph,
11491149
entryFilepath: string,
11501150
filepath: string,
11511151
visited = new Set<string>(),

packages/react-router-dev/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@
9292
"react-refresh": "^0.14.0",
9393
"semver": "^7.8.0",
9494
"tinyglobby": "^0.2.14",
95-
"valibot": "^1.4.0",
96-
"vite-node": "^3.2.2"
95+
"valibot": "^1.4.0"
9796
},
9897
"devDependencies": {
9998
"@react-router/serve": "workspace:*",

packages/react-router-dev/vite/plugin.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,16 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
663663
// change or route file addition/removal.
664664
let ctx: ReactRouterPluginContext;
665665

666+
let closePluginResources = async () => {
667+
await viteChildCompiler?.close();
668+
viteChildCompiler = null;
669+
await reactRouterConfigLoader.close();
670+
671+
let typegenWatcher = await typegenWatcherPromise;
672+
await typegenWatcher?.close();
673+
typegenWatcherPromise = undefined;
674+
};
675+
666676
/** Mutates `ctx` as a side effect */
667677
let updatePluginContext = async (): Promise<void> => {
668678
let reactRouterConfig: ResolvedReactRouterConfig;
@@ -1336,29 +1346,33 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
13361346
invariant(viteConfig);
13371347
viteConfig.logger.info("Using Vite Environment API");
13381348

1339-
let { reactRouterConfig } = ctx;
1349+
try {
1350+
let { reactRouterConfig } = ctx;
13401351

1341-
await cleanBuildDirectory(viteConfig, ctx);
1352+
await cleanBuildDirectory(viteConfig, ctx);
13421353

1343-
await builder.build(builder.environments.client);
1354+
await builder.build(builder.environments.client);
13441355

1345-
let serverEnvironments = getServerEnvironmentValues(
1346-
ctx,
1347-
builder.environments,
1348-
);
1356+
let serverEnvironments = getServerEnvironmentValues(
1357+
ctx,
1358+
builder.environments,
1359+
);
13491360

1350-
await Promise.all(serverEnvironments.map(builder.build));
1361+
await Promise.all(serverEnvironments.map(builder.build));
13511362

1352-
await cleanViteManifests(environments, ctx);
1363+
await cleanViteManifests(environments, ctx);
13531364

1354-
let { buildManifest } = ctx;
1355-
invariant(buildManifest, "Expected build manifest");
1365+
let { buildManifest } = ctx;
1366+
invariant(buildManifest, "Expected build manifest");
13561367

1357-
await reactRouterConfig.buildEnd?.({
1358-
buildManifest,
1359-
reactRouterConfig,
1360-
viteConfig,
1361-
});
1368+
await reactRouterConfig.buildEnd?.({
1369+
buildManifest,
1370+
reactRouterConfig,
1371+
viteConfig,
1372+
});
1373+
} finally {
1374+
await closePluginResources();
1375+
}
13621376
},
13631377
},
13641378
};
@@ -1522,7 +1536,12 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
15221536
// stack trace so it maps back to the actual source code
15231537
processRequestError: (error) => {
15241538
if (error instanceof Error) {
1525-
viteDevServer.ssrFixStacktrace(error);
1539+
let vite = getVite();
1540+
if (
1541+
!vite.isRunnableDevEnvironment(viteDevServer.environments.ssr)
1542+
) {
1543+
viteDevServer.ssrFixStacktrace(error);
1544+
}
15261545
}
15271546
},
15281547
});
@@ -1891,11 +1910,9 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
18911910
},
18921911
},
18931912
async buildEnd() {
1894-
await viteChildCompiler?.close();
1895-
await reactRouterConfigLoader.close();
1896-
1897-
let typegenWatcher = await typegenWatcherPromise;
1898-
await typegenWatcher?.close();
1913+
if (viteConfig?.command !== "build") {
1914+
await closePluginResources();
1915+
}
18991916
},
19001917
},
19011918
{

packages/react-router-dev/vite/rsc/plugin.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,11 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
445445
};
446446
}
447447
},
448-
async buildEnd() {
449-
await configLoader.close();
448+
buildApp: {
449+
order: "post",
450+
async handler() {
451+
await configLoader.close();
452+
},
450453
},
451454
},
452455
(() => {
Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
// We can only import types from vite-node at the top level since we're in a CJS
2-
// context but want to use vite-node's ESM build since Vite 7+ is ESM only
3-
import type { ViteNodeServer as ViteNodeServerType } from "vite-node/server";
4-
import type { ViteNodeRunner as ViteNodeRunnerType } from "vite-node/client";
51
import type * as Vite from "vite";
62

73
import { preloadVite, getVite } from "./vite";
84
import { ssrExternals } from "./ssr-externals";
95

106
export type Context = {
117
devServer: Vite.ViteDevServer;
12-
server: ViteNodeServerType;
13-
runner: ViteNodeRunnerType;
8+
environment: Vite.RunnableDevEnvironment;
9+
runner: Vite.RunnableDevEnvironment["runner"];
1410
};
1511

1612
export async function createContext({
@@ -25,14 +21,6 @@ export async function createContext({
2521
await preloadVite();
2622
const vite = getVite();
2723

28-
// Ensure we're using the ESM build of vite-node since Vite 7+ is ESM only
29-
const [{ ViteNodeServer }, { ViteNodeRunner }, { installSourcemapsSupport }] =
30-
await Promise.all([
31-
import("vite-node/server"),
32-
import("vite-node/client"),
33-
import("vite-node/source-map"),
34-
]);
35-
3624
const devServer = await vite.createServer({
3725
root,
3826
mode,
@@ -54,31 +42,31 @@ export async function createContext({
5442
// there's also an issue in Vite 5 when using a .ts PostCSS config file in
5543
// an ESM project: https://github.com/vitejs/vite/issues/15869. Consumers
5644
// can work around this in their own Vite config file, but they can't
57-
// configure this internal usage of vite-node.
45+
// configure this internal usage of Vite's module runner.
5846
postcss: {},
5947
},
6048
configFile: false,
6149
envFile: false,
6250
plugins: [],
51+
environments: {
52+
__config_loader: {
53+
consumer: "server",
54+
dev: {
55+
createEnvironment: (name, config, context) =>
56+
vite.createRunnableDevEnvironment(name, config),
57+
},
58+
},
59+
},
6360
});
64-
await devServer.pluginContainer.buildStart({});
6561

66-
const server = new ViteNodeServer(devServer);
67-
68-
installSourcemapsSupport({
69-
getSourceMap: (source) => server.getSourceMap(source),
70-
});
62+
const environment = devServer.environments.__config_loader;
7163

72-
const runner = new ViteNodeRunner({
73-
root: devServer.config.root,
74-
base: devServer.config.base,
75-
fetchModule(id) {
76-
return server.fetchModule(id);
77-
},
78-
resolveId(id, importer) {
79-
return server.resolveId(id, importer);
80-
},
81-
});
64+
if (!vite.isRunnableDevEnvironment(environment)) {
65+
await devServer.close();
66+
throw new Error(
67+
"React Router config loading requires Vite's __config_loader environment to be runnable.",
68+
);
69+
}
8270

83-
return { devServer, server, runner };
71+
return { devServer, environment, runner: environment.runner };
8472
}

pnpm-lock.yaml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)