rsbuild-plugin-react-router 0.6.6 → 0.7.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
@@ -7356,7 +7356,7 @@ let createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=>{
7356
7356
  routeModuleAnalysis
7357
7357
  } : {}
7358
7358
  };
7359
- }, collectManifestFilesByName = (items, names, getFiles)=>{
7359
+ }, isManifestJsAsset = (asset)=>/(?<!\.hot-update)\.[cm]?js(?:\?.*)?$/.test(asset), isManifestCssAsset = (asset)=>/\.css(?:\?.*)?$/.test(asset), collectManifestFilesByName = (items, names, getFiles)=>{
7360
7360
  let filesByName = {};
7361
7361
  if (!names) {
7362
7362
  for (let [name, item] of items)null != item && (filesByName[name] = getFiles(name, item));
@@ -7372,15 +7372,7 @@ let createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=>{
7372
7372
  return filesByName;
7373
7373
  }, createReactRouterManifestStats = (compilation, chunkNames)=>{
7374
7374
  if (!compilation) return;
7375
- let assetsByChunkName = collectManifestFilesByName(compilation.namedChunks, chunkNames, (chunkName, chunk)=>{
7376
- var files;
7377
- let ownChunkAsset, ownFileIndex;
7378
- return files = Array.from(chunk.files ?? []), ownChunkAsset = `${chunkName}.js`, (ownFileIndex = files.findIndex((file)=>file.endsWith(ownChunkAsset))) <= 0 ? files : [
7379
- files[ownFileIndex],
7380
- ...files.slice(0, ownFileIndex),
7381
- ...files.slice(ownFileIndex + 1)
7382
- ];
7383
- }), entrypointFilesByName = compilation.entrypoints ? collectManifestFilesByName(compilation.entrypoints, chunkNames, (_name, entrypoint)=>Array.from(entrypoint.getFiles?.() ?? [])) : {};
7375
+ 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?.() ?? [])) : {};
7384
7376
  return Object.keys(entrypointFilesByName).length > 0 ? {
7385
7377
  assetsByChunkName,
7386
7378
  entrypointFilesByName
@@ -7416,9 +7408,9 @@ async function generateReactRouterManifestForDev(routes, _options, clientStats,
7416
7408
  return chunkAssetsByName.set(chunkName, result), result;
7417
7409
  }
7418
7410
  let cssAssets = new Set(), jsAssets = new Set();
7419
- for (let asset of assets)asset.endsWith('.css') ? cssAssets.add(asset) : asset.endsWith('.js') && jsAssets.add(asset);
7420
- for (let asset of clientStats?.entrypointFilesByName?.[chunkName] ?? [])asset.endsWith('.css') ? cssAssets.add(asset) : isBuild && asset.endsWith('.js') && jsAssets.add(asset);
7421
- 0 === jsAssets.size && jsAssets.add(`${DEFAULT_JS_DIST_PATH}/${chunkName}.js`);
7411
+ for (let asset of assets)isManifestCssAsset(asset) ? cssAssets.add(asset) : isManifestJsAsset(asset) && jsAssets.add(asset);
7412
+ for (let asset of clientStats?.entrypointFilesByName?.[chunkName] ?? [])isManifestCssAsset(asset) ? cssAssets.add(asset) : isBuild && isManifestJsAsset(asset) && jsAssets.add(asset);
7413
+ 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.`);
7422
7414
  let result = {
7423
7415
  js: [
7424
7416
  ...jsAssets
@@ -7906,7 +7898,7 @@ let redirectStatusCodes = new Set([
7906
7898
  }
7907
7899
  };
7908
7900
  }, BROWSER_MANIFEST_ASSET = 'static/js/virtual/react-router/browser-manifest.js', ABSOLUTE_URL_RE = /^[a-zA-Z][a-zA-Z\d+\-.]*:/, addIntegrity = (sri, assetPrefix, assetName, integrity)=>{
7909
- 'string' == typeof assetName && assetName.endsWith('.js') && 'string' == typeof integrity && (sri[ABSOLUTE_URL_RE.test(assetName) || assetName.startsWith('//') || assetName.startsWith('/') ? assetName : combineURLs(assetPrefix, assetName)] = integrity);
7901
+ 'string' == typeof assetName && isManifestJsAsset(assetName) && 'string' == typeof integrity && (sri[ABSOLUTE_URL_RE.test(assetName) || assetName.startsWith('//') || assetName.startsWith('/') ? assetName : combineURLs(assetPrefix, assetName)] = integrity);
7910
7902
  }, computeSubresourceIntegrity = (source)=>{
7911
7903
  if (source) return `sha384-${createHash('sha384').update(source.source()).digest('base64')}`;
7912
7904
  };
@@ -8596,12 +8588,14 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
8596
8588
  }, createServerRouteEntry = async (options)=>{
8597
8589
  let plan = await createRscRouteExportPlan(options);
8598
8590
  validateRscRouteExportPlan(plan, options);
8599
- let lines = [], needsReactImport = !1, needsEnsureHmrImport = !1, needsStyleEntryImport = !1, streamsClientRouteCss = !plan.exportNames.some(isServerComponentExport) && plan.exportNames.includes('default') && programHasSideEffectStyleImports(getProgram(yuku_parse(options.code, {
8591
+ let lines = [], needsReactImport = !1, needsEnsureHmrImport = !1, needsStyleEntryImport = !1, pushStylesheetLinks = (entryCssFilesExpression)=>{
8592
+ lines.push(` ...(${entryCssFilesExpression} ?? []).map(href =>`), lines.push(' React.createElement("link", { key: href, rel: "stylesheet", href: href, precedence: "default" })),');
8593
+ }, streamsClientRouteCss = !plan.exportNames.some(isServerComponentExport) && plan.exportNames.includes('default') && programHasSideEffectStyleImports(getProgram(yuku_parse(options.code, {
8600
8594
  sourceType: 'module'
8601
8595
  })));
8602
8596
  for (let exportName of plan.exportNames){
8603
8597
  if (streamsClientRouteCss && 'default' === exportName) {
8604
- 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('}');
8598
+ 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('}');
8605
8599
  continue;
8606
8600
  }
8607
8601
  if (isClientRouteExport(exportName)) {
@@ -8609,7 +8603,7 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
8609
8603
  continue;
8610
8604
  }
8611
8605
  if (isServerComponentExport(exportName)) {
8612
- 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('}');
8606
+ 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('}');
8613
8607
  continue;
8614
8608
  }
8615
8609
  lines.push(createReexport(exportName, plan.serverTarget));
@@ -8803,6 +8797,7 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
8803
8797
  'allowed-action-origins',
8804
8798
  'client-version',
8805
8799
  'react-router-serve-config',
8800
+ 'manifest-prefix',
8806
8801
  "bootstrap-scripts",
8807
8802
  'server-manifest'
8808
8803
  ], setupReactRouterRscPlugin = async ({ api, entryRscPath, entrySsrPath, pluginName, rsc })=>{
@@ -9528,8 +9523,8 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
9528
9523
  layer: RSC_LAYERS.rsc
9529
9524
  }
9530
9525
  },
9531
- createVirtualModules: (publicPath, jsDistPath)=>(({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, jsDistPath, outputClientPath, publicPath, routeDiscovery, routes, ssr })=>{
9532
- let rscAssetsBuildDirectory = relative(external_pathe_resolve(buildDirectory, 'server'), outputClientPath), bootstrapPublicPath = normalizeAssetPrefix(publicPath);
9526
+ createVirtualModules: (publicPath)=>(({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, outputClientPath, publicPath, routeDiscovery, routes, ssr })=>{
9527
+ let rscAssetsBuildDirectory = relative(external_pathe_resolve(buildDirectory, 'server'), outputClientPath), serverPublicPath = normalizeAssetPrefix(publicPath);
9533
9528
  return {
9534
9529
  'virtual/react-router/unstable_rsc/routes': (({ appDirectory, routes })=>{
9535
9530
  let componentResolutionCode = RSC_ROUTE_COMPONENT_EXPORTS.map(({ routeProperty, clientExport, serverExport })=>{
@@ -9625,9 +9620,33 @@ export default [`;
9625
9620
  assetsBuildDirectory: rscAssetsBuildDirectory,
9626
9621
  publicPath
9627
9622
  }),
9628
- "virtual/react-router/unstable_rsc/bootstrap-scripts": defaultExport([
9629
- combineURLs(bootstrapPublicPath, `${jsDistPath}/index.js`)
9630
- ]),
9623
+ 'virtual/react-router/unstable_rsc/manifest-prefix': `const manifest = __webpack_require__.rscM;
9624
+ const serverPrefix = ${JSON.stringify(serverPublicPath)};
9625
+ const appliedPrefix = manifest?.moduleLoading?.prefix;
9626
+ if (appliedPrefix && appliedPrefix !== serverPrefix) {
9627
+ const rewrite = url =>
9628
+ typeof url === "string" && url.startsWith(appliedPrefix)
9629
+ ? serverPrefix + url.slice(appliedPrefix.length)
9630
+ : url;
9631
+ const rewriteAll = list => {
9632
+ if (Array.isArray(list)) for (let i = 0; i < list.length; i++) list[i] = rewrite(list[i]);
9633
+ };
9634
+ manifest.moduleLoading.prefix = serverPrefix;
9635
+ rewriteAll(manifest.entryJsFiles);
9636
+ for (const files of Object.values(manifest.entryCssFiles ?? {})) rewriteAll(files);
9637
+ for (const reference of Object.values(manifest.clientManifest ?? {})) rewriteAll(reference.cssFiles);
9638
+ }
9639
+ export const rscManifest = manifest;
9640
+ `,
9641
+ "virtual/react-router/unstable_rsc/bootstrap-scripts": `import { rscManifest } from "virtual/react-router/unstable_rsc/manifest-prefix";
9642
+ const entryJsFiles = rscManifest?.entryJsFiles;
9643
+ if (!entryJsFiles?.length) {
9644
+ throw new Error(
9645
+ "[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."
9646
+ );
9647
+ }
9648
+ export default entryJsFiles;
9649
+ `,
9631
9650
  'virtual/react-router/unstable_rsc/server-manifest': `export default function getServerManifest() {
9632
9651
  return __webpack_require__.rscM?.serverManifest;
9633
9652
  }
@@ -9667,7 +9686,6 @@ export {
9667
9686
  basename,
9668
9687
  buildDirectory,
9669
9688
  isBuild,
9670
- jsDistPath,
9671
9689
  outputClientPath,
9672
9690
  publicPath,
9673
9691
  routeDiscovery,
@@ -9849,7 +9867,7 @@ export {
9849
9867
  defaultEntryName,
9850
9868
  serverBundleEntries: artifacts.serverBundleEntries
9851
9869
  }),
9852
- createVirtualModules: (publicPath, _jsDistPath)=>(({ allowedActionOrigins, appDirectory, assetsBuildDirectory, basename, devHmrRuntimeModule, entryServerPath, future, isSpaMode, prerenderPaths, publicPath, routeDiscovery, routes, routesByServerBundleId, ssr })=>{
9870
+ createVirtualModules: (publicPath)=>(({ allowedActionOrigins, appDirectory, assetsBuildDirectory, basename, devHmrRuntimeModule, entryServerPath, future, isSpaMode, prerenderPaths, publicPath, routeDiscovery, routes, routesByServerBundleId, ssr })=>{
9853
9871
  let serverBuildOptions = {
9854
9872
  entryServerPath,
9855
9873
  assetsBuildDirectory,
@@ -9938,10 +9956,7 @@ export {
9938
9956
  library: {
9939
9957
  type: 'module'
9940
9958
  },
9941
- module: !0,
9942
- ...isBuild ? {
9943
- chunkFilename: 'static/js/async/[id]-[contenthash:16].js'
9944
- } : {}
9959
+ module: !0
9945
9960
  },
9946
9961
  webOptimization: {
9947
9962
  avoidEntryIife: !0,
@@ -10105,11 +10120,14 @@ export {
10105
10120
  (isSourceMapEnabled(sourceMapSetting) || !0 === devtoolSetting || ('string' == typeof devtoolSetting ? devtoolSetting.includes('source-map') : null !== devtoolSetting && 'object' == typeof devtoolSetting)) && warn("\n WARNING: Source maps are enabled in production\n This makes your server code publicly visible in the browser.\n This is highly discouraged! If you insist, ensure that you are using\n environment variables for secrets and not hard-coding them in your source code.\n");
10106
10121
  }(api.getNormalizedConfig(), (msg)=>api.logger.warn(msg), 'web');
10107
10122
  }), api.onBeforeCreateCompiler(()=>{
10108
- let normalized = api.getNormalizedConfig();
10123
+ let root = api.getNormalizedConfig(), web = root.environments.web;
10109
10124
  assetPrefix = resolveEffectiveAssetPrefix({
10110
- dev: normalized.dev,
10111
- output: normalized.output,
10125
+ dev: web?.dev,
10126
+ output: web?.output,
10112
10127
  isBuild: 'build' === api.context.action
10128
+ }, {
10129
+ dev: root.dev,
10130
+ output: root.output
10113
10131
  });
10114
10132
  });
10115
10133
  let configPath = findEntryFile(external_pathe_resolve('react-router.config')), configExists = existsSync(configPath), configWatchPaths = configExists ? configPath : JS_EXTENSIONS.map((extension)=>external_pathe_resolve(`react-router.config${extension}`)), reactRouterUserConfig1 = {};
@@ -10934,16 +10952,19 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
10934
10952
  basename
10935
10953
  })))), api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig })=>{
10936
10954
  var existing;
10937
- let publicPath, jsDistPath, webDistPath = config.environments?.web?.output?.distPath, rootDistPath = config.output?.distPath, jsDistPath1 = ('object' == typeof webDistPath ? webDistPath.js : void 0) ?? ('object' == typeof rootDistPath ? rootDistPath.js : void 0) ?? DEFAULT_JS_DIST_PATH, assetPrefix = resolveEffectiveAssetPrefix({
10938
- dev: config.dev,
10939
- output: config.output,
10955
+ let publicPath, webConfig = config.environments?.web, webJsFilename = webConfig?.output?.filename?.js ?? config.output?.filename?.js;
10956
+ if (isRscMode && 'string' == typeof webJsFilename && !/\.js$/.test(webJsFilename)) throw Error(`[${PLUGIN_NAME}] RSC mode requires web \`output.filename.js\` to end in ".js" (got ${JSON.stringify(webJsFilename)}): rspack's RSC manifest omits entry files with a query or another extension, so the server could not render bootstrap scripts.`);
10957
+ let vmodPlugin = (publicPath = resolveEffectiveAssetPrefix({
10958
+ dev: webConfig?.dev,
10959
+ output: webConfig?.output,
10940
10960
  isBuild
10941
- }), vmodPlugin = (publicPath = assetPrefix, jsDistPath = jsDistPath1, new rspack.experiments.VirtualModulesPlugin(Object.fromEntries(Object.entries(modePlan.createVirtualModules(publicPath, jsDistPath)).map(([moduleId, contents])=>[
10961
+ }, {
10962
+ dev: config.dev,
10963
+ output: config.output
10964
+ }), new rspack.experiments.VirtualModulesPlugin(Object.fromEntries(Object.entries(modePlan.createVirtualModules(publicPath)).map(([moduleId, contents])=>[
10942
10965
  getVirtualModuleFilePath(moduleId),
10943
10966
  contents
10944
- ])))), useAsyncNodeChunkLoading = options.federation && 'commonjs' === resolvedServerOutput, nodeChunkLoading = 'require';
10945
- 'module' === resolvedServerOutput ? nodeChunkLoading = 'import' : useAsyncNodeChunkLoading && (nodeChunkLoading = 'async-node');
10946
- let guardedLazyCompilation = (({ lazyCompilation, entryClientPath, prewarmReactRouterModules = !1 })=>{
10967
+ ])))), guardedLazyCompilation = (({ lazyCompilation, entryClientPath, prewarmReactRouterModules = !1 })=>{
10947
10968
  if (void 0 === lazyCompilation || !1 === lazyCompilation) return lazyCompilation;
10948
10969
  let options = !0 === lazyCompilation ? {
10949
10970
  entries: !0,
@@ -11035,9 +11056,6 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11035
11056
  }
11036
11057
  },
11037
11058
  output: {
11038
- filename: {
11039
- js: '[name].js'
11040
- },
11041
11059
  distPath: {
11042
11060
  root: outputClientPath
11043
11061
  }
@@ -11056,13 +11074,6 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11056
11074
  ]
11057
11075
  },
