rsbuild-plugin-react-router 0.3.1 → 0.4.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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  let globalStore, o, origin, bigint1e6, characters, charactersLength, O, quoteValue, whitespace, options, boundaries, description, options1, f;
2
- import { existsSync, readFileSync, realpathSync, watch } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, watch, writeFileSync } from "node:fs";
3
3
  import fs_extra from "fs-extra";
4
4
  import { rspack } from "@rsbuild/core";
5
5
  import { createJiti } from "jiti";
6
- import { dirname, isAbsolute, relative, resolve as external_pathe_resolve } from "pathe";
6
+ import { dirname, isAbsolute, join as external_pathe_join, relative, resolve as external_pathe_resolve } from "pathe";
7
7
  import { availableParallelism, cpus } from "node:os";
8
8
  import { createRequestHandler as external_react_router_createRequestHandler, matchRoutes } from "react-router";
9
9
  import { access, mkdir, readdir, writeFile } from "node:fs/promises";
@@ -13,7 +13,7 @@ import jsesc from "jsesc";
13
13
  import { Worker } from "node:worker_threads";
14
14
  import { createRequire } from "node:module";
15
15
  import { dirname as external_node_path_dirname, isAbsolute as external_node_path_isAbsolute, relative as external_node_path_relative, resolve as external_node_path_resolve, sep as external_node_path_sep, dirname as __rspack_dirname } from "node:path";
16
- import { createRouteId, SERVER_EXPORTS, combineURLs, JS_EXTENSIONS, executeRouteTransformTask, setBoundedCacheEntry, PLUGIN_NAME, generateWithProps, getRouteModuleAnalysis, createEmptyRouteChunkByExportName, findEntryFile, routeChunkExportNames, validateRouteChunks, CLIENT_EXPORTS, buildManifestChunkValidity, BUILD_CLIENT_ROUTE_QUERY_STRING, getRouteChunkEntryName, getRouteChunkModuleId, normalizeAssetPrefix, detectRouteChunksIfEnabled, createBundlerRouteExportResolver } from "./451.js";
16
+ import { createRouteId, HMR_PATCHABLE_ROUTE_FLAGS, SERVER_EXPORTS, combineURLs, JS_EXTENSIONS, executeRouteTransformTask, setBoundedCacheEntry, PLUGIN_NAME, generateWithProps, getRouteModuleAnalysis, createEmptyRouteChunkByExportName, findEntryFile, routeChunkExportNames, validateRouteChunks, CLIENT_EXPORTS, buildManifestChunkValidity, BUILD_CLIENT_ROUTE_QUERY_STRING, getRouteChunkEntryName, getRouteChunkModuleId, normalizeAssetPrefix, detectRouteChunksIfEnabled, createBundlerRouteExportResolver } from "./451.js";
17
17
  let getAvailableCpuCount = ()=>'function' == typeof availableParallelism ? availableParallelism() : cpus().length, getDefaultConcurrency = (cpuCount = getAvailableCpuCount())=>Math.max(0, Math.floor(cpuCount) - 2), getCappedPluginConcurrency = (cap = 16)=>Math.max(1, Math.min(cap, getDefaultConcurrency() || 1)), createReactRouterHydrationModuleTest = (entryClientPath)=>{
18
18
  let eagerPatterns = [
19
19
  'virtual/react-router/browser-manifest',
@@ -7118,7 +7118,7 @@ let normalizePath = (value)=>value.replace(/\/\/+/g, '/').replace(/(.+)\/$/, '$1
7118
7118
  }))([
7119
7119
  'Warning: Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.',
7120
7120
  'You may want to use the `prerender()` API to prerender the following paths:',
7121
- ...paramRoutes1.map((path)=>` - ${path}`)
7121
+ ...paramRoutes1.map((path)=>` - ${path.replace(/^\/(?=[:*])/, '')}`)
7122
7122
  ].join('\n')), paths1;
