rsbuild-plugin-react-router 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,6 +76,16 @@ rsbuild.config.ts
76
76
 
77
77
  ## Configuration
78
78
 
79
+ This plugin requires Rsbuild 2.2.8 or newer. Development hot data revalidation
80
+ uses its custom-event connection API and no longer writes an HDR revision file.
81
+
82
+ For server-only changes, `rsbuild build --environment node` can reuse the
83
+ finalized browser manifest from a previous full build in the same project.
84
+ Keep the browser output and build cache. Run a full build after changing routes
85
+ or browser assets, or adding or removing a route's `loader` or `action` export.
86
+ Node-only builds check the compiled exports against the cached manifest. If the
87
+ manifest is missing or incompatible, the build fails and asks for a full build.
88
+
79
89
  React Router application settings live in `react-router.config.*`. The Rsbuild
80
90
  plugin only needs options for Rsbuild-specific behavior.
81
91
 
@@ -83,6 +93,7 @@ plugin only needs options for Rsbuild-specific behavior.
83
93
 
84
94
  ```ts
85
95
  pluginReactRouter({
96
+ typegen: true,
86
97
  customServer: false,
87
98
  lazyCompilation: true,
88
99
  unstableLazyCompilationPrewarm: false,
@@ -93,12 +104,13 @@ pluginReactRouter({
93
104
 
94
105
  | Option | Default | Description |
95
106
  | -------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | `typegen` | `true` | Generates React Router route types during development and builds. Set to `false` when type generation is managed separately. |
96
108
  | `customServer` | `false` | Disables the built-in development SSR middleware. Enable this when an app owns the server with `createDevServer()` or an adapter. |
97
109
  | `serverOutput` | Derived | Emitted Rsbuild server format: `'module'` or `'commonjs'`. When omitted, React Router's `serverModuleFormat` selects the format (`'esm'` -> `'module'`, `'cjs'` -> `'commonjs'`); setting `serverOutput` overrides it. |
98
110
  | `lazyCompilation` | `true` | Optional Rsbuild dev lazy-compilation config. When enabled here or through `dev.lazyCompilation`, React Router hydration-critical modules stay eager so the browser manifest and route modules are not replaced by lazy proxies. |
99
111
  | `unstableLazyCompilationPrewarm` | `false` | Experimental prewarm for emitted lazy-compilation proxy modules after dev compiles. Enable with `true` when route JS proxy startup should happen shortly after compiler readiness. |
100
112
  | `logPerformance` | `false` | Logs structured React Router plugin timing information. |
101
- | `parallelRouteTransform` | `undefined` | Controls worker-thread route transforms. `undefined` and `false` keep transforms inline, `true` uses an automatic worker count, and a positive integer sets the maximum worker count. |
113
+ | `parallelRouteTransform` | `undefined` | Controls worker-thread route transforms. `undefined` and `false` keep transforms inline, `true` uses an automatic worker count, and a positive integer sets the maximum worker count. |
102
114
  | `onRouteTopologyChange` | `undefined` | Notification for programmatic/custom dev servers. Recreate the Rsbuild server when route files are added, removed, or moved. The callback is not awaited. |
103
115
  | `federation` | `false` | Enables the plugin's experimental Module Federation integration. |
104
116
 
package/dist/468.js CHANGED
@@ -44,5 +44,5 @@ let PLUGIN_NAME = 'rsbuild:react-router', DEFAULT_JS_DIST_PATH = 'static/js', JS
44
44
  links: 'links',
45
45
  meta: 'meta',
46
46
  shouldRevalidate: 'shouldRevalidate'
47
- }, SPA_FALLBACK_HTML_FILE = '__spa-fallback.html';
48
- export { BUILD_CLIENT_ROUTE_QUERY_STRING, CLIENT_EXPORTS, CLIENT_NON_COMPONENT_EXPORTS, CLIENT_ROUTE_EXPORTS_SET, DEFAULT_JS_DIST_PATH, JS_EXTENSIONS, NAMED_COMPONENT_EXPORTS_SET, PLUGIN_NAME, SERVER_EXPORTS, SERVER_ONLY_ROUTE_EXPORTS, SERVER_ONLY_ROUTE_EXPORTS_SET, SPA_FALLBACK_HTML_FILE };
47
+ }, SPA_FALLBACK_HTML_FILE = '__spa-fallback.html', BROWSER_MANIFEST_ENTRY_NAME = 'virtual/react-router/browser-manifest';
48
+ export { BROWSER_MANIFEST_ENTRY_NAME, BUILD_CLIENT_ROUTE_QUERY_STRING, CLIENT_EXPORTS, CLIENT_NON_COMPONENT_EXPORTS, CLIENT_ROUTE_EXPORTS_SET, DEFAULT_JS_DIST_PATH, JS_EXTENSIONS, NAMED_COMPONENT_EXPORTS_SET, PLUGIN_NAME, SERVER_EXPORTS, SERVER_ONLY_ROUTE_EXPORTS, SERVER_ONLY_ROUTE_EXPORTS_SET, SPA_FALLBACK_HTML_FILE };
package/dist/511.js CHANGED
@@ -2,7 +2,7 @@ import { basename, dirname, normalize, relative, resolve } from "pathe";
2
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { langFromPath, parse, walk as external_yuku_parser_walk } from "yuku-parser";
5
- import { Analyzer } from "yuku-analyzer";
5
+ import { Analyzer, SymbolFlags } from "yuku-analyzer";
6
6
  import { print } from "yuku-codegen";
7
7
  import { readFile, stat } from "node:fs/promises";
8
8
  import { rspack } from "@rsbuild/core";
@@ -300,9 +300,9 @@ let getProgram = (ast)=>ast.program ?? ast, getPatternIdentifierNames = (pattern
300
300
  if (declaration && declarationIncludesName(declaration, name)) return !0;
301
301
  }
302
302
  return !1;
303
- }, requireFromApp = createRequire(resolve(process.cwd(), 'package.json')), resolveAppPackagePath = (specifier)=>{
303
+ }, requireFromApp = createRequire(resolve(process.cwd(), 'package.json')), resolveAppPackagePath = (specifier, rootPath)=>{
304
304
  try {
305
- return requireFromApp.resolve(specifier);
305
+ return (rootPath ? createRequire(resolve(rootPath, 'package.json')) : requireFromApp).resolve(specifier);
306
306
  } catch {
307
307
  return;
308
308
  }
@@ -408,12 +408,7 @@ let mightContainRouteChunkExportName = (source)=>routeChunkExportNames.some((exp
408
408
  module,
409
409
  program: module.ast
410
410
  };
411
- }), isTopLevelExportedVariableDeclarator = (module, node)=>{
412
- let declaration = module.parentOf(node);
413
- if (declaration?.type !== 'VariableDeclaration') return !1;
414
- let statement = module.parentOf(declaration);
415
- return statement?.type === 'ExportNamedDeclaration';
416
- }, route_chunks_getExportedName = (exported)=>'Identifier' === exported.type ? exported.name : String(exported.value), setsIntersect = (set1, set2)=>{
411
+ }), route_chunks_getExportedName = (exported)=>'Identifier' === exported.type ? exported.name : String(exported.value), setsIntersect = (set1, set2)=>{
417
412
  let smallerSet = set1, largerSet = set2;
418
413
  for (let element of (set1.size > set2.size && (smallerSet = set2, largerSet = set1), smallerSet))if (largerSet.has(element)) return !0;
419
414
  return !1;
@@ -422,7 +417,13 @@ let mightContainRouteChunkExportName = (source)=>routeChunkExportNames.some((exp
422
417
  let resolved = normalize(resolve('/', dirname(importer), source));
423
418
  return /(?:^|\/)entry\.client(?:\.[cm]?[jt]sx?)?$/.test(resolved);
424
419
  }, getExportDependencies = (code, cache, cacheKey)=>getOrSetFromCache(cache, `${cacheKey}::getExportDependencies`, code, ()=>{
425
- let { module } = analyzeCode(code, cache, cacheKey), exportDependencies = new Map(), topLevelStatementCache = new Map(), variableDeclaratorCache = new Map(), getCachedTopLevelStatementForNode = (node)=>{
420
+ let { module } = analyzeCode(code, cache, cacheKey), namedExports = module.exports.filter((exp)=>null !== exp.name && !exp.typeOnly && !exp.isStar && !exp.isExportEquals), hasDecorators = !1;
421
+ external_yuku_parser_walk(module.ast, {
422
+ Decorator (_node, context) {
423
+ hasDecorators = !0, context.stop();
424
+ }
425
+ });
426
+ let exportDependencies = new Map(), topLevelStatementCache = new Map(), exportedVariableDeclaratorCache = new Map(), getCachedTopLevelStatementForNode = (node)=>{
426
427
  let cached = topLevelStatementCache.get(node);
427
428
  if (cached) return cached;
428
429
  let statement = ((module, node)=>{
@@ -431,17 +432,34 @@ let mightContainRouteChunkExportName = (source)=>routeChunkExportNames.some((exp
431
432
  return invariant(parent?.type === 'Program', 'Expected node to be within Program'), current;
432
433
  })(module, node);
433
434
  return topLevelStatementCache.set(node, statement), statement;
434
- }, getCachedVariableDeclaratorForNode = (node)=>{
435
- if (variableDeclaratorCache.has(node)) return variableDeclaratorCache.get(node) ?? null;
435
+ }, nonShareableExportedSymbols = new Set();
436
+ for (let { local } of namedExports){
437
+ if (!local?.has(SymbolFlags.ValueSpace | SymbolFlags.ValueImport)) continue;
438
+ let isImport = local.declarations.every((declaration)=>'ImportDeclaration' === getCachedTopLevelStatementForNode(declaration).type), hasSetup = isImport && local.references.some((reference)=>{
439
+ let statement = getCachedTopLevelStatementForNode(reference.node);
440
+ return 'value' === reference.kind && 'ImportDeclaration' !== statement.type && !statement.type.startsWith('Export');
441
+ });
442
+ (!isImport || hasSetup) && nonShareableExportedSymbols.add(local);
443
+ }
444
+ let isRuntimeRelevantReference = (reference)=>hasDecorators || 'value' === reference.kind || ((reference)=>{
445
+ let node = reference.node, parent = module.parentOf(node);
446
+ for(; parent?.type === 'TSQualifiedName';)node = parent, parent = module.parentOf(node);
447
+ return parent?.type === 'TSImportEqualsDeclaration' && parent.moduleReference === node && 'type' !== parent.importKind;
448
+ })(reference), getCachedExportedVariableDeclaratorForNode = (node)=>{
449
+ if (exportedVariableDeclaratorCache.has(node)) return exportedVariableDeclaratorCache.get(node) ?? null;
436
450
  let declarator = ((module, node)=>{
437
451
  let current = node;
438
- for(; current;){
439
- if ('VariableDeclarator' === current.type) return current;
440
- current = module.parentOf(current);
452
+ for(;;){
453
+ let parent = module.parentOf(current);
454
+ if (!parent || 'Program' === parent.type) return null;
455
+ if ('VariableDeclarator' === current.type && 'VariableDeclaration' === parent.type) {
456
+ let exported = module.parentOf(parent);
457
+ if (exported?.type === 'ExportNamedDeclaration' && module.parentOf(exported)?.type === 'Program') return current;
458
+ }
459
+ current = parent;
441
460
  }
442
- return null;
443
461
  })(module, node);
444
- return variableDeclaratorCache.set(node, declarator), declarator;
462
+ return exportedVariableDeclaratorCache.set(node, declarator), declarator;
445
463
  }, addCachedTopLevelStatement = (dependencies, node)=>{
446
464
  let statement = getCachedTopLevelStatementForNode(node);
447
465
  return dependencies.topLevelStatements.add(statement), 'ImportDeclaration' === statement.type || statement.type.startsWith('Export') || dependencies.topLevelNonModuleStatements.add(statement), statement;
@@ -451,46 +469,47 @@ let mightContainRouteChunkExportName = (source)=>routeChunkExportNames.some((exp
451
469
  topLevelNonModuleStatements: new Set(),
452
470
  importedIdentifierNames: new Set(),
453
471
  importSources: new Set(),
454
- exportedVariableDeclarators: new Set()
455
- }, visitedSymbols = new Set(), scannedNodes = new Set(), scanNode = (node)=>{
472
+ exportedVariableDeclarators: new Set(),
473
+ exportedLocalSymbols: new Set()
474
+ }, visitedSymbols = new Set(), scannedNodes = new Set(), visitIdentifier = (node)=>{
475
+ let reference = module.referenceOf(node);
476
+ if (reference) {
477
+ reference.symbol && isRuntimeRelevantReference(reference) && visitSymbol(reference.symbol);
478
+ return;
479
+ }
480
+ let symbol = module.symbolOf(node);
481
+ symbol?.scope === module.rootScope && symbol.has(SymbolFlags.ValueSpace | SymbolFlags.ValueImport) && dependencies.topLevelNonModuleStatements.has(getCachedTopLevelStatementForNode(node)) && visitSymbol(symbol);
482
+ }, scanNode = (node)=>{
456
483
  scannedNodes.has(node) || (scannedNodes.add(node), external_yuku_parser_walk(node, {
457
- Identifier (node) {
458
- let reference = module.referenceOf(node);
459
- reference?.symbol && visitSymbol(reference.symbol);
460
- }
484
+ Identifier: visitIdentifier,
485
+ JSXIdentifier: visitIdentifier
461
486
  }));
462
487
  }, visitSymbol = (symbol)=>{
463
488
  if (!visitedSymbols.has(symbol) && (visitedSymbols.add(symbol), 0 !== symbol.declarations.length)) {
464
- for (let declaration of symbol.declarations){
489
+ for (let declaration of (nonShareableExportedSymbols.has(symbol) && dependencies.exportedLocalSymbols.add(symbol), symbol.declarations)){
465
490
  let statement = addCachedTopLevelStatement(dependencies, declaration);
466
- if ('ImportDeclaration' === statement.type) {
467
- dependencies.importedIdentifierNames.add(symbol.name), 'string' == typeof statement.source?.value && dependencies.importSources.add(statement.source.value);
468
- return;
469
- }
470
- let declarator = getCachedVariableDeclaratorForNode(declaration);
471
- declarator && isTopLevelExportedVariableDeclarator(module, declarator) && dependencies.exportedVariableDeclarators.add(declarator), scanNode(declarator ?? statement);
491
+ if ('ImportDeclaration' === statement.type && (dependencies.importedIdentifierNames.add(symbol.name), 'string' == typeof statement.source?.value && dependencies.importSources.add(statement.source.value), symbol !== localSymbol)) return;
492
+ let declarator = getCachedExportedVariableDeclaratorForNode(declaration);
493
+ declarator && dependencies.exportedVariableDeclarators.add(declarator), scanNode(declarator ?? statement);
472
494
  }
473
495
  for (let reference of symbol.references){
496
+ if (!isRuntimeRelevantReference(reference)) continue;
474
497
  let statement = addCachedTopLevelStatement(dependencies, reference.node);
475
- scanNode(getCachedVariableDeclaratorForNode(reference.node) ?? statement);
498
+ scanNode(getCachedExportedVariableDeclaratorForNode(reference.node) ?? statement);
476
499
  }
477
500
  }
478
501
  };
479
502
  addCachedTopLevelStatement(dependencies, exportNode), localSymbol ? visitSymbol(localSymbol) : scanNode(getCachedTopLevelStatementForNode(exportNode)), exportDependencies.set(exportName, dependencies);
480
503
  };
481
- for (let exp of module.exports)exp.typeOnly || exp.isStar || exp.isExportEquals || handleExport(exp.name, exp.node, exp.local ?? null);
504
+ for (let exp of namedExports)handleExport(exp.name, exp.node, exp.local ?? null);
482
505
  return exportDependencies;
483
506
  }), isExportChunkable = (exportName, exportDependencies, importer)=>{
484
507
  let dependencies = exportDependencies.get(exportName);
485
- if (!dependencies || 'clientLoader' === exportName && hasHydrateAssignment(dependencies) || 'clientLoader' === exportName && ((dependencies, importer)=>{
508
+ if (!dependencies || dependencies.exportedVariableDeclarators.size > 1 || 'clientLoader' === exportName && hasHydrateAssignment(dependencies) || 'clientLoader' === exportName && ((dependencies, importer)=>{
486
509
  for (let source of dependencies.importSources)if (isEntryClientImport(source, importer)) return !0;
487
510
  return !1;
488
511
  })(dependencies, importer)) return !1;
489
- for (let [currentExportName, currentDependencies] of exportDependencies)if (currentExportName !== exportName && setsIntersect(currentDependencies.topLevelNonModuleStatements, dependencies.topLevelNonModuleStatements)) return !1;
490
- if (dependencies.exportedVariableDeclarators.size > 1) return !1;
491
- if (dependencies.exportedVariableDeclarators.size > 0) {
492
- for (let [currentExportName, currentDependencies] of exportDependencies)if (currentExportName !== exportName && setsIntersect(currentDependencies.exportedVariableDeclarators, dependencies.exportedVariableDeclarators)) return !1;
493
- }
512
+ for (let [currentExportName, currentDependencies] of exportDependencies)if (currentExportName !== exportName && (setsIntersect(currentDependencies.topLevelNonModuleStatements, dependencies.topLevelNonModuleStatements) || setsIntersect(currentDependencies.exportedVariableDeclarators, dependencies.exportedVariableDeclarators) || setsIntersect(currentDependencies.exportedLocalSymbols, dependencies.exportedLocalSymbols))) return !1;
494
513
  return !0;
495
514
  }, hasHydrateAssignment = (dependencies)=>Array.from(dependencies.topLevelNonModuleStatements).some((statement)=>{
496
515
  let expression = statement.expression, left = expression?.left;
@@ -39,3 +39,4 @@ export declare const CLIENT_EXPORTS: {
39
39
  readonly shouldRevalidate: "shouldRevalidate";
40
40
  };
41
41
  export declare const SPA_FALLBACK_HTML_FILE = "__spa-fallback.html";
42
+ export declare const BROWSER_MANIFEST_ENTRY_NAME = "virtual/react-router/browser-manifest";
@@ -0,0 +1,9 @@
1
+ import type { RsbuildDevServer } from '@rsbuild/core';
2
+ export declare const DEV_HDR_UPDATE_EVENT = "react-router:hdr-update";
3
+ export declare const createDevHdrChannel: ({ hot, isEnabled, }: {
4
+ hot: RsbuildDevServer["environments"][string]["hot"];
5
+ isEnabled: () => boolean;
6
+ }) => {
7
+ publish(): void;
8
+ close(): void;
9
+ };
package/dist/dev-hmr.d.ts CHANGED
@@ -4,7 +4,6 @@ export declare const DEV_MANIFEST_UPDATE_EVENT = "react-router:manifest-update";
4
4
  export type DevHmrPlanOptions = {
5
5
  isEnabled: () => boolean;
6
6
  runtimeModule: string;
7
- onNodeRebuildCommitted: () => void;
8
7
  };
9
8
  export declare const isRspackSwcReactRefreshEnabled: (rspackConfig: Rspack.Configuration) => boolean;
10
9
  /**
@@ -16,25 +15,6 @@ export declare const isRspackSwcReactRefreshEnabled: (rspackConfig: Rspack.Confi
16
15
  * dev HMR falls back to full reloads.
17
16
  */
18
17
  export declare const resolveReactRefreshRuntimePath: (rootPath: string) => string | undefined;
19
- /**
20
- * The HDR revision module is a real file (not a virtual module) because it
21
- * must wake the web compiler through the regular file watcher: the browser
22
- * HMR runtime imports it, so bumping the revision produces a web hot update
23
- * whenever server code changes, which the client answers by revalidating
24
- * React Router loader data.
25
- */
26
- export declare const getDevHdrRevisionFilePath: (rootPath: string) => string;
27
- export type DevHdrRevisionSignal = {
28
- filePath: string;
29
- /** Writes the initial revision module so the first compile can resolve it. */
30
- ensure: () => void;
31
- /** Increments the revision, signaling hot data revalidation to the client. */
32
- bump: () => void;
33
- };
34
- export declare const createDevHdrRevisionSignal: ({ filePath, onError, }: {
35
- filePath: string;
36
- onError?: (error: Error) => void;
37
- }) => DevHdrRevisionSignal;
38
18
  /**
39
19
  * Browser-side HMR runtime shared by all route client entries in development.
40
20
  *
@@ -45,7 +25,6 @@ export declare const createDevHdrRevisionSignal: ({ filePath, onError, }: {
45
25
  * recreating the client routes with revalidation opt-out, revalidating loader
46
26
  * data, and finally performing a React refresh.
47
27
  */
48
- export declare const generateDevHmrRuntimeModule: ({ reactRefreshRuntimePath, hdrRevisionFilePath, }: {
28
+ export declare const generateDevHmrRuntimeModule: ({ reactRefreshRuntimePath, }: {
49
29
  reactRefreshRuntimePath: string;
50
- hdrRevisionFilePath: string;
51
30
  }) => string;
@@ -14,11 +14,6 @@ type CreateControllerOptions = {
14
14
  * flags) in place, so metadata-only changes no longer need a full reload.
15
15
  */
16
16
  clientPatchesRouteMetadata?: boolean | (() => boolean);
17
- /**
18
- * Invoked after a development attempt commits a re-evaluated node build for
19
- * changed server files. Used to signal hot data revalidation to the client.
20
- */
21
- onNodeRebuildCommitted?: () => void;
22
17
  };
23
- export declare const createReactRouterDevRuntimeController: ({ api, isBuild, buildPlan, clientPatchesRouteMetadata, onNodeRebuildCommitted, }: CreateControllerOptions) => ReactRouterDevRuntimeController;
18
+ export declare const createReactRouterDevRuntimeController: ({ api, isBuild, buildPlan, clientPatchesRouteMetadata, }: CreateControllerOptions) => ReactRouterDevRuntimeController;
24
19
  export {};
@@ -6,12 +6,14 @@ export type DevServerMiddleware = (req: IncomingMessage, res: ServerResponse, ne
6
6
  type RequestHandler = (request: Request) => Response | Promise<Response>;
7
7
  type BuildProvider = () => Promise<ServerBuild>;
8
8
  export type DevServerMiddlewareDependencies = {
9
+ rootPath: string;
9
10
  loadBuild: BuildProvider;
10
11
  createRequestHandler?: (build: BuildProvider, mode: 'development') => RequestHandler;
11
12
  createRequestListener?: (handler: RequestHandler) => (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
12
13
  };
13
14
  export declare const createDevServerMiddleware: (dependencies: DevServerMiddlewareDependencies) => DevServerMiddleware;
14
- export declare const createReactRouterDevServerSetup: ({ loadBuild, }: {
15
+ export declare const createReactRouterDevServerSetup: ({ loadBuild, rootPath, }: {
15
16
  loadBuild: BuildProvider;
17
+ rootPath: string;
16
18
  }) => ServerSetup;
17
19
  export {};