rsbuild-plugin-react-router 0.6.6 → 0.7.1

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
@@ -103,9 +103,17 @@ pluginReactRouter({
103
103
  | `federation` | `false` | Enables the plugin's experimental Module Federation integration. |
104
104
 
105
105
  When `federation` is enabled, configure the Module Federation plugin with
106
- `experiments.asyncStartup: true`. The dev server resolves async server build
106
+ `experiments.asyncStartup: true` on every compiler (the plugin enforces it) and
107
+ keep shared dependencies non-eager. The dev server resolves async server build
107
108
  exports automatically; production custom servers or adapters should resolve
108
- async exports before passing the build to React Router's request handler.
109
+ async exports before passing the build to React Router's request handler
110
+ (`resolveReactRouterServerBuild`). Give remote containers an explicit
111
+ `filename` (for example `static/js/remote.js`); every other browser chunk keeps
112
+ Rsbuild's content hash. Under the hood the plugin gives each container its own
113
+ runtime chunk (so importing a container does not start the app's own share
114
+ consumes), makes browser route-module entries async so their exports resolve
115
+ through the async startup, and keeps server code splitting async-only so the
116
+ `@module-federation/node` chunk loader can satisfy the server build's startup.
109
117
 
110
118
  ### React Router Config
111
119
 
@@ -343,6 +351,38 @@ If you configure `output.assetPrefix` in Rsbuild, the plugin uses that value
343
351
  for the React Router browser manifest and server build `publicPath` so asset
344
352
  URLs resolve correctly when serving from a CDN or sub-path.
345
353
 
354
+ The web environment's own `output.assetPrefix` is passed through to the browser
355
+ compiler untouched, so `environments.web.output.assetPrefix: 'auto'` lets the
356
+ browser runtime resolve async chunks and stylesheets relative to the loaded
357
+ script. The server build and browser manifest need an absolute prefix: they use
358
+ the web environment's prefix when it is usable and otherwise fall back to the
359
+ root `output.assetPrefix`. A common CDN setup is therefore:
360
+
361
+ ```ts
362
+ export default defineConfig({
363
+ output: { assetPrefix: 'https://cdn.example.com/app/' }, // server-rendered URLs
364
+ environments: {
365
+ web: { output: { assetPrefix: 'auto' } }, // browser runtime resolves itself
366
+ },
367
+ });
368
+ ```
369
+
370
+ The plugin does not set `output.filename`, `chunkFilename`, or `publicPath`
371
+ for the web environment. Production browser entries use Rsbuild's default
372
+ content-hashed filenames, and `output.filename`, `output.filenameHash`,
373
+ `output.distPath`, and `tools.rspack` output settings you configure govern
374
+ the emitted files and the manifest URLs that reference them.
375
+
376
+ Supported browser filename forms differ by mode:
377
+
378
+ - **Classic mode** accepts any scheme, including `.mjs`/`.cjs` and query-hash
379
+ names such as `[name].js?v=[contenthash:8]`; the browser manifest classifies
380
+ emitted assets by pathname and keeps the full reference.
381
+ - **RSC mode** requires every browser JavaScript asset to be named `*.js`
382
+ (hashes are fine, e.g. `[contenthash:8]-[name].js`): rspack's RSC manifest
383
+ only records `.js` files, so the build fails with a clear error for query
384
+ hashes or other extensions.
385
+
346
386
  ## Custom Server Setup
347
387
 
348
388
  The plugin supports two ways to handle server-side rendering:
package/dist/511.js CHANGED
@@ -375,9 +375,16 @@ function combineURLs(baseURL, relativeURL) {
375
375
  function normalizeAssetPrefix(assetPrefix) {
376
376
  return assetPrefix && 'auto' !== assetPrefix ? assetPrefix.endsWith('/') ? assetPrefix : `${assetPrefix}/` : '/';
377
377
  }
378
- function resolveEffectiveAssetPrefix(config) {
379
- let outputPrefix = 'string' == typeof config.output?.assetPrefix ? config.output.assetPrefix : void 0;
380
- return config.isBuild ? normalizeAssetPrefix(outputPrefix) : normalizeAssetPrefix(('string' == typeof config.dev?.assetPrefix ? config.dev.assetPrefix : void 0) ?? outputPrefix);
378
+ let asString = (value)=>'string' == typeof value ? value : void 0, pickConfiguredAssetPrefix = ({ dev, output }, isBuild)=>isBuild ? asString(output?.assetPrefix) : asString(dev?.assetPrefix) ?? asString(output?.assetPrefix);
379
+ function resolveEffectiveAssetPrefix(config, ...fallbacks) {
380
+ for (let candidate of [
381
+ config,
382
+ ...fallbacks
383
+ ]){
384
+ let prefix = pickConfiguredAssetPrefix(candidate, config.isBuild);
385
+ if (prefix && 'auto' !== prefix) return normalizeAssetPrefix(prefix);
386
+ }
387
+ return '/';
381
388
  }
382
389
  function createRouteId(file) {
383
390
  return normalize(file.replace(/\.[^/.]+$/, ''));
@@ -1,6 +1,20 @@
1
- import type { RsbuildPluginAPI } from '@rsbuild/core';
2
- export declare const registerReactRouterEnvironmentOutput: ({ api, federation, resolvedServerOutput, }: {
1
+ import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
2
+ /**
3
+ * Rspack `output` policy for the web and node environments, in two tiers:
4
+ *
5
+ * 1. Overridable defaults, registered through `modifyRspackConfig`. Rsbuild
6
+ * runs the user's `tools.rspack` (object or function form) after this hook,
7
+ * so user output settings such as `chunkFilename` take precedence
8
+ * (#129, #130). Neither `filename` nor `publicPath` is set here: Rsbuild
9
+ * derives them from `output.filename`/`output.filenameHash`/`output.distPath`
10
+ * and the environment's `output.assetPrefix`, which keeps `'auto'` intact.
11
+ * 2. Enforced invariants, registered through a `tools.rspack` function that
12
+ * runs after the user's own `tools.rspack`: the server library type must
13
+ * match the server module format, and federation builds need async startup.
14
+ */
15
+ export declare const registerReactRouterEnvironmentOutput: ({ api, federation, resolvedServerOutput, webOutput, }: {
3
16
  api: RsbuildPluginAPI;
4
17
  federation: boolean | undefined;
5
18
  resolvedServerOutput: "commonjs" | "module";
19
+ webOutput: NonNullable<Rspack.Configuration["output"]>;
6
20
  }) => void;
@@ -1,2 +1,28 @@
1
1
  import type { Rspack } from '@rsbuild/core';
2
+ /**
3
+ * The Module Federation container name(s) configured on this compiler, i.e.
4
+ * the entry names of the remote containers it emits.
5
+ */
6
+ export declare const getFederationContainerNames: (rspackConfig: Rspack.Configuration | undefined) => string[];
7
+ /**
8
+ * Classic mode shares one runtime chunk across every browser entry so route
9
+ * module entries share a module registry. A federation container must not
10
+ * share it: importing the container would run the app entries' async startup
11
+ * (share-scope consumes) before the host has initialized the share scope,
12
+ * yielding duplicate singletons (a second React). Give containers their own
13
+ * runtime chunk.
14
+ */
15
+ export declare const isolateFederationContainerRuntime: (rspackConfig: Rspack.Configuration | undefined) => void;
2
16
  export declare const ensureFederationAsyncStartup: (rspackConfig: Rspack.Configuration | undefined) => void;
17
+ /**
18
+ * `@module-federation/node` replaces Rspack's `readFileVm` chunk loader with one
19
+ * that tracks loaded chunks privately, so initial chunks split off a
20
+ * multi-entry server build never satisfy Rspack's startup gate
21
+ * (`__webpack_require__.O`) and the async startup resolves to `undefined`
22
+ * instead of the server build's exports. Keep server code splitting to async
23
+ * chunks only. Runs at the final `tools.rspack` boundary so a user
24
+ * `optimization.splitChunks` override or a preset whose cache group selects
25
+ * `chunks: 'all'` (e.g. Rsbuild's `single-vendor`, `enforce: true`) cannot
26
+ * reintroduce initial chunk dependencies. A disabled `splitChunks` is kept.
27
+ */
28
+ export declare const enforceAsyncOnlyServerSplitChunks: (rspackConfig: Rspack.Configuration | undefined) => void;
package/dist/index.cjs CHANGED
@@ -543,9 +543,16 @@ function combineURLs(baseURL, relativeURL) {
543
543
  function normalizeAssetPrefix(assetPrefix) {
544
544
  return assetPrefix && 'auto' !== assetPrefix ? assetPrefix.endsWith('/') ? assetPrefix : `${assetPrefix}/` : '/';
545
545
  }
546
- function resolveEffectiveAssetPrefix(config) {
547
- let outputPrefix = 'string' == typeof config.output?.assetPrefix ? config.output.assetPrefix : void 0;
548
- return config.isBuild ? normalizeAssetPrefix(outputPrefix) : normalizeAssetPrefix(('string' == typeof config.dev?.assetPrefix ? config.dev.assetPrefix : void 0) ?? outputPrefix);
546
+ const asString = (value)=>'string' == typeof value ? value : void 0, pickConfiguredAssetPrefix = ({ dev, output }, isBuild)=>isBuild ? asString(output?.assetPrefix) : asString(dev?.assetPrefix) ?? asString(output?.assetPrefix);
547
+ function resolveEffectiveAssetPrefix(config, ...fallbacks) {
548
+ for (let candidate of [
549
+ config,
550
+ ...fallbacks
551
+ ]){
552
+ let prefix = pickConfiguredAssetPrefix(candidate, config.isBuild);
553
+ if (prefix && 'auto' !== prefix) return normalizeAssetPrefix(prefix);
554
+ }
555
+ return '/';
549
556
  }
550
557
  function createRouteId(file) {
551
558
  return (0, external_pathe_namespaceObject.normalize)(file.replace(/\.[^/.]+$/, ''));
@@ -610,20 +617,53 @@ const resolveEntryWithTemplate = ({ appDirectory, entryName, templateName, templ
610
617
  hasServerApp,
611
618
  serverAppPath
612
619
  };
620
+ }, getModuleFederationOptions = (plugin)=>{
621
+ if (plugin && 'object' == typeof plugin && ('ModuleFederationPlugin' === plugin.name || 'RspackModuleFederationPlugin' === plugin.name)) return plugin._options ?? plugin.options;
622
+ }, getFederationContainerNames = (rspackConfig)=>(rspackConfig?.plugins ?? []).map(getModuleFederationOptions).map((options)=>options?.name).filter((name)=>'string' == typeof name), isolateFederationContainerRuntime = (rspackConfig)=>{
623
+ let containers = new Set(getFederationContainerNames(rspackConfig));
624
+ if (!rspackConfig || 0 === containers.size) return;
625
+ let current = rspackConfig.optimization?.runtimeChunk, appRuntimeName = 'object' == typeof current && 'string' == typeof current?.name ? current.name : 'runtime';
626
+ rspackConfig.optimization = {
627
+ ...rspackConfig.optimization,
628
+ runtimeChunk: {
629
+ name: (entrypoint)=>containers.has(entrypoint.name) ? `runtime-${entrypoint.name}` : appRuntimeName
630
+ }
631
+ };
613
632
  }, ensureFederationAsyncStartup = (rspackConfig)=>{
614
633
  if (rspackConfig?.plugins?.length) for (let plugin of rspackConfig.plugins){
615
- if (!plugin || 'object' != typeof plugin || 'ModuleFederationPlugin' !== plugin.name && 'RspackModuleFederationPlugin' !== plugin.name) continue;
616
- let pluginOptions = plugin._options ?? plugin.options;
634
+ let pluginOptions = getModuleFederationOptions(plugin);
617
635
  pluginOptions && (pluginOptions.experiments = {
618
636
  ...pluginOptions.experiments,
619
637
  asyncStartup: !0
620
638
  });
621
639
  }
622
- }, registerReactRouterEnvironmentOutput = ({ api, federation, resolvedServerOutput })=>{
623
- api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
640
+ }, enforceAsyncOnlyServerSplitChunks = (rspackConfig)=>{
641
+ let splitChunks = rspackConfig?.optimization?.splitChunks;
642
+ if (splitChunks) for (let group of (splitChunks.chunks = 'async', Object.values(splitChunks.cacheGroups ?? {})))group && 'object' == typeof group && 'chunks' in group && (group.chunks = 'async');
643
+ }, registerReactRouterEnvironmentOutput = ({ api, federation, resolvedServerOutput, webOutput })=>{
644
+ let nodeChunkLoading = 'module' === resolvedServerOutput ? 'import' : federation ? 'async-node' : 'require';
645
+ api.modifyRspackConfig((rspackConfig, { environment, mergeConfig })=>'web' === environment.name ? mergeConfig(rspackConfig, {
646
+ output: {
647
+ ...webOutput,
648
+ ...federation ? {
649
+ chunkLoading: 'import'
650
+ } : {}
651
+ }
652
+ }) : 'node' === environment.name ? mergeConfig(rspackConfig, {
653
+ output: {
654
+ chunkFormat: resolvedServerOutput,
655
+ chunkLoading: nodeChunkLoading,
656
+ devtoolModuleFilenameTemplate: '[absolute-resource-path]',
657
+ devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]',
658
+ workerChunkLoading: nodeChunkLoading,
659
+ wasmLoading: 'fetch',
660
+ module: 'module' === resolvedServerOutput,
661
+ chunkFilename: 'static/js/async/[name].js'
662
+ }
663
+ }) : rspackConfig), api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
624
664
  tools: {
625
665
  rspack: (rspackConfig)=>{
626
- if (federation && ensureFederationAsyncStartup(rspackConfig), 'node' === name) {
666
+ if (federation && (ensureFederationAsyncStartup(rspackConfig), 'web' === name ? isolateFederationContainerRuntime(rspackConfig) : enforceAsyncOnlyServerSplitChunks(rspackConfig)), 'node' === name) {
627
667
  let output = rspackConfig.output;
628
668
  if (output) {
629
669
  let library = output.library, libraryOptions = library && 'object' == typeof library && !Array.isArray(library) ? library : {};
@@ -1444,7 +1484,7 @@ const getCurrentVersion = ()=>moduleVersion, setCurrentVersion = (version)=>{
1444
1484
  }), findLast = null, zip = null, Iterable_zipWith = null, intersperse = null, Iterable_containsWith = (isEquivalent)=>dual(2, (self, a)=>{
1445
1485
  for (let i of self)if (isEquivalent(a, i)) return !0;
1446
1486
  return !1;
1447
- }), Iterable_equivalence = null, Iterable_contains = null, chunksOf = null, groupWith = null, group = null, groupBy = null, constEmpty = {
1487
+ }), Iterable_equivalence = null, Iterable_contains = null, chunksOf = null, groupWith = null, Iterable_group = null, groupBy = null, constEmpty = {
1448
1488
  [Symbol.iterator]: ()=>constEmptyIterator
1449
1489
  }, constEmptyIterator = {
1450
1490
  next: ()=>({
@@ -11790,12 +11830,23 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
11790
11830
  routeModuleAnalysis
11791
11831
  } : {}
11792
11832
  };
11793
- }, orderChunkFiles = (chunkName, files)=>{
11794
- let ownChunkAsset = `${chunkName}.js`, ownFileIndex = files.findIndex((file)=>file.endsWith(ownChunkAsset));
11795
- return ownFileIndex <= 0 ? files : [
11796
- files[ownFileIndex],
11797
- ...files.slice(0, ownFileIndex),
11798
- ...files.slice(ownFileIndex + 1)
11833
+ }, isManifestJsAsset = (asset)=>/(?<!\.hot-update)\.[cm]?js(?:\?.*)?$/.test(asset), isManifestCssAsset = (asset)=>/\.css(?:\?.*)?$/.test(asset), hasSome = (iterable)=>{
11834
+ for (let _ of iterable)return !0;
11835
+ return !1;
11836
+ }, collectUnsupportedRscScriptAssets = (compilation)=>{
11837
+ let unsupported = new Set();
11838
+ for (let chunk of compilation.chunks){
11839
+ if (!(chunk.contentHash?.javascript !== void 0 || hasSome(compilation.chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript")))) continue;
11840
+ let pathData = {
11841
+ chunk,
11842
+ contentHashType: "javascript"
11843
+ }, template = chunk.canBeInitial() ? compilation.outputOptions.filename : compilation.outputOptions.chunkFilename, resolvedTemplate = 'function' == typeof template ? template(pathData) : template;
11844
+ if ('string' != typeof resolvedTemplate) continue;
11845
+ let file = compilation.getPath(resolvedTemplate, pathData);
11846
+ file.endsWith('.js') || unsupported.add(file);
11847
+ }
11848
+ return [
11849
+ ...unsupported
11799
11850
  ];
11800
11851
  }, collectManifestFilesByName = (items, names, getFiles)=>{
11801
11852
  let filesByName = {};
@@ -11813,14 +11864,14 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
11813
11864
  return filesByName;
11814
11865
  }, createReactRouterManifestStats = (compilation, chunkNames)=>{
11815
11866
  if (!compilation) return;
11816
- let assetsByChunkName = collectManifestFilesByName(compilation.namedChunks, chunkNames, (chunkName, chunk)=>orderChunkFiles(chunkName, Array.from(chunk.files ?? []))), entrypointFilesByName = compilation.entrypoints ? collectManifestFilesByName(compilation.entrypoints, chunkNames, (_name, entrypoint)=>Array.from(entrypoint.getFiles?.() ?? [])) : {};
11867
+ let assetsByChunkName = collectManifestFilesByName(compilation.namedChunks, chunkNames, (_chunkName, chunk)=>Array.from(chunk.files ?? [])), entrypointFilesByName = compilation.entrypoints ? collectManifestFilesByName(compilation.entrypoints, chunkNames, (_name, entrypoint)=>Array.from(entrypoint.getFiles?.() ?? [])) : {};
11817
11868
  return Object.keys(entrypointFilesByName).length > 0 ? {
11818
11869
  assetsByChunkName,
11819
11870
  entrypointFilesByName
11820
11871
  } : {
11821
11872
  assetsByChunkName
11822
11873
  };
11823
- }, DEFAULT_MANIFEST_DIR = DEFAULT_JS_DIST_PATH, CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/, createChunkAssetResolver = (clientStats, includeEntrypointJs)=>{
11874
+ }, DEFAULT_MANIFEST_DIR = 'static/js', CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/, createChunkAssetResolver = (clientStats, includeEntrypointJs)=>{
11824
11875
  let chunkAssetsByName = new Map();
11825
11876
  return (chunkName)=>{
11826
11877
  let cached = chunkAssetsByName.get(chunkName);
@@ -11829,16 +11880,16 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
11829
11880
  if (!assets) {
11830
11881
  let result = {
11831
11882
  js: [
11832
- `${DEFAULT_JS_DIST_PATH}/${chunkName}.js`
11883
+ `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`
11833
11884
  ],
11834
11885
  css: []
11835
11886
  };
11836
11887
  return chunkAssetsByName.set(chunkName, result), result;
11837
11888
  }
11838
11889
  let cssAssets = new Set(), jsAssets = new Set();
11839
- for (let asset of assets)asset.endsWith('.css') ? cssAssets.add(asset) : asset.endsWith('.js') && jsAssets.add(asset);
11840
- for (let asset of clientStats?.entrypointFilesByName?.[chunkName] ?? [])asset.endsWith('.css') ? cssAssets.add(asset) : includeEntrypointJs && asset.endsWith('.js') && jsAssets.add(asset);
11841
- 0 === jsAssets.size && jsAssets.add(`${DEFAULT_JS_DIST_PATH}/${chunkName}.js`);
11890
+ for (let asset of assets)isManifestCssAsset(asset) ? cssAssets.add(asset) : isManifestJsAsset(asset) && jsAssets.add(asset);
11891
+ for (let asset of clientStats?.entrypointFilesByName?.[chunkName] ?? [])isManifestCssAsset(asset) ? cssAssets.add(asset) : includeEntrypointJs && isManifestJsAsset(asset) && jsAssets.add(asset);
11892
+ if (0 === jsAssets.size) throw Error(`[react-router] Chunk "${chunkName}" emitted no JavaScript asset the browser manifest can reference (files: ${assets.join(', ') || 'none'}). Check the web \`output.filename.js\` scheme.`);
11842
11893
  let result = {
11843
11894
  js: [
11844
11895
  ...jsAssets
@@ -11851,7 +11902,7 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
11851
11902
  };
11852
11903
  }, analyzeRouteForManifestEffect = ({ discoveredCssAssets, isBuild, routeChunkCache, routeChunkConfig, routeEntryName, routeFilePath, route, routeModuleAnalysis })=>tryPluginPromise(async ()=>{
11853
11904
  let { code, exports: exportNames } = await routeModuleAnalysis?.(routeFilePath, route) ?? await getRouteModuleAnalysis(routeFilePath), cssAssets = !isBuild && 0 === discoveredCssAssets.length && CSS_IMPORT_RE.test(code) ? [
11854
- `${DEFAULT_JS_DIST_PATH.replace('/js', '/css')}/${routeEntryName}.css`
11905
+ `${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`
11855
11906
  ] : discoveredCssAssets, chunkInfo = isBuild && routeChunkConfig ? await detectRouteChunksIfEnabled(routeChunkCache, routeChunkConfig, routeFilePath, code) : null;
11856
11907
  return {
11857
11908
  cssAssets,
@@ -11865,9 +11916,9 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
11865
11916
  routeModuleExports: [],
11866
11917
  hasRouteChunkByExportName: null
11867
11918
  })))), getManifestDirFromEntryAsset = (entryModulePath)=>{
11868
- if (!entryModulePath) return DEFAULT_JS_DIST_PATH;
11919
+ if (!entryModulePath) return DEFAULT_MANIFEST_DIR;
11869
11920
  let dir = (0, external_pathe_namespaceObject.dirname)(entryModulePath);
11870
- return '.' === dir ? DEFAULT_JS_DIST_PATH : dir;
11921
+ return '.' === dir ? DEFAULT_MANIFEST_DIR : dir;
11871
11922
  }, getReactRouterManifestPath = ({ version, isBuild, entryModulePath })=>{
11872
11923
  if (!isBuild) return 'static/js/virtual/react-router/browser-manifest.js';
11873
11924
  let dir = getManifestDirFromEntryAsset(entryModulePath);
@@ -12418,13 +12469,13 @@ const redirectStatusCodes = new Set([
12418
12469
  }), external_jsesc_namespaceObject = require("jsesc");
12419
12470
  var external_jsesc_default = __webpack_require__.n(external_jsesc_namespaceObject);
12420
12471
  const BROWSER_MANIFEST_ASSET = 'static/js/virtual/react-router/browser-manifest.js', ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/, toManifestAssetUrl = (assetPrefix, assetName)=>ABSOLUTE_URL_RE.test(assetName) || assetName.startsWith('//') || assetName.startsWith('/') ? assetName : combineURLs(assetPrefix, assetName), addIntegrity = (sri, assetPrefix, assetName, integrity)=>{
12421
- 'string' == typeof assetName && assetName.endsWith('.js') && 'string' == typeof integrity && (sri[toManifestAssetUrl(assetPrefix, assetName)] = integrity);
12472
+ 'string' == typeof assetName && isManifestJsAsset(assetName) && 'string' == typeof integrity && (sri[toManifestAssetUrl(assetPrefix, assetName)] = integrity);
12422
12473
  }, computeSubresourceIntegrity = (source)=>{
12423
12474
  if (source) return `sha384-${(0, external_node_crypto_namespaceObject.createHash)('sha384').update(source.source()).digest('base64')}`;
12424
12475
  }, collectSubresourceIntegrity = (stats, compilation, assetPrefix = '/')=>{
12425
12476
  let sri = {};
12426
12477
  for (let asset of stats?.assets ?? [])addIntegrity(sri, assetPrefix, asset.name, asset.integrity);
12427
- if ('function' == typeof compilation?.getAssets) for (let asset of compilation.getAssets())asset.name.endsWith('.js') && addIntegrity(sri, assetPrefix, asset.name, computeSubresourceIntegrity(asset.source) ?? asset.info?.integrity);
12478
+ if ('function' == typeof compilation?.getAssets) for (let asset of compilation.getAssets())isManifestJsAsset(asset.name) && addIntegrity(sri, assetPrefix, asset.name, computeSubresourceIntegrity(asset.source) ?? asset.info?.integrity);
12428
12479
  return Object.keys(sri).length > 0 ? sri : void 0;
12429
12480
  };
12430
12481
  function registerModifyBrowserManifestAssets(api, routes, pluginOptions, appDirectory, assetPrefix = '/', routeChunkOptions, options) {
@@ -12444,7 +12495,7 @@ function registerModifyBrowserManifestAssets(api, routes, pluginOptions, appDire
12444
12495
  compilation.updateAsset(BROWSER_MANIFEST_ASSET, new sources.RawSource(newSource));
12445
12496
  }
12446
12497
  if (isBuild) {
12447
- let entryAssets = stats?.assetsByChunkName?.['entry.client'], entryJsAssets = entryAssets?.filter((asset)=>asset.endsWith('.js')) || [], manifestPath = getReactRouterManifestPath({
12498
+ let entryAssets = stats?.assetsByChunkName?.['entry.client'], entryJsAssets = entryAssets?.filter(isManifestJsAsset) || [], manifestPath = getReactRouterManifestPath({
12448
12499
  version: manifest.version,
12449
12500
  isBuild: !0,
12450
12501
  entryModulePath: entryJsAssets[0]
@@ -14418,12 +14469,14 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
14418
14469
  }, createServerRouteEntry = async (options)=>{
14419
14470
  let ast, plan = await createRscRouteExportPlan(options);
14420
14471
  validateRscRouteExportPlan(plan, options);
14421
- let lines = [], needsReactImport = !1, needsEnsureHmrImport = !1, needsStyleEntryImport = !1, streamsClientRouteCss = !plan.exportNames.some(isServerComponentExport) && plan.exportNames.includes('default') && programHasSideEffectStyleImports((ast = yuku_parse(options.code, {
14472
+ let lines = [], needsReactImport = !1, needsEnsureHmrImport = !1, needsStyleEntryImport = !1, pushStylesheetLinks = (entryCssFilesExpression)=>{
14473
+ lines.push(` ...(${entryCssFilesExpression} ?? []).map(href =>`), lines.push(' React.createElement("link", { key: href, rel: "stylesheet", href: href, precedence: "default" })),');
14474
+ }, streamsClientRouteCss = !plan.exportNames.some(isServerComponentExport) && plan.exportNames.includes('default') && programHasSideEffectStyleImports((ast = yuku_parse(options.code, {
14422
14475
  sourceType: 'module'
14423
14476
  })).program ?? ast);
14424
14477
  for (let exportName of plan.exportNames){
14425
14478
  if (streamsClientRouteCss && 'default' === exportName) {
14426
- needsReactImport = !0, needsStyleEntryImport = !0, lines.push(`import RscClientRouteDefault___ from ${JSON.stringify(plan.clientTargetFor('default'))};`), lines.push('export default function RscClientRouteWithStyles___(props) {'), lines.push(' return React.createElement(React.Fragment, null,'), lines.push(` ...(${RSC_ROUTE_STYLE_ENTRY_EXPORT}.entryCssFiles ?? []).map(href =>`), lines.push(' React.createElement("link", { key: href, rel: "stylesheet", href: href, precedence: "default" })),'), lines.push(' React.createElement(RscClientRouteDefault___, props),'), lines.push(' );'), lines.push('}');
14479
+ needsReactImport = !0, needsStyleEntryImport = !0, lines.push(`import RscClientRouteDefault___ from ${JSON.stringify(plan.clientTargetFor('default'))};`), lines.push('export default function RscClientRouteWithStyles___(props) {'), lines.push(' return React.createElement(React.Fragment, null,'), pushStylesheetLinks(`${RSC_ROUTE_STYLE_ENTRY_EXPORT}.entryCssFiles`), lines.push(' React.createElement(RscClientRouteDefault___, props),'), lines.push(' );'), lines.push('}');
14427
14480
  continue;
14428
14481
  }
14429
14482
  if (isClientRouteExport(exportName)) {
@@ -14431,7 +14484,7 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
14431
14484
  continue;
14432
14485
  }
14433
14486
  if (isServerComponentExport(exportName)) {
14434
- needsReactImport = !0, needsEnsureHmrImport = !0, lines.push(`import { ${exportName} as ${exportName}WithoutClientChunk } from ${JSON.stringify(plan.serverTarget)};`), lines.push(`export function ${exportName}(props) {`), lines.push(' return React.createElement(React.Fragment, null,'), lines.push(` ...(${exportName}WithoutClientChunk.entryCssFiles ?? []).map(href =>`), lines.push(' React.createElement("link", { key: href, rel: "stylesheet", href: href, precedence: "default" })),'), lines.push(' React.createElement(EnsureClientRouteModuleForHMR___, null),'), lines.push(` React.createElement(${exportName}WithoutClientChunk, props),`), lines.push(' );'), lines.push('}');
14487
+ needsReactImport = !0, needsEnsureHmrImport = !0, lines.push(`import { ${exportName} as ${exportName}WithoutClientChunk } from ${JSON.stringify(plan.serverTarget)};`), lines.push(`export function ${exportName}(props) {`), lines.push(' return React.createElement(React.Fragment, null,'), pushStylesheetLinks(`${exportName}WithoutClientChunk.entryCssFiles`), lines.push(' React.createElement(EnsureClientRouteModuleForHMR___, null),'), lines.push(` React.createElement(${exportName}WithoutClientChunk, props),`), lines.push(' );'), lines.push('}');
14435
14488
  continue;
14436
14489
  }
14437
14490
  lines.push(createReexport(exportName, plan.serverTarget));
@@ -14689,6 +14742,7 @@ export {
14689
14742
  'allowed-action-origins',
14690
14743
  'client-version',
14691
14744
  'react-router-serve-config',
14745
+ 'manifest-prefix',
14692
14746
  "bootstrap-scripts",
14693
14747
  'server-manifest'
14694
14748
  ], createReactRouterRscResolveAliases = (rootPath, options = {})=>({
@@ -14710,8 +14764,8 @@ export {
14710
14764
  ];
14711
14765
  })),
14712
14766
  'react-router/internal/react-server-client': (0, external_pathe_namespaceObject.resolve)(rootPath, getVirtualModuleFilePath('virtual/react-router/rsc-internal-client'))
14713
- }), createReactRouterRscVirtualModules = ({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, jsDistPath, outputClientPath, publicPath, routeDiscovery, routes, ssr })=>{
14714
- let rscAssetsBuildDirectory = (0, external_pathe_namespaceObject.relative)((0, external_pathe_namespaceObject.resolve)(buildDirectory, 'server'), outputClientPath), bootstrapPublicPath = normalizeAssetPrefix(publicPath);
14767
+ }), createReactRouterRscVirtualModules = ({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, outputClientPath, publicPath, routeDiscovery, routes, ssr })=>{
14768
+ let rscAssetsBuildDirectory = (0, external_pathe_namespaceObject.relative)((0, external_pathe_namespaceObject.resolve)(buildDirectory, 'server'), outputClientPath), serverPublicPath = normalizeAssetPrefix(publicPath);
14715
14769
  return {
14716
14770
  'virtual/react-router/unstable_rsc/routes': createRscRouteConfig({
14717
14771
  appDirectory,
@@ -14733,9 +14787,41 @@ export {
14733
14787
  assetsBuildDirectory: rscAssetsBuildDirectory,
14734
14788
  publicPath
14735
14789
  }),
14736
- "virtual/react-router/unstable_rsc/bootstrap-scripts": defaultExport([
14737
- combineURLs(bootstrapPublicPath, `${jsDistPath}/index.js`)
14738
- ]),
14790
+ 'virtual/react-router/unstable_rsc/manifest-prefix': `const manifest = __webpack_require__.rscM;
14791
+ const serverPrefix = ${JSON.stringify(serverPublicPath)};
14792
+ const appliedPrefix = manifest?.moduleLoading?.prefix;
14793
+ // An empty applied prefix (web \`assetPrefix: ''\`) yields relative references
14794
+ // such as "static/js/index.js"; those are rebased too, while absolute and
14795
+ // protocol-relative URLs are left alone.
14796
+ const isAbsoluteUrl = url => /^(?:[a-z][a-z\\d+.-]*:|\\/\\/|\\/)/i.test(url);
14797
+ if (typeof appliedPrefix === "string" && appliedPrefix !== serverPrefix) {
14798
+ const rewrite = url =>
14799
+ typeof url !== "string"
14800
+ ? url
14801
+ : appliedPrefix !== "" && url.startsWith(appliedPrefix)
14802
+ ? serverPrefix + url.slice(appliedPrefix.length)
14803
+ : appliedPrefix === "" && !isAbsoluteUrl(url)
14804
+ ? serverPrefix + url
14805
+ : url;
14806
+ const rewriteAll = list => {
14807
+ if (Array.isArray(list)) for (let i = 0; i < list.length; i++) list[i] = rewrite(list[i]);
14808
+ };
14809
+ manifest.moduleLoading.prefix = serverPrefix;
14810
+ rewriteAll(manifest.entryJsFiles);
14811
+ for (const files of Object.values(manifest.entryCssFiles ?? {})) rewriteAll(files);
14812
+ for (const reference of Object.values(manifest.clientManifest ?? {})) rewriteAll(reference.cssFiles);
14813
+ }
14814
+ export const rscManifest = manifest;
14815
+ `,
14816
+ "virtual/react-router/unstable_rsc/bootstrap-scripts": `import { rscManifest } from "virtual/react-router/unstable_rsc/manifest-prefix";
14817
+ const entryJsFiles = rscManifest?.entryJsFiles;
14818
+ if (!entryJsFiles?.length) {
14819
+ throw new Error(
14820
+ "[rsbuild-plugin-react-router] The rspack RSC manifest lists no browser entry script (entryJsFiles is empty), so the server cannot render bootstrap scripts. Rspack only records entry files whose name ends in \\".js\\"; web output.filename.js values with a query (for example \\"[name].js?v=[contenthash:8]\\") or another extension are not supported in RSC mode."
14821
+ );
14822
+ }
14823
+ export default entryJsFiles;
14824
+ `,
14739
14825
  'virtual/react-router/unstable_rsc/server-manifest': `export default function getServerManifest() {
14740
14826
  return __webpack_require__.rscM?.serverManifest;
14741
14827
  }
@@ -15591,13 +15677,12 @@ export {
15591
15677
  layer: RSC_LAYERS.rsc
15592
15678
  }
15593
15679
  },
15594
- createVirtualModules: (publicPath, jsDistPath)=>createReactRouterRscVirtualModules({
15680
+ createVirtualModules: (publicPath)=>createReactRouterRscVirtualModules({
15595
15681
  allowedActionOrigins: allowedActionOriginsForBuild,
15596
15682
  appDirectory,
15597
15683
  basename,
15598
15684
  buildDirectory,
15599
15685
  isBuild,
15600
- jsDistPath,
15601
15686
  outputClientPath,
15602
15687
  publicPath,
15603
15688
  routeDiscovery,
@@ -15691,7 +15776,7 @@ export {
15691
15776
  defaultEntryName,
15692
15777
  serverBundleEntries: artifacts.serverBundleEntries
15693
15778
  }),
15694
- createVirtualModules: (publicPath, _jsDistPath)=>createClassicVirtualModules({
15779
+ createVirtualModules: (publicPath)=>createClassicVirtualModules({
15695
15780
  allowedActionOrigins: allowedActionOriginsForBuild,
15696
15781
  appDirectory,
15697
15782
  assetsBuildDirectory,
@@ -15726,10 +15811,7 @@ export {
15726
15811
  library: {
15727
15812
  type: 'module'
15728
15813
  },
15729
- module: !0,
15730
- ...isBuild ? {
15731
- chunkFilename: 'static/js/async/[id]-[contenthash:16].js'
15732
- } : {}
15814
+ module: !0
15733
15815
  },
15734
15816
  webOptimization: {
15735
15817
  avoidEntryIife: !0,
@@ -15811,11 +15893,14 @@ export {
15811
15893
  }), api.onBeforeBuild(()=>{
15812
15894
  warnOnClientSourceMaps(api.getNormalizedConfig(), (msg)=>api.logger.warn(msg), 'web');
15813
15895
  }), api.onBeforeCreateCompiler(()=>{
15814
- let normalized = api.getNormalizedConfig();
15896
+ let root = api.getNormalizedConfig(), web = root.environments.web;
15815
15897
  assetPrefix = resolveEffectiveAssetPrefix({
15816
- dev: normalized.dev,
15817
- output: normalized.output,
15898
+ dev: web?.dev,
15899
+ output: web?.output,
15818
15900
  isBuild: 'build' === api.context.action
15901
+ }, {
15902
+ dev: root.dev,
15903
+ output: root.output
15819
15904
  });
15820
15905
  });
15821
15906
  let configPath = findEntryFile((0, external_pathe_namespaceObject.resolve)('react-router.config')), configExists = (0, external_node_fs_namespaceObject.existsSync)(configPath), configWatchPaths = configExists ? configPath : JS_EXTENSIONS.map((extension)=>(0, external_pathe_namespaceObject.resolve)(`react-router.config${extension}`)), reactRouterUserConfig = {};
@@ -16039,8 +16124,12 @@ export {
16039
16124
  initialRouteTopology: routeTopology.initialRouteTopology,
16040
16125
  onRouteTopologyChange: pluginOptions.onRouteTopologyChange
16041
16126
  });
16042
- api.onAfterEnvironmentCompile(({ stats, environment })=>{
16043
- if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames)), pluginOptions.federation && ssr) {
16127
+ if (api.onAfterEnvironmentCompile(({ stats, environment })=>{
16128
+ if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames), isRscMode && stats)) {
16129
+ let unsupported = collectUnsupportedRscScriptAssets(stats.compilation);
16130
+ if (unsupported.length > 0) throw Error(`[${PLUGIN_NAME}] RSC mode requires every browser JavaScript asset to be named "*.js" (no query, no other extension): rspack's RSC manifest omits ${unsupported.slice(0, 5).map((asset)=>JSON.stringify(asset)).join(', ')}${unsupported.length > 5 ? ` and ${unsupported.length - 5} more` : ''}. Adjust web \`output.filename.js\` / \`chunkFilename\`.`);
16131
+ }
16132
+ if (pluginOptions.federation && ssr) {
16044
16133
  let serverBuildDir = (0, external_pathe_namespaceObject.resolve)(buildDirectory, 'server'), clientBuildDir = (0, external_pathe_namespaceObject.resolve)(buildDirectory, 'client');
16045
16134
  if ((0, external_node_fs_namespaceObject.existsSync)(serverBuildDir)) {
16046
16135
  let ssrDir = (0, external_pathe_namespaceObject.resolve)(clientBuildDir, 'static');
@@ -16083,13 +16172,14 @@ export {
16083
16172
  prerenderPaths: modePlan.prerenderPaths,
16084
16173
  basename
16085
16174
  })))), api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig })=>{
16086
- let webDistPath = config.environments?.web?.output?.distPath, rootDistPath = config.output?.distPath, jsDistPath = ('object' == typeof webDistPath ? webDistPath.js : void 0) ?? ('object' == typeof rootDistPath ? rootDistPath.js : void 0) ?? DEFAULT_JS_DIST_PATH, assetPrefix = resolveEffectiveAssetPrefix({
16087
- dev: config.dev,
16088
- output: config.output,
16175
+ let publicPath, webConfig = config.environments?.web, vmodPlugin = (publicPath = resolveEffectiveAssetPrefix({
16176
+ dev: webConfig?.dev,
16177
+ output: webConfig?.output,
16089
16178
  isBuild
16090
- }), vmodPlugin = new core_namespaceObject.rspack.experiments.VirtualModulesPlugin(mapVirtualModules(modePlan.createVirtualModules(assetPrefix, jsDistPath))), useAsyncNodeChunkLoading = options.federation && 'commonjs' === resolvedServerOutput, nodeChunkLoading = 'require';
16091
- 'module' === resolvedServerOutput ? nodeChunkLoading = 'import' : useAsyncNodeChunkLoading && (nodeChunkLoading = 'async-node');
16092
- let guardedLazyCompilation = guardReactRouterLazyCompilation({
16179
+ }, {
16180
+ dev: config.dev,
16181
+ output: config.output
16182
+ }), new core_namespaceObject.rspack.experiments.VirtualModulesPlugin(mapVirtualModules(modePlan.createVirtualModules(publicPath)))), guardedLazyCompilation = guardReactRouterLazyCompilation({
16093
16183
  lazyCompilation: Object.prototype.hasOwnProperty.call(options, 'lazyCompilation') ? pluginOptions.lazyCompilation : config.dev?.lazyCompilation ?? pluginOptions.lazyCompilation,
16094
16184
  entryClientPath: isRscMode ? finalEntryRscClientPath : finalEntryClientPath,
16095
16185
  prewarmReactRouterModules: !!pluginOptions.unstableLazyCompilationPrewarm
@@ -16143,9 +16233,6 @@ export {
16143
16233
  }
16144
16234
  },
16145
16235
  output: {
16146
- filename: {
16147
- js: '[name].js'
16148
- },
16149
16236
  distPath: {
16150
16237
  root: outputClientPath
16151
16238
  }
@@ -16164,13 +16251,6 @@ export {
16164
16251
  ]
16165
16252
  },
16166
16253
  externalsType: modePlan.webExternalsType,
16167
- output: {
16168
- ...modePlan.webOutput,
16169
- publicPath: assetPrefix,
16170
- ...options.federation ? {
16171
- chunkLoading: 'import'
16172
- } : {}
16173
- },
16174
16254
  optimization: modePlan.webOptimization
16175
16255
  }
16176
16256
  }
@@ -16208,17 +16288,7 @@ export {
16208
16288
  },
16209
16289
  externals: modePlan.nodeExternals,
16210
16290
  ...modePlan.nodeDependencies,
16211
- externalsType: resolvedServerOutput,
16212
- output: {
16213
- chunkFormat: resolvedServerOutput,
16214
- chunkLoading: nodeChunkLoading,
16215
- devtoolModuleFilenameTemplate: '[absolute-resource-path]',
16216
- devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]',
16217
- workerChunkLoading: nodeChunkLoading,
16218
- wasmLoading: 'fetch',
16219
- module: 'module' === resolvedServerOutput,
16220
- chunkFilename: 'static/js/async/[name].js'
16221
- }
16291
+ externalsType: resolvedServerOutput
16222
16292
  }
16223
16293
  }
16224
16294
  }
@@ -16227,8 +16297,22 @@ export {
16227
16297
  }), registerReactRouterEnvironmentOutput({
16228
16298
  api,
16229
16299
  federation: pluginOptions.federation,
16230
- resolvedServerOutput
16231
- }), 'classic' === modePlan.kind && useRouteModuleTransformLoader && api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
16300
+ resolvedServerOutput,
16301
+ webOutput: modePlan.webOutput
16302
+ }), pluginOptions.federation && 'classic' === modePlan.kind) {
16303
+ let browserEntryModules = new Set([
16304
+ finalEntryClientPath,
16305
+ ...routeByFilePath.keys()
16306
+ ]);
16307
+ api.transform({
16308
+ environments: [
16309
+ 'web'
16310
+ ],
16311
+ order: 'post',
16312
+ test: (resourcePath)=>browserEntryModules.has(resourcePath)
16313
+ }, ({ code })=>`${code}\nexport {};\nawait Promise.resolve();\n`);
16314
+ }
16315
+ 'classic' === modePlan.kind && useRouteModuleTransformLoader && api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
16232
16316
  tools: {
16233
16317
  rspack: (rspackConfig)=>{
16234
16318
  let environmentDevHmrEnabled = 'web' === name && !isBuild && void 0 !== devHmrRefreshRuntimePath && 'development' === config.mode && config.dev?.hmr !== !1 && isRspackSwcReactRefreshEnabled(rspackConfig);