7123
7123
  if ('function' == typeof pathsConfig) {
7124
7124
  let resolved = await pathsConfig({
@@ -7334,11 +7334,11 @@ let redirectStatusCodes = new Set([
7334
7334
  }))), (controller)=>sync(()=>{
7335
7335
  controller.abort();
7336
7336
  }))), prerenderData = async ({ handler, prerenderPath, onlyRoutes, clientBuildDir, basename, trailingSlashAwareDataRequests, api, requestInit })=>{
7337
- let dataRequestPath = trailingSlashAwareDataRequests ? prerenderPath.endsWith('/') ? `${prerenderPath}_.data` : `${prerenderPath}.data` : '/' === prerenderPath ? '/_root.data' : `${prerenderPath.replace(/\/$/, '')}.data`, normalizedPath = `${basename}${dataRequestPath}`.replace(/\/\/+/g, '/'), url = new URL(`http://localhost${normalizedPath}`);
7337
+ let dataOutputPath = trailingSlashAwareDataRequests ? prerenderPath.endsWith('/') ? `${prerenderPath}_.data` : `${prerenderPath}.data` : '/' === prerenderPath ? '/_root.data' : `${prerenderPath.replace(/\/$/, '')}.data`, dataRequestPath = trailingSlashAwareDataRequests && '/' === prerenderPath ? '/_root.data' : dataOutputPath, normalizedPath = `${basename}${dataRequestPath}`.replace(/\/\/+/g, '/'), outputNormalizedPath = dataOutputPath === dataRequestPath ? normalizedPath : `${basename}${dataOutputPath}`.replace(/\/\/+/g, '/'), url = new URL(`http://localhost${normalizedPath}`);
7338
7338
  return onlyRoutes?.length && url.searchParams.set('_routes', onlyRoutes.join(',')), withBuildRequest(url, requestInit, async (request)=>{
7339
7339
  let response = await handler(request), data = await response.text();
7340
7340
  if (200 !== response.status && 202 !== response.status) throw Error(`Prerender (data): Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${prerenderPath}\` path.\n${normalizedPath}`);
7341
- let outputPath = external_pathe_resolve(clientBuildDir, ...normalizedPath.split('/'));
7341
+ let outputPath = external_pathe_resolve(clientBuildDir, ...outputNormalizedPath.split('/'));
7342
7342
  return await mkdir(dirname(outputPath), {
7343
7343
  recursive: !0
7344
7344
  }), await writeFile(outputPath, data), api.logger.info(`Prerender (data): ${prerenderPath} -> ${relative(process.cwd(), outputPath)}`), data;
@@ -7431,18 +7431,21 @@ let redirectStatusCodes = new Set([
7431
7431
  trailingSlashAwareDataRequests: future.unstable_trailingSlashAwareDataRequests,
7432
7432
  api
7433
7433
  })) : void 0;
7434
- yield* tryPluginPromise(()=>prerenderRoute({
7434
+ yield* tryPluginPromise(()=>{
7435
+ let encodedData;
7436
+ return prerenderRoute({
7435
7437
  handler: requestHandler,
7436
7438
  prerenderPath: path,
7437
7439
  clientBuildDir,
7438
7440
  basename,
7439
7441
  api,
7440
- requestInit: data ? {
7442
+ requestInit: data && (encodedData = encodeURI(data)).length < 8192 ? {
7441
7443
  headers: {
7442
- 'X-React-Router-Prerender-Data': encodeURI(data)
7444
+ 'X-React-Router-Prerender-Data': encodedData
7443
7445
  }
7444
7446
  } : void 0
7445
- }));
7447
+ });
7448
+ });
7446
7449
  }))({
7447
7450
  path,
7448
7451
  build,
@@ -8002,7 +8005,7 @@ let roundMs = (value)=>Math.round(10 * value) / 10, VIRTUAL_MODULE_PREFIX = 'vir
8002
8005
  }, hasSameRouteMetadata = (previous, next)=>previous.caseSensitive === next.caseSensitive && previous.clientActionModule === next.clientActionModule && previous.clientLoaderModule === next.clientLoaderModule && previous.clientMiddlewareModule === next.clientMiddlewareModule && previous.hasErrorBoundary === next.hasErrorBoundary && previous.hasAction === next.hasAction && previous.hasClientAction === next.hasClientAction && previous.hasClientLoader === next.hasClientLoader && previous.hasClientMiddleware === next.hasClientMiddleware && previous.hasDefaultExport === next.hasDefaultExport && previous.hasLoader === next.hasLoader && previous.hydrateFallbackModule === next.hydrateFallbackModule && previous.id === next.id && previous.index === next.index && previous.parentId === next.parentId && previous.path === next.path, DEV_RUNTIME_KEY = Symbol.for('rsbuild-plugin-react-router.dev-runtime.v1'), loadReactRouterServerBuild = (server, entryName)=>{
8003
8006
  let runtime = Reflect.get(server, DEV_RUNTIME_KEY);
8004
8007
  return runtime ? runtime.load(entryName) : Promise.reject(Error('[rsbuild-plugin-react-router] This Rsbuild development server is not registered with the React Router plugin. Add pluginReactRouter() before calling loadReactRouterServerBuild().'));
8005
- }, TYPEGEN_IDLE_DELAY_MS = 10000, loadDefaultExeca = async ()=>{
8008
+ }, dev_hmr_isObject = (value)=>null !== value && 'object' == typeof value, isSwcLoader = (loader)=>'string' == typeof loader && loader.includes('builtin:swc-loader'), hasReactRefresh = (options)=>options?.jsc?.transform?.react?.refresh === !0, readSwcLoaderRefresh = (value)=>Array.isArray(value) ? value.some(readSwcLoaderRefresh) : !!dev_hmr_isObject(value) && (isSwcLoader(value.loader) ? hasReactRefresh(value.options) : Object.values(value).some(readSwcLoaderRefresh)), readRuleSwcRefresh = (rule)=>!!dev_hmr_isObject(rule) && (isSwcLoader(rule.loader) ? hasReactRefresh(rule.options) : readSwcLoaderRefresh(rule.use) || readRuleSetSwcRefresh(rule.oneOf) || readRuleSetSwcRefresh(rule.rules)), readRuleSetSwcRefresh = (rules)=>Array.isArray(rules) && rules.some(readRuleSwcRefresh), DEV_HDR_REVISION_RELATIVE_PATH = '.react-router/hdr-revision.mjs', isHdrRevisionFile = (file)=>file.includes(DEV_HDR_REVISION_RELATIVE_PATH), isCssSourceFile = (file)=>/\.css(?:\.[cm]?[jt]s)?$/.test(file), TYPEGEN_IDLE_DELAY_MS = 10000, loadDefaultExeca = async ()=>{
8006
8009
  let { execa } = await import("execa");
8007
8010
  return execa;
8008
8011
  }, config_imports_normalizePath = (filePath)=>external_pathe_resolve(filePath), isNodeModulePath = (filePath)=>filePath.split(/[\\/]/).includes('node_modules'), collectConfigImportWatchPaths = (configPath, moduleCache, previousCacheKeys)=>{
@@ -8780,7 +8783,35 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
8780
8783
  routesByServerBundleId,
8781
8784
  serverBuildFile,
8782
8785
  defaultEntryName: devServerBuildEntryName
8783
- }), { serverBundleEntries } = serverBuildPlan, devRuntime = (({ api, isBuild, buildPlan })=>{
8786
+ }), { serverBundleEntries } = serverBuildPlan, devHmrRefreshRuntimePath = isBuild ? void 0 : ((rootPath)=>{
8787
+ let resolveFrom = (base, request)=>createRequire(base).resolve(request), rootPackageJson = external_pathe_join(rootPath, 'package.json');
8788
+ try {
8789
+ let pluginReactEntry = resolveFrom(rootPackageJson, '@rsbuild/plugin-react'), refreshPluginEntry = resolveFrom(pluginReactEntry, '@rspack/plugin-react-refresh');
8790
+ return resolveFrom(refreshPluginEntry, 'react-refresh/runtime');
8791
+ } catch {
8792
+ return;
8793
+ }
8794
+ })(api.context.rootPath), devHdrSignal = devHmrRefreshRuntimePath ? (({ filePath, onError })=>{
8795
+ let revision = 0, dirEnsured = !1, write = ()=>{
8796
+ try {
8797
+ let revision1;
8798
+ dirEnsured || (mkdirSync(dirname(filePath), {
8799
+ recursive: !0
8800
+ }), dirEnsured = !0), writeFileSync(filePath, (revision1 = revision, `export default ${revision1};\n`));
8801
+ } catch (error) {
8802
+ onError?.(error instanceof Error ? error : Error(String(error)));
8803
+ }
8804
+ };
8805
+ return {
8806
+ ensure: write,
8807
+ bump () {
8808
+ revision += 1, write();
8809
+ }
8810
+ };
8811
+ })({
8812
+ filePath: external_pathe_join(api.context.rootPath, DEV_HDR_REVISION_RELATIVE_PATH),
8813
+ onError: (error)=>api.logger.debug(`[${PLUGIN_NAME}] Failed to signal hot data revalidation: ${error.message}`)
8814
+ }) : void 0, devHmrEnabled = !1, devRuntime = (({ api, isBuild, buildPlan, onNodeRebuildCommitted })=>{
8784
8815
  let scheduledCssAssetOwnershipReload, state, nextSessionId, closeObservationByServer, isCurrentBinding, applyCloseOutcome, observeClose, identityByCompilation, webIdentityByNodeCompilation, attemptIdentityByCompilation;
8785
8816
  if (isBuild) return {
8786
8817
  captureWeb () {},
@@ -8887,8 +8918,14 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
8887
8918
  setWebIdentityForNodeCompilation (compilation, identity) {
8888
8919
  webIdentityByNodeCompilation.set(compilation, identity);
8889
8920
  }
8890
- }), { getCompilationIdentity } = compilationIdentities, finishRuntimeAttempt = (binding, pair, stats, changes, identity)=>runPluginEffect(tryPluginPromise(()=>binding.runtime.finishAttempt(stats, changes, identity)).pipe(core_flatMap((result)=>tryPluginSync(()=>{
8891
- 'retry-node' === result && sessions.getActiveBinding()?.id === binding.id && pair.node.watching?.invalidate();
8921
+ }), { getCompilationIdentity } = compilationIdentities, hdrSignaledNodeIdentity = new WeakMap(), finishRuntimeAttempt = (binding, pair, stats, changes, identity)=>runPluginEffect(tryPluginPromise(()=>binding.runtime.finishAttempt(stats, changes, identity)).pipe(core_flatMap((result)=>tryPluginSync(()=>{
8922
+ if (sessions.getActiveBinding()?.id === binding.id) {
8923
+ if ('retry-node' === result) return void pair.node.watching?.invalidate();
8924
+ 'committed' === result && changes.node.known && void 0 !== identity.node && hdrSignaledNodeIdentity.get(pair) !== identity.node && ((files)=>{
8925
+ for (let file of files)if (!isHdrRevisionFile(file) && !isCssSourceFile(file)) return !0;
8926
+ return !1;
8927
+ })(changes.node.files) && (hdrSignaledNodeIdentity.set(pair, identity.node), onNodeRebuildCommitted?.());
8928
+ }
8892
8929
  })), catchAll((cause)=>tryPluginSync(()=>{
8893
8930
  sessions.getActiveBinding()?.id === binding.id && binding.runtime.failAttempt(normalizeEffectError(cause));
8894
8931
  })))), flushSettledAttempt = (binding, pair)=>{
@@ -9233,7 +9270,8 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9233
9270
  })({
9234
9271
  api,
9235
9272
  isBuild,
9236
- buildPlan: serverBuildPlan
9273
+ buildPlan: serverBuildPlan,
9274
+ onNodeRebuildCommitted: ()=>devHdrSignal?.bump()
9237
9275
  });
9238
9276
  api.onAfterEnvironmentCompile(({ stats, environment })=>{
9239
9277
  if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames)), pluginOptions.federation && ssr) {
@@ -9314,7 +9352,288 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9314
9352
  }),
9315
9353
  ...bundleVirtualModules,
9316
9354
  ...bundleManifestModules,
9317
- 'virtual/react-router/with-props': generateWithProps()
9355
+ 'virtual/react-router/with-props': generateWithProps(),
9356
+ ...devHmrRefreshRuntimePath ? {
9357
+ 'virtual/react-router/hmr-runtime': (({ reactRefreshRuntimePath, hdrRevisionFilePath })=>`
9358
+ import * as __refreshRuntimeModule from ${JSON.stringify(reactRefreshRuntimePath)};
9359
+ // Read revision so the import survives sideEffects: false tree-shaking.
9360
+ import __hdrRevision from ${JSON.stringify(hdrRevisionFilePath)};
9361
+
9362
+ void __hdrRevision;
9363
+
9364
+ const RefreshRuntime =
9365
+ __refreshRuntimeModule && __refreshRuntimeModule.performReactRefresh
9366
+ ? __refreshRuntimeModule
9367
+ : __refreshRuntimeModule.default;
9368
+
9369
+ const pendingRouteUpdates = new Map();
9370
+ let flushTimeout;
9371
+ let pendingRevalidation = false;
9372
+
9373
+ function getCurrentRouterPath(router) {
9374
+ const basename = router.basename || '/';
9375
+ let pathname = window.location.pathname;
9376
+ if (basename !== '/' && pathname.startsWith(basename)) {
9377
+ pathname = pathname.slice(basename.length) || '/';
9378
+ // A trailing-slash basename (e.g. "/mybase/") consumes the leading slash,
9379
+ // leaving a relative path that react-router resolves against the current
9380
+ // location and doubles. Force it back to absolute.
9381
+ if (pathname[0] !== '/') pathname = '/' + pathname;
9382
+ }
9383
+ return pathname + window.location.search + window.location.hash;
9384
+ }
9385
+
9386
+ export function registerReactRouterRouteExports(routeId, moduleExports) {
9387
+ if (
9388
+ typeof window === 'undefined' ||
9389
+ !RefreshRuntime ||
9390
+ typeof RefreshRuntime.register !== 'function'
9391
+ ) {
9392
+ return;
9393
+ }
9394
+ for (const key in moduleExports) {
9395
+ if (key === '__esModule') continue;
9396
+ const exportValue = moduleExports[key];
9397
+ if (RefreshRuntime.isLikelyComponentType(exportValue)) {
9398
+ RefreshRuntime.register(exportValue, routeId + ' export ' + key);
9399
+ }
9400
+ }
9401
+ }
9402
+
9403
+ export function scheduleReactRouterRouteUpdate(
9404
+ routeId,
9405
+ routeFlags,
9406
+ getRouteModuleExports
9407
+ ) {
9408
+ pendingRouteUpdates.set(routeId, { routeFlags, getRouteModuleExports });
9409
+ scheduleFlush();
9410
+ }
9411
+
9412
+ export function scheduleReactRouterRevalidation() {
9413
+ pendingRevalidation = true;
9414
+ scheduleFlush();
9415
+ }
9416
+
9417
+ function scheduleFlush() {
9418
+ if (typeof window === 'undefined') {
9419
+ return;
9420
+ }
9421
+ clearTimeout(flushTimeout);
9422
+ flushTimeout = setTimeout(flush, 16);
9423
+ }
9424
+
9425
+ function takePendingRouteUpdates() {
9426
+ const updates = Array.from(pendingRouteUpdates, ([routeId, update]) => ({
9427
+ routeId,
9428
+ update,
9429
+ }));
9430
+ pendingRouteUpdates.clear();
9431
+ return updates;
9432
+ }
9433
+
9434
+ function getRouteMetadata(routeFlags) {
9435
+ return {
9436
+ ${HMR_PATCHABLE_ROUTE_FLAGS.map((flag, index)=>` ${flag}: Boolean(routeFlags & ${1 << index}),`).join('\n')}
9437
+ };
9438
+ }
9439
+
9440
+ function applyRouteModuleUpdate(routeId, update, routeEntry, routeModules) {
9441
+ Object.assign(routeEntry, getRouteMetadata(update.routeFlags));
9442
+ const imported = update.getRouteModuleExports();
9443
+ registerReactRouterRouteExports(routeId, imported);
9444
+ const current = routeModules[routeId];
9445
+ const preserveIdentity = key =>
9446
+ imported[key] ? (current && current[key]) || imported[key] : imported[key];
9447
+ routeModules[routeId] = {
9448
+ ...imported,
9449
+ default: preserveIdentity('default'),
9450
+ ErrorBoundary: preserveIdentity('ErrorBoundary'),
9451
+ HydrateFallback: preserveIdentity('HydrateFallback'),
9452
+ };
9453
+ }
9454
+
9455
+ function getRouteById(routes, routeId) {
9456
+ for (const route of routes) {
9457
+ if (route.id === routeId) {
9458
+ return route;
9459
+ }
9460
+ if (route.children) {
9461
+ const child = getRouteById(route.children, routeId);
9462
+ if (child) {
9463
+ return child;
9464
+ }
9465
+ }
9466
+ }
9467
+ }
9468
+
9469
+ // Deliberate coupling to React Router's private dev API: patching the live
9470
+ // match objects is the only way to swap route implementations without a
9471
+ // navigation. The typeof guard below degrades to a no-op if RR removes it.
9472
+ function patchCurrentRouteMatches(router, routes) {
9473
+ if (
9474
+ !router.state ||
9475
+ !Array.isArray(router.state.matches) ||
9476
+ typeof router._internalSetStateDoNotUseOrYouWillBreakYourApp !== 'function'
9477
+ ) {
9478
+ return;
9479
+ }
9480
+
9481
+ let changed = false;
9482
+ const matches = router.state.matches.map(match => {
9483
+ const route = getRouteById(routes, match.route.id);
9484
+ if (!route || route === match.route) {
9485
+ return match;
9486
+ }
9487
+ changed = true;
9488
+ return { ...match, route };
9489
+ });
9490
+
9491
+ if (changed) {
9492
+ router._internalSetStateDoNotUseOrYouWillBreakYourApp({ matches });
9493
+ }
9494
+ }
9495
+
9496
+ function applyPendingRouteUpdates(router, routeModules, manifest, context) {
9497
+ if (pendingRouteUpdates.size === 0) {
9498
+ return {
9499
+ nextManifest: undefined,
9500
+ shouldRefreshRouteState: false,
9501
+ routesToRevalidate: new Set(),
9502
+ };
9503
+ }
9504
+
9505
+ // Clone only entries mutated before the manifest is committed in flush().
9506
+ const nextManifest = { ...manifest, routes: { ...manifest.routes } };
9507
+ const routesToRevalidate = new Set();
9508
+ let shouldRefreshRouteState = false;
9509
+ for (const { routeId, update } of takePendingRouteUpdates()) {
9510
+ const existingEntry = nextManifest.routes[routeId];
9511
+ if (!existingEntry) continue;
9512
+
9513
+ // Shallow clone is enough: only top-level flags are mutated below.
9514
+ const routeEntry = { ...existingEntry };
9515
+ nextManifest.routes[routeId] = routeEntry;
9516
+ applyRouteModuleUpdate(routeId, update, routeEntry, routeModules);
9517
+ if (
9518
+ routeEntry.hasLoader ||
9519
+ routeEntry.hasClientLoader ||
9520
+ routeEntry.hasClientMiddleware
9521
+ ) {
9522
+ routesToRevalidate.add(routeId);
9523
+ }
9524
+ if (
9525
+ existingEntry.hasLoader ||
9526
+ existingEntry.hasClientLoader ||
9527
+ existingEntry.hasClientMiddleware ||
9528
+ routeEntry.hasLoader ||
9529
+ routeEntry.hasClientLoader ||
9530
+ routeEntry.hasClientMiddleware
9531
+ ) {
9532
+ shouldRefreshRouteState = true;
9533
+ }
9534
+ }
9535
+
9536
+ if (
9537
+ typeof router.createRoutesForHMR === 'function' &&
9538
+ typeof router._internalSetRoutes === 'function'
9539
+ ) {
9540
+ const routes = router.createRoutesForHMR(
9541
+ routesToRevalidate,
9542
+ nextManifest.routes,
9543
+ routeModules,
9544
+ context.ssr,
9545
+ context.isSpaMode
9546
+ );
9547
+ router._internalSetRoutes(routes);
9548
+ patchCurrentRouteMatches(router, routes);
9549
+ }
9550
+
9551
+ return { nextManifest, shouldRefreshRouteState, routesToRevalidate };
9552
+ }
9553
+
9554
+ async function withHdrActive(fn) {
9555
+ try {
9556
+ window.__reactRouterHdrActive = true;
9557
+ await fn();
9558
+ } finally {
9559
+ window.__reactRouterHdrActive = false;
9560
+ }
9561
+ }
9562
+
9563
+ async function revalidateRouter(router) {
9564
+ if (typeof router.revalidate === 'function') {
9565
+ await withHdrActive(() => router.revalidate());
9566
+ return;
9567
+ }
9568
+ if (typeof router.navigate === 'function') {
9569
+ await withHdrActive(() =>
9570
+ router.navigate(getCurrentRouterPath(router), {
9571
+ replace: true,
9572
+ preventScrollReset: true,
9573
+ })
9574
+ );
9575
+ }
9576
+ }
9577
+
9578
+ async function refreshRouteState(router) {
9579
+ if (typeof router.revalidate === 'function') {
9580
+ await withHdrActive(() => router.revalidate());
9581
+ return true;
9582
+ }
9583
+ return false;
9584
+ }
9585
+
9586
+ function performReactRefresh() {
9587
+ if (
9588
+ RefreshRuntime &&
9589
+ typeof RefreshRuntime.performReactRefresh === 'function'
9590
+ ) {
9591
+ RefreshRuntime.performReactRefresh();
9592
+ }
9593
+ }
9594
+
9595
+ async function flush() {
9596
+ const router = window.__reactRouterDataRouter;
9597
+ const routeModules = window.__reactRouterRouteModules;
9598
+ const manifest = window.__reactRouterManifest;
9599
+ const context = window.__reactRouterContext;
9600
+ if (!router || !routeModules || !manifest || !context) {
9601
+ return;
9602
+ }
9603
+
9604
+ let shouldRevalidate = pendingRevalidation;
9605
+ pendingRevalidation = false;
9606
+ const { nextManifest, shouldRefreshRouteState, routesToRevalidate } =
9607
+ applyPendingRouteUpdates(router, routeModules, manifest, context);
9608
+ if (nextManifest) {
9609
+ Object.assign(manifest, nextManifest);
9610
+ }
9611
+ // Component-only updates do not need a full loader revalidation.
9612
+ if (
9613
+ shouldRefreshRouteState &&
9614
+ (routesToRevalidate.size > 0 || shouldRevalidate)
9615
+ ) {
9616
+ if (await refreshRouteState(router)) {
9617
+ shouldRevalidate = false;
9618
+ }
9619
+ }
9620
+ if (shouldRevalidate) {
9621
+ await revalidateRouter(router);
9622
+ }
9623
+ performReactRefresh();
9624
+ }
9625
+
9626
+ if (typeof window !== 'undefined' && import.meta.webpackHot) {
9627
+ import.meta.webpackHot.accept(
9628
+ ${JSON.stringify(hdrRevisionFilePath)},
9629
+ scheduleReactRouterRevalidation
9630
+ );
9631
+ }
9632
+ `)({
9633
+ reactRefreshRuntimePath: devHmrRefreshRuntimePath,
9634
+ hdrRevisionFilePath: external_pathe_join(api.context.rootPath, DEV_HDR_REVISION_RELATIVE_PATH)
9635
+ })
9636
+ } : {}
9318
9637
  }).map(([moduleId, contents])=>[
9319
9638
  ((moduleId)=>{
9320
9639
  if (!moduleId.startsWith(VIRTUAL_MODULE_PREFIX)) throw Error(`Virtual module id must start with ${JSON.stringify(VIRTUAL_MODULE_PREFIX)}: ${moduleId}`);
@@ -9530,7 +9849,7 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9530
9849
  }), api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
9531
9850
  tools: {
9532
9851
  rspack: (rspackConfig)=>{
9533
- if (pluginOptions.federation && ((rspackConfig)=>{
9852
+ (pluginOptions.federation && ((rspackConfig)=>{
9534
9853
  if (rspackConfig?.plugins?.length) for (let plugin of rspackConfig.plugins){
9535
9854
  if (!plugin || 'object' != typeof plugin || 'ModuleFederationPlugin' !== plugin.name) continue;
9536
9855
  let pluginOptions = plugin._options ?? plugin.options;
@@ -9539,7 +9858,8 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9539
9858
  asyncStartup: !0
9540
9859
  });
9541
9860
  }
9542
- })(rspackConfig), 'node' === name) {
9861
+ })(rspackConfig), 'web' === name) && (devHmrEnabled = !isBuild && void 0 !== devHmrRefreshRuntimePath && 'development' === config.mode && config.dev?.hmr !== !1 && readRuleSetSwcRefresh(rspackConfig.module?.rules)) && devHdrSignal?.ensure();
9862
+ if ('node' === name) {
9543
9863
  let output = rspackConfig.output;
9544
9864
  if (output) {
9545
9865
  let library = output.library, libraryOptions = library && 'object' == typeof library && !Array.isArray(library) ? library : {};
@@ -9649,7 +9969,7 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9649
9969
  isBuild || devRuntime.captureWeb(compilation, manifestsByEntryName);
9650
9970
  });
9651
9971
  }
9652
- }), (({ api, resolvedServerOutput, performanceProfiler, getLatestServerManifest, getLatestServerManifestByBundleId, routes, pluginOptions, getClientStats, appDirectory, getAssetPrefix, routeChunkOptions, routeTransformExecutor, routeByFilePath, routeChunkConfig, isBuild, splitRouteModules, ssr, isSpaMode, rootRoutePath })=>{
9972
+ }), (({ api, resolvedServerOutput, performanceProfiler, getLatestServerManifest, getLatestServerManifestByBundleId, routes, pluginOptions, getClientStats, appDirectory, getAssetPrefix, routeChunkOptions, routeTransformExecutor, routeByFilePath, routeChunkConfig, isBuild, splitRouteModules, ssr, isSpaMode, rootRoutePath, isDevHmrEnabled = ()=>!1 })=>{
9653
9973
  let transformRouteModule = async (args)=>performanceProfiler.record(args.environment?.name, 'route:module', args.resource, async ()=>routeTransformExecutor.run({
9654
9974
  kind: 'routeModule',
9655
9975
  code: args.code,
@@ -9660,7 +9980,8 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9660
9980
  ssr,
9661
9981
  isBuild,
9662
9982
  isSpaMode,
9663
- rootRoutePath
9983
+ rootRoutePath,
9984
+ devHmr: isDevHmrEnabled()
9664
9985
  }));
9665
9986
  api.processAssets({
9666
9987
  stage: 'additional',
@@ -9690,7 +10011,9 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9690
10011
  resourcePath: args.resourcePath,
9691
10012
  environmentName: args.environment?.name,
9692
10013
  isBuild,
9693
- routeChunkConfig
10014
+ routeChunkConfig,
10015
+ routeId: routeByFilePath.get(args.resourcePath)?.id,
10016
+ devHmr: isDevHmrEnabled()
9694
10017
  }))), api.transform({
9695
10018
  resourceQuery: /route-chunk=/,
9696
10019
  environments: [
@@ -9767,7 +10090,8 @@ let shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefault
9767
10090
  splitRouteModules: !!splitRouteModules,
9768
10091
  ssr,
9769
10092
  isSpaMode,
9770
- rootRoutePath
10093
+ rootRoutePath,
10094
+ isDevHmrEnabled: ()=>devHmrEnabled
9771
10095
  });
9772
10096
  }
