Skip to content

Commit c03da62

Browse files
chore: stage over updates from mf branch
1 parent 92d4042 commit c03da62

File tree

8 files changed

+485
-261
lines changed

8 files changed

+485
-261
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
],
3434
"scripts": {
3535
"build": "rslib build",
36+
"e2e": "pnpm build && cd examples/default-template; pnpm test:e2e",
3637
"dev": "rslib build --watch",
3738
"test": "vitest run",
3839
"test:watch": "vitest watch",

src/constants.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
export const PLUGIN_NAME = 'rsbuild:react-router';
22

3+
export const JS_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.mjs'] as const;
4+
5+
export const JS_LOADERS = {
6+
'.ts': 'ts',
7+
'.tsx': 'tsx',
8+
'.js': 'js',
9+
'.jsx': 'jsx'
10+
} as const;
11+
312
export const SERVER_ONLY_ROUTE_EXPORTS = [
413
'loader',
514
'action',
@@ -41,4 +50,4 @@ export const CLIENT_EXPORTS = {
4150
links: 'links',
4251
meta: 'meta',
4352
shouldRevalidate: 'shouldRevalidate',
44-
} as const;
53+
} as const;

src/index.ts

Lines changed: 19 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@ import type { RsbuildPlugin, Rspack } from '@rsbuild/core';
55
import * as esbuild from 'esbuild';
66
import { createJiti } from 'jiti';
77
import jsesc from 'jsesc';
8-
import { isAbsolute, relative, resolve } from 'pathe';
8+
import { relative, resolve } from 'pathe';
99
import { RspackVirtualModulePlugin } from 'rspack-plugin-virtual-module';
1010
import { generate, parse } from './babel.js';
11-
import { PLUGIN_NAME, SERVER_EXPORTS, CLIENT_EXPORTS, SERVER_ONLY_ROUTE_EXPORTS } from './constants.js';
11+
import { PLUGIN_NAME, SERVER_ONLY_ROUTE_EXPORTS } from './constants.js';
1212
import { createDevServerMiddleware } from './dev-server.js';
1313
import {
14-
combineURLs,
15-
createRouteId,
16-
generateWithProps,
17-
removeExports,
18-
transformRoute,
14+
generateWithProps,
15+
removeExports,
16+
transformRoute,
17+
findEntryFile
1918
} from './plugin-utils.js';
19+
import {
20+
configRoutesToRouteManifest,
21+
getReactRouterManifestForDev
22+
} from './manifest.js';
23+
import { generateServerBuild } from './server-utils.js';
24+
import { createModifyBrowserManifestPlugin } from './modify-browser-manifest.js';
2025

2126
export type PluginOptions = {
2227
/**
@@ -140,18 +145,9 @@ export const pluginReactRouter = (
140145
return [] as RouteConfigEntry[];
141146
});
142147

143-
const jsExtensions = ['.tsx', '.ts', '.jsx', '.js', '.mjs'];
144-
145-
const findEntryFile = (basePath: string) => {
146-
for (const ext of jsExtensions) {
147-
const filePath = `${basePath}${ext}`;
148-
if (existsSync(filePath)) {
149-
return filePath;
150-
}
151-
}
152-
return `${basePath}.tsx`; // Default to .tsx if no file exists
153-
};
154-
148+
// Remove local JS_EXTENSIONS definition - use the imported one instead
149+
150+
// Remove duplicate findEntryFile implementation
155151
const entryClientPath = findEntryFile(
156152
resolve(appDirectory, 'entry.client'),
157153
);
@@ -208,6 +204,7 @@ export const pluginReactRouter = (
208204
basename,
209205
appDirectory,
210206
ssr,
207+
federation: false,
211208
}),
212209
'virtual/react-router/with-props': generateWithProps(),
213210
});
@@ -329,61 +326,9 @@ export const pluginReactRouter = (
329326
tools: {
330327
rspack: (rspackConfig) => {
331328
if (rspackConfig.plugins) {
332-
rspackConfig.plugins.push({
333-
apply(compiler: Rspack.Compiler) {
334-
compiler.hooks.emit.tapAsync(
335-
'ModifyBrowserManifest',
336-
async (compilation: Rspack.Compilation, callback) => {
337-
const manifest = await getReactRouterManifestForDev(
338-
routes,
339-
pluginOptions,
340-
compilation.getStats().toJson(),
341-
appDirectory,
342-
);
343-
344-
const manifestPath =
345-
'static/js/virtual/react-router/browser-manifest.js';
346-
if (compilation.assets[manifestPath]) {
347-
const originalSource = compilation.assets[
348-
manifestPath
349-
]
350-
.source()
351-
.toString();
352-
const newSource = originalSource.replace(
353-
/["'`]PLACEHOLDER["'`]/,
354-
jsesc(manifest, { es6: true }),
355-
);
356-
compilation.assets[manifestPath] = {
357-
source: () => newSource,
358-
size: () => newSource.length,
359-
map: () => ({
360-
version: 3,
361-
sources: [manifestPath],
362-
names: [],
363-
mappings: '',
364-
file: manifestPath,
365-
sourcesContent: [newSource],
366-
}),
367-
sourceAndMap: () => ({
368-
source: newSource,
369-
map: {
370-
version: 3,
371-
sources: [manifestPath],
372-
names: [],
373-
mappings: '',
374-
file: manifestPath,
375-
sourcesContent: [newSource],
376-
},
377-
}),
378-
updateHash: (hash) => hash.update(newSource),
379-
buffer: () => Buffer.from(newSource),
380-
};
381-
}
382-
callback();
383-
},
384-
);
385-
},
386-
});
329+
rspackConfig.plugins.push(
330+
createModifyBrowserManifestPlugin(routes, pluginOptions, appDirectory)
331+
);
387332
}
388333
return rspackConfig;
389334
},
@@ -478,187 +423,3 @@ export const pluginReactRouter = (
478423
);
479424
},
480425
});
481-
482-
// Helper functions
483-
function configRoutesToRouteManifest(
484-
appDirectory: string,
485-
routes: RouteConfigEntry[],
486-
rootId = 'root',
487-
) {
488-
const routeManifest: Record<string, Route> = {};
489-
490-
function walk(route: RouteConfigEntry, parentId: string) {
491-
const id = route.id || createRouteId(route.file);
492-
const manifestItem = {
493-
id,
494-
parentId,
495-
file: isAbsolute(route.file)
496-
? relative(appDirectory, route.file)
497-
: route.file,
498-
path: route.path,
499-
index: route.index,
500-
caseSensitive: route.caseSensitive,
501-
};
502-
503-
if (Object.prototype.hasOwnProperty.call(routeManifest, id)) {
504-
throw new Error(
505-
`Unable to define routes with duplicate route id: "${id}"`,
506-
);
507-
}
508-
routeManifest[id] = manifestItem;
509-
510-
if (route.children) {
511-
for (const child of route.children) {
512-
walk(child, id);
513-
}
514-
}
515-
}
516-
517-
for (const route of routes) {
518-
walk(route, rootId);
519-
}
520-
521-
return routeManifest;
522-
}
523-
524-
async function getReactRouterManifestForDev(
525-
routes: Record<string, Route>,
526-
//@ts-ignore
527-
options: PluginOptions,
528-
clientStats: Rspack.StatsCompilation | undefined,
529-
context: string,
530-
): Promise<{
531-
version: string;
532-
url: string;
533-
entry: {
534-
module: string;
535-
imports: string[];
536-
css: string[];
537-
};
538-
routes: Record<string, RouteManifestItem>;
539-
}> {
540-
const result: Record<string, RouteManifestItem> = {};
541-
542-
for (const [key, route] of Object.entries(routes)) {
543-
const assets = clientStats?.assetsByChunkName?.[route.id];
544-
const jsAssets = assets?.filter((asset) => asset.endsWith('.js')) || [];
545-
const cssAssets = assets?.filter((asset) => asset.endsWith('.css')) || [];
546-
// Read and analyze the route file to check for exports
547-
const routeFilePath = resolve(context, route.file);
548-
let exports = new Set<string>();
549-
550-
try {
551-
const buildResult = await esbuild.build({
552-
entryPoints: [routeFilePath],
553-
bundle: false,
554-
write: false,
555-
metafile: true,
556-
jsx: 'automatic',
557-
format: 'esm',
558-
platform: 'neutral',
559-
loader: {
560-
'.ts': 'ts',
561-
'.tsx': 'tsx',
562-
'.js': 'js',
563-
'.jsx': 'jsx'
564-
},
565-
});
566-
567-
// Get exports from the metafile
568-
const entryPoint = Object.values(buildResult.metafile.outputs)[0];
569-
if (entryPoint?.exports) {
570-
exports = new Set(entryPoint.exports);
571-
}
572-
} catch (error) {
573-
console.error(`Failed to analyze route file ${routeFilePath}:`, error);
574-
}
575-
576-
result[key] = {
577-
id: route.id,
578-
parentId: route.parentId,
579-
path: route.path,
580-
index: route.index,
581-
caseSensitive: route.caseSensitive,
582-
module: combineURLs('/', jsAssets[0] || ''),
583-
hasAction: exports.has(SERVER_EXPORTS.action),
584-
hasLoader: exports.has(SERVER_EXPORTS.loader),
585-
hasClientAction: exports.has(CLIENT_EXPORTS.clientAction),
586-
hasClientLoader: exports.has(CLIENT_EXPORTS.clientLoader),
587-
hasErrorBoundary: exports.has(CLIENT_EXPORTS.ErrorBoundary),
588-
imports: jsAssets.map((asset) => combineURLs('/', asset)),
589-
css: cssAssets.map((asset) => combineURLs('/', asset)),
590-
};
591-
}
592-
593-
const entryAssets = clientStats?.assetsByChunkName?.['entry.client'];
594-
const entryJsAssets =
595-
entryAssets?.filter((asset) => asset.endsWith('.js')) || [];
596-
const entryCssAssets =
597-
entryAssets?.filter((asset) => asset.endsWith('.css')) || [];
598-
599-
return {
600-
version: String(Math.random()),
601-
url: '/static/js/virtual/react-router/browser-manifest.js',
602-
entry: {
603-
module: combineURLs('/', entryJsAssets[0] || ''),
604-
imports: entryJsAssets.map((asset) => combineURLs('/', asset)),
605-
css: entryCssAssets.map((asset) => combineURLs('/', asset)),
606-
},
607-
routes: result,
608-
};
609-
}
610-
611-
/**
612-
* Generates the server build module content
613-
* @param routes The route manifest
614-
* @param options Build options
615-
* @returns The generated module content as a string
616-
*/
617-
function generateServerBuild(
618-
routes: Record<string, Route>,
619-
options: {
620-
entryServerPath: string;
621-
assetsBuildDirectory: string;
622-
basename: string;
623-
appDirectory: string;
624-
ssr: boolean;
625-
},
626-
): string {
627-
return `
628-
import * as entryServer from ${JSON.stringify(options.entryServerPath)};
629-
${Object.keys(routes)
630-
.map((key, index) => {
631-
const route = routes[key];
632-
return `import * as route${index} from ${JSON.stringify(
633-
`${resolve(options.appDirectory, route.file)}?react-router-route`,
634-
)};`;
635-
})
636-
.join('\n')}
637-
638-
export { default as assets } from "virtual/react-router/server-manifest";
639-
export const assetsBuildDirectory = ${JSON.stringify(
640-
options.assetsBuildDirectory,
641-
)};
642-
export const basename = ${JSON.stringify(options.basename)};
643-
export const future = ${JSON.stringify({})};
644-
export const isSpaMode = ${!options.ssr};
645-
export const ssr = ${options.ssr};
646-
export const publicPath = "/";
647-
export const entry = { module: entryServer };
648-
export const routes = {
649-
${Object.keys(routes)
650-
.map((key, index) => {
651-
const route = routes[key];
652-
return `${JSON.stringify(key)}: {
653-
id: ${JSON.stringify(route.id)},
654-
parentId: ${JSON.stringify(route.parentId)},
655-
path: ${JSON.stringify(route.path)},
656-
index: ${JSON.stringify(route.index)},
657-
caseSensitive: ${JSON.stringify(route.caseSensitive)},
658-
module: route${index}
659-
}`;
660-
})
661-
.join(',\n ')}
662-
};
663-
`;
664-
}

0 commit comments

Comments
 (0)