Skip to content

Commit

Permalink
Fix duplicate route chunk code
Browse files Browse the repository at this point in the history
  • Loading branch information
markdalgleish authored Oct 1, 2024
1 parent f252822 commit 61cfbd6
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 36 deletions.
111 changes: 90 additions & 21 deletions packages/react-router-dev/vite/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1416,6 +1416,74 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
await routeConfigViteServer?.close();
},
},
{
name: "react-router-route-index",
// This plugin provides the route module "index" since route modules can
// be chunked and may be made up of multiple smaller modules. This plugin
// primarily ensures code is never duplicated across a route module and
// its chunks. If we didn't have this plugin, any app that explicitly
// imports a route module would result in duplicate code since the app
// would contain code for both the unprocessed route module as well as its
// individual chunks. This is because, since they have different module
// IDs, they are treated as completely separate modules even though they
// all reference the same underlying file. This plugin addresses this by
// ensuring that any explicit imports of a route module resolve to a
// module that simply re-exports from its underlying chunks, if present.
async transform(code, id, options) {
// Routes aren't chunked on the server
if (options?.ssr) {
return;
}

// Ensure we're only operating on routes
if (!isRoute(ctx.reactRouterConfig, id)) {
return;
}

// Ensure we're only operating on raw route module imports
if (isRouteVirtualModule(id)) {
return;
}

let {
hasRouteChunks,
hasClientActionChunk,
hasClientLoaderChunk,
chunkedExports,
} = await detectRouteChunksIfEnabled(cache, ctx, id, code);

// If there are no chunks, we can let this resolve to the raw route
// module since there's no risk of duplication
if (!hasRouteChunks) {
return;
}

let sourceExports = await getRouteModuleExports(
viteChildCompiler,
ctx,
id
);

let isMainChunkExport = (name: string) =>
!chunkedExports.includes(name as string & RouteChunkName);

let mainChunkReexports = sourceExports
.filter(isMainChunkExport)
.join(", ");

let chunkBasePath = `./${path.basename(id)}`;

return [
`export { ${mainChunkReexports} } from "${chunkBasePath}${MAIN_ROUTE_CHUNK_QUERY_STRING}";`,
hasClientActionChunk &&
`export { clientAction } from "${chunkBasePath}${CLIENT_ACTION_CHUNK_QUERY_STRING}";`,
hasClientLoaderChunk &&
`export { clientLoader } from "${chunkBasePath}${CLIENT_LOADER_CHUNK_QUERY_STRING}";`,
]
.filter(Boolean)
.join("\n");
},
},
{
name: "react-router-route-entry",
async transform(code, id, options) {
Expand All @@ -1430,32 +1498,26 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
routeModuleId
);

let {
hasRouteChunks = false,
hasClientActionChunk = false,
hasClientLoaderChunk = false,
} = options?.ssr
let { chunkedExports = [] } = options?.ssr
? {}
: await detectRouteChunksIfEnabled(cache, ctx, id, code);

let reexports = sourceExports
.filter(
(exportName) =>
.filter((exportName) => {
let isRouteEntryExport =
(options?.ssr &&
SERVER_ONLY_ROUTE_EXPORTS.includes(exportName)) ||
CLIENT_ROUTE_EXPORTS.includes(exportName)
)
.filter((exportName) =>
hasClientActionChunk ? exportName !== "clientAction" : true
)
.filter((exportName) =>
hasClientLoaderChunk ? exportName !== "clientLoader" : true
)
CLIENT_ROUTE_EXPORTS.includes(exportName);

let isChunkedExport = chunkedExports.includes(
exportName as string & RouteChunkName
);

return isRouteEntryExport && !isChunkedExport;
})
.join(", ");

return `export { ${reexports} } from "./${routeFileName}${
hasRouteChunks ? MAIN_ROUTE_CHUNK_QUERY_STRING : ""
}";`;
return `export { ${reexports} } from "./${routeFileName}";`;
},
},
{
Expand Down Expand Up @@ -1558,9 +1620,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
let importerShort = vite.normalizePath(
path.relative(ctx.rootDirectory, importer)
);
let isRoute = getRoute(ctx.reactRouterConfig, importer);

if (isRoute) {
if (isRoute(ctx.reactRouterConfig, importer)) {
let serverOnlyExports = SERVER_ONLY_ROUTE_EXPORTS.map(
(xport) => `\`${xport}\``
).join(", ");
Expand Down Expand Up @@ -1971,6 +2031,13 @@ function getRoute(
return route;
}

function isRoute(
pluginConfig: ResolvedReactRouterConfig,
file: string
): boolean {
return Boolean(getRoute(pluginConfig, file));
}

async function getRouteMetadata(
cache: Cache,
ctx: ReactRouterPluginContext,
Expand Down Expand Up @@ -2368,6 +2435,7 @@ async function detectRouteChunksIfEnabled(
): Promise<ReturnType<typeof detectRouteChunks>> {
if (!ctx.reactRouterConfig.future.unstable_routeChunks) {
return {
chunkedExports: [],
hasClientActionChunk: false,
hasClientLoaderChunk: false,
hasRouteChunks: false,
Expand All @@ -2377,6 +2445,7 @@ async function detectRouteChunksIfEnabled(
let code = await resolveRouteFileCode(ctx, input);
if (!code.includes("clientLoader") && !code.includes("clientAction")) {
return {
chunkedExports: [],
hasClientActionChunk: false,
hasClientLoaderChunk: false,
hasRouteChunks: false,
Expand Down
31 changes: 16 additions & 15 deletions packages/react-router-dev/vite/route-chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -925,25 +925,26 @@ export function detectRouteChunks(
hasClientActionChunk: boolean;
hasClientLoaderChunk: boolean;
hasRouteChunks: boolean;
chunkedExports: RouteChunkName[];
} {
let hasClientActionChunk = hasChunkableExport(
code,
"clientAction",
cache,
cacheKey
);
let hasClientLoaderChunk = hasChunkableExport(
code,
"clientLoader",
cache,
cacheKey
);
let hasRouteChunks = hasClientActionChunk || hasClientLoaderChunk;
const chunkStatus = Object.fromEntries(
chunkedExportNames.map((exportName) => [
exportName,
hasChunkableExport(code, exportName, cache, cacheKey),
])
) as Record<RouteChunkName, boolean>;

const chunkedExports = Object.entries(chunkStatus)
.filter(([, isChunked]) => isChunked)
.map(([exportName]) => exportName as RouteChunkName);

const hasRouteChunks = chunkedExports.length > 0;

return {
hasClientActionChunk,
hasClientLoaderChunk,
hasClientActionChunk: chunkStatus.clientAction,
hasClientLoaderChunk: chunkStatus.clientLoader,
hasRouteChunks,
chunkedExports,
};
}

Expand Down

0 comments on commit 61cfbd6

Please sign in to comment.