9773
10097
  });
@@ -6,6 +6,10 @@ export type RouteClientEntryArtifactOptions = {
6
6
  isBuild: boolean;
7
7
  routeChunkCache?: RouteChunkCache;
8
8
  routeChunkConfig: RouteChunkConfig;
9
+ /** React Router route id for this route module, when known. */
10
+ routeId?: string;
11
+ /** Emit development HMR/HDR glue into web route client entries. */
12
+ devHmr?: boolean;
9
13
  };
10
14
  type RouteClientEntryArtifact = {
11
15
  code: string;
@@ -22,12 +26,16 @@ type RouteChunkArtifact = {
22
26
  code: string;
23
27
  map: null;
24
28
  };
25
- export declare const buildRouteClientEntryCode: ({ exportNames, chunkedExports, isServer, resourcePath, }: {
29
+ export declare const HMR_PATCHABLE_ROUTE_FLAGS: readonly ["hasAction", "hasClientAction", "hasClientLoader", "hasClientMiddleware", "hasErrorBoundary", "hasLoader"];
30
+ export declare const buildRouteHmrFlags: (exportNames: readonly string[]) => number;
31
+ export declare const buildRouteClientEntryCode: ({ exportNames, chunkedExports, isServer, resourcePath, routeId, devHmr, }: {
26
32
  exportNames: readonly string[];
27
33
  chunkedExports: readonly string[];
28
34
  isServer: boolean;
29
35
  resourcePath: string;
36
+ routeId?: string;
37
+ devHmr?: boolean;
30
38
  }) => string;
31
- export declare const createRouteClientEntryArtifact: ({ code, resourcePath, environmentName, isBuild, routeChunkCache, routeChunkConfig, }: RouteClientEntryArtifactOptions) => Promise<RouteClientEntryArtifact>;
39
+ export declare const createRouteClientEntryArtifact: ({ code, resourcePath, environmentName, isBuild, routeChunkCache, routeChunkConfig, routeId, devHmr, }: RouteClientEntryArtifactOptions) => Promise<RouteClientEntryArtifact>;
32
40
  export declare const createRouteChunkArtifact: ({ code, resource, resourcePath, isBuild, routeChunkCache, routeChunkConfig, }: RouteChunkArtifactOptions) => Promise<RouteChunkArtifact>;
33
41
  export {};
@@ -14,6 +14,8 @@ export type RouteClientEntryTransformTask = BaseRouteTransformTask & {
14
14
  environmentName?: string;
15
15
  isBuild: boolean;
16
16
  routeChunkConfig: RouteChunkConfig;
17
+ routeId?: string;
18
+ devHmr?: boolean;
17
19
  };
18
20
  export type RouteChunkTransformTask = BaseRouteTransformTask & {
19
21
  kind: 'routeChunk';
@@ -38,6 +40,7 @@ export type RouteModuleTransformTask = BaseRouteTransformTask & {
38
40
  isBuild: boolean;
39
41
  isSpaMode: boolean;
40
42
  rootRoutePath: string | null;
43
+ devHmr?: boolean;
41
44
  };
42
45
  export type RouteTransformTask = RouteClientEntryTransformTask | RouteChunkTransformTask | SplitRouteExportsTransformTask | ClientOnlyStubTransformTask | RouteModuleTransformTask;
43
46
  export type RouteTransformTaskOptions = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rsbuild-plugin-react-router",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "React Router plugin for Rsbuild",
5
5
  "repository": {
6
6
  "type": "git",
@@ -82,7 +82,7 @@
82
82
  "es-module-lexer": "1.7.0",
83
83
  "kill-port": "^2.0.1",
84
84
  "pkg-pr-new": "^0.0.75",
85
- "playwright": "1.58.0",
85
+ "playwright": "^1.61.1",
86
86
  "prettier": "3.8.1",
87
87
  "react": "^19.2.4",
88
88
  "react-dom": "^19.2.4",
@@ -92,8 +92,14 @@
92
92
  "@rsbuild/config": "1.0.1"
93
93
  },
94
94
  "peerDependencies": {
95
+ "@react-router/dev": "^7.13.0 || ^8.0.0",
95
96
  "@rsbuild/core": "^2.0.0"
96
97
  },
98
+ "peerDependenciesMeta": {
99
+ "@react-router/dev": {
100
+ "optional": true
101
+ }
102
+ },
97
103
  "publishConfig": {
98
104
  "access": "public",
99
105
  "provenance": false,
@@ -39,6 +39,7 @@ type RegisterBuildOutputTransformsOptions = {
39
39
  ssr: boolean;
40
40
  isSpaMode: boolean;
41
41
  rootRoutePath: string;
42
+ isDevHmrEnabled?: () => boolean;
42
43
  };
43
44
 
44
45
  export const registerBuildOutputTransforms = ({
@@ -61,6 +62,7 @@ export const registerBuildOutputTransforms = ({
61
62
  ssr,
62
63
  isSpaMode,
63
64
  rootRoutePath,
65
+ isDevHmrEnabled = () => false,
64
66
  }: RegisterBuildOutputTransformsOptions): void => {
65
67
  const transformRouteModule = async (args: Parameters<TransformHandler>[0]) =>
66
68
  performanceProfiler.record(
@@ -81,6 +83,7 @@ export const registerBuildOutputTransforms = ({
81
83
  isBuild,
82
84
  isSpaMode,
83
85
  rootRoutePath,
86
+ devHmr: isDevHmrEnabled(),
84
87
  })
85
88
  );
86
89
 
@@ -158,6 +161,8 @@ export const registerBuildOutputTransforms = ({
158
161
  environmentName: args.environment?.name,
159
162
  isBuild,
160
163
  routeChunkConfig,
164
+ routeId: routeByFilePath.get(args.resourcePath)?.id,
165
+ devHmr: isDevHmrEnabled(),
161
166
  })
162
167
  )
163
168
  );