rsbuild-plugin-react-router 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -201
- package/dist/451.js +1103 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +7 -4
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +3 -0
- package/dist/config-imports.d.ts +10 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +47 -0
- package/dist/dev-runtime-compilation.d.ts +47 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +16 -2
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +13775 -1412
- package/dist/index.d.ts +8 -1
- package/dist/index.js +9429 -1501
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +6 -0
- package/dist/manifest.d.ts +48 -10
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +29 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +36 -0
- package/dist/prerender.d.ts +27 -2
- package/dist/react-router-config.d.ts +15 -5
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/templates/entry.server.cjs +3 -3
- package/dist/templates/entry.server.js +3 -3
- package/dist/typegen.d.ts +15 -0
- package/dist/types.d.ts +41 -14
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +24 -19
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +205 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +15 -0
- package/src/config-imports.ts +83 -0
- package/src/constants.ts +91 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +690 -0
- package/src/dev-runtime-artifacts.ts +239 -0
- package/src/dev-runtime-compilation.ts +171 -0
- package/src/dev-runtime-controller.ts +542 -0
- package/src/dev-runtime-session.ts +191 -0
- package/src/dev-server.ts +88 -0
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +278 -0
- package/src/index.ts +990 -0
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +101 -0
- package/src/manifest.ts +591 -0
- package/src/modify-browser-manifest.ts +246 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +458 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +687 -0
- package/src/prerender.ts +349 -0
- package/src/react-router-config.ts +245 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +357 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +111 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +178 -0
- package/src/types.ts +92 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/0~rslib-runtime.js +0 -16
- package/dist/babel.d.ts +0 -8
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve } from 'pathe';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import type { Route, PluginOptions, RouteManifestItem } from './types.js';
|
|
5
|
+
import { combineURLs, createRouteId } from './plugin-utils.js';
|
|
6
|
+
import { SERVER_EXPORTS, CLIENT_EXPORTS } from './constants.js';
|
|
7
|
+
import {
|
|
8
|
+
buildManifestChunkValidity,
|
|
9
|
+
createEmptyRouteChunkByExportName,
|
|
10
|
+
detectRouteChunksIfEnabled,
|
|
11
|
+
getRouteChunkEntryName,
|
|
12
|
+
routeChunkExportNames,
|
|
13
|
+
validateRouteChunks,
|
|
14
|
+
type RouteChunkCache,
|
|
15
|
+
type RouteChunkConfig,
|
|
16
|
+
} from './route-chunks.js';
|
|
17
|
+
import { getRouteModuleAnalysis } from './export-utils.js';
|
|
18
|
+
import { getCappedPluginConcurrency } from './concurrency.js';
|
|
19
|
+
import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
|
|
20
|
+
|
|
21
|
+
const ROUTE_ANALYSIS_CONCURRENCY = getCappedPluginConcurrency();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Structural equivalent of `RouteConfigEntry` from `@react-router/dev/routes`,
|
|
25
|
+
* inlined so the public declaration files do not reference a devDependency
|
|
26
|
+
* (which would fail to resolve for consumers with `skipLibCheck: false`).
|
|
27
|
+
*/
|
|
28
|
+
export interface RouteConfigEntry {
|
|
29
|
+
id?: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
index?: boolean;
|
|
32
|
+
caseSensitive?: boolean;
|
|
33
|
+
file: string;
|
|
34
|
+
children?: RouteConfigEntry[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function configRoutesToRouteManifest(
|
|
38
|
+
appDirectory: string,
|
|
39
|
+
routes: RouteConfigEntry[],
|
|
40
|
+
rootId = 'root'
|
|
41
|
+
): Record<string, Route> {
|
|
42
|
+
return Object.fromEntries(
|
|
43
|
+
configRoutesToRouteManifestEntries(appDirectory, routes, rootId)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function configRoutesToRouteManifestEntries(
|
|
48
|
+
appDirectory: string,
|
|
49
|
+
routes: RouteConfigEntry[],
|
|
50
|
+
rootId = 'root'
|
|
51
|
+
): Array<[string, Route]> {
|
|
52
|
+
const routeManifestEntries: Array<[string, Route]> = [];
|
|
53
|
+
const routeIds = new Set<string>();
|
|
54
|
+
|
|
55
|
+
function walk(route: RouteConfigEntry, parentId: string) {
|
|
56
|
+
const id = route.id || createRouteId(route.file);
|
|
57
|
+
const manifestItem = {
|
|
58
|
+
id,
|
|
59
|
+
parentId,
|
|
60
|
+
file: isAbsolute(route.file)
|
|
61
|
+
? relative(appDirectory, route.file)
|
|
62
|
+
: route.file,
|
|
63
|
+
path: route.path,
|
|
64
|
+
index: route.index,
|
|
65
|
+
caseSensitive: route.caseSensitive,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (routeIds.has(id)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Unable to define routes with duplicate route id: "${id}"`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
routeIds.add(id);
|
|
74
|
+
routeManifestEntries.push([id, manifestItem]);
|
|
75
|
+
|
|
76
|
+
if (route.children) {
|
|
77
|
+
for (const child of route.children) {
|
|
78
|
+
walk(child, id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const route of routes) {
|
|
84
|
+
walk(route, rootId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return routeManifestEntries;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type RouteChunkManifestOptions = {
|
|
91
|
+
splitRouteModules?: boolean | 'enforce';
|
|
92
|
+
rootRouteFile?: string;
|
|
93
|
+
isBuild?: boolean;
|
|
94
|
+
cache?: RouteChunkCache;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type ReactRouterManifestForDev = {
|
|
98
|
+
version: string;
|
|
99
|
+
url: string;
|
|
100
|
+
hmr?: {
|
|
101
|
+
runtime: string;
|
|
102
|
+
};
|
|
103
|
+
entry: {
|
|
104
|
+
module: string;
|
|
105
|
+
imports: string[];
|
|
106
|
+
css: string[];
|
|
107
|
+
};
|
|
108
|
+
sri?: Record<string, string> | true;
|
|
109
|
+
routes: Record<string, RouteManifestItem>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type ReactRouterManifestStats = {
|
|
113
|
+
assetsByChunkName?: Record<string, string[]>;
|
|
114
|
+
entrypointFilesByName?: Record<string, string[]>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type ReactRouterManifestStatsChunk = {
|
|
118
|
+
files?: Iterable<string>;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type ReactRouterManifestStatsEntrypoint = {
|
|
122
|
+
getFiles?: () => Iterable<string>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type ReactRouterManifestStatsLookup<T> = Iterable<[string, T]> & {
|
|
126
|
+
get?: (name: string) => T | undefined;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
type ReactRouterManifestStatsCompilation = {
|
|
130
|
+
namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
|
|
131
|
+
entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const orderChunkFiles = (chunkName: string, files: string[]): string[] => {
|
|
135
|
+
const ownChunkAsset = `${chunkName}.js`;
|
|
136
|
+
const ownFileIndex = files.findIndex(file => file.endsWith(ownChunkAsset));
|
|
137
|
+
if (ownFileIndex <= 0) {
|
|
138
|
+
return files;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
files[ownFileIndex],
|
|
143
|
+
...files.slice(0, ownFileIndex),
|
|
144
|
+
...files.slice(ownFileIndex + 1),
|
|
145
|
+
];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const collectManifestFilesByName = <T>(
|
|
149
|
+
items: ReactRouterManifestStatsLookup<T>,
|
|
150
|
+
names: ReadonlySet<string> | undefined,
|
|
151
|
+
getFiles: (name: string, item: T) => string[]
|
|
152
|
+
): Record<string, string[]> => {
|
|
153
|
+
const filesByName: Record<string, string[]> = {};
|
|
154
|
+
if (!names) {
|
|
155
|
+
for (const [name, item] of items) {
|
|
156
|
+
filesByName[name] = getFiles(name, item);
|
|
157
|
+
}
|
|
158
|
+
return filesByName;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const missingNames = new Set(names);
|
|
162
|
+
if (typeof items.get === 'function') {
|
|
163
|
+
for (const name of names) {
|
|
164
|
+
const item = items.get(name);
|
|
165
|
+
if (!item) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
filesByName[name] = getFiles(name, item);
|
|
169
|
+
missingNames.delete(name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (missingNames.size === 0) {
|
|
174
|
+
return filesByName;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const [name, item] of items) {
|
|
178
|
+
if (!missingNames.has(name)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
filesByName[name] = getFiles(name, item);
|
|
182
|
+
missingNames.delete(name);
|
|
183
|
+
if (missingNames.size === 0) {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return filesByName;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const createReactRouterManifestStats = (
|
|
192
|
+
compilation: ReactRouterManifestStatsCompilation | undefined,
|
|
193
|
+
chunkNames?: ReadonlySet<string>
|
|
194
|
+
): ReactRouterManifestStats | undefined => {
|
|
195
|
+
if (!compilation) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const assetsByChunkName = collectManifestFilesByName(
|
|
200
|
+
compilation.namedChunks,
|
|
201
|
+
chunkNames,
|
|
202
|
+
(chunkName, chunk) =>
|
|
203
|
+
orderChunkFiles(chunkName, Array.from(chunk.files ?? []))
|
|
204
|
+
);
|
|
205
|
+
const entrypointFilesByName = compilation.entrypoints
|
|
206
|
+
? collectManifestFilesByName(
|
|
207
|
+
compilation.entrypoints,
|
|
208
|
+
chunkNames,
|
|
209
|
+
(_name, entrypoint) => Array.from(entrypoint.getFiles?.() ?? [])
|
|
210
|
+
)
|
|
211
|
+
: {};
|
|
212
|
+
|
|
213
|
+
return Object.keys(entrypointFilesByName).length > 0
|
|
214
|
+
? { assetsByChunkName, entrypointFilesByName }
|
|
215
|
+
: { assetsByChunkName };
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export type RouteManifestModuleExports = Record<string, readonly string[]>;
|
|
219
|
+
|
|
220
|
+
export type ReactRouterManifestGenerationResult = {
|
|
221
|
+
manifest: ReactRouterManifestForDev;
|
|
222
|
+
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type ChunkAssets = {
|
|
226
|
+
js: string[];
|
|
227
|
+
css: string[];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type RouteManifestAnalysis = {
|
|
231
|
+
cssAssets: string[];
|
|
232
|
+
exports: Set<string>;
|
|
233
|
+
routeModuleExports: readonly string[];
|
|
234
|
+
hasRouteChunkByExportName: ReturnType<
|
|
235
|
+
typeof createEmptyRouteChunkByExportName
|
|
236
|
+
> | null;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const DEFAULT_MANIFEST_DIR = 'static/js';
|
|
240
|
+
const CSS_IMPORT_RE = /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/;
|
|
241
|
+
|
|
242
|
+
const createChunkAssetResolver = (
|
|
243
|
+
clientStats: ReactRouterManifestStats | undefined
|
|
244
|
+
): ((chunkName: string) => ChunkAssets) => {
|
|
245
|
+
const chunkAssetsByName = new Map<string, ChunkAssets>();
|
|
246
|
+
|
|
247
|
+
return (chunkName: string): ChunkAssets => {
|
|
248
|
+
const cached = chunkAssetsByName.get(chunkName);
|
|
249
|
+
if (cached) {
|
|
250
|
+
return cached;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const assets = clientStats?.assetsByChunkName?.[chunkName];
|
|
254
|
+
if (!assets) {
|
|
255
|
+
const fallback = `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`;
|
|
256
|
+
const result = { js: [fallback], css: [] };
|
|
257
|
+
chunkAssetsByName.set(chunkName, result);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const cssAssets = new Set<string>();
|
|
262
|
+
const jsAssets: string[] = [];
|
|
263
|
+
for (const asset of assets) {
|
|
264
|
+
if (asset.endsWith('.css')) {
|
|
265
|
+
cssAssets.add(asset);
|
|
266
|
+
} else if (asset.endsWith('.js')) {
|
|
267
|
+
jsAssets.push(asset);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const asset of clientStats?.entrypointFilesByName?.[chunkName] ?? []) {
|
|
271
|
+
if (asset.endsWith('.css')) {
|
|
272
|
+
cssAssets.add(asset);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (jsAssets.length === 0) {
|
|
276
|
+
jsAssets.push(`${DEFAULT_MANIFEST_DIR}/${chunkName}.js`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const result = { js: jsAssets, css: [...cssAssets] };
|
|
280
|
+
chunkAssetsByName.set(chunkName, result);
|
|
281
|
+
return result;
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const analyzeRouteForManifestEffect = ({
|
|
286
|
+
discoveredCssAssets,
|
|
287
|
+
isBuild,
|
|
288
|
+
routeChunkCache,
|
|
289
|
+
routeChunkConfig,
|
|
290
|
+
routeEntryName,
|
|
291
|
+
routeFilePath,
|
|
292
|
+
}: {
|
|
293
|
+
discoveredCssAssets: string[];
|
|
294
|
+
isBuild: boolean;
|
|
295
|
+
routeChunkCache: RouteChunkCache | undefined;
|
|
296
|
+
routeChunkConfig: RouteChunkConfig | null;
|
|
297
|
+
routeEntryName: string;
|
|
298
|
+
routeFilePath: string;
|
|
299
|
+
}): Effect.Effect<RouteManifestAnalysis, Error, never> =>
|
|
300
|
+
tryPluginPromise(async () => {
|
|
301
|
+
const { code, exports: exportNames } =
|
|
302
|
+
await getRouteModuleAnalysis(routeFilePath);
|
|
303
|
+
const cssAssets =
|
|
304
|
+
!isBuild && discoveredCssAssets.length === 0 && CSS_IMPORT_RE.test(code)
|
|
305
|
+
? [
|
|
306
|
+
`${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`,
|
|
307
|
+
]
|
|
308
|
+
: discoveredCssAssets;
|
|
309
|
+
const chunkInfo =
|
|
310
|
+
isBuild && routeChunkConfig
|
|
311
|
+
? await detectRouteChunksIfEnabled(
|
|
312
|
+
routeChunkCache,
|
|
313
|
+
routeChunkConfig,
|
|
314
|
+
routeFilePath,
|
|
315
|
+
code
|
|
316
|
+
)
|
|
317
|
+
: null;
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
cssAssets,
|
|
321
|
+
exports: new Set(exportNames),
|
|
322
|
+
routeModuleExports: exportNames,
|
|
323
|
+
hasRouteChunkByExportName: chunkInfo?.hasRouteChunkByExportName ?? null,
|
|
324
|
+
};
|
|
325
|
+
}).pipe(
|
|
326
|
+
Effect.catchAll(error => {
|
|
327
|
+
if (isBuild) {
|
|
328
|
+
return Effect.fail(error);
|
|
329
|
+
}
|
|
330
|
+
return Effect.sync(() => {
|
|
331
|
+
console.error(`Failed to analyze route file ${routeFilePath}:`, error);
|
|
332
|
+
return {
|
|
333
|
+
cssAssets: discoveredCssAssets,
|
|
334
|
+
exports: new Set<string>(),
|
|
335
|
+
routeModuleExports: [],
|
|
336
|
+
hasRouteChunkByExportName: null,
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const getManifestDirFromEntryAsset = (entryModulePath?: string): string => {
|
|
343
|
+
if (!entryModulePath) {
|
|
344
|
+
return DEFAULT_MANIFEST_DIR;
|
|
345
|
+
}
|
|
346
|
+
const dir = dirname(entryModulePath);
|
|
347
|
+
return dir === '.' ? DEFAULT_MANIFEST_DIR : dir;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const getReactRouterManifestPath = ({
|
|
351
|
+
version,
|
|
352
|
+
isBuild,
|
|
353
|
+
entryModulePath,
|
|
354
|
+
}: {
|
|
355
|
+
version: string;
|
|
356
|
+
isBuild: boolean;
|
|
357
|
+
entryModulePath?: string;
|
|
358
|
+
}): string => {
|
|
359
|
+
if (!isBuild) {
|
|
360
|
+
return 'static/js/virtual/react-router/browser-manifest.js';
|
|
361
|
+
}
|
|
362
|
+
const dir = getManifestDirFromEntryAsset(entryModulePath);
|
|
363
|
+
return `${dir}/manifest-${version}.js`;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const getManifestVersion = (
|
|
367
|
+
fingerprintedValues: { entry: unknown; routes: unknown },
|
|
368
|
+
isBuild: boolean
|
|
369
|
+
): string => {
|
|
370
|
+
if (!isBuild) {
|
|
371
|
+
return String(Math.random());
|
|
372
|
+
}
|
|
373
|
+
return createHash('md5')
|
|
374
|
+
.update(JSON.stringify(fingerprintedValues))
|
|
375
|
+
.digest('hex')
|
|
376
|
+
.slice(0, 8);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const getRouteEntryName = (route: Route): string => {
|
|
380
|
+
const extensionIndex = route.file.lastIndexOf('.');
|
|
381
|
+
return extensionIndex >= 0 ? route.file.slice(0, extensionIndex) : route.file;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export const getReactRouterManifestChunkNames = (
|
|
385
|
+
routes: Record<string, Route>,
|
|
386
|
+
splitRouteModules: boolean | 'enforce' = false
|
|
387
|
+
): Set<string> => {
|
|
388
|
+
const chunkNames = new Set<string>(['entry.client']);
|
|
389
|
+
for (const route of Object.values(routes)) {
|
|
390
|
+
chunkNames.add(getRouteEntryName(route));
|
|
391
|
+
if (!splitRouteModules || route.id === 'root') {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
for (const exportName of routeChunkExportNames) {
|
|
395
|
+
chunkNames.add(getRouteChunkEntryName(route.id, exportName));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return chunkNames;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
function generateReactRouterManifestForDevEffect(
|
|
402
|
+
routes: Record<string, Route>,
|
|
403
|
+
_options: PluginOptions,
|
|
404
|
+
clientStats: ReactRouterManifestStats | undefined,
|
|
405
|
+
context: string,
|
|
406
|
+
assetPrefix: string,
|
|
407
|
+
routeChunkOptions?: RouteChunkManifestOptions
|
|
408
|
+
): Effect.Effect<ReactRouterManifestGenerationResult, Error, never> {
|
|
409
|
+
return Effect.gen(function* () {
|
|
410
|
+
const result: Record<string, RouteManifestItem> = {};
|
|
411
|
+
const splitRouteModules = routeChunkOptions?.splitRouteModules ?? false;
|
|
412
|
+
const enforceSplitRouteModules = splitRouteModules === 'enforce';
|
|
413
|
+
const isBuild = routeChunkOptions?.isBuild ?? false;
|
|
414
|
+
const routeChunkConfig: RouteChunkConfig | null =
|
|
415
|
+
splitRouteModules && routeChunkOptions?.rootRouteFile
|
|
416
|
+
? {
|
|
417
|
+
splitRouteModules,
|
|
418
|
+
appDirectory: context,
|
|
419
|
+
rootRouteFile: routeChunkOptions.rootRouteFile,
|
|
420
|
+
}
|
|
421
|
+
: null;
|
|
422
|
+
|
|
423
|
+
const getAssetsForChunk = createChunkAssetResolver(clientStats);
|
|
424
|
+
const getModulePathForChunk = (chunkName: string): string | undefined => {
|
|
425
|
+
const { js: jsAssets } = getAssetsForChunk(chunkName);
|
|
426
|
+
return jsAssets[0] ? combineURLs(assetPrefix, jsAssets[0]) : undefined;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const manifestEntries = yield* Effect.forEach(
|
|
430
|
+
Object.entries(routes),
|
|
431
|
+
([key, route]) =>
|
|
432
|
+
Effect.gen(function* () {
|
|
433
|
+
const routeEntryName = getRouteEntryName(route);
|
|
434
|
+
const { js: jsAssets, css: discoveredCssAssets } =
|
|
435
|
+
getAssetsForChunk(routeEntryName);
|
|
436
|
+
const routeFilePath = resolve(context, route.file);
|
|
437
|
+
const routeAnalysis = yield* analyzeRouteForManifestEffect({
|
|
438
|
+
discoveredCssAssets,
|
|
439
|
+
isBuild,
|
|
440
|
+
routeChunkCache: routeChunkOptions?.cache,
|
|
441
|
+
routeChunkConfig,
|
|
442
|
+
routeEntryName,
|
|
443
|
+
routeFilePath,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const hasClientAction = routeAnalysis.exports.has(
|
|
447
|
+
CLIENT_EXPORTS.clientAction
|
|
448
|
+
);
|
|
449
|
+
const hasClientLoader = routeAnalysis.exports.has(
|
|
450
|
+
CLIENT_EXPORTS.clientLoader
|
|
451
|
+
);
|
|
452
|
+
const hasClientMiddleware = routeAnalysis.exports.has(
|
|
453
|
+
CLIENT_EXPORTS.clientMiddleware
|
|
454
|
+
);
|
|
455
|
+
const hasDefaultExport = routeAnalysis.exports.has('default');
|
|
456
|
+
const routeChunkMap = routeAnalysis.hasRouteChunkByExportName;
|
|
457
|
+
|
|
458
|
+
if (isBuild && enforceSplitRouteModules && routeChunkConfig) {
|
|
459
|
+
validateRouteChunks({
|
|
460
|
+
config: routeChunkConfig,
|
|
461
|
+
id: routeFilePath,
|
|
462
|
+
valid: buildManifestChunkValidity(
|
|
463
|
+
routeAnalysis.exports,
|
|
464
|
+
routeChunkMap ?? createEmptyRouteChunkByExportName()
|
|
465
|
+
),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return [
|
|
470
|
+
key,
|
|
471
|
+
{
|
|
472
|
+
id: route.id,
|
|
473
|
+
parentId: route.parentId,
|
|
474
|
+
path: route.path,
|
|
475
|
+
index: route.index,
|
|
476
|
+
caseSensitive: route.caseSensitive,
|
|
477
|
+
module: combineURLs(assetPrefix, jsAssets[0] || ''),
|
|
478
|
+
clientActionModule: routeChunkMap?.clientAction
|
|
479
|
+
? getModulePathForChunk(
|
|
480
|
+
getRouteChunkEntryName(route.id, 'clientAction')
|
|
481
|
+
)
|
|
482
|
+
: undefined,
|
|
483
|
+
clientLoaderModule: routeChunkMap?.clientLoader
|
|
484
|
+
? getModulePathForChunk(
|
|
485
|
+
getRouteChunkEntryName(route.id, 'clientLoader')
|
|
486
|
+
)
|
|
487
|
+
: undefined,
|
|
488
|
+
clientMiddlewareModule: routeChunkMap?.clientMiddleware
|
|
489
|
+
? getModulePathForChunk(
|
|
490
|
+
getRouteChunkEntryName(route.id, 'clientMiddleware')
|
|
491
|
+
)
|
|
492
|
+
: undefined,
|
|
493
|
+
hydrateFallbackModule: routeChunkMap?.HydrateFallback
|
|
494
|
+
? getModulePathForChunk(
|
|
495
|
+
getRouteChunkEntryName(route.id, 'HydrateFallback')
|
|
496
|
+
)
|
|
497
|
+
: undefined,
|
|
498
|
+
hasAction: routeAnalysis.exports.has(SERVER_EXPORTS.action),
|
|
499
|
+
hasLoader: routeAnalysis.exports.has(SERVER_EXPORTS.loader),
|
|
500
|
+
hasClientAction,
|
|
501
|
+
hasClientLoader,
|
|
502
|
+
hasClientMiddleware,
|
|
503
|
+
hasDefaultExport,
|
|
504
|
+
hasErrorBoundary: routeAnalysis.exports.has(
|
|
505
|
+
CLIENT_EXPORTS.ErrorBoundary
|
|
506
|
+
),
|
|
507
|
+
imports: jsAssets.map(asset => combineURLs(assetPrefix, asset)),
|
|
508
|
+
css: routeAnalysis.cssAssets.map(asset =>
|
|
509
|
+
combineURLs(assetPrefix, asset)
|
|
510
|
+
),
|
|
511
|
+
},
|
|
512
|
+
routeAnalysis.routeModuleExports,
|
|
513
|
+
] as const;
|
|
514
|
+
}),
|
|
515
|
+
{
|
|
516
|
+
concurrency: Math.max(
|
|
517
|
+
1,
|
|
518
|
+
Math.min(ROUTE_ANALYSIS_CONCURRENCY, Object.keys(routes).length)
|
|
519
|
+
),
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
const routeModuleExportsByRouteId: RouteManifestModuleExports = {};
|
|
524
|
+
for (const [
|
|
525
|
+
key,
|
|
526
|
+
routeManifestItem,
|
|
527
|
+
routeModuleExports,
|
|
528
|
+
] of manifestEntries) {
|
|
529
|
+
result[key] = routeManifestItem;
|
|
530
|
+
routeModuleExportsByRouteId[key] = routeModuleExports;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const { js: entryJsAssets, css: entryCssAssets } =
|
|
534
|
+
getAssetsForChunk('entry.client');
|
|
535
|
+
|
|
536
|
+
const fingerprintedValues = {
|
|
537
|
+
entry: {
|
|
538
|
+
module: combineURLs(assetPrefix, entryJsAssets[0] || ''),
|
|
539
|
+
imports: entryJsAssets.map(asset => combineURLs(assetPrefix, asset)),
|
|
540
|
+
css: entryCssAssets.map(asset => combineURLs(assetPrefix, asset)),
|
|
541
|
+
},
|
|
542
|
+
routes: result,
|
|
543
|
+
};
|
|
544
|
+
const version = getManifestVersion(fingerprintedValues, isBuild);
|
|
545
|
+
const manifestPath = getReactRouterManifestPath({
|
|
546
|
+
version,
|
|
547
|
+
isBuild,
|
|
548
|
+
entryModulePath: entryJsAssets[0],
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const manifest = {
|
|
552
|
+
version,
|
|
553
|
+
url: combineURLs(assetPrefix, manifestPath),
|
|
554
|
+
hmr: undefined,
|
|
555
|
+
entry: fingerprintedValues.entry,
|
|
556
|
+
sri: undefined,
|
|
557
|
+
routes: result,
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
manifest,
|
|
562
|
+
moduleExportsByRouteId: routeModuleExportsByRouteId,
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export async function generateReactRouterManifestForDev(
|
|
568
|
+
routes: Record<string, Route>,
|
|
569
|
+
options: PluginOptions,
|
|
570
|
+
clientStats: ReactRouterManifestStats | undefined,
|
|
571
|
+
context: string,
|
|
572
|
+
assetPrefix = '/',
|
|
573
|
+
routeChunkOptions?: RouteChunkManifestOptions
|
|
574
|
+
): Promise<ReactRouterManifestGenerationResult> {
|
|
575
|
+
return runPluginEffect(
|
|
576
|
+
generateReactRouterManifestForDevEffect(
|
|
577
|
+
routes,
|
|
578
|
+
options,
|
|
579
|
+
clientStats,
|
|
580
|
+
context,
|
|
581
|
+
assetPrefix,
|
|
582
|
+
routeChunkOptions
|
|
583
|
+
)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export async function getReactRouterManifestForDev(
|
|
588
|
+
...args: Parameters<typeof generateReactRouterManifestForDev>
|
|
589
|
+
): Promise<ReactRouterManifestForDev> {
|
|
590
|
+
return (await generateReactRouterManifestForDev(...args)).manifest;
|
|
591
|
+
}
|