rsbuild-plugin-react-router 0.7.0 → 0.7.2
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 +20 -2
- package/dist/dev-generation.d.ts +3 -1
- package/dist/dev-hdr-intent.d.ts +5 -0
- package/dist/federation.d.ts +26 -0
- package/dist/index.cjs +93 -19
- package/dist/index.js +94 -19
- package/dist/manifest.d.ts +29 -0
- package/package.json +1 -1
- package/src/dev-generation.ts +6 -0
- package/src/dev-hdr-intent.ts +27 -0
- package/src/dev-runtime-controller.ts +20 -13
- package/src/environment-output.ts +10 -1
- package/src/federation.ts +93 -14
- package/src/index.ts +47 -16
- package/src/manifest.ts +73 -0
- package/src/rsc-virtual-modules.ts +12 -4
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
|
|
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
|
|
|
@@ -365,6 +373,16 @@ content-hashed filenames, and `output.filename`, `output.filenameHash`,
|
|
|
365
373
|
`output.distPath`, and `tools.rspack` output settings you configure govern
|
|
366
374
|
the emitted files and the manifest URLs that reference them.
|
|
367
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
|
+
|
|
368
386
|
## Custom Server Setup
|
|
369
387
|
|
|
370
388
|
The plugin supports two ways to handle server-side rendering:
|
package/dist/dev-generation.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { RsbuildDevServer, Rspack } from '@rsbuild/core';
|
|
2
2
|
import type { ServerBuild } from 'react-router';
|
|
3
|
-
import { type DevGraphChanges, type DevGraphIdentity, type DevRuntimeStats, type ReactRouterDevBuildPlan, type ReactRouterDevManifestSet } from './dev-runtime-artifacts.js';
|
|
3
|
+
import { type DevCompilationIdentity, type DevGraphChanges, type DevGraphIdentity, type DevRuntimeStats, type ReactRouterDevBuildPlan, type ReactRouterDevManifestSet } from './dev-runtime-artifacts.js';
|
|
4
4
|
export type { DevGraphChanges, DevGraphIdentity, ReactRouterDevManifest, } from './dev-runtime-artifacts.js';
|
|
5
5
|
export type ReactRouterDevRuntime = {
|
|
6
6
|
beginAttempt: () => void;
|
|
7
7
|
captureWeb: (compilation: Rspack.Compilation, manifestsByEntryName: ReactRouterDevManifestSet) => void;
|
|
8
8
|
finishAttempt: (stats: DevRuntimeStats, changes: DevGraphChanges, identity: DevGraphIdentity) => Promise<'committed' | 'ignored' | 'retry-node'>;
|
|
9
|
+
/** Node identity actually retained by the last successful generation. */
|
|
10
|
+
getCommittedNodeIdentity: () => DevCompilationIdentity | undefined;
|
|
9
11
|
failAttempt: (error: Error) => void;
|
|
10
12
|
load: (entryName?: string) => Promise<ServerBuild>;
|
|
11
13
|
close: (error?: Error) => void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Tracks unsignaled node edits across compiler retries within one dev session. */
|
|
2
|
+
export declare const createDevHdrIntentTracker: () => {
|
|
3
|
+
capture(compilation: object, hasRelevantChanges: boolean): void;
|
|
4
|
+
signalCommitted(compilation: object, signal: () => void): void;
|
|
5
|
+
};
|
package/dist/federation.d.ts
CHANGED
|
@@ -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
|
@@ -617,15 +617,29 @@ const resolveEntryWithTemplate = ({ appDirectory, entryName, templateName, templ
|
|
|
617
617
|
hasServerApp,
|
|
618
618
|
serverAppPath
|
|
619
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
|
+
};
|
|
620
632
|
}, ensureFederationAsyncStartup = (rspackConfig)=>{
|
|
621
633
|
if (rspackConfig?.plugins?.length) for (let plugin of rspackConfig.plugins){
|
|
622
|
-
|
|
623
|
-
let pluginOptions = plugin._options ?? plugin.options;
|
|
634
|
+
let pluginOptions = getModuleFederationOptions(plugin);
|
|
624
635
|
pluginOptions && (pluginOptions.experiments = {
|
|
625
636
|
...pluginOptions.experiments,
|
|
626
637
|
asyncStartup: !0
|
|
627
638
|
});
|
|
628
639
|
}
|
|
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');
|
|
629
643
|
}, registerReactRouterEnvironmentOutput = ({ api, federation, resolvedServerOutput, webOutput })=>{
|
|
630
644
|
let nodeChunkLoading = 'module' === resolvedServerOutput ? 'import' : federation ? 'async-node' : 'require';
|
|
631
645
|
api.modifyRspackConfig((rspackConfig, { environment, mergeConfig })=>'web' === environment.name ? mergeConfig(rspackConfig, {
|
|
@@ -649,7 +663,7 @@ const resolveEntryWithTemplate = ({ appDirectory, entryName, templateName, templ
|
|
|
649
663
|
}) : rspackConfig), api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
|
|
650
664
|
tools: {
|
|
651
665
|
rspack: (rspackConfig)=>{
|
|
652
|
-
if (federation && ensureFederationAsyncStartup(rspackConfig), 'node' === name) {
|
|
666
|
+
if (federation && (ensureFederationAsyncStartup(rspackConfig), 'web' === name ? isolateFederationContainerRuntime(rspackConfig) : enforceAsyncOnlyServerSplitChunks(rspackConfig)), 'node' === name) {
|
|
653
667
|
let output = rspackConfig.output;
|
|
654
668
|
if (output) {
|
|
655
669
|
let library = output.library, libraryOptions = library && 'object' == typeof library && !Array.isArray(library) ? library : {};
|
|
@@ -1470,7 +1484,7 @@ const getCurrentVersion = ()=>moduleVersion, setCurrentVersion = (version)=>{
|
|
|
1470
1484
|
}), findLast = null, zip = null, Iterable_zipWith = null, intersperse = null, Iterable_containsWith = (isEquivalent)=>dual(2, (self, a)=>{
|
|
1471
1485
|
for (let i of self)if (isEquivalent(a, i)) return !0;
|
|
1472
1486
|
return !1;
|
|
1473
|
-
}), Iterable_equivalence = null, Iterable_contains = null, chunksOf = null, groupWith = null,
|
|
1487
|
+
}), Iterable_equivalence = null, Iterable_contains = null, chunksOf = null, groupWith = null, Iterable_group = null, groupBy = null, constEmpty = {
|
|
1474
1488
|
[Symbol.iterator]: ()=>constEmptyIterator
|
|
1475
1489
|
}, constEmptyIterator = {
|
|
1476
1490
|
next: ()=>({
|
|
@@ -11816,7 +11830,25 @@ const createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=
|
|
|
11816
11830
|
routeModuleAnalysis
|
|
11817
11831
|
} : {}
|
|
11818
11832
|
};
|
|
11819
|
-
}, isManifestJsAsset = (asset)=>/(?<!\.hot-update)\.[cm]?js(?:\?.*)?$/.test(asset), isManifestCssAsset = (asset)=>/\.css(?:\?.*)?$/.test(asset),
|
|
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
|
|
11850
|
+
];
|
|
11851
|
+
}, collectManifestFilesByName = (items, names, getFiles)=>{
|
|
11820
11852
|
let filesByName = {};
|
|
11821
11853
|
if (!names) {
|
|
11822
11854
|
for (let [name, item] of items)null != item && (filesByName[name] = getFiles(name, item));
|
|
@@ -14758,11 +14790,19 @@ export {
|
|
|
14758
14790
|
'virtual/react-router/unstable_rsc/manifest-prefix': `const manifest = __webpack_require__.rscM;
|
|
14759
14791
|
const serverPrefix = ${JSON.stringify(serverPublicPath)};
|
|
14760
14792
|
const appliedPrefix = manifest?.moduleLoading?.prefix;
|
|
14761
|
-
|
|
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) {
|
|
14762
14798
|
const rewrite = url =>
|
|
14763
|
-
typeof url
|
|
14764
|
-
?
|
|
14765
|
-
: 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;
|
|
14766
14806
|
const rewriteAll = list => {
|
|
14767
14807
|
if (Array.isArray(list)) for (let i = 0; i < list.length; i++) list[i] = rewrite(list[i]);
|
|
14768
14808
|
};
|
|
@@ -15206,6 +15246,7 @@ export default entryJsFiles;
|
|
|
15206
15246
|
return rejectAttempt(attemptId, normalizeEffectError(cause), !0), 'ignored';
|
|
15207
15247
|
}
|
|
15208
15248
|
},
|
|
15249
|
+
getCommittedNodeIdentity: ()=>'ready' === state.kind ? state.committed.nodeIdentity : void 0,
|
|
15209
15250
|
failAttempt (error) {
|
|
15210
15251
|
let attemptId = getCurrentAttemptId();
|
|
15211
15252
|
null !== attemptId && rejectAttempt(attemptId, error, !1);
|
|
@@ -15239,6 +15280,17 @@ export default entryJsFiles;
|
|
|
15239
15280
|
}, loadReactRouterServerBuild = (server, entryName)=>{
|
|
15240
15281
|
let runtime = Reflect.get(server, DEV_RUNTIME_KEY);
|
|
15241
15282
|
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().'));
|
|
15283
|
+
}, createDevHdrIntentTracker = ()=>{
|
|
15284
|
+
let latestRelevantEditRevision = 0, signaledRevision = 0, revisionByCompilation = new WeakMap();
|
|
15285
|
+
return {
|
|
15286
|
+
capture (compilation, hasRelevantChanges) {
|
|
15287
|
+
hasRelevantChanges && (latestRelevantEditRevision += 1), revisionByCompilation.set(compilation, latestRelevantEditRevision);
|
|
15288
|
+
},
|
|
15289
|
+
signalCommitted (compilation, signal) {
|
|
15290
|
+
let compilationRevision = revisionByCompilation.get(compilation);
|
|
15291
|
+
void 0 === compilationRevision || compilationRevision <= signaledRevision || (signal(), signaledRevision = Math.max(signaledRevision, compilationRevision));
|
|
15292
|
+
}
|
|
15293
|
+
};
|
|
15242
15294
|
}, createDevRuntimeSessionManager = (closeBinding)=>{
|
|
15243
15295
|
let state = {
|
|
15244
15296
|
status: 'idle'
|
|
@@ -15344,12 +15396,15 @@ export default entryJsFiles;
|
|
|
15344
15396
|
scheduledCssAssetOwnershipReload && (clearTimeout(scheduledCssAssetOwnershipReload), scheduledCssAssetOwnershipReload = void 0), reloadAfterCssAssetOwnershipRemoval = !1;
|
|
15345
15397
|
let pair = binding.compilers;
|
|
15346
15398
|
pair && resetDevCompilerPair(pair), binding.compilers = void 0, binding.runtime.close(error), unregisterReactRouterDevRuntime(binding.server, binding.runtime);
|
|
15347
|
-
}, sessions = createDevRuntimeSessionManager(closeBinding), compilationIdentities = createCompilationIdentityTracker(), { getCompilationIdentity } = compilationIdentities,
|
|
15399
|
+
}, sessions = createDevRuntimeSessionManager(closeBinding), compilationIdentities = createCompilationIdentityTracker(), { getCompilationIdentity } = compilationIdentities, hdrIntentsByPair = new WeakMap(), finishRuntimeAttempt = async (binding, pair, stats, changes, identity1)=>{
|
|
15348
15400
|
try {
|
|
15349
15401
|
let result = await binding.runtime.finishAttempt(stats, changes, identity1);
|
|
15350
15402
|
if (sessions.getActiveBinding()?.id !== binding.id) return;
|
|
15351
15403
|
if ('retry-node' === result) return void pair.node.watching?.invalidate();
|
|
15352
|
-
|
|
15404
|
+
let nodeCompilation = getEnvironmentStats(stats, 'node')?.compilation;
|
|
15405
|
+
'committed' === result && nodeCompilation && identity1.node === binding.runtime.getCommittedNodeIdentity() && hdrIntentsByPair.get(pair)?.signalCommitted(nodeCompilation, ()=>{
|
|
15406
|
+
onNodeRebuildCommitted?.();
|
|
15407
|
+
});
|
|
15353
15408
|
} catch (cause) {
|
|
15354
15409
|
sessions.getActiveBinding()?.id === binding.id && binding.runtime.failAttempt(normalizeEffectError(cause));
|
|
15355
15410
|
}
|
|
@@ -15436,6 +15491,8 @@ export default entryJsFiles;
|
|
|
15436
15491
|
node
|
|
15437
15492
|
});
|
|
15438
15493
|
binding.compilers = pair;
|
|
15494
|
+
let hdrIntents = createDevHdrIntentTracker();
|
|
15495
|
+
hdrIntentsByPair.set(pair, hdrIntents);
|
|
15439
15496
|
let sessionId = binding.id, runtime = binding.runtime, failCurrentAttempt = (side, error)=>{
|
|
15440
15497
|
sessions.getActiveBinding()?.id === sessionId && ('web' === side ? clearDevCompilerStart(pair, 'latestWebStart') : clearDevCompilerStart(pair, 'latestNodeStart'), runtime.failAttempt(error));
|
|
15441
15498
|
}, beginCompilerAttempt = (side)=>{
|
|
@@ -15457,10 +15514,12 @@ export default entryJsFiles;
|
|
|
15457
15514
|
identity: getCompilationIdentity(compilation)
|
|
15458
15515
|
}, reloadAfterCssAssetOwnershipRemoval && (reloadAfterCssAssetOwnershipRemoval = !1, scheduleCssAssetOwnershipReload()));
|
|
15459
15516
|
}), node.hooks.thisCompilation.tap(`${PLUGIN_NAME}:dev-node-web-compilation`, (compilation)=>{
|
|
15460
|
-
sessions.getActiveBinding()?.id
|
|
15517
|
+
if (sessions.getActiveBinding()?.id !== sessionId) return;
|
|
15518
|
+
let changes = snapshotDevChangedFiles(pair.node);
|
|
15519
|
+
hdrIntents.capture(compilation, changes.known && Array.from(changes.files).some((file)=>!file.includes('.react-router/hdr-revision.mjs') && !/\.css(?:\.[cm]?[jt]s)?$/.test(file))), pair.latestNodeStart = {
|
|
15461
15520
|
status: 'started',
|
|
15462
15521
|
identity: getCompilationIdentity(compilation)
|
|
15463
|
-
}, pair.currentAttemptIdentity && compilationIdentities.setAttemptIdentityForCompilation(compilation, pair.currentAttemptIdentity), pair.latestCompletedWebIdentity && compilationIdentities.setWebIdentityForNodeCompilation(compilation, pair.latestCompletedWebIdentity)
|
|
15522
|
+
}, pair.currentAttemptIdentity && compilationIdentities.setAttemptIdentityForCompilation(compilation, pair.currentAttemptIdentity), pair.latestCompletedWebIdentity && compilationIdentities.setWebIdentityForNodeCompilation(compilation, pair.latestCompletedWebIdentity);
|
|
15464
15523
|
});
|
|
15465
15524
|
let settleCompilation = (stats)=>{
|
|
15466
15525
|
sessions.getActiveBinding()?.id === sessionId && (pair.settledCompilations.add(stats.compilation), flushSettledAttempt(binding, pair));
|
|
@@ -16084,8 +16143,12 @@ export default entryJsFiles;
|
|
|
16084
16143
|
initialRouteTopology: routeTopology.initialRouteTopology,
|
|
16085
16144
|
onRouteTopologyChange: pluginOptions.onRouteTopologyChange
|
|
16086
16145
|
});
|
|
16087
|
-
api.onAfterEnvironmentCompile(({ stats, environment })=>{
|
|
16088
|
-
if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames)
|
|
16146
|
+
if (api.onAfterEnvironmentCompile(({ stats, environment })=>{
|
|
16147
|
+
if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames), isRscMode && stats)) {
|
|
16148
|
+
let unsupported = collectUnsupportedRscScriptAssets(stats.compilation);
|
|
16149
|
+
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\`.`);
|
|
16150
|
+
}
|
|
16151
|
+
if (pluginOptions.federation && ssr) {
|
|
16089
16152
|
let serverBuildDir = (0, external_pathe_namespaceObject.resolve)(buildDirectory, 'server'), clientBuildDir = (0, external_pathe_namespaceObject.resolve)(buildDirectory, 'client');
|
|
16090
16153
|
if ((0, external_node_fs_namespaceObject.existsSync)(serverBuildDir)) {
|
|
16091
16154
|
let ssrDir = (0, external_pathe_namespaceObject.resolve)(clientBuildDir, 'static');
|
|
@@ -16128,9 +16191,7 @@ export default entryJsFiles;
|
|
|
16128
16191
|
prerenderPaths: modePlan.prerenderPaths,
|
|
16129
16192
|
basename
|
|
16130
16193
|
})))), api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig })=>{
|
|
16131
|
-
let publicPath, webConfig = config.environments?.web,
|
|
16132
|
-
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.`);
|
|
16133
|
-
let vmodPlugin = (publicPath = resolveEffectiveAssetPrefix({
|
|
16194
|
+
let publicPath, webConfig = config.environments?.web, vmodPlugin = (publicPath = resolveEffectiveAssetPrefix({
|
|
16134
16195
|
dev: webConfig?.dev,
|
|
16135
16196
|
output: webConfig?.output,
|
|
16136
16197
|
isBuild
|
|
@@ -16257,7 +16318,20 @@ export default entryJsFiles;
|
|
|
16257
16318
|
federation: pluginOptions.federation,
|
|
16258
16319
|
resolvedServerOutput,
|
|
16259
16320
|
webOutput: modePlan.webOutput
|
|
16260
|
-
}), 'classic' === modePlan.kind
|
|
16321
|
+
}), pluginOptions.federation && 'classic' === modePlan.kind) {
|
|
16322
|
+
let browserEntryModules = new Set([
|
|
16323
|
+
finalEntryClientPath,
|
|
16324
|
+
...routeByFilePath.keys()
|
|
16325
|
+
]);
|
|
16326
|
+
api.transform({
|
|
16327
|
+
environments: [
|
|
16328
|
+
'web'
|
|
16329
|
+
],
|
|
16330
|
+
order: 'post',
|
|
16331
|
+
test: (resourcePath)=>browserEntryModules.has(resourcePath)
|
|
16332
|
+
}, ({ code })=>`${code}\nexport {};\nawait Promise.resolve();\n`);
|
|
16333
|
+
}
|
|
16334
|
+
'classic' === modePlan.kind && useRouteModuleTransformLoader && api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
|
|
16261
16335
|
tools: {
|
|
16262
16336
|
rspack: (rspackConfig)=>{
|
|
16263
16337
|
let environmentDevHmrEnabled = 'web' === name && !isBuild && void 0 !== devHmrRefreshRuntimePath && 'development' === config.mode && config.dev?.hmr !== !1 && isRspackSwcReactRefreshEnabled(rspackConfig);
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,8 @@ let getAvailableCpuCount = ()=>'function' == typeof availableParallelism ? avail
|
|
|
38
38
|
}, resolveEntryWithTemplate = ({ appDirectory, entryName, templateName, templatesDirectory })=>{
|
|
39
39
|
let userEntryPath = findEntryFile(external_pathe_resolve(appDirectory, entryName));
|
|
40
40
|
return existsSync(userEntryPath) ? userEntryPath : external_pathe_resolve(templatesDirectory, templateName);
|
|
41
|
+
}, getModuleFederationOptions = (plugin)=>{
|
|
42
|
+
if (plugin && 'object' == typeof plugin && ('ModuleFederationPlugin' === plugin.name || 'RspackModuleFederationPlugin' === plugin.name)) return plugin._options ?? plugin.options;
|
|
41
43
|
}, Function_dual = function(arity, body) {
|
|
42
44
|
if ("function" == typeof arity) return function() {
|
|
43
45
|
return arity(arguments) ? body.apply(this, arguments) : (self)=>body(self, ...arguments);
|
|
@@ -7356,7 +7358,10 @@ let createReactRouterManifestOptions = ({ routeChunks, routeModuleAnalysis })=>{
|
|
|
7356
7358
|
routeModuleAnalysis
|
|
7357
7359
|
} : {}
|
|
7358
7360
|
};
|
|
7359
|
-
}, isManifestJsAsset = (asset)=>/(?<!\.hot-update)\.[cm]?js(?:\?.*)?$/.test(asset), isManifestCssAsset = (asset)=>/\.css(?:\?.*)?$/.test(asset),
|
|
7361
|
+
}, isManifestJsAsset = (asset)=>/(?<!\.hot-update)\.[cm]?js(?:\?.*)?$/.test(asset), isManifestCssAsset = (asset)=>/\.css(?:\?.*)?$/.test(asset), hasSome = (iterable)=>{
|
|
7362
|
+
for (let _ of iterable)return !0;
|
|
7363
|
+
return !1;
|
|
7364
|
+
}, collectManifestFilesByName = (items, names, getFiles)=>{
|
|
7360
7365
|
let filesByName = {};
|
|
7361
7366
|
if (!names) {
|
|
7362
7367
|
for (let [name, item] of items)null != item && (filesByName[name] = getFiles(name, item));
|
|
@@ -9134,12 +9139,15 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
|
|
|
9134
9139
|
setWebIdentityForNodeCompilation (compilation, identity) {
|
|
9135
9140
|
webIdentityByNodeCompilation.set(compilation, identity);
|
|
9136
9141
|
}
|
|
9137
|
-
}), { getCompilationIdentity } = compilationIdentities,
|
|
9142
|
+
}), { getCompilationIdentity } = compilationIdentities, hdrIntentsByPair = new WeakMap(), finishRuntimeAttempt = async (binding, pair, stats, changes, identity)=>{
|
|
9138
9143
|
try {
|
|
9139
9144
|
let result = await binding.runtime.finishAttempt(stats, changes, identity);
|
|
9140
9145
|
if (sessions.getActiveBinding()?.id !== binding.id) return;
|
|
9141
9146
|
if ('retry-node' === result) return void pair.node.watching?.invalidate();
|
|
9142
|
-
|
|
9147
|
+
let nodeCompilation = getEnvironmentStats(stats, 'node')?.compilation;
|
|
9148
|
+
'committed' === result && nodeCompilation && identity.node === binding.runtime.getCommittedNodeIdentity() && hdrIntentsByPair.get(pair)?.signalCommitted(nodeCompilation, ()=>{
|
|
9149
|
+
onNodeRebuildCommitted?.();
|
|
9150
|
+
});
|
|
9143
9151
|
} catch (cause) {
|
|
9144
9152
|
sessions.getActiveBinding()?.id === binding.id && binding.runtime.failAttempt(normalizeEffectError(cause));
|
|
9145
9153
|
}
|
|
@@ -9329,6 +9337,7 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
|
|
|
9329
9337
|
return rejectAttempt(attemptId, normalizeEffectError(cause), !0), 'ignored';
|
|
9330
9338
|
}
|
|
9331
9339
|
},
|
|
9340
|
+
getCommittedNodeIdentity: ()=>'ready' === state.kind ? state.committed.nodeIdentity : void 0,
|
|
9332
9341
|
failAttempt (error) {
|
|
9333
9342
|
let attemptId = getCurrentAttemptId();
|
|
9334
9343
|
null !== attemptId && rejectAttempt(attemptId, error, !1);
|
|
@@ -9394,6 +9403,7 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
|
|
|
9394
9403
|
!(!binding || !pair || hasPendingCompilation(pair)) && (pair.pendingAttempt = void 0, pair.currentAttemptIdentity = Symbol(), binding.runtime.beginAttempt());
|
|
9395
9404
|
}
|
|
9396
9405
|
}), api.onAfterCreateCompiler(({ compiler })=>{
|
|
9406
|
+
let latestRelevantEditRevision, signaledRevision, revisionByCompilation;
|
|
9397
9407
|
if (!('compilers' in compiler)) return void rejectUnsupportedCompiler('Rsbuild did not create a multi-compiler');
|
|
9398
9408
|
let web = compiler.compilers.find((item)=>'web' === item.name), node = compiler.compilers.find((item)=>'node' === item.name);
|
|
9399
9409
|
if (!web || !node) return void rejectUnsupportedCompiler('the web or node compiler was missing');
|
|
@@ -9408,6 +9418,16 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
|
|
|
9408
9418
|
node
|
|
9409
9419
|
});
|
|
9410
9420
|
binding.compilers = pair;
|
|
9421
|
+
let hdrIntents = (latestRelevantEditRevision = 0, signaledRevision = 0, revisionByCompilation = new WeakMap(), {
|
|
9422
|
+
capture (compilation, hasRelevantChanges) {
|
|
9423
|
+
hasRelevantChanges && (latestRelevantEditRevision += 1), revisionByCompilation.set(compilation, latestRelevantEditRevision);
|
|
9424
|
+
},
|
|
9425
|
+
signalCommitted (compilation, signal) {
|
|
9426
|
+
let compilationRevision = revisionByCompilation.get(compilation);
|
|
9427
|
+
void 0 === compilationRevision || compilationRevision <= signaledRevision || (signal(), signaledRevision = Math.max(signaledRevision, compilationRevision));
|
|
9428
|
+
}
|
|
9429
|
+
});
|
|
9430
|
+
hdrIntentsByPair.set(pair, hdrIntents);
|
|
9411
9431
|
let sessionId = binding.id, runtime = binding.runtime, failCurrentAttempt = (side, error)=>{
|
|
9412
9432
|
sessions.getActiveBinding()?.id === sessionId && ('web' === side ? clearDevCompilerStart(pair, 'latestWebStart') : clearDevCompilerStart(pair, 'latestNodeStart'), runtime.failAttempt(error));
|
|
9413
9433
|
}, beginCompilerAttempt = (side)=>{
|
|
@@ -9434,10 +9454,12 @@ export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRoute
|
|
|
9434
9454
|
identity: getCompilationIdentity(compilation)
|
|
9435
9455
|
}, reloadAfterCssAssetOwnershipRemoval && (reloadAfterCssAssetOwnershipRemoval = !1, scheduleCssAssetOwnershipReload()));
|
|
9436
9456
|
}), node.hooks.thisCompilation.tap(`${PLUGIN_NAME}:dev-node-web-compilation`, (compilation)=>{
|
|
9437
|
-
sessions.getActiveBinding()?.id
|
|
9457
|
+
if (sessions.getActiveBinding()?.id !== sessionId) return;
|
|
9458
|
+
let changes = snapshotDevChangedFiles(pair.node);
|
|
9459
|
+
hdrIntents.capture(compilation, changes.known && Array.from(changes.files).some((file)=>!file.includes('.react-router/hdr-revision.mjs') && !/\.css(?:\.[cm]?[jt]s)?$/.test(file))), pair.latestNodeStart = {
|
|
9438
9460
|
status: 'started',
|
|
9439
9461
|
identity: getCompilationIdentity(compilation)
|
|
9440
|
-
}, pair.currentAttemptIdentity && compilationIdentities.setAttemptIdentityForCompilation(compilation, pair.currentAttemptIdentity), pair.latestCompletedWebIdentity && compilationIdentities.setWebIdentityForNodeCompilation(compilation, pair.latestCompletedWebIdentity)
|
|
9462
|
+
}, pair.currentAttemptIdentity && compilationIdentities.setAttemptIdentityForCompilation(compilation, pair.currentAttemptIdentity), pair.latestCompletedWebIdentity && compilationIdentities.setWebIdentityForNodeCompilation(compilation, pair.latestCompletedWebIdentity);
|
|
9441
9463
|
});
|
|
9442
9464
|
let settleCompilation = (stats)=>{
|
|
9443
9465
|
sessions.getActiveBinding()?.id === sessionId && (pair.settledCompilations.add(stats.compilation), flushSettledAttempt(binding, pair));
|
|
@@ -9623,11 +9645,19 @@ export default [`;
|
|
|
9623
9645
|
'virtual/react-router/unstable_rsc/manifest-prefix': `const manifest = __webpack_require__.rscM;
|
|
9624
9646
|
const serverPrefix = ${JSON.stringify(serverPublicPath)};
|
|
9625
9647
|
const appliedPrefix = manifest?.moduleLoading?.prefix;
|
|
9626
|
-
|
|
9648
|
+
// An empty applied prefix (web \`assetPrefix: ''\`) yields relative references
|
|
9649
|
+
// such as "static/js/index.js"; those are rebased too, while absolute and
|
|
9650
|
+
// protocol-relative URLs are left alone.
|
|
9651
|
+
const isAbsoluteUrl = url => /^(?:[a-z][a-z\\d+.-]*:|\\/\\/|\\/)/i.test(url);
|
|
9652
|
+
if (typeof appliedPrefix === "string" && appliedPrefix !== serverPrefix) {
|
|
9627
9653
|
const rewrite = url =>
|
|
9628
|
-
typeof url
|
|
9629
|
-
?
|
|
9630
|
-
: url
|
|
9654
|
+
typeof url !== "string"
|
|
9655
|
+
? url
|
|
9656
|
+
: appliedPrefix !== "" && url.startsWith(appliedPrefix)
|
|
9657
|
+
? serverPrefix + url.slice(appliedPrefix.length)
|
|
9658
|
+
: appliedPrefix === "" && !isAbsoluteUrl(url)
|
|
9659
|
+
? serverPrefix + url
|
|
9660
|
+
: url;
|
|
9631
9661
|
const rewriteAll = list => {
|
|
9632
9662
|
if (Array.isArray(list)) for (let i = 0; i < list.length; i++) list[i] = rewrite(list[i]);
|
|
9633
9663
|
};
|
|
@@ -10907,8 +10937,27 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
|
10907
10937
|
initialRouteTopology: routeTopology.initialRouteTopology,
|
|
10908
10938
|
onRouteTopologyChange: pluginOptions.onRouteTopologyChange
|
|
10909
10939
|
});
|
|
10910
|
-
api.onAfterEnvironmentCompile(({ stats, environment })=>{
|
|
10911
|
-
if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames)
|
|
10940
|
+
if (api.onAfterEnvironmentCompile(({ stats, environment })=>{
|
|
10941
|
+
if ('web' === environment.name && (clientStats = createReactRouterManifestStats(stats?.compilation, manifestChunkNames), isRscMode && stats)) {
|
|
10942
|
+
let unsupported = ((compilation)=>{
|
|
10943
|
+
let unsupported = new Set();
|
|
10944
|
+
for (let chunk of compilation.chunks){
|
|
10945
|
+
if (!(chunk.contentHash?.javascript !== void 0 || hasSome(compilation.chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript")))) continue;
|
|
10946
|
+
let pathData = {
|
|
10947
|
+
chunk,
|
|
10948
|
+
contentHashType: "javascript"
|
|
10949
|
+
}, template = chunk.canBeInitial() ? compilation.outputOptions.filename : compilation.outputOptions.chunkFilename, resolvedTemplate = 'function' == typeof template ? template(pathData) : template;
|
|
10950
|
+
if ('string' != typeof resolvedTemplate) continue;
|
|
10951
|
+
let file = compilation.getPath(resolvedTemplate, pathData);
|
|
10952
|
+
file.endsWith('.js') || unsupported.add(file);
|
|
10953
|
+
}
|
|
10954
|
+
return [
|
|
10955
|
+
...unsupported
|
|
10956
|
+
];
|
|
10957
|
+
})(stats.compilation);
|
|
10958
|
+
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\`.`);
|
|
10959
|
+
}
|
|
10960
|
+
if (pluginOptions.federation && ssr) {
|
|
10912
10961
|
let serverBuildDir = external_pathe_resolve(buildDirectory, 'server'), clientBuildDir = external_pathe_resolve(buildDirectory, 'client');
|
|
10913
10962
|
if (existsSync(serverBuildDir)) {
|
|
10914
10963
|
let ssrDir = external_pathe_resolve(clientBuildDir, 'static');
|
|
@@ -10952,9 +11001,7 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
|
10952
11001
|
basename
|
|
10953
11002
|
})))), api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig })=>{
|
|
10954
11003
|
var existing;
|
|
10955
|
-
let publicPath, webConfig = config.environments?.web,
|
|
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({
|
|
11004
|
+
let publicPath, webConfig = config.environments?.web, vmodPlugin = (publicPath = resolveEffectiveAssetPrefix({
|
|
10958
11005
|
dev: webConfig?.dev,
|
|
10959
11006
|
output: webConfig?.output,
|
|
10960
11007
|
isBuild
|
|
@@ -11140,16 +11187,31 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
|
11140
11187
|
}) : rspackConfig), api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
|
|
11141
11188
|
tools: {
|
|
11142
11189
|
rspack: (rspackConfig)=>{
|
|
11143
|
-
if (federation
|
|
11190
|
+
if (federation) {
|
|
11144
11191
|
if (rspackConfig?.plugins?.length) for (let plugin of rspackConfig.plugins){
|
|
11145
|
-
|
|
11146
|
-
let pluginOptions = plugin._options ?? plugin.options;
|
|
11192
|
+
let pluginOptions = getModuleFederationOptions(plugin);
|
|
11147
11193
|
pluginOptions && (pluginOptions.experiments = {
|
|
11148
11194
|
...pluginOptions.experiments,
|
|
11149
11195
|
asyncStartup: !0
|
|
11150
11196
|
});
|
|
11151
11197
|
}
|
|
11152
|
-
|
|
11198
|
+
if ('web' === name) ((rspackConfig)=>{
|
|
11199
|
+
let containers = new Set((rspackConfig?.plugins ?? []).map(getModuleFederationOptions).map((options)=>options?.name).filter((name)=>'string' == typeof name));
|
|
11200
|
+
if (!rspackConfig || 0 === containers.size) return;
|
|
11201
|
+
let current = rspackConfig.optimization?.runtimeChunk, appRuntimeName = 'object' == typeof current && 'string' == typeof current?.name ? current.name : 'runtime';
|
|
11202
|
+
rspackConfig.optimization = {
|
|
11203
|
+
...rspackConfig.optimization,
|
|
11204
|
+
runtimeChunk: {
|
|
11205
|
+
name: (entrypoint)=>containers.has(entrypoint.name) ? `runtime-${entrypoint.name}` : appRuntimeName
|
|
11206
|
+
}
|
|
11207
|
+
};
|
|
11208
|
+
})(rspackConfig);
|
|
11209
|
+
else {
|
|
11210
|
+
let splitChunks = rspackConfig?.optimization?.splitChunks;
|
|
11211
|
+
if (splitChunks) for (let group of (splitChunks.chunks = 'async', Object.values(splitChunks.cacheGroups ?? {})))group && 'object' == typeof group && 'chunks' in group && (group.chunks = 'async');
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
if ('node' === name) {
|
|
11153
11215
|
let output = rspackConfig.output;
|
|
11154
11216
|
if (output) {
|
|
11155
11217
|
let library = output.library, libraryOptions = library && 'object' == typeof library && !Array.isArray(library) ? library : {};
|
|
@@ -11171,7 +11233,20 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
|
11171
11233
|
federation: pluginOptions.federation,
|
|
11172
11234
|
resolvedServerOutput,
|
|
11173
11235
|
webOutput: modePlan.webOutput
|
|
11174
|
-
}), 'classic' === modePlan.kind
|
|
11236
|
+
}), pluginOptions.federation && 'classic' === modePlan.kind) {
|
|
11237
|
+
let browserEntryModules = new Set([
|
|
11238
|
+
finalEntryClientPath,
|
|
11239
|
+
...routeByFilePath.keys()
|
|
11240
|
+
]);
|
|
11241
|
+
api.transform({
|
|
11242
|
+
environments: [
|
|
11243
|
+
'web'
|
|
11244
|
+
],
|
|
11245
|
+
order: 'post',
|
|
11246
|
+
test: (resourcePath)=>browserEntryModules.has(resourcePath)
|
|
11247
|
+
}, ({ code })=>`${code}\nexport {};\nawait Promise.resolve();\n`);
|
|
11248
|
+
}
|
|
11249
|
+
'classic' === modePlan.kind && useRouteModuleTransformLoader && api.modifyEnvironmentConfig(async (config, { name, mergeEnvironmentConfig })=>'web' !== name && 'node' !== name ? config : mergeEnvironmentConfig(config, {
|
|
11175
11250
|
tools: {
|
|
11176
11251
|
rspack: (rspackConfig)=>{
|
|
11177
11252
|
let environmentDevHmrEnabled = 'web' === name && !isBuild && void 0 !== devHmrRefreshRuntimePath && 'development' === config.mode && config.dev?.hmr !== !1 && isRspackSwcReactRefreshEnabled(rspackConfig);
|
package/dist/manifest.d.ts
CHANGED
|
@@ -66,6 +66,35 @@ type ReactRouterManifestStatsCompilation = {
|
|
|
66
66
|
};
|
|
67
67
|
export declare const isManifestJsAsset: (asset: string) => boolean;
|
|
68
68
|
export declare const isManifestCssAsset: (asset: string) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The minimal compilation surface for `collectUnsupportedRscScriptAssets`.
|
|
71
|
+
* Chunks are classified as JavaScript-emitting from compilation metadata (their
|
|
72
|
+
* `javascript` content hash / modules), never from a filename.
|
|
73
|
+
*/
|
|
74
|
+
export type RscScriptAssetCompilation = {
|
|
75
|
+
chunks: Iterable<RscScriptAssetChunk>;
|
|
76
|
+
chunkGraph: {
|
|
77
|
+
getChunkModulesIterableBySourceType(chunk: RscScriptAssetChunk, sourceType: string): Iterable<unknown>;
|
|
78
|
+
};
|
|
79
|
+
outputOptions: {
|
|
80
|
+
filename?: unknown;
|
|
81
|
+
chunkFilename?: unknown;
|
|
82
|
+
};
|
|
83
|
+
getPath(filename: string, data: Record<string, unknown>): string;
|
|
84
|
+
};
|
|
85
|
+
export type RscScriptAssetChunk = {
|
|
86
|
+
contentHash?: Record<string, string>;
|
|
87
|
+
canBeInitial(): boolean;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Browser JavaScript assets rspack's RSC manifest would drop: it only records
|
|
91
|
+
* chunk files whose emitted name ends in ".js", so `.mjs` names, query-hash
|
|
92
|
+
* names (`[name].js?v=...`), or any other extension vanish from
|
|
93
|
+
* `entryJsFiles` and the client manifest. The emitted script name is derived
|
|
94
|
+
* from the chunk's own filename template (entry or async) the same way rspack
|
|
95
|
+
* emits it, so function templates and `tools.rspack` overrides are covered.
|
|
96
|
+
*/
|
|
97
|
+
export declare const collectUnsupportedRscScriptAssets: (compilation: RscScriptAssetCompilation) => string[];
|
|
69
98
|
export declare const createReactRouterManifestStats: (compilation: ReactRouterManifestStatsCompilation | undefined, chunkNames?: ReadonlySet<string>) => ReactRouterManifestStats | undefined;
|
|
70
99
|
export type RouteManifestModuleExports = Record<string, readonly string[]>;
|
|
71
100
|
export type ReactRouterManifestGenerationResult = {
|
package/package.json
CHANGED
package/src/dev-generation.ts
CHANGED
|
@@ -61,6 +61,8 @@ export type ReactRouterDevRuntime = {
|
|
|
61
61
|
changes: DevGraphChanges,
|
|
62
62
|
identity: DevGraphIdentity
|
|
63
63
|
) => Promise<'committed' | 'ignored' | 'retry-node'>;
|
|
64
|
+
/** Node identity actually retained by the last successful generation. */
|
|
65
|
+
getCommittedNodeIdentity: () => DevCompilationIdentity | undefined;
|
|
64
66
|
failAttempt: (error: Error) => void;
|
|
65
67
|
load: (entryName?: string) => Promise<ServerBuild>;
|
|
66
68
|
close: (error?: Error) => void;
|
|
@@ -615,6 +617,10 @@ export const createReactRouterDevRuntime = ({
|
|
|
615
617
|
}
|
|
616
618
|
},
|
|
617
619
|
|
|
620
|
+
getCommittedNodeIdentity() {
|
|
621
|
+
return state.kind === 'ready' ? state.committed.nodeIdentity : undefined;
|
|
622
|
+
},
|
|
623
|
+
|
|
618
624
|
failAttempt(error): void {
|
|
619
625
|
const attemptId = getCurrentAttemptId();
|
|
620
626
|
if (attemptId !== null) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Tracks unsignaled node edits across compiler retries within one dev session. */
|
|
2
|
+
export const createDevHdrIntentTracker = () => {
|
|
3
|
+
let latestRelevantEditRevision = 0;
|
|
4
|
+
let signaledRevision = 0;
|
|
5
|
+
const revisionByCompilation = new WeakMap<object, number>();
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
capture(compilation: object, hasRelevantChanges: boolean): void {
|
|
9
|
+
if (hasRelevantChanges) {
|
|
10
|
+
latestRelevantEditRevision += 1;
|
|
11
|
+
}
|
|
12
|
+
revisionByCompilation.set(compilation, latestRelevantEditRevision);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
signalCommitted(compilation: object, signal: () => void): void {
|
|
16
|
+
const compilationRevision = revisionByCompilation.get(compilation);
|
|
17
|
+
if (
|
|
18
|
+
compilationRevision === undefined ||
|
|
19
|
+
compilationRevision <= signaledRevision
|
|
20
|
+
) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
signal();
|
|
24
|
+
signaledRevision = Math.max(signaledRevision, compilationRevision);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
registerReactRouterDevRuntime,
|
|
20
20
|
unregisterReactRouterDevRuntime,
|
|
21
21
|
} from './dev-generation.js';
|
|
22
|
+
import { createDevHdrIntentTracker } from './dev-hdr-intent.js';
|
|
22
23
|
import { DEV_MANIFEST_UPDATE_EVENT } from './dev-hmr.js';
|
|
23
24
|
import {
|
|
24
25
|
getEnvironmentStats,
|
|
@@ -140,12 +141,10 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
140
141
|
const compilationIdentities = createCompilationIdentityTracker();
|
|
141
142
|
const { getCompilationIdentity } = compilationIdentities;
|
|
142
143
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
// once per node compilation identity or the bump loop self-sustains.
|
|
146
|
-
const hdrSignaledNodeIdentity = new WeakMap<
|
|
144
|
+
// Pending node-edit intent until a coherent commit retains that compilation.
|
|
145
|
+
const hdrIntentsByPair = new WeakMap<
|
|
147
146
|
DevCompilerPair,
|
|
148
|
-
|
|
147
|
+
ReturnType<typeof createDevHdrIntentTracker>
|
|
149
148
|
>();
|
|
150
149
|
|
|
151
150
|
const finishRuntimeAttempt = async (
|
|
@@ -168,17 +167,15 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
168
167
|
pair.node.watching?.invalidate();
|
|
169
168
|
return;
|
|
170
169
|
}
|
|
170
|
+
const nodeCompilation = getEnvironmentStats(stats, 'node')?.compilation;
|
|
171
171
|
if (
|
|
172
172
|
result === 'committed' &&
|
|
173
|
-
|
|
174
|
-
identity.node
|
|
175
|
-
hdrSignaledNodeIdentity.get(pair) !== identity.node &&
|
|
176
|
-
Array.from(changes.node.files).some(
|
|
177
|
-
file => !isHdrRevisionFile(file) && !isCssSourceFile(file)
|
|
178
|
-
)
|
|
173
|
+
nodeCompilation &&
|
|
174
|
+
identity.node === binding.runtime.getCommittedNodeIdentity()
|
|
179
175
|
) {
|
|
180
|
-
|
|
181
|
-
|
|
176
|
+
hdrIntentsByPair.get(pair)?.signalCommitted(nodeCompilation, () => {
|
|
177
|
+
onNodeRebuildCommitted?.();
|
|
178
|
+
});
|
|
182
179
|
}
|
|
183
180
|
} catch (cause) {
|
|
184
181
|
if (sessions.getActiveBinding()?.id === binding.id) {
|
|
@@ -344,6 +341,8 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
344
341
|
}
|
|
345
342
|
const pair: DevCompilerPair = createDevCompilerPair({ web, node });
|
|
346
343
|
binding.compilers = pair;
|
|
344
|
+
const hdrIntents = createDevHdrIntentTracker();
|
|
345
|
+
hdrIntentsByPair.set(pair, hdrIntents);
|
|
347
346
|
const sessionId = binding.id;
|
|
348
347
|
const runtime = binding.runtime;
|
|
349
348
|
const failCurrentAttempt = (side: 'web' | 'node', error: Error): void => {
|
|
@@ -427,6 +426,14 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
427
426
|
if (sessions.getActiveBinding()?.id !== sessionId) {
|
|
428
427
|
return;
|
|
429
428
|
}
|
|
429
|
+
const changes = snapshotDevChangedFiles(pair.node);
|
|
430
|
+
hdrIntents.capture(
|
|
431
|
+
compilation,
|
|
432
|
+
changes.known &&
|
|
433
|
+
Array.from(changes.files).some(
|
|
434
|
+
file => !isHdrRevisionFile(file) && !isCssSourceFile(file)
|
|
435
|
+
)
|
|
436
|
+
);
|
|
430
437
|
pair.latestNodeStart = {
|
|
431
438
|
status: 'started',
|
|
432
439
|
identity: getCompilationIdentity(compilation),
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
enforceAsyncOnlyServerSplitChunks,
|
|
4
|
+
ensureFederationAsyncStartup,
|
|
5
|
+
isolateFederationContainerRuntime,
|
|
6
|
+
} from './federation.js';
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Rspack `output` policy for the web and node environments, in two tiers:
|
|
@@ -70,6 +74,11 @@ export const registerReactRouterEnvironmentOutput = ({
|
|
|
70
74
|
rspack: rspackConfig => {
|
|
71
75
|
if (federation) {
|
|
72
76
|
ensureFederationAsyncStartup(rspackConfig);
|
|
77
|
+
if (name === 'web') {
|
|
78
|
+
isolateFederationContainerRuntime(rspackConfig);
|
|
79
|
+
} else {
|
|
80
|
+
enforceAsyncOnlyServerSplitChunks(rspackConfig);
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
if (name === 'node') {
|
package/src/federation.ts
CHANGED
|
@@ -1,9 +1,73 @@
|
|
|
1
1
|
import type { Rspack } from '@rsbuild/core';
|
|
2
2
|
|
|
3
|
+
type ModuleFederationPluginOptionsLike = {
|
|
4
|
+
name?: string;
|
|
5
|
+
experiments?: { asyncStartup?: boolean };
|
|
6
|
+
};
|
|
7
|
+
|
|
3
8
|
type ModuleFederationPluginLike = {
|
|
4
9
|
name?: string;
|
|
5
|
-
_options?:
|
|
6
|
-
options?:
|
|
10
|
+
_options?: ModuleFederationPluginOptionsLike;
|
|
11
|
+
options?: ModuleFederationPluginOptionsLike;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const getModuleFederationOptions = (
|
|
15
|
+
plugin: unknown
|
|
16
|
+
): ModuleFederationPluginOptionsLike | undefined => {
|
|
17
|
+
if (!plugin || typeof plugin !== 'object') {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const federationPlugin = plugin as ModuleFederationPluginLike;
|
|
21
|
+
if (
|
|
22
|
+
federationPlugin.name !== 'ModuleFederationPlugin' &&
|
|
23
|
+
federationPlugin.name !== 'RspackModuleFederationPlugin'
|
|
24
|
+
) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return federationPlugin._options ?? federationPlugin.options;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The Module Federation container name(s) configured on this compiler, i.e.
|
|
32
|
+
* the entry names of the remote containers it emits.
|
|
33
|
+
*/
|
|
34
|
+
export const getFederationContainerNames = (
|
|
35
|
+
rspackConfig: Rspack.Configuration | undefined
|
|
36
|
+
): string[] =>
|
|
37
|
+
(rspackConfig?.plugins ?? [])
|
|
38
|
+
.map(getModuleFederationOptions)
|
|
39
|
+
.map(options => options?.name)
|
|
40
|
+
.filter((name): name is string => typeof name === 'string');
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Classic mode shares one runtime chunk across every browser entry so route
|
|
44
|
+
* module entries share a module registry. A federation container must not
|
|
45
|
+
* share it: importing the container would run the app entries' async startup
|
|
46
|
+
* (share-scope consumes) before the host has initialized the share scope,
|
|
47
|
+
* yielding duplicate singletons (a second React). Give containers their own
|
|
48
|
+
* runtime chunk.
|
|
49
|
+
*/
|
|
50
|
+
export const isolateFederationContainerRuntime = (
|
|
51
|
+
rspackConfig: Rspack.Configuration | undefined
|
|
52
|
+
): void => {
|
|
53
|
+
const containers = new Set(getFederationContainerNames(rspackConfig));
|
|
54
|
+
if (!rspackConfig || containers.size === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const current = rspackConfig.optimization?.runtimeChunk;
|
|
58
|
+
const appRuntimeName =
|
|
59
|
+
typeof current === 'object' && typeof current?.name === 'string'
|
|
60
|
+
? current.name
|
|
61
|
+
: 'runtime';
|
|
62
|
+
rspackConfig.optimization = {
|
|
63
|
+
...rspackConfig.optimization,
|
|
64
|
+
runtimeChunk: {
|
|
65
|
+
name: (entrypoint: { name: string }) =>
|
|
66
|
+
containers.has(entrypoint.name)
|
|
67
|
+
? `runtime-${entrypoint.name}`
|
|
68
|
+
: appRuntimeName,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
7
71
|
};
|
|
8
72
|
|
|
9
73
|
export const ensureFederationAsyncStartup = (
|
|
@@ -14,18 +78,7 @@ export const ensureFederationAsyncStartup = (
|
|
|
14
78
|
}
|
|
15
79
|
|
|
16
80
|
for (const plugin of rspackConfig.plugins) {
|
|
17
|
-
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
const federationPlugin = plugin as ModuleFederationPluginLike;
|
|
21
|
-
if (
|
|
22
|
-
federationPlugin.name !== 'ModuleFederationPlugin' &&
|
|
23
|
-
federationPlugin.name !== 'RspackModuleFederationPlugin'
|
|
24
|
-
) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const pluginOptions = federationPlugin._options ?? federationPlugin.options;
|
|
81
|
+
const pluginOptions = getModuleFederationOptions(plugin);
|
|
29
82
|
if (!pluginOptions) {
|
|
30
83
|
continue;
|
|
31
84
|
}
|
|
@@ -36,3 +89,29 @@ export const ensureFederationAsyncStartup = (
|
|
|
36
89
|
};
|
|
37
90
|
}
|
|
38
91
|
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `@module-federation/node` replaces Rspack's `readFileVm` chunk loader with one
|
|
95
|
+
* that tracks loaded chunks privately, so initial chunks split off a
|
|
96
|
+
* multi-entry server build never satisfy Rspack's startup gate
|
|
97
|
+
* (`__webpack_require__.O`) and the async startup resolves to `undefined`
|
|
98
|
+
* instead of the server build's exports. Keep server code splitting to async
|
|
99
|
+
* chunks only. Runs at the final `tools.rspack` boundary so a user
|
|
100
|
+
* `optimization.splitChunks` override or a preset whose cache group selects
|
|
101
|
+
* `chunks: 'all'` (e.g. Rsbuild's `single-vendor`, `enforce: true`) cannot
|
|
102
|
+
* reintroduce initial chunk dependencies. A disabled `splitChunks` is kept.
|
|
103
|
+
*/
|
|
104
|
+
export const enforceAsyncOnlyServerSplitChunks = (
|
|
105
|
+
rspackConfig: Rspack.Configuration | undefined
|
|
106
|
+
): void => {
|
|
107
|
+
const splitChunks = rspackConfig?.optimization?.splitChunks;
|
|
108
|
+
if (!splitChunks) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
splitChunks.chunks = 'async';
|
|
112
|
+
for (const group of Object.values(splitChunks.cacheGroups ?? {})) {
|
|
113
|
+
if (group && typeof group === 'object' && 'chunks' in group) {
|
|
114
|
+
group.chunks = 'async';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type ResolvedReactRouterConfig,
|
|
27
27
|
} from './react-router-config.js';
|
|
28
28
|
import {
|
|
29
|
+
collectUnsupportedRscScriptAssets,
|
|
29
30
|
configRoutesToRouteManifest,
|
|
30
31
|
createReactRouterManifestStats,
|
|
31
32
|
type ReactRouterManifestForDev as ReactRouterManifest,
|
|
@@ -773,6 +774,27 @@ export const pluginReactRouter = (
|
|
|
773
774
|
stats?.compilation,
|
|
774
775
|
manifestChunkNames
|
|
775
776
|
);
|
|
777
|
+
if (isRscMode && stats) {
|
|
778
|
+
// Rspack's RSC manifest only records browser scripts whose emitted
|
|
779
|
+
// name ends in ".js" (entry files and client-reference chunks
|
|
780
|
+
// alike); anything else silently disappears from `entryJsFiles` and
|
|
781
|
+
// the client manifest, and the server cannot bootstrap or preload
|
|
782
|
+
// it. Check the emitted output, which is what the manifest saw, so
|
|
783
|
+
// function filenames and `tools.rspack` overrides are covered too.
|
|
784
|
+
const unsupported = collectUnsupportedRscScriptAssets(
|
|
785
|
+
stats.compilation
|
|
786
|
+
);
|
|
787
|
+
if (unsupported.length > 0) {
|
|
788
|
+
throw new Error(
|
|
789
|
+
`[${PLUGIN_NAME}] RSC mode requires every browser JavaScript asset to be named "*.js" (no query, no other extension): rspack's RSC manifest omits ${unsupported
|
|
790
|
+
.slice(0, 5)
|
|
791
|
+
.map(asset => JSON.stringify(asset))
|
|
792
|
+
.join(
|
|
793
|
+
', '
|
|
794
|
+
)}${unsupported.length > 5 ? ` and ${unsupported.length - 5} more` : ''}. Adjust web \`output.filename.js\` / \`chunkFilename\`.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
776
798
|
}
|
|
777
799
|
if (pluginOptions.federation && ssr) {
|
|
778
800
|
const serverBuildDir = resolve(buildDirectory, 'server');
|
|
@@ -848,22 +870,6 @@ export const pluginReactRouter = (
|
|
|
848
870
|
|
|
849
871
|
api.modifyRsbuildConfig(async (config, { mergeRsbuildConfig }) => {
|
|
850
872
|
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
|
-
);
|
|
866
|
-
}
|
|
867
873
|
const assetPrefix = resolveEffectiveAssetPrefix(
|
|
868
874
|
{ dev: webConfig?.dev, output: webConfig?.output, isBuild },
|
|
869
875
|
{ dev: config.dev, output: config.output }
|
|
@@ -1044,6 +1050,31 @@ export const pluginReactRouter = (
|
|
|
1044
1050
|
webOutput: modePlan.webOutput,
|
|
1045
1051
|
});
|
|
1046
1052
|
|
|
1053
|
+
if (pluginOptions.federation && modePlan.kind === 'classic') {
|
|
1054
|
+
// Module Federation's async startup makes every entry's startup a
|
|
1055
|
+
// promise. React Router imports each browser route-module entry
|
|
1056
|
+
// synchronously (`import * as route0 from ".../root.js"`) and reads its
|
|
1057
|
+
// exports right away, and `import()`s split route chunks the same way.
|
|
1058
|
+
// Making those entry modules async (top-level await) turns Rspack's
|
|
1059
|
+
// module-library export into `(await startup).default`, so importers
|
|
1060
|
+
// wait for the awaited startup instead of reading a snapshot of the
|
|
1061
|
+
// promise (#132). Runs after SWC so it applies to the final module code.
|
|
1062
|
+
const browserEntryModules = new Set([
|
|
1063
|
+
finalEntryClientPath,
|
|
1064
|
+
...routeByFilePath.keys(),
|
|
1065
|
+
]);
|
|
1066
|
+
api.transform(
|
|
1067
|
+
{
|
|
1068
|
+
environments: ['web'],
|
|
1069
|
+
order: 'post',
|
|
1070
|
+
test: (resourcePath: string) => browserEntryModules.has(resourcePath),
|
|
1071
|
+
},
|
|
1072
|
+
// `export {}` keeps an otherwise-empty client module (a route with only
|
|
1073
|
+
// server exports) parsed as ESM, which top-level await requires.
|
|
1074
|
+
({ code }) => `${code}\nexport {};\nawait Promise.resolve();\n`
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1047
1078
|
if (modePlan.kind === 'classic' && useRouteModuleTransformLoader) {
|
|
1048
1079
|
api.modifyEnvironmentConfig(
|
|
1049
1080
|
async (config, { name, mergeEnvironmentConfig }) => {
|
package/src/manifest.ts
CHANGED
|
@@ -179,6 +179,79 @@ export const isManifestJsAsset = (asset: string): boolean =>
|
|
|
179
179
|
export const isManifestCssAsset = (asset: string): boolean =>
|
|
180
180
|
/\.css(?:\?.*)?$/.test(asset);
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* The minimal compilation surface for `collectUnsupportedRscScriptAssets`.
|
|
184
|
+
* Chunks are classified as JavaScript-emitting from compilation metadata (their
|
|
185
|
+
* `javascript` content hash / modules), never from a filename.
|
|
186
|
+
*/
|
|
187
|
+
export type RscScriptAssetCompilation = {
|
|
188
|
+
chunks: Iterable<RscScriptAssetChunk>;
|
|
189
|
+
chunkGraph: {
|
|
190
|
+
getChunkModulesIterableBySourceType(
|
|
191
|
+
chunk: RscScriptAssetChunk,
|
|
192
|
+
sourceType: string
|
|
193
|
+
): Iterable<unknown>;
|
|
194
|
+
};
|
|
195
|
+
outputOptions: {
|
|
196
|
+
filename?: unknown;
|
|
197
|
+
chunkFilename?: unknown;
|
|
198
|
+
};
|
|
199
|
+
getPath(filename: string, data: Record<string, unknown>): string;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type RscScriptAssetChunk = {
|
|
203
|
+
contentHash?: Record<string, string>;
|
|
204
|
+
canBeInitial(): boolean;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const hasSome = (iterable: Iterable<unknown>): boolean => {
|
|
208
|
+
for (const _ of iterable) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Browser JavaScript assets rspack's RSC manifest would drop: it only records
|
|
216
|
+
* chunk files whose emitted name ends in ".js", so `.mjs` names, query-hash
|
|
217
|
+
* names (`[name].js?v=...`), or any other extension vanish from
|
|
218
|
+
* `entryJsFiles` and the client manifest. The emitted script name is derived
|
|
219
|
+
* from the chunk's own filename template (entry or async) the same way rspack
|
|
220
|
+
* emits it, so function templates and `tools.rspack` overrides are covered.
|
|
221
|
+
*/
|
|
222
|
+
export const collectUnsupportedRscScriptAssets = (
|
|
223
|
+
compilation: RscScriptAssetCompilation
|
|
224
|
+
): string[] => {
|
|
225
|
+
const unsupported = new Set<string>();
|
|
226
|
+
for (const chunk of compilation.chunks) {
|
|
227
|
+
const emitsJavaScript =
|
|
228
|
+
chunk.contentHash?.javascript !== undefined ||
|
|
229
|
+
hasSome(
|
|
230
|
+
compilation.chunkGraph.getChunkModulesIterableBySourceType(
|
|
231
|
+
chunk,
|
|
232
|
+
'javascript'
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
if (!emitsJavaScript) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const pathData = { chunk, contentHashType: 'javascript' };
|
|
239
|
+
const template = chunk.canBeInitial()
|
|
240
|
+
? compilation.outputOptions.filename
|
|
241
|
+
: compilation.outputOptions.chunkFilename;
|
|
242
|
+
const resolvedTemplate =
|
|
243
|
+
typeof template === 'function' ? template(pathData) : template;
|
|
244
|
+
if (typeof resolvedTemplate !== 'string') {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const file = compilation.getPath(resolvedTemplate, pathData);
|
|
248
|
+
if (!file.endsWith('.js')) {
|
|
249
|
+
unsupported.add(file);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return [...unsupported];
|
|
253
|
+
};
|
|
254
|
+
|
|
182
255
|
const collectManifestFilesByName = <T>(
|
|
183
256
|
items: ReactRouterManifestStatsLookup<T>,
|
|
184
257
|
names: ReadonlySet<string> | undefined,
|
|
@@ -127,11 +127,19 @@ export const createReactRouterRscVirtualModules = ({
|
|
|
127
127
|
'virtual/react-router/unstable_rsc/manifest-prefix': `const manifest = __webpack_require__.rscM;
|
|
128
128
|
const serverPrefix = ${JSON.stringify(serverPublicPath)};
|
|
129
129
|
const appliedPrefix = manifest?.moduleLoading?.prefix;
|
|
130
|
-
|
|
130
|
+
// An empty applied prefix (web \`assetPrefix: ''\`) yields relative references
|
|
131
|
+
// such as "static/js/index.js"; those are rebased too, while absolute and
|
|
132
|
+
// protocol-relative URLs are left alone.
|
|
133
|
+
const isAbsoluteUrl = url => /^(?:[a-z][a-z\\d+.-]*:|\\/\\/|\\/)/i.test(url);
|
|
134
|
+
if (typeof appliedPrefix === "string" && appliedPrefix !== serverPrefix) {
|
|
131
135
|
const rewrite = url =>
|
|
132
|
-
typeof url
|
|
133
|
-
?
|
|
134
|
-
: url
|
|
136
|
+
typeof url !== "string"
|
|
137
|
+
? url
|
|
138
|
+
: appliedPrefix !== "" && url.startsWith(appliedPrefix)
|
|
139
|
+
? serverPrefix + url.slice(appliedPrefix.length)
|
|
140
|
+
: appliedPrefix === "" && !isAbsoluteUrl(url)
|
|
141
|
+
? serverPrefix + url
|
|
142
|
+
: url;
|
|
135
143
|
const rewriteAll = list => {
|
|
136
144
|
if (Array.isArray(list)) for (let i = 0; i < list.length; i++) list[i] = rewrite(list[i]);
|
|
137
145
|
};
|