11058
11076
  externalsType: modePlan.webExternalsType,
11059
- output: {
11060
- ...modePlan.webOutput,
11061
- publicPath: assetPrefix,
11062
- ...options.federation ? {
11063
- chunkLoading: 'import'
11064
- } : {}
11065
- },
11066
11077
  optimization: modePlan.webOptimization
11067
11078
  }
11068
11079
  }
@@ -11100,24 +11111,33 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11100
11111
  },
11101
11112
  externals: modePlan.nodeExternals,
11102
11113
  ...modePlan.nodeDependencies,
11103
- externalsType: resolvedServerOutput,
11104
- output: {
11105
- chunkFormat: resolvedServerOutput,
11106
- chunkLoading: nodeChunkLoading,
11107
- devtoolModuleFilenameTemplate: '[absolute-resource-path]',
11108
- devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]',
11109
- workerChunkLoading: nodeChunkLoading,
11110
- wasmLoading: 'fetch',
11111
- module: 'module' === resolvedServerOutput,
11112
- chunkFilename: 'static/js/async/[name].js'
11113
- }
11114
+ externalsType: resolvedServerOutput
11114
11115
  }
11115
11116
  }
11116
11117
  }
11117
11118
  }
11118
11119
  });
11119
- }), (({ api, federation, resolvedServerOutput })=>{
11120
- api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
11120
+ }), (({ api, federation, resolvedServerOutput, webOutput })=>{
11121
+ let nodeChunkLoading = 'module' === resolvedServerOutput ? 'import' : federation ? 'async-node' : 'require';
11122
+ api.modifyRspackConfig((rspackConfig, { environment, mergeConfig })=>'web' === environment.name ? mergeConfig(rspackConfig, {
11123
+ output: {
11124
+ ...webOutput,
11125
+ ...federation ? {
11126
+ chunkLoading: 'import'
11127
+ } : {}
11128
+ }
11129
+ }) : 'node' === environment.name ? mergeConfig(rspackConfig, {
11130
+ output: {
11131
+ chunkFormat: resolvedServerOutput,
11132
+ chunkLoading: nodeChunkLoading,
11133
+ devtoolModuleFilenameTemplate: '[absolute-resource-path]',
11134
+ devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]',
11135
+ workerChunkLoading: nodeChunkLoading,
11136
+ wasmLoading: 'fetch',
11137
+ module: 'module' === resolvedServerOutput,
11138
+ chunkFilename: 'static/js/async/[name].js'
11139
+ }
11140
+ }) : rspackConfig), api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
11121
11141
  tools: {
11122
11142
  rspack: (rspackConfig)=>{
11123
11143
  if (federation && ((rspackConfig)=>{
@@ -11149,7 +11169,8 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11149
11169
  })({
11150
11170
  api,
11151
11171
  federation: pluginOptions.federation,
11152
- resolvedServerOutput
11172
+ resolvedServerOutput,
11173
+ webOutput: modePlan.webOutput
11153
11174
  }), 'classic' === modePlan.kind && useRouteModuleTransformLoader && api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
11154
11175
  tools: {
11155
11176
  rspack: (rspackConfig)=>{
@@ -11286,7 +11307,7 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11286
11307
  compilation.updateAsset(BROWSER_MANIFEST_ASSET, new sources.RawSource(newSource));
11287
11308
  }
11288
11309
  if (isBuild) {
11289
- let entryAssets = stats?.assetsByChunkName?.['entry.client'], entryJsAssets = entryAssets?.filter((asset)=>asset.endsWith('.js')) || [], manifestPath = getReactRouterManifestPath({
11310
+ let entryAssets = stats?.assetsByChunkName?.['entry.client'], entryJsAssets = entryAssets?.filter(isManifestJsAsset) || [], manifestPath = getReactRouterManifestPath({
11290
11311
  version: manifest.version,
11291
11312
  isBuild: !0,
11292
11313
  entryModulePath: entryJsAssets[0]
@@ -11298,7 +11319,7 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
11298
11319
  let sri = withSri ? ((stats, compilation, assetPrefix = '/')=>{
11299
11320
  let sri = {};
11300
11321
  for (let asset of stats?.assets ?? [])addIntegrity(sri, assetPrefix, asset.name, asset.integrity);
11301
- 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);
11322
+ 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);
11302
11323
  return Object.keys(sri).length > 0 ? sri : void 0;
11303
11324
  })(compilation.getStats().toJson({
11304
11325
  all: !1,
@@ -64,6 +64,8 @@ type ReactRouterManifestStatsCompilation = {
64
64
  namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
65
65
  entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
66
66
  };
67
+ export declare const isManifestJsAsset: (asset: string) => boolean;
68
+ export declare const isManifestCssAsset: (asset: string) => boolean;
67
69
  export declare const createReactRouterManifestStats: (compilation: ReactRouterManifestStatsCompilation | undefined, chunkNames?: ReadonlySet<string>) => ReactRouterManifestStats | undefined;
68
70
  export type RouteManifestModuleExports = Record<string, readonly string[]>;
69
71
  export type ReactRouterManifestGenerationResult = {
@@ -10,7 +10,7 @@ type CommonModePlan = {
10
10
  manifestChunkNames: Set<string>;
11
11
  webEntries: Record<string, string | RsbuildEntryDescription>;
12
12
  nodeEntries: Record<string, string | RsbuildEntryDescription>;
13
- createVirtualModules(publicPath: string, jsDistPath: string): Record<string, string>;
13
+ createVirtualModules(publicPath: string): Record<string, string>;
14
14
  createResolveConfig(rootPath: string): Rspack.Configuration['resolve'];
15
15
  server: RsbuildConfig['server'] | undefined;
16
16
  webExternalsType: 'module' | undefined;
@@ -7,26 +7,32 @@ export declare const getPackageVersion: (packageName: string, resolvePackagePath
7
7
  export declare const escapeHtml: (value: string) => string;
8
8
  export declare function combineURLs(baseURL: string, relativeURL: string): string;
9
9
  export declare function normalizeAssetPrefix(assetPrefix?: string): string;
10
- /**
11
- * Resolve the asset prefix Rsbuild applies to emitted asset URLs for the given
12
- * mode. In development the effective prefix is `dev.assetPrefix` (which Rsbuild
13
- * defaults from `server.base`, falling back to `output.assetPrefix`); in a
14
- * production build it is `output.assetPrefix`. Both fields are already resolved
15
- * on the normalized config, so this mirrors Rsbuild's own precedence rather than
16
- * re-deriving it from `server.base`.
17
- *
18
- * `dev.assetPrefix` may be a boolean on the raw config (`false` disables it);
19
- * boolean/`'auto'`/empty values normalize to the root prefix `'/'`.
20
- */
21
- export declare function resolveEffectiveAssetPrefix(config: {
10
+ type AssetPrefixConfig = {
22
11
  dev?: {
23
12
  assetPrefix?: unknown;
24
13
  };
25
14
  output?: {
26
15
  assetPrefix?: unknown;
27
16
  };
17
+ };
18
+ /**
19
+ * Resolve the absolute asset prefix the server build and browser manifest use
20
+ * for asset URLs. In development the effective prefix is `dev.assetPrefix`
21
+ * (which Rsbuild defaults from `server.base`, falling back to
22
+ * `output.assetPrefix`); in a production build it is `output.assetPrefix`.
23
+ *
24
+ * `fallbacks` are consulted in order (e.g. the root config after the web
25
+ * environment) when the preceding config only offers a prefix the server
26
+ * cannot use: `'auto'` and empty values are browser-runtime-only, so a root
27
+ * CDN prefix must survive a web `'auto'`. The choice happens before
28
+ * normalization so that `'auto'` is not first folded into `'/'`.
29
+ *
30
+ * `dev.assetPrefix` may be a boolean on the raw config (`false` disables it);
31
+ * boolean/`'auto'`/empty values ultimately normalize to the root prefix `'/'`.
32
+ */
33
+ export declare function resolveEffectiveAssetPrefix(config: AssetPrefixConfig & {
28
34
  isBuild: boolean;
29
- }): string;
35
+ }, ...fallbacks: AssetPrefixConfig[]): string;
30
36
  export declare function createRouteId(file: string): string;
31
37
  export declare function findEntryFile(basePath: string): string;
32
38
  export declare function generateWithProps(): string;
@@ -6,8 +6,6 @@ type RscVirtualModulesOptions = {
6
6
  basename: string;
7
7
  buildDirectory: string;
8
8
  isBuild: boolean;
9
- /** Resolved web `output.distPath.js` segment, e.g. `static/js`. */
10
- jsDistPath: string;
11
9
  outputClientPath: string;
12
10
  publicPath: string;
13
11
  routeDiscovery: Config['routeDiscovery'];
@@ -17,5 +15,5 @@ type RscVirtualModulesOptions = {
17
15
  export declare const createReactRouterRscResolveAliases: (rootPath: string, options?: {
18
16
  entrySsrPath?: string;
19
17
  }) => Record<string, string>;
20
- export declare const createReactRouterRscVirtualModules: ({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, jsDistPath, outputClientPath, publicPath, routeDiscovery, routes, ssr, }: RscVirtualModulesOptions) => Record<string, string>;
18
+ export declare const createReactRouterRscVirtualModules: ({ allowedActionOrigins, appDirectory, basename, buildDirectory, isBuild, outputClientPath, publicPath, routeDiscovery, routes, ssr, }: RscVirtualModulesOptions) => Record<string, string>;
21
19
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rsbuild-plugin-react-router",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "React Router plugin for Rsbuild",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,15 +1,64 @@
1
- import type { RsbuildPluginAPI } from '@rsbuild/core';
1
+ import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
2
2
  import { ensureFederationAsyncStartup } from './federation.js';
3
3
 
4
+ /**
5
+ * Rspack `output` policy for the web and node environments, in two tiers:
6
+ *
7
+ * 1. Overridable defaults, registered through `modifyRspackConfig`. Rsbuild
8
+ * runs the user's `tools.rspack` (object or function form) after this hook,
9
+ * so user output settings such as `chunkFilename` take precedence
10
+ * (#129, #130). Neither `filename` nor `publicPath` is set here: Rsbuild
11
+ * derives them from `output.filename`/`output.filenameHash`/`output.distPath`
12
+ * and the environment's `output.assetPrefix`, which keeps `'auto'` intact.
13
+ * 2. Enforced invariants, registered through a `tools.rspack` function that
14
+ * runs after the user's own `tools.rspack`: the server library type must
15
+ * match the server module format, and federation builds need async startup.
16
+ */
4
17
  export const registerReactRouterEnvironmentOutput = ({
5
18
  api,
6
19
  federation,
7
20
  resolvedServerOutput,
21
+ webOutput,
8
22
  }: {
9
23
  api: RsbuildPluginAPI;
10
24
  federation: boolean | undefined;
11
25
  resolvedServerOutput: 'commonjs' | 'module';
26
+ webOutput: NonNullable<Rspack.Configuration['output']>;
12
27
  }): void => {
28
+ const nodeChunkLoading =
29
+ resolvedServerOutput === 'module'
30
+ ? 'import'
31
+ : federation
32
+ ? 'async-node'
33
+ : 'require';
34
+
35
+ api.modifyRspackConfig((rspackConfig, { environment, mergeConfig }) => {
36
+ if (environment.name === 'web') {
37
+ return mergeConfig(rspackConfig, {
38
+ output: {
39
+ ...webOutput,
40
+ ...(federation ? { chunkLoading: 'import' } : {}),
41
+ },
42
+ });
43
+ }
44
+ if (environment.name === 'node') {
45
+ return mergeConfig(rspackConfig, {
46
+ output: {
47
+ chunkFormat: resolvedServerOutput,
48
+ chunkLoading: nodeChunkLoading,
49
+ devtoolModuleFilenameTemplate: '[absolute-resource-path]',
50
+ devtoolFallbackModuleFilenameTemplate:
51
+ '[absolute-resource-path]?[hash]',
52
+ workerChunkLoading: nodeChunkLoading,
53
+ wasmLoading: 'fetch',
54
+ module: resolvedServerOutput === 'module',
55
+ chunkFilename: 'static/js/async/[name].js',
56
+ },
57
+ });
58
+ }
59
+ return rspackConfig;
60
+ });
61
+
13
62
  api.modifyEnvironmentConfig(
14
63
  async (config, { name, mergeEnvironmentConfig }) => {
15
64
  if (name !== 'web' && name !== 'node') {
package/src/index.ts CHANGED
@@ -6,11 +6,7 @@ import { rspack, type RsbuildPlugin, type Rspack } from '@rsbuild/core';
6
6
  import { relative, resolve } from 'pathe';
7
7
 
8
8
  import { getDefaultConcurrency } from './concurrency.js';
9
- import {
10
- DEFAULT_JS_DIST_PATH,
11
- JS_EXTENSIONS,
12
- PLUGIN_NAME,
13
- } from './constants.js';
9
+ import { JS_EXTENSIONS, PLUGIN_NAME } from './constants.js';
14
10
  import { guardReactRouterLazyCompilation } from './lazy-compilation.js';
15
11
  import {
16
12
  findEntryFile,
@@ -222,13 +218,25 @@ export const pluginReactRouter = (
222
218
  warnOnClientSourceMaps(normalized, msg => api.logger.warn(msg), 'web');
223
219
  });
224
220
 
221
+ // The manifest / server `publicPath` follows the web environment's asset
222
+ // prefix because that is where the browser assets are served from. A web
223
+ // prefix the server cannot use (`'auto'`) falls back to the root prefix,
224
+ // so `output.assetPrefix: 'https://cdn/'` + web `'auto'` still emits CDN
225
+ // URLs from the server.
225
226
  api.onBeforeCreateCompiler(() => {
226
- const normalized = api.getNormalizedConfig();
227
- assetPrefix = resolveEffectiveAssetPrefix({
228
- dev: normalized.dev,
229
- output: normalized.output,
230
- isBuild: api.context.action === 'build',
231
- });
227
+ const root = api.getNormalizedConfig();
228
+ // `getNormalizedConfig({ environment: 'web' })` throws when the build was
229
+ // narrowed to other environments (`--environment node`), so look the web
230
+ // environment up on the root config instead.
231
+ const web = root.environments.web;
232
+ assetPrefix = resolveEffectiveAssetPrefix(
233
+ {
234
+ dev: web?.dev,
235
+ output: web?.output,
236
+ isBuild: api.context.action === 'build',
237
+ },
238
+ { dev: root.dev, output: root.output }
239
+ );
232
240
  });
233
241
 
234
242
  const configPath = findEntryFile(resolve('react-router.config'));
@@ -832,39 +840,35 @@ export const pluginReactRouter = (
832
840
  }
833
841
 
834
842
  // Public requests stay bare while Rspack resolves seeded virtual files.
835
- const createVirtualModulePlugin = (
836
- publicPath: string,
837
- jsDistPath: string
838
- ) => {
843
+ const createVirtualModulePlugin = (publicPath: string) => {
839
844
  return new rspack.experiments.VirtualModulesPlugin(
840
- mapVirtualModules(modePlan.createVirtualModules(publicPath, jsDistPath))
845
+ mapVirtualModules(modePlan.createVirtualModules(publicPath))
841
846
  );
842
847
  };
843
848
 
844
849
  api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig }) => {
845
- // The RSC bootstrap script URL must reflect the user's web js distPath;
846
- // the entry filename itself is deterministic because the plugin forces
847
- // web `output.filename.js` to '[name].js' below.
848
- const webDistPath = config.environments?.web?.output?.distPath;
849
- const rootDistPath = config.output?.distPath;
850
- const jsDistPath =
851
- (typeof webDistPath === 'object' ? webDistPath.js : undefined) ??
852
- (typeof rootDistPath === 'object' ? rootDistPath.js : undefined) ??
853
- DEFAULT_JS_DIST_PATH;
854
- const assetPrefix = resolveEffectiveAssetPrefix({
855
- dev: config.dev,
856
- output: config.output,
857
- isBuild,
858
- });
859
- const vmodPlugin = createVirtualModulePlugin(assetPrefix, jsDistPath);
860
- const useAsyncNodeChunkLoading =
861
- options.federation && resolvedServerOutput === 'commonjs';
862
- let nodeChunkLoading: 'import' | 'async-node' | 'require' = 'require';
863
- if (resolvedServerOutput === 'module') {
864
- nodeChunkLoading = 'import';
865
- } else if (useAsyncNodeChunkLoading) {
866
- nodeChunkLoading = 'async-node';
850
+ const webConfig = config.environments?.web;
851
+ const webJsFilename =
852
+ webConfig?.output?.filename?.js ?? config.output?.filename?.js;
853
+ // Rspack's RSC manifest only records browser entry files named `*.js`
854
+ // (`entryJsFiles`), and the server renders its bootstrap scripts from
855
+ // that list. Reject filename schemes it would silently drop up front.
856
+ if (
857
+ isRscMode &&
858
+ typeof webJsFilename === 'string' &&
859
+ !/\.js$/.test(webJsFilename)
860
+ ) {
861
+ throw new Error(
862
+ `[${PLUGIN_NAME}] RSC mode requires web \`output.filename.js\` to end in ".js" (got ${JSON.stringify(
863
+ webJsFilename
864
+ )}): rspack's RSC manifest omits entry files with a query or another extension, so the server could not render bootstrap scripts.`
865
+ );
867
866
  }
867
+ const assetPrefix = resolveEffectiveAssetPrefix(
868
+ { dev: webConfig?.dev, output: webConfig?.output, isBuild },
869
+ { dev: config.dev, output: config.output }
870
+ );
871
+ const vmodPlugin = createVirtualModulePlugin(assetPrefix);
868
872
  const configuredLazyCompilation = Object.prototype.hasOwnProperty.call(
869
873
  options,
870
874
  'lazyCompilation'
@@ -964,9 +968,6 @@ export const pluginReactRouter = (
964
968
  }),
965
969
  },
966
970
  output: {
967
- filename: {
968
- js: '[name].js',
969
- },
970
971
  distPath: {
971
972
  root: outputClientPath,
972
973
  },
@@ -985,15 +986,6 @@ export const pluginReactRouter = (
985
986
  ],
986
987
  },
987
988
  externalsType: modePlan.webExternalsType,
988
- output: {
989
- ...modePlan.webOutput,
990
- publicPath: assetPrefix,
991
- ...(options.federation
992
- ? {
993
- chunkLoading: 'import',
994
- }
995
- : {}),
996
- },
997
989
  optimization: modePlan.webOptimization,
998
990
  },
999
991
  },
@@ -1038,17 +1030,6 @@ export const pluginReactRouter = (
1038
1030
  externals: modePlan.nodeExternals,
1039
1031
  ...modePlan.nodeDependencies,
1040
1032
  externalsType: resolvedServerOutput,
1041
- output: {
1042
- chunkFormat: resolvedServerOutput,
1043
- chunkLoading: nodeChunkLoading,
1044
- devtoolModuleFilenameTemplate: '[absolute-resource-path]',
1045
- devtoolFallbackModuleFilenameTemplate:
1046
- '[absolute-resource-path]?[hash]',
1047
- workerChunkLoading: nodeChunkLoading,
1048
- wasmLoading: 'fetch',
1049
- module: resolvedServerOutput === 'module',
1050
- chunkFilename: 'static/js/async/[name].js',
1051
- },
1052
1033
  },
1053
1034
  },
1054
1035
  },
@@ -1060,6 +1041,7 @@ export const pluginReactRouter = (
1060
1041
  api,
1061
1042
  federation: pluginOptions.federation,
1062
1043
  resolvedServerOutput,
1044
+ webOutput: modePlan.webOutput,
1063
1045
  });
1064
1046
 
1065
1047
  if (modePlan.kind === 'classic' && useRouteModuleTransformLoader) {