diff --git a/packages/react-router-dev/vite/optimize-deps-entries.ts b/packages/react-router-dev/vite/optimize-deps-entries.ts new file mode 100644 index 0000000000..50e840369b --- /dev/null +++ b/packages/react-router-dev/vite/optimize-deps-entries.ts @@ -0,0 +1,30 @@ +import { escapePath as escapePathAsGlob } from "tinyglobby"; +import type { ResolvedReactRouterConfig } from "../config/config"; +import { resolveRelativeRouteFilePath } from "./resolve-relative-route-file-path"; +import { getVite } from "./vite"; + +export function getOptimizeDepsEntries({ + entryClientFilePath, + reactRouterConfig, +}: { + entryClientFilePath: string; + reactRouterConfig: ResolvedReactRouterConfig; +}) { + if (!reactRouterConfig.future.unstable_optimizeDeps) { + return []; + } + + const vite = getVite(); + const viteMajorVersion = parseInt(vite.version.split(".")[0], 10); + + return [ + vite.normalizePath(entryClientFilePath), + ...Object.values(reactRouterConfig.routes).map((route) => + resolveRelativeRouteFilePath(route, reactRouterConfig), + ), + ].map((entry) => + // In Vite 7, the `optimizeDeps.entries` option only accepts glob patterns. + // In prior versions, absolute file paths were treated differently. + viteMajorVersion >= 7 ? escapePathAsGlob(entry) : entry, + ); +} diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index 4292ec1e0e..d64f59fd53 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -55,6 +55,7 @@ import { } from "./styles"; import * as VirtualModule from "./virtual-module"; import { resolveFileUrl } from "./resolve-file-url"; +import { resolveRelativeRouteFilePath } from "./resolve-relative-route-file-path"; import { combineURLs } from "./combine-urls"; import { removeExports } from "./remove-exports"; import { ssrExternals } from "./ssr-externals"; @@ -79,6 +80,7 @@ import { resolveEntryFiles, configRouteToBranchRoute, } from "../config/config"; +import { getOptimizeDepsEntries } from "./optimize-deps-entries"; import { decorateComponentExportsWithProps } from "./with-props"; import validatePluginOrder from "./plugins/validate-plugin-order"; @@ -263,17 +265,6 @@ const normalizeRelativeFilePath = ( return vite.normalizePath(relativePath).split("?")[0]; }; -const resolveRelativeRouteFilePath = ( - route: RouteManifestEntry, - reactRouterConfig: ResolvedReactRouterConfig, -) => { - let vite = getVite(); - let file = route.file; - let fullPath = path.resolve(reactRouterConfig.appDirectory, file); - - return vite.normalizePath(fullPath); -}; - let virtual = { serverBuild: VirtualModule.create("server-build"), serverManifest: VirtualModule.create("server-manifest"), @@ -1177,7 +1168,6 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { // Ensure sync import of Vite works after async preload let vite = getVite(); - let viteMajorVersion = parseInt(vite.version.split(".")[0], 10); viteUserConfig = _viteUserConfig; viteConfigEnv = _viteConfigEnv; @@ -1259,18 +1249,10 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { resolve: serverEnvironment.resolve, }, optimizeDeps: { - entries: ctx.reactRouterConfig.future.unstable_optimizeDeps - ? [ - vite.normalizePath(ctx.entryClientFilePath), - ...Object.values(ctx.reactRouterConfig.routes).map((route) => - resolveRelativeRouteFilePath(route, ctx.reactRouterConfig), - ), - ].map((entry) => - // In Vite 7, the `optimizeDeps.entries` option only accepts glob patterns. - // In prior versions, absolute file paths were treated differently. - viteMajorVersion >= 7 ? escapePathAsGlob(entry) : entry, - ) - : [], + entries: getOptimizeDepsEntries({ + entryClientFilePath: ctx.entryClientFilePath, + reactRouterConfig: ctx.reactRouterConfig, + }), include: [ // Pre-bundle React dependencies to avoid React duplicates, // even if React dependencies are not direct dependencies. diff --git a/packages/react-router-dev/vite/resolve-relative-route-file-path.ts b/packages/react-router-dev/vite/resolve-relative-route-file-path.ts new file mode 100644 index 0000000000..5ea168c051 --- /dev/null +++ b/packages/react-router-dev/vite/resolve-relative-route-file-path.ts @@ -0,0 +1,14 @@ +import path from "pathe"; +import type { ResolvedReactRouterConfig } from "../config/config"; +import type { RouteManifestEntry } from "../config/routes"; +import { getVite } from "./vite"; + +export function resolveRelativeRouteFilePath( + route: RouteManifestEntry, + reactRouterConfig: ResolvedReactRouterConfig, +) { + let vite = getVite(); + let file = route.file; + let fullPath = path.resolve(reactRouterConfig.appDirectory, file); + return vite.normalizePath(fullPath); +} diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts index 9da90a9415..1dd2acc196 100644 --- a/packages/react-router-dev/vite/rsc/plugin.ts +++ b/packages/react-router-dev/vite/rsc/plugin.ts @@ -13,7 +13,9 @@ import { type ResolvedReactRouterConfig, createConfigLoader, } from "../../config/config"; +import { preloadVite } from "../vite"; import { hasDependency } from "../has-dependency"; +import { getOptimizeDepsEntries } from "../optimize-deps-entries"; import { createVirtualRouteConfig } from "./virtual-route-config"; import { transformVirtualRouteModules, @@ -38,6 +40,7 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { name: "react-router/rsc", async config(viteUserConfig, { command, mode }) { await initEsModuleLexer; + await preloadVite(); viteCommand = command; const rootDirectory = getRootDirectory(viteUserConfig); @@ -84,6 +87,10 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { ], }, optimizeDeps: { + entries: getOptimizeDepsEntries({ + entryClientFilePath: defaultEntries.client, + reactRouterConfig: config, + }), esbuildOptions: { jsx: "automatic", }, @@ -106,19 +113,46 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { environments: { client: { build: { - rollupOptions: { input: { index: virtual.clientEntry.id } }, + rollupOptions: { + input: { + index: defaultEntries.client, + }, + }, outDir: join(config.buildDirectory, "client"), }, }, rsc: { build: { - rollupOptions: { input: { index: virtual.rscEntry.id } }, + rollupOptions: { + input: { + // We use a virtual entry here so that consumers can import + // it as `virtual:react-router/unstable_rsc/rsc-entry` + // without needing to know the actual file path, which is + // important when using the default entries. + index: defaultEntries.rsc, + }, + output: { + entryFileNames: config.serverBuildFile, + format: config.serverModuleFormat, + }, + }, outDir: join(config.buildDirectory, "server"), }, }, ssr: { build: { - rollupOptions: { input: { index: virtual.ssrEntry.id } }, + rollupOptions: { + input: { + index: defaultEntries.ssr, + }, + output: { + // Note: We don't set `entryFileNames` here because it's + // considered private to the RSC environment build, and + // @vitejs/plugin-rsc currently breaks if it's set to + // something other than `index.js`. + format: config.serverModuleFormat, + }, + }, outDir: join(config.buildDirectory, "server/__ssr_build"), }, }, @@ -223,11 +257,9 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] { }, { - name: "react-router/rsc/virtual-entries", + name: "react-router/rsc/virtual-rsc-entry", resolveId(id) { if (id === virtual.rscEntry.id) return defaultEntries.rsc; - if (id === virtual.ssrEntry.id) return defaultEntries.ssr; - if (id === virtual.clientEntry.id) return defaultEntries.client; }, }, { @@ -433,8 +465,6 @@ const virtual = { hmrRuntime: create("unstable_rsc/runtime"), basename: create("unstable_rsc/basename"), rscEntry: create("unstable_rsc/rsc-entry"), - ssrEntry: create("unstable_rsc/ssr-entry"), - clientEntry: create("unstable_rsc/client-entry"), }; function invalidateVirtualModules(viteDevServer: Vite.ViteDevServer) {