@@ -40,6 +40,7 @@ import { combineURLs } from "./combine-urls";
4040import { removeExports } from "./remove-exports" ;
4141import {
4242 type RouteChunkName ,
43+ chunkedExportNames ,
4344 detectRouteChunks ,
4445 isRouteChunkName ,
4546 getRouteChunk ,
@@ -218,7 +219,7 @@ const normalizeRelativeFilePath = (
218219 let fullPath = path . resolve ( reactRouterConfig . appDirectory , file ) ;
219220 let relativePath = path . relative ( reactRouterConfig . appDirectory , fullPath ) ;
220221
221- return vite . normalizePath ( relativePath ) ;
222+ return vite . normalizePath ( relativePath ) . split ( "?" ) [ 0 ] ;
222223} ;
223224
224225const resolveRelativeRouteFilePath = (
@@ -342,6 +343,11 @@ const writeFileSafe = async (file: string, contents: string): Promise<void> => {
342343 await fse . writeFile ( file , contents ) ;
343344} ;
344345
346+ const getExportNames = ( code : string ) : string [ ] => {
347+ let [ , exportSpecifiers ] = esModuleLexer ( code ) ;
348+ return exportSpecifiers . map ( ( { n : name } ) => name ) ;
349+ } ;
350+
345351const getRouteManifestModuleExports = async (
346352 viteChildCompiler : Vite . ViteDevServer | null ,
347353 ctx : ReactRouterPluginContext
@@ -414,10 +420,7 @@ const getRouteModuleExports = async (
414420 readRouteFile
415421 ) ;
416422
417- let [ , exports ] = esModuleLexer ( code ) ;
418- let exportNames = exports . map ( ( e ) => e . n ) ;
419-
420- return exportNames ;
423+ return getExportNames ( code ) ;
421424} ;
422425
423426const getServerBundleBuildConfig = (
@@ -691,6 +694,9 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
691694 ctx
692695 ) ;
693696
697+ let enforceRouteChunks =
698+ ctx . reactRouterConfig . future . unstable_routeChunks === "enforce" ;
699+
694700 for ( let [ key , route ] of Object . entries ( ctx . reactRouterConfig . routes ) ) {
695701 let routeFile = path . join ( ctx . reactRouterConfig . appDirectory , route . file ) ;
696702 let sourceExports = routeManifestExports [ key ] ;
@@ -719,6 +725,17 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
719725 )
720726 : null ;
721727
728+ if ( enforceRouteChunks ) {
729+ validateRouteChunks ( {
730+ ctx,
731+ id : route . file ,
732+ valid : {
733+ clientAction : ! hasClientAction || hasClientActionChunk ,
734+ clientLoader : ! hasClientLoader || hasClientLoaderChunk ,
735+ } ,
736+ } ) ;
737+ }
738+
722739 let routeManifestEntry : ReactRouterManifest [ "routes" ] [ string ] = {
723740 id : route . id ,
724741 parentId : route . parentId ,
@@ -800,6 +817,9 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
800817 ctx
801818 ) ;
802819
820+ let enforceRouteChunks =
821+ ctx . reactRouterConfig . future . unstable_routeChunks === "enforce" ;
822+
803823 for ( let [ key , route ] of Object . entries ( ctx . reactRouterConfig . routes ) ) {
804824 let routeFile = route . file ;
805825 let sourceExports = routeManifestExports [ key ] ;
@@ -819,6 +839,17 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
819839 viteChildCompiler,
820840 } ) ;
821841
842+ if ( enforceRouteChunks ) {
843+ validateRouteChunks ( {
844+ ctx,
845+ id : route . file ,
846+ valid : {
847+ clientAction : ! hasClientAction || hasClientActionChunk ,
848+ clientLoader : ! hasClientLoader || hasClientLoaderChunk ,
849+ } ,
850+ } ) ;
851+ }
852+
822853 routes [ key ] = {
823854 id : route . id ,
824855 parentId : route . parentId ,
@@ -1523,6 +1554,22 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
15231554 return "// Route chunks disabled" ;
15241555 }
15251556
1557+ let enforceRouteChunks =
1558+ ctx . reactRouterConfig . future . unstable_routeChunks === "enforce" ;
1559+
1560+ if ( enforceRouteChunks && chunkName === "main" && chunk ) {
1561+ let exportNames = getExportNames ( chunk . code ) ;
1562+
1563+ validateRouteChunks ( {
1564+ ctx,
1565+ id,
1566+ valid : {
1567+ clientAction : ! exportNames . includes ( "clientAction" ) ,
1568+ clientLoader : ! exportNames . includes ( "clientLoader" ) ,
1569+ } ,
1570+ } ) ;
1571+ }
1572+
15261573 return chunk ?? `// No ${ chunkName } chunk` ;
15271574 } ,
15281575 } ,
@@ -1636,10 +1683,10 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
16361683 let clientFileRE = / \. c l i e n t ( \. [ c m ] ? [ j t ] s x ? ) ? $ / ;
16371684 let clientDirRE = / \/ \. c l i e n t \/ / ;
16381685 if ( clientFileRE . test ( id ) || clientDirRE . test ( id ) ) {
1639- let exports = esModuleLexer ( code ) [ 1 ] ;
1686+ let exports = getExportNames ( code ) ;
16401687 return {
16411688 code : exports
1642- . map ( ( { n : name } ) =>
1689+ . map ( ( name ) =>
16431690 name === "default"
16441691 ? "export default undefined;"
16451692 : `export const ${ name } = undefined;`
@@ -1658,9 +1705,10 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
16581705 if ( ! route ) return ;
16591706
16601707 if ( ! options ?. ssr && ! ctx . reactRouterConfig . ssr ) {
1661- let serverOnlyExports = esModuleLexer ( code ) [ 1 ]
1662- . map ( ( exp ) => exp . n )
1663- . filter ( ( exp ) => SERVER_ONLY_ROUTE_EXPORTS . includes ( exp ) ) ;
1708+ let exportNames = getExportNames ( code ) ;
1709+ let serverOnlyExports = exportNames . filter ( ( exp ) =>
1710+ SERVER_ONLY_ROUTE_EXPORTS . includes ( exp )
1711+ ) ;
16641712 if ( serverOnlyExports . length > 0 ) {
16651713 let str = serverOnlyExports . map ( ( e ) => `\`${ e } \`` ) . join ( ", " ) ;
16661714 let message =
@@ -1671,9 +1719,9 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = (_config) => {
16711719 }
16721720
16731721 if ( route . id !== "root" ) {
1674- let hasHydrateFallback = esModuleLexer ( code ) [ 1 ]
1675- . map ( ( exp ) => exp . n )
1676- . some ( ( exp ) => exp === "HydrateFallback" ) ;
1722+ let hasHydrateFallback = exportNames . some (
1723+ ( exp ) => exp === "HydrateFallback"
1724+ ) ;
16771725 if ( hasHydrateFallback ) {
16781726 let message =
16791727 `SPA Mode: Invalid \`HydrateFallback\` export found in ` +
@@ -2458,3 +2506,46 @@ async function getRouteChunkIfEnabled(
24582506
24592507 return getRouteChunk ( code , chunkName , cache , cacheKey ) ;
24602508}
2509+
2510+ function validateRouteChunks ( {
2511+ ctx,
2512+ id,
2513+ valid,
2514+ } : {
2515+ ctx : ReactRouterPluginContext ;
2516+ id : string ;
2517+ valid : Record < Exclude < RouteChunkName , "main" > , boolean > ;
2518+ } ) : void {
2519+ let invalidChunks = Object . entries ( valid )
2520+ . filter ( ( [ _ , isValid ] ) => ! isValid )
2521+ . map ( ( [ chunkName ] ) => chunkName ) ;
2522+
2523+ if ( invalidChunks . length === 0 ) {
2524+ return ;
2525+ }
2526+
2527+ let plural = invalidChunks . length > 1 ;
2528+
2529+ throw new Error (
2530+ [
2531+ `Route chunks error: ${ normalizeRelativeFilePath (
2532+ id ,
2533+ ctx . reactRouterConfig
2534+ ) } `,
2535+
2536+ invalidChunks . map ( ( name ) => `- ${ name } ` ) . join ( "\n" ) ,
2537+
2538+ `${
2539+ plural ? `These exports were` : `This export was`
2540+ } unable to be split into ${
2541+ plural ? "their own chunks" : "its own chunk"
2542+ } because ${
2543+ plural ? "they reference" : "it references"
2544+ } code in the same file that is used by other route module exports.`,
2545+
2546+ `If you need to share code between ${
2547+ plural ? `these` : `this`
2548+ } and other exports, you should extract the shared code into a separate module.`,
2549+ ] . join ( "\n\n" )
2550+ ) ;
2551+ }
0 commit comments