rsbuild-plugin-react-router 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/468.js +48 -0
- package/dist/511.js +60 -86
- package/dist/819.js +64 -0
- package/dist/build-output-transforms.d.ts +3 -1
- package/dist/constants.d.ts +1 -0
- package/dist/dev-hdr-channel.d.ts +9 -0
- package/dist/dev-hmr.d.ts +1 -22
- package/dist/dev-runtime-controller.d.ts +1 -6
- package/dist/dev-server.d.ts +3 -1
- package/dist/index.cjs +5417 -5080
- package/dist/index.js +1701 -1441
- package/dist/manifest-assets.d.ts +34 -0
- package/dist/manifest-snapshot.d.ts +17 -0
- package/dist/manifest-state.d.ts +12 -0
- package/dist/manifest.d.ts +2 -22
- package/dist/modify-browser-manifest.d.ts +2 -0
- package/dist/node-only-manifest.d.ts +8 -0
- package/dist/plugin-utils.d.ts +1 -1
- package/dist/rsc-prerender.d.ts +3 -2
- package/dist/server-build-worker-client.d.ts +20 -0
- package/dist/server-build-worker-protocol.d.ts +84 -0
- package/dist/server-build-worker.d.ts +1 -0
- package/dist/server-build-worker.js +123 -0
- package/dist/server-utils.d.ts +1 -2
- package/dist/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/build-output-transforms.ts +22 -1
- package/src/classic-mode.ts +0 -1
- package/src/constants.ts +3 -0
- package/src/dev-hdr-channel.ts +38 -0
- package/src/dev-hmr.ts +57 -100
- package/src/dev-runtime-controller.ts +23 -18
- package/src/dev-server.ts +24 -4
- package/src/index.ts +229 -144
- package/src/lazy-compilation.ts +7 -2
- package/src/manifest-assets.ts +228 -0
- package/src/manifest-snapshot.ts +80 -0
- package/src/manifest-state.ts +71 -0
- package/src/manifest.ts +23 -161
- package/src/mode-plan.ts +12 -4
- package/src/modify-browser-manifest.ts +47 -18
- package/src/node-only-manifest.ts +52 -0
- package/src/plugin-utils.ts +6 -2
- package/src/prerender-build.ts +76 -86
- package/src/route-chunks.ts +152 -63
- package/src/rsc-prerender.ts +7 -35
- package/src/server-build-resolution.ts +1 -2
- package/src/server-build-worker-client.ts +221 -0
- package/src/server-build-worker-protocol.ts +69 -0
- package/src/server-build-worker.ts +192 -0
- package/src/server-utils.ts +0 -2
- package/src/types.ts +7 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { DEFAULT_JS_DIST_PATH } from './constants.js';
|
|
2
|
+
|
|
3
|
+
export type ReactRouterManifestStats = {
|
|
4
|
+
assetsByChunkName?: Record<string, string[]>;
|
|
5
|
+
entrypointFilesByName?: Record<string, string[]>;
|
|
6
|
+
assetTypesByName?: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ManifestStatsChunk = {
|
|
10
|
+
files?: Iterable<string>;
|
|
11
|
+
};
|
|
12
|
+
type ManifestStatsEntrypoint = {
|
|
13
|
+
getFiles?: () => Iterable<string>;
|
|
14
|
+
};
|
|
15
|
+
type ManifestStatsLookup<T> = Iterable<[string, T | null | undefined]> & {
|
|
16
|
+
get?: (name: string) => T | null | undefined;
|
|
17
|
+
};
|
|
18
|
+
type ManifestStatsCompilation = {
|
|
19
|
+
namedChunks: ManifestStatsLookup<ManifestStatsChunk>;
|
|
20
|
+
entrypoints?: ManifestStatsLookup<ManifestStatsEntrypoint>;
|
|
21
|
+
getAsset?: (name: string) => {
|
|
22
|
+
name?: string;
|
|
23
|
+
info?: { assetType?: string; javascriptModule?: boolean };
|
|
24
|
+
} | void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const stripAssetQuery = (name: string): string =>
|
|
28
|
+
name.replace(/[?#].*$/, '');
|
|
29
|
+
|
|
30
|
+
export const getManifestAssetType = (
|
|
31
|
+
name: string,
|
|
32
|
+
assetTypesByName?: Readonly<Record<string, string>>
|
|
33
|
+
): string | undefined => {
|
|
34
|
+
if (/\.hot-update\.[cm]?js(?:[?#]|$)/.test(name)) return undefined;
|
|
35
|
+
const assetType = assetTypesByName?.[name];
|
|
36
|
+
if (assetType !== undefined) {
|
|
37
|
+
return assetType === 'extract-css' ? 'css' : assetType;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Synthetic stats and older bundlers may not supply asset metadata.
|
|
41
|
+
const path = stripAssetQuery(name);
|
|
42
|
+
if (/\.[cm]?js$/.test(path)) {
|
|
43
|
+
return 'javascript';
|
|
44
|
+
}
|
|
45
|
+
return path.endsWith('.css') ? 'css' : undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const orderChunkFiles = (
|
|
49
|
+
chunkName: string,
|
|
50
|
+
files: string[],
|
|
51
|
+
assetTypesByName: Record<string, string>
|
|
52
|
+
): string[] => {
|
|
53
|
+
const ownFileIndex = files.findIndex(
|
|
54
|
+
file =>
|
|
55
|
+
getManifestAssetType(file, assetTypesByName) === 'javascript' &&
|
|
56
|
+
stripAssetQuery(file)
|
|
57
|
+
.replace(/\.[^.]+$/, '')
|
|
58
|
+
.endsWith(chunkName)
|
|
59
|
+
);
|
|
60
|
+
if (ownFileIndex <= 0) {
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
files[ownFileIndex],
|
|
65
|
+
...files.slice(0, ownFileIndex),
|
|
66
|
+
...files.slice(ownFileIndex + 1),
|
|
67
|
+
];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const collectManifestFilesByName = <T>(
|
|
71
|
+
items: ManifestStatsLookup<T>,
|
|
72
|
+
names: ReadonlySet<string> | undefined,
|
|
73
|
+
getFiles: (item: T) => string[]
|
|
74
|
+
): Record<string, string[]> => {
|
|
75
|
+
const filesByName: Record<string, string[]> = {};
|
|
76
|
+
if (!names) {
|
|
77
|
+
for (const [name, item] of items) {
|
|
78
|
+
if (item == null) continue;
|
|
79
|
+
filesByName[name] = getFiles(item);
|
|
80
|
+
}
|
|
81
|
+
return filesByName;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const missingNames = new Set(names);
|
|
85
|
+
if (typeof items.get === 'function') {
|
|
86
|
+
for (const name of names) {
|
|
87
|
+
const item = items.get(name);
|
|
88
|
+
if (!item) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (item == null) continue;
|
|
92
|
+
filesByName[name] = getFiles(item);
|
|
93
|
+
missingNames.delete(name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (missingNames.size === 0) {
|
|
97
|
+
return filesByName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const [name, item] of items) {
|
|
101
|
+
if (!missingNames.has(name)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (item == null) continue;
|
|
105
|
+
filesByName[name] = getFiles(item);
|
|
106
|
+
missingNames.delete(name);
|
|
107
|
+
if (missingNames.size === 0) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return filesByName;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const createReactRouterManifestStats = (
|
|
115
|
+
compilation: ManifestStatsCompilation | undefined,
|
|
116
|
+
chunkNames?: ReadonlySet<string>
|
|
117
|
+
): ReactRouterManifestStats | undefined => {
|
|
118
|
+
if (!compilation) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const assetsByChunkName = collectManifestFilesByName(
|
|
123
|
+
compilation.namedChunks,
|
|
124
|
+
chunkNames,
|
|
125
|
+
chunk => Array.from(chunk.files ?? [])
|
|
126
|
+
);
|
|
127
|
+
const entrypointFilesByName = compilation.entrypoints
|
|
128
|
+
? collectManifestFilesByName(
|
|
129
|
+
compilation.entrypoints,
|
|
130
|
+
chunkNames,
|
|
131
|
+
entrypoint => Array.from(entrypoint.getFiles?.() ?? [])
|
|
132
|
+
)
|
|
133
|
+
: {};
|
|
134
|
+
const assetTypesByName: Record<string, string> = {};
|
|
135
|
+
if (compilation.getAsset) {
|
|
136
|
+
const files = new Set([
|
|
137
|
+
...Object.values(assetsByChunkName).flat(),
|
|
138
|
+
...Object.values(entrypointFilesByName).flat(),
|
|
139
|
+
]);
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
const info = compilation.getAsset(file)?.info;
|
|
142
|
+
const assetType =
|
|
143
|
+
info?.assetType ??
|
|
144
|
+
(typeof info?.javascriptModule === 'boolean'
|
|
145
|
+
? 'javascript'
|
|
146
|
+
: undefined);
|
|
147
|
+
if (assetType !== undefined) {
|
|
148
|
+
assetTypesByName[file] = assetType;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const [chunkName, files] of Object.entries(assetsByChunkName)) {
|
|
153
|
+
assetsByChunkName[chunkName] = orderChunkFiles(
|
|
154
|
+
chunkName,
|
|
155
|
+
files,
|
|
156
|
+
assetTypesByName
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
assetsByChunkName,
|
|
162
|
+
...(Object.keys(entrypointFilesByName).length > 0
|
|
163
|
+
? { entrypointFilesByName }
|
|
164
|
+
: {}),
|
|
165
|
+
...(Object.keys(assetTypesByName).length > 0 ? { assetTypesByName } : {}),
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type ChunkAssets = { js: string[]; css: string[] };
|
|
170
|
+
|
|
171
|
+
export const createChunkAssetResolver = (
|
|
172
|
+
clientStats: ReactRouterManifestStats | undefined,
|
|
173
|
+
includeEntrypointJavaScript: boolean
|
|
174
|
+
): ((chunkName: string) => ChunkAssets) => {
|
|
175
|
+
const chunkAssetsByName = new Map<string, ChunkAssets>();
|
|
176
|
+
const getAssetType = (name: string) =>
|
|
177
|
+
getManifestAssetType(name, clientStats?.assetTypesByName);
|
|
178
|
+
|
|
179
|
+
return (chunkName: string): ChunkAssets => {
|
|
180
|
+
const cached = chunkAssetsByName.get(chunkName);
|
|
181
|
+
if (cached) {
|
|
182
|
+
return cached;
|
|
183
|
+
}
|
|
184
|
+
const assets = clientStats?.assetsByChunkName?.[chunkName];
|
|
185
|
+
if (!assets) {
|
|
186
|
+
const result = {
|
|
187
|
+
js: [`${DEFAULT_JS_DIST_PATH}/${chunkName}.js`],
|
|
188
|
+
css: [],
|
|
189
|
+
};
|
|
190
|
+
chunkAssetsByName.set(chunkName, result);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const cssAssets = new Set<string>();
|
|
195
|
+
const jsAssets: string[] = [];
|
|
196
|
+
for (const asset of assets) {
|
|
197
|
+
const assetType = getAssetType(asset);
|
|
198
|
+
if (assetType === 'css') {
|
|
199
|
+
cssAssets.add(asset);
|
|
200
|
+
} else if (assetType === 'javascript') {
|
|
201
|
+
jsAssets.push(asset);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (jsAssets.length === 0) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`[react-router] Chunk "${chunkName}" emitted no JavaScript asset the browser manifest can reference (files: ${assets.join(', ') || 'none'}). Check the web \`output.filename.js\` scheme.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
// Entrypoint files contain the synchronous chunk group, not async children.
|
|
211
|
+
// Keep the route's own module first even when the runtime is listed first.
|
|
212
|
+
for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
|
|
213
|
+
const assetType = getAssetType(asset);
|
|
214
|
+
if (assetType === 'css') {
|
|
215
|
+
cssAssets.add(asset);
|
|
216
|
+
} else if (includeEntrypointJavaScript && assetType === 'javascript') {
|
|
217
|
+
jsAssets.push(asset);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = {
|
|
222
|
+
js: [...new Set(jsAssets)],
|
|
223
|
+
css: [...cssAssets],
|
|
224
|
+
};
|
|
225
|
+
chunkAssetsByName.set(chunkName, result);
|
|
226
|
+
return result;
|
|
227
|
+
};
|
|
228
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ReactRouterManifestForDev,
|
|
3
|
+
RouteManifestModuleExports,
|
|
4
|
+
} from './manifest.js';
|
|
5
|
+
import type { ReactRouterServerBuildPlan } from './server-build-plan.js';
|
|
6
|
+
import type { Route } from './types.js';
|
|
7
|
+
|
|
8
|
+
export type ReactRouterManifestSnapshot = {
|
|
9
|
+
browser: ReactRouterManifestForDev;
|
|
10
|
+
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
11
|
+
server: ReactRouterManifestForDev;
|
|
12
|
+
serverByBundleId: Readonly<Record<string, ReactRouterManifestForDev>>;
|
|
13
|
+
serverByEntryName: Readonly<Record<string, ReactRouterManifestForDev>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const createReactRouterManifestSnapshot = ({
|
|
17
|
+
manifest,
|
|
18
|
+
sri,
|
|
19
|
+
moduleExportsByRouteId,
|
|
20
|
+
serverBuildPlan,
|
|
21
|
+
routesByServerBundleId,
|
|
22
|
+
}: {
|
|
23
|
+
manifest: ReactRouterManifestForDev;
|
|
24
|
+
sri: ReactRouterManifestForDev['sri'];
|
|
25
|
+
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
26
|
+
serverBuildPlan: Pick<
|
|
27
|
+
ReactRouterServerBuildPlan,
|
|
28
|
+
'defaultEntryName' | 'serverBundleEntries'
|
|
29
|
+
>;
|
|
30
|
+
routesByServerBundleId: Readonly<
|
|
31
|
+
Record<string, Readonly<Record<string, Route>>>
|
|
32
|
+
>;
|
|
33
|
+
}): ReactRouterManifestSnapshot => {
|
|
34
|
+
// Detach caller-owned data once so every derived view belongs to this snapshot.
|
|
35
|
+
const { server, moduleExportsByRouteId: detachedModuleExports } =
|
|
36
|
+
structuredClone({
|
|
37
|
+
server: { ...manifest, sri },
|
|
38
|
+
moduleExportsByRouteId,
|
|
39
|
+
});
|
|
40
|
+
const browser: ReactRouterManifestForDev = {
|
|
41
|
+
...server,
|
|
42
|
+
sri: undefined,
|
|
43
|
+
};
|
|
44
|
+
const bundles = serverBuildPlan.serverBundleEntries.flatMap(
|
|
45
|
+
({ bundleId, entryName }) => {
|
|
46
|
+
const bundleRoutes = routesByServerBundleId[bundleId];
|
|
47
|
+
if (!bundleRoutes) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
bundleId,
|
|
54
|
+
entryName,
|
|
55
|
+
manifest: {
|
|
56
|
+
...server,
|
|
57
|
+
routes: Object.fromEntries(
|
|
58
|
+
Object.entries(server.routes).filter(([routeId]) =>
|
|
59
|
+
Object.hasOwn(bundleRoutes, routeId)
|
|
60
|
+
)
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
browser,
|
|
70
|
+
moduleExportsByRouteId: detachedModuleExports,
|
|
71
|
+
server,
|
|
72
|
+
serverByBundleId: Object.fromEntries(
|
|
73
|
+
bundles.map(({ bundleId, manifest }) => [bundleId, manifest])
|
|
74
|
+
),
|
|
75
|
+
serverByEntryName: Object.fromEntries([
|
|
76
|
+
[serverBuildPlan.defaultEntryName, server],
|
|
77
|
+
...bundles.map(({ entryName, manifest }) => [entryName, manifest]),
|
|
78
|
+
]),
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
+
import type { ReactRouterManifestSnapshot } from './manifest-snapshot.js';
|
|
3
|
+
|
|
4
|
+
type ReactRouterManifestState = {
|
|
5
|
+
stage(
|
|
6
|
+
compilation: Rspack.Compilation,
|
|
7
|
+
snapshot: ReactRouterManifestSnapshot
|
|
8
|
+
): void;
|
|
9
|
+
read(): ReactRouterManifestSnapshot | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const createReactRouterManifestState = ({
|
|
13
|
+
api,
|
|
14
|
+
isBuild,
|
|
15
|
+
onPublish,
|
|
16
|
+
}: {
|
|
17
|
+
api: Pick<
|
|
18
|
+
RsbuildPluginAPI,
|
|
19
|
+
'onBeforeEnvironmentCompile' | 'onAfterEnvironmentCompile'
|
|
20
|
+
>;
|
|
21
|
+
isBuild: boolean;
|
|
22
|
+
onPublish: (
|
|
23
|
+
compilation: Rspack.Compilation,
|
|
24
|
+
snapshot: ReactRouterManifestSnapshot
|
|
25
|
+
) => void;
|
|
26
|
+
}): ReactRouterManifestState => {
|
|
27
|
+
let latest: ReactRouterManifestSnapshot | null = null;
|
|
28
|
+
const pending = new WeakMap<
|
|
29
|
+
Rspack.Compilation,
|
|
30
|
+
ReactRouterManifestSnapshot
|
|
31
|
+
>();
|
|
32
|
+
const publish = (
|
|
33
|
+
compilation: Rspack.Compilation,
|
|
34
|
+
snapshot: ReactRouterManifestSnapshot
|
|
35
|
+
) => {
|
|
36
|
+
latest = snapshot;
|
|
37
|
+
onPublish(compilation, snapshot);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (isBuild) {
|
|
41
|
+
api.onBeforeEnvironmentCompile(({ environment }) => {
|
|
42
|
+
if (environment.name === 'web') {
|
|
43
|
+
// A failed web rebuild must not expose the previous build's manifest.
|
|
44
|
+
// A node-only rebuild may continue using the last successful snapshot.
|
|
45
|
+
latest = null;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
api.onAfterEnvironmentCompile(({ environment, stats }) => {
|
|
50
|
+
if (environment.name !== 'web') {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const snapshot =
|
|
54
|
+
stats && !stats.hasErrors() ? pending.get(stats.compilation) : undefined;
|
|
55
|
+
if (stats) {
|
|
56
|
+
pending.delete(stats.compilation);
|
|
57
|
+
}
|
|
58
|
+
if (snapshot && stats) {
|
|
59
|
+
publish(stats.compilation, snapshot);
|
|
60
|
+
} else if (isBuild) {
|
|
61
|
+
latest = null;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
stage(compilation, snapshot) {
|
|
67
|
+
pending.set(compilation, snapshot);
|
|
68
|
+
},
|
|
69
|
+
read: () => latest,
|
|
70
|
+
};
|
|
71
|
+
};
|
package/src/manifest.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChunkAssetResolver,
|
|
3
|
+
type ReactRouterManifestStats,
|
|
4
|
+
stripAssetQuery,
|
|
5
|
+
getManifestAssetType,
|
|
6
|
+
} from './manifest-assets.js';
|
|
7
|
+
export {
|
|
8
|
+
createReactRouterManifestStats,
|
|
9
|
+
type ReactRouterManifestStats,
|
|
10
|
+
} from './manifest-assets.js';
|
|
11
|
+
import { BROWSER_MANIFEST_ENTRY_NAME } from './constants.js';
|
|
1
12
|
import { createHash } from 'node:crypto';
|
|
2
13
|
import { dirname, isAbsolute, relative, resolve } from 'pathe';
|
|
3
14
|
import * as Effect from 'effect/Effect';
|
|
@@ -143,30 +154,6 @@ export type ReactRouterManifestForDev = {
|
|
|
143
154
|
routes: Record<string, RouteManifestItem>;
|
|
144
155
|
};
|
|
145
156
|
|
|
146
|
-
export type ReactRouterManifestStats = {
|
|
147
|
-
assetsByChunkName?: Record<string, string[]>;
|
|
148
|
-
entrypointFilesByName?: Record<string, string[]>;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
type ReactRouterManifestStatsChunk = {
|
|
152
|
-
files?: Iterable<string>;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
type ReactRouterManifestStatsEntrypoint = {
|
|
156
|
-
getFiles?: () => Iterable<string>;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
type ReactRouterManifestStatsLookup<T> = Iterable<
|
|
160
|
-
[string, T | null | undefined]
|
|
161
|
-
> & {
|
|
162
|
-
get?: (name: string) => T | null | undefined;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
type ReactRouterManifestStatsCompilation = {
|
|
166
|
-
namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
|
|
167
|
-
entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
157
|
// Emitted asset names may carry a query (`output.filename.js:
|
|
171
158
|
// '[name].js?v=[contenthash:8]'`); classify on the pathname but keep the full
|
|
172
159
|
// reference, since the query is part of the URL the browser must request.
|
|
@@ -252,81 +239,6 @@ export const collectUnsupportedRscScriptAssets = (
|
|
|
252
239
|
return [...unsupported];
|
|
253
240
|
};
|
|
254
241
|
|
|
255
|
-
const collectManifestFilesByName = <T>(
|
|
256
|
-
items: ReactRouterManifestStatsLookup<T>,
|
|
257
|
-
names: ReadonlySet<string> | undefined,
|
|
258
|
-
getFiles: (name: string, item: T) => string[]
|
|
259
|
-
): Record<string, string[]> => {
|
|
260
|
-
const filesByName: Record<string, string[]> = {};
|
|
261
|
-
if (!names) {
|
|
262
|
-
for (const [name, item] of items) {
|
|
263
|
-
if (item == null) {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
filesByName[name] = getFiles(name, item);
|
|
267
|
-
}
|
|
268
|
-
return filesByName;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const missingNames = new Set(names);
|
|
272
|
-
if (typeof items.get === 'function') {
|
|
273
|
-
for (const name of names) {
|
|
274
|
-
const item = items.get(name);
|
|
275
|
-
if (item == null) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
filesByName[name] = getFiles(name, item);
|
|
279
|
-
missingNames.delete(name);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (missingNames.size === 0) {
|
|
284
|
-
return filesByName;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
for (const [name, item] of items) {
|
|
288
|
-
if (!missingNames.has(name)) {
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
if (item == null) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
filesByName[name] = getFiles(name, item);
|
|
295
|
-
missingNames.delete(name);
|
|
296
|
-
if (missingNames.size === 0) {
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return filesByName;
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
export const createReactRouterManifestStats = (
|
|
305
|
-
compilation: ReactRouterManifestStatsCompilation | undefined,
|
|
306
|
-
chunkNames?: ReadonlySet<string>
|
|
307
|
-
): ReactRouterManifestStats | undefined => {
|
|
308
|
-
if (!compilation) {
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const assetsByChunkName = collectManifestFilesByName(
|
|
313
|
-
compilation.namedChunks,
|
|
314
|
-
chunkNames,
|
|
315
|
-
(_chunkName, chunk) => Array.from(chunk.files ?? [])
|
|
316
|
-
);
|
|
317
|
-
const entrypointFilesByName = compilation.entrypoints
|
|
318
|
-
? collectManifestFilesByName(
|
|
319
|
-
compilation.entrypoints,
|
|
320
|
-
chunkNames,
|
|
321
|
-
(_name, entrypoint) => Array.from(entrypoint.getFiles?.() ?? [])
|
|
322
|
-
)
|
|
323
|
-
: {};
|
|
324
|
-
|
|
325
|
-
return Object.keys(entrypointFilesByName).length > 0
|
|
326
|
-
? { assetsByChunkName, entrypointFilesByName }
|
|
327
|
-
: { assetsByChunkName };
|
|
328
|
-
};
|
|
329
|
-
|
|
330
242
|
export type RouteManifestModuleExports = Record<string, readonly string[]>;
|
|
331
243
|
|
|
332
244
|
export type ReactRouterManifestGenerationResult = {
|
|
@@ -334,11 +246,6 @@ export type ReactRouterManifestGenerationResult = {
|
|
|
334
246
|
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
335
247
|
};
|
|
336
248
|
|
|
337
|
-
type ChunkAssets = {
|
|
338
|
-
js: string[];
|
|
339
|
-
css: string[];
|
|
340
|
-
};
|
|
341
|
-
|
|
342
249
|
type RouteManifestAnalysis = {
|
|
343
250
|
cssAssets: string[];
|
|
344
251
|
exports: Set<string>;
|
|
@@ -351,61 +258,6 @@ type RouteManifestAnalysis = {
|
|
|
351
258
|
const DEFAULT_MANIFEST_DIR = DEFAULT_JS_DIST_PATH;
|
|
352
259
|
const CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/;
|
|
353
260
|
|
|
354
|
-
const createChunkAssetResolver = (
|
|
355
|
-
clientStats: ReactRouterManifestStats | undefined,
|
|
356
|
-
includeEntrypointJs: boolean
|
|
357
|
-
): ((chunkName: string) => ChunkAssets) => {
|
|
358
|
-
const chunkAssetsByName = new Map<string, ChunkAssets>();
|
|
359
|
-
|
|
360
|
-
return (chunkName: string): ChunkAssets => {
|
|
361
|
-
const cached = chunkAssetsByName.get(chunkName);
|
|
362
|
-
if (cached) {
|
|
363
|
-
return cached;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const assets = clientStats?.assetsByChunkName?.[chunkName];
|
|
367
|
-
if (!assets) {
|
|
368
|
-
const fallback = `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`;
|
|
369
|
-
const result = { js: [fallback], css: [] };
|
|
370
|
-
chunkAssetsByName.set(chunkName, result);
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const cssAssets = new Set<string>();
|
|
375
|
-
const jsAssets = new Set<string>();
|
|
376
|
-
// The chunk's own files come first so `js[0]` is the route module itself;
|
|
377
|
-
// entrypoint files (runtime, shared vendor chunks) follow as `imports`.
|
|
378
|
-
for (const asset of assets) {
|
|
379
|
-
if (isManifestCssAsset(asset)) {
|
|
380
|
-
cssAssets.add(asset);
|
|
381
|
-
} else if (isManifestJsAsset(asset)) {
|
|
382
|
-
jsAssets.add(asset);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
|
|
386
|
-
if (isManifestCssAsset(asset)) {
|
|
387
|
-
cssAssets.add(asset);
|
|
388
|
-
} else if (includeEntrypointJs && isManifestJsAsset(asset)) {
|
|
389
|
-
jsAssets.add(asset);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if (jsAssets.size === 0) {
|
|
393
|
-
// Compilation metadata exists for this chunk but names no module script.
|
|
394
|
-
// Guessing `<dir>/<chunk>.js` here would turn an identifiable build
|
|
395
|
-
// problem into a browser 404, so surface it at build time instead.
|
|
396
|
-
throw new Error(
|
|
397
|
-
`[react-router] Chunk "${chunkName}" emitted no JavaScript asset the browser manifest can reference (files: ${
|
|
398
|
-
assets.join(', ') || 'none'
|
|
399
|
-
}). Check the web \`output.filename.js\` scheme.`
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const result = { js: [...jsAssets], css: [...cssAssets] };
|
|
404
|
-
chunkAssetsByName.set(chunkName, result);
|
|
405
|
-
return result;
|
|
406
|
-
};
|
|
407
|
-
};
|
|
408
|
-
|
|
409
261
|
const analyzeRouteForManifestEffect = ({
|
|
410
262
|
discoveredCssAssets,
|
|
411
263
|
isBuild,
|
|
@@ -696,14 +548,24 @@ function generateReactRouterManifestForDevEffect(
|
|
|
696
548
|
const manifestPath = getReactRouterManifestPath({
|
|
697
549
|
version,
|
|
698
550
|
isBuild,
|
|
699
|
-
entryModulePath: entryJsAssets[0],
|
|
551
|
+
entryModulePath: stripAssetQuery(entryJsAssets[0] ?? ''),
|
|
700
552
|
});
|
|
701
553
|
|
|
554
|
+
const browserManifestPath =
|
|
555
|
+
clientStats?.assetsByChunkName?.[BROWSER_MANIFEST_ENTRY_NAME]?.find(
|
|
556
|
+
name =>
|
|
557
|
+
getManifestAssetType(name, clientStats.assetTypesByName) ===
|
|
558
|
+
'javascript'
|
|
559
|
+
) ?? manifestPath;
|
|
560
|
+
// Report-stage serialization happens after hashing, so retain the version
|
|
561
|
+
// query even when the virtual manifest has a content-hashed filename.
|
|
702
562
|
const manifest = {
|
|
703
563
|
version,
|
|
704
564
|
url: combineURLs(
|
|
705
565
|
assetPrefix,
|
|
706
|
-
isBuild
|
|
566
|
+
isBuild
|
|
567
|
+
? manifestPath
|
|
568
|
+
: `${browserManifestPath}${browserManifestPath.includes('?') ? '&' : '?'}v=${version}`
|
|
707
569
|
),
|
|
708
570
|
hmr: undefined,
|
|
709
571
|
entry: fingerprintedValues.entry,
|
package/src/mode-plan.ts
CHANGED
|
@@ -113,9 +113,14 @@ type CreateReactRouterModePlanOptions =
|
|
|
113
113
|
|
|
114
114
|
const RSC_LAYERS = rspack.experiments.rsc.Layers;
|
|
115
115
|
|
|
116
|
-
const createReactRouterPackageAliases = (
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
const createReactRouterPackageAliases = (
|
|
117
|
+
rootPath: string
|
|
118
|
+
): Record<string, string> => {
|
|
119
|
+
const reactRouterPath = resolveAppPackagePath('react-router', rootPath);
|
|
120
|
+
const reactRouterDomPath = resolveAppPackagePath(
|
|
121
|
+
'react-router/dom',
|
|
122
|
+
rootPath
|
|
123
|
+
);
|
|
119
124
|
return {
|
|
120
125
|
...(reactRouterPath ? { 'react-router$': reactRouterPath } : {}),
|
|
121
126
|
...(reactRouterDomPath ? { 'react-router/dom$': reactRouterDomPath } : {}),
|
|
@@ -284,7 +289,9 @@ const createClassicModePlan = async ({
|
|
|
284
289
|
ssr,
|
|
285
290
|
devHmr,
|
|
286
291
|
});
|
|
287
|
-
const reactRouterAliases = createReactRouterPackageAliases(
|
|
292
|
+
const reactRouterAliases = createReactRouterPackageAliases(
|
|
293
|
+
api.context.rootPath
|
|
294
|
+
);
|
|
288
295
|
return {
|
|
289
296
|
kind: 'classic',
|
|
290
297
|
artifacts,
|
|
@@ -334,6 +341,7 @@ const createClassicModePlan = async ({
|
|
|
334
341
|
setup: [
|
|
335
342
|
createReactRouterDevServerSetup({
|
|
336
343
|
loadBuild: artifacts.devRuntime.createBuildLoader(),
|
|
344
|
+
rootPath: api.context.rootPath,
|
|
337
345
|
}),
|
|
338
346
|
],
|
|
339
347
|
},
|