rsbuild-plugin-react-router 0.1.1 → 0.3.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 +185 -204
- package/dist/451.js +1087 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +1 -1
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/config-imports.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +39 -0
- package/dist/dev-runtime-compilation.d.ts +32 -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 +11 -2
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +3085 -1272
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2039 -1299
- package/dist/lazy-compilation.d.ts +5 -0
- package/dist/manifest.d.ts +32 -9
- 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 +14 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +32 -0
- package/dist/prerender.d.ts +28 -2
- package/dist/react-router-config.d.ts +8 -1
- 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-utils.d.ts +3 -2
- package/dist/templates/entry.client.js +3 -3
- package/dist/templates/entry.server.cjs +11 -8
- package/dist/templates/entry.server.js +5 -5
- package/dist/typegen.d.ts +2 -0
- package/dist/types.d.ts +30 -12
- 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 +26 -22
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +168 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +34 -0
- package/src/config-imports.ts +45 -0
- package/src/constants.ts +91 -0
- package/src/dev-generation.ts +700 -0
- package/src/dev-runtime-artifacts.ts +207 -0
- package/src/dev-runtime-compilation.ts +92 -0
- package/src/dev-runtime-controller.ts +462 -0
- package/src/dev-runtime-session.ts +184 -0
- package/src/dev-server.ts +58 -0
- package/src/export-utils.ts +219 -0
- package/src/index.ts +1025 -0
- package/src/lazy-compilation.ts +94 -0
- package/src/manifest.ts +480 -0
- package/src/modify-browser-manifest.ts +245 -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 +332 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +623 -0
- package/src/prerender.ts +367 -0
- package/src/react-router-config.ts +220 -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 +322 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-utils.ts +210 -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 +49 -0
- package/src/types.ts +82 -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/906.js +0 -1
- package/dist/946.js +0 -1
- package/dist/babel.d.ts +0 -8
- package/dist/rslib-runtime.js +0 -23
package/src/prerender.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import type { Config } from './react-router-config.js';
|
|
2
|
+
import type { RouteConfigEntry } from '@react-router/dev/routes';
|
|
3
|
+
import { matchRoutes } from 'react-router';
|
|
4
|
+
|
|
5
|
+
type ReactRouterPrerenderConfig = Config['prerender'];
|
|
6
|
+
type MatchRouteObject =
|
|
7
|
+
Parameters<typeof matchRoutes>[0] extends Array<infer R> ? R : never;
|
|
8
|
+
|
|
9
|
+
type PrerenderPathsConfig =
|
|
10
|
+
| boolean
|
|
11
|
+
| string[]
|
|
12
|
+
| ((args: {
|
|
13
|
+
getStaticPaths: () => string[];
|
|
14
|
+
}) => boolean | string[] | Promise<boolean | string[]>);
|
|
15
|
+
|
|
16
|
+
type PrerenderConfigObject = Extract<
|
|
17
|
+
NonNullable<ReactRouterPrerenderConfig>,
|
|
18
|
+
{ paths: unknown }
|
|
19
|
+
> & {
|
|
20
|
+
unstable_concurrency?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type PrerenderConfig = ReactRouterPrerenderConfig | PrerenderConfigObject;
|
|
24
|
+
type PrerenderConcurrencyConfig =
|
|
25
|
+
| {
|
|
26
|
+
key: 'prerender.concurrency' | 'prerender.unstable_concurrency';
|
|
27
|
+
value: number;
|
|
28
|
+
}
|
|
29
|
+
| undefined;
|
|
30
|
+
|
|
31
|
+
type PrerenderResolveOptions = {
|
|
32
|
+
logWarning?: boolean;
|
|
33
|
+
warn?: (message: string) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type StaticPrerenderPaths = {
|
|
37
|
+
paths: string[];
|
|
38
|
+
paramRoutes: string[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type SsrFalsePrerenderRoute = {
|
|
42
|
+
id: string;
|
|
43
|
+
parentId?: string;
|
|
44
|
+
path?: string;
|
|
45
|
+
index?: boolean;
|
|
46
|
+
hasLoader?: boolean;
|
|
47
|
+
hasClientLoader?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type SsrFalsePrerenderExportOptions = {
|
|
51
|
+
routes: Record<string, SsrFalsePrerenderRoute>;
|
|
52
|
+
manifestRoutes: Record<string, SsrFalsePrerenderRoute>;
|
|
53
|
+
routeExports: Record<string, readonly string[] | undefined>;
|
|
54
|
+
prerenderPaths: string[];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const normalizePath = (value: string): string =>
|
|
58
|
+
value.replace(/\/\/+/g, '/').replace(/(.+)\/$/, '$1');
|
|
59
|
+
|
|
60
|
+
const groupRoutesByParentId = (
|
|
61
|
+
manifest: Record<string, any>
|
|
62
|
+
): Record<string, any[]> => {
|
|
63
|
+
const grouped: Record<string, any[]> = {};
|
|
64
|
+
Object.values(manifest).forEach(route => {
|
|
65
|
+
if (!route) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const parentId = route.parentId || '';
|
|
69
|
+
grouped[parentId] ??= [];
|
|
70
|
+
grouped[parentId].push(route);
|
|
71
|
+
});
|
|
72
|
+
return grouped;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const createPrerenderRoutes = (
|
|
76
|
+
manifest: Record<string, any>,
|
|
77
|
+
parentId = '',
|
|
78
|
+
grouped: Record<string, any[]> = groupRoutesByParentId(manifest)
|
|
79
|
+
): MatchRouteObject[] => {
|
|
80
|
+
return (grouped[parentId] || []).map(route => {
|
|
81
|
+
const common = { id: route.id, path: route.path };
|
|
82
|
+
if (route.index) {
|
|
83
|
+
return { index: true, ...common } as MatchRouteObject;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
...common,
|
|
87
|
+
children: createPrerenderRoutes(manifest, route.id, grouped),
|
|
88
|
+
} as MatchRouteObject;
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const normalizePrerenderMatchPath = (path: string): string =>
|
|
93
|
+
`/${path}/`.replace(/^\/\/+/, '/');
|
|
94
|
+
|
|
95
|
+
export const withBuildRequest = async <T>(
|
|
96
|
+
input: string | URL,
|
|
97
|
+
init: RequestInit | undefined,
|
|
98
|
+
handle: (request: Request) => Promise<T>
|
|
99
|
+
): Promise<T> => {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
try {
|
|
102
|
+
return await handle(
|
|
103
|
+
new Request(input, {
|
|
104
|
+
...init,
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
} finally {
|
|
109
|
+
controller.abort();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const getSsrFalsePrerenderExportErrors = ({
|
|
114
|
+
routes,
|
|
115
|
+
manifestRoutes,
|
|
116
|
+
routeExports,
|
|
117
|
+
prerenderPaths,
|
|
118
|
+
}: SsrFalsePrerenderExportOptions): string[] => {
|
|
119
|
+
if (prerenderPaths.length === 0) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const prerenderRoutes = createPrerenderRoutes(routes);
|
|
124
|
+
const prerenderedRoutes = new Set<string>();
|
|
125
|
+
for (const path of prerenderPaths) {
|
|
126
|
+
const matches = matchRoutes(
|
|
127
|
+
prerenderRoutes,
|
|
128
|
+
normalizePrerenderMatchPath(path)
|
|
129
|
+
);
|
|
130
|
+
if (!matches) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Unable to prerender path because it does not match any routes: ${path}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
matches.forEach(match => prerenderedRoutes.add(match.route.id as string));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const errors: string[] = [];
|
|
139
|
+
for (const [routeId, route] of Object.entries(manifestRoutes)) {
|
|
140
|
+
const exports = routeExports[routeId] ?? [];
|
|
141
|
+
const invalidApis: string[] = [];
|
|
142
|
+
|
|
143
|
+
if (exports.includes('headers')) invalidApis.push('headers');
|
|
144
|
+
if (exports.includes('action')) invalidApis.push('action');
|
|
145
|
+
|
|
146
|
+
if (invalidApis.length > 0) {
|
|
147
|
+
errors.push(
|
|
148
|
+
`Prerender: ${invalidApis.length} invalid route export(s) in ` +
|
|
149
|
+
`\`${routeId}\` when pre-rendering with \`ssr:false\`: ` +
|
|
150
|
+
`${invalidApis.map(api => `\`${api}\``).join(', ')}. ` +
|
|
151
|
+
`See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!prerenderedRoutes.has(routeId)) {
|
|
156
|
+
if (exports.includes('loader')) {
|
|
157
|
+
errors.push(
|
|
158
|
+
`Prerender: 1 invalid route export in \`${routeId}\` when pre-rendering with ` +
|
|
159
|
+
`\`ssr:false\`: \`loader\`. ` +
|
|
160
|
+
`See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let parentRoute =
|
|
165
|
+
route.parentId && manifestRoutes[route.parentId]
|
|
166
|
+
? manifestRoutes[route.parentId]
|
|
167
|
+
: null;
|
|
168
|
+
while (parentRoute) {
|
|
169
|
+
if (parentRoute.hasLoader && !parentRoute.hasClientLoader) {
|
|
170
|
+
errors.push(
|
|
171
|
+
`Prerender: 1 invalid route export in \`${parentRoute.id}\` when ` +
|
|
172
|
+
`pre-rendering with \`ssr:false\`: \`loader\`. ` +
|
|
173
|
+
`See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
parentRoute =
|
|
177
|
+
parentRoute.parentId && manifestRoutes[parentRoute.parentId]
|
|
178
|
+
? manifestRoutes[parentRoute.parentId]
|
|
179
|
+
: null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return errors;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const getStaticPrerenderPaths = (
|
|
188
|
+
routes: RouteConfigEntry[]
|
|
189
|
+
): StaticPrerenderPaths => {
|
|
190
|
+
const paths = ['/'];
|
|
191
|
+
const paramRoutes: string[] = [];
|
|
192
|
+
|
|
193
|
+
const recurse = (
|
|
194
|
+
entries: RouteConfigEntry[],
|
|
195
|
+
prefix = '',
|
|
196
|
+
hasDynamicParent = false
|
|
197
|
+
) => {
|
|
198
|
+
for (const route of entries) {
|
|
199
|
+
const routePath = route.path ?? '';
|
|
200
|
+
const segments = routePath.split('/').filter(Boolean);
|
|
201
|
+
const hasDynamicSegment =
|
|
202
|
+
segments.some(segment => segment.startsWith(':') || segment === '*') ||
|
|
203
|
+
routePath === '*';
|
|
204
|
+
const nextHasDynamic = hasDynamicParent || hasDynamicSegment;
|
|
205
|
+
|
|
206
|
+
if (routePath) {
|
|
207
|
+
const fullPath = normalizePath([prefix, routePath].join('/'));
|
|
208
|
+
if (nextHasDynamic) {
|
|
209
|
+
paramRoutes.push(fullPath);
|
|
210
|
+
} else {
|
|
211
|
+
paths.push(fullPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (route.children) {
|
|
216
|
+
const newPrefix = routePath
|
|
217
|
+
? normalizePath([prefix, routePath].join('/'))
|
|
218
|
+
: prefix;
|
|
219
|
+
recurse(route.children, newPrefix, nextHasDynamic);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
recurse(routes);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
paths: paths.map(normalizePath),
|
|
228
|
+
paramRoutes: paramRoutes.map(normalizePath),
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const resolvePrerenderPaths = async (
|
|
233
|
+
prerender: PrerenderConfig,
|
|
234
|
+
ssr: boolean,
|
|
235
|
+
routes: RouteConfigEntry[],
|
|
236
|
+
options: PrerenderResolveOptions = {}
|
|
237
|
+
): Promise<string[]> => {
|
|
238
|
+
if (prerender == null || prerender === false) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let pathsConfig: PrerenderPathsConfig | undefined;
|
|
243
|
+
if (
|
|
244
|
+
typeof prerender === 'object' &&
|
|
245
|
+
prerender !== null &&
|
|
246
|
+
'paths' in prerender
|
|
247
|
+
) {
|
|
248
|
+
pathsConfig = (prerender as PrerenderConfigObject)?.paths;
|
|
249
|
+
} else {
|
|
250
|
+
pathsConfig = prerender as PrerenderPathsConfig;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (pathsConfig === false) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const { paths, paramRoutes } = getStaticPrerenderPaths(routes);
|
|
258
|
+
|
|
259
|
+
if (pathsConfig === true) {
|
|
260
|
+
if (options.logWarning && !ssr && paramRoutes.length > 0) {
|
|
261
|
+
const warn =
|
|
262
|
+
options.warn ??
|
|
263
|
+
((message: string) => {
|
|
264
|
+
console.warn(message);
|
|
265
|
+
});
|
|
266
|
+
warn(
|
|
267
|
+
[
|
|
268
|
+
'Warning: Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.',
|
|
269
|
+
'You may want to use the `prerender()` API to prerender the following paths:',
|
|
270
|
+
...paramRoutes.map(path => ` - ${path}`),
|
|
271
|
+
].join('\n')
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return paths;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (typeof pathsConfig === 'function') {
|
|
278
|
+
const resolved = await pathsConfig({
|
|
279
|
+
getStaticPaths: () => paths,
|
|
280
|
+
});
|
|
281
|
+
if (resolved === true) {
|
|
282
|
+
return paths;
|
|
283
|
+
}
|
|
284
|
+
if (resolved === false || resolved == null) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
return resolved;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return pathsConfig ?? [];
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const getPrerenderConcurrency = (
|
|
294
|
+
prerender: PrerenderConfig,
|
|
295
|
+
_cpuCount?: number
|
|
296
|
+
): number => {
|
|
297
|
+
const config = getPrerenderConfigObject(prerender);
|
|
298
|
+
const value = getPrerenderConcurrencyConfig(config)?.value;
|
|
299
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
// Match React Router's default. Parallel prerendering can fan out loaders and
|
|
303
|
+
// external requests, so it must remain explicitly opt-in.
|
|
304
|
+
return 1;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const getPrerenderConfigObject = (
|
|
308
|
+
prerender: PrerenderConfig
|
|
309
|
+
): PrerenderConfigObject | null =>
|
|
310
|
+
typeof prerender === 'object' &&
|
|
311
|
+
prerender !== null &&
|
|
312
|
+
!Array.isArray(prerender)
|
|
313
|
+
? (prerender as PrerenderConfigObject)
|
|
314
|
+
: null;
|
|
315
|
+
|
|
316
|
+
const getPrerenderConcurrencyConfig = (
|
|
317
|
+
config: PrerenderConfigObject | null
|
|
318
|
+
): PrerenderConcurrencyConfig => {
|
|
319
|
+
if (config?.concurrency !== undefined) {
|
|
320
|
+
return {
|
|
321
|
+
key: 'prerender.concurrency',
|
|
322
|
+
value: config.concurrency,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (config?.unstable_concurrency !== undefined) {
|
|
327
|
+
return {
|
|
328
|
+
key: 'prerender.unstable_concurrency',
|
|
329
|
+
value: config.unstable_concurrency,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const isValidPrerenderPathsConfig = (
|
|
335
|
+
value: unknown
|
|
336
|
+
): value is PrerenderPathsConfig =>
|
|
337
|
+
typeof value === 'boolean' ||
|
|
338
|
+
typeof value === 'function' ||
|
|
339
|
+
Array.isArray(value);
|
|
340
|
+
|
|
341
|
+
export const validatePrerenderConfig = (
|
|
342
|
+
prerender: PrerenderConfig
|
|
343
|
+
): string | null => {
|
|
344
|
+
if (!prerender) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const config = getPrerenderConfigObject(prerender);
|
|
349
|
+
const pathsConfig = config && 'paths' in config ? config.paths : prerender;
|
|
350
|
+
|
|
351
|
+
const isValidConfig = isValidPrerenderPathsConfig(pathsConfig);
|
|
352
|
+
|
|
353
|
+
if (!isValidConfig) {
|
|
354
|
+
return 'The `prerender`/`prerender.paths` config must be a boolean, an array of string paths, or a function returning a boolean or array of string paths.';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const concurrency = getPrerenderConcurrencyConfig(config);
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
concurrency &&
|
|
361
|
+
(!Number.isInteger(concurrency.value) || concurrency.value <= 0)
|
|
362
|
+
) {
|
|
363
|
+
return `The \`${concurrency.key}\` config must be a positive integer if specified.`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return null;
|
|
367
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BuildManifest as ReactRouterBuildManifest,
|
|
3
|
+
Config as ReactRouterConfig,
|
|
4
|
+
} from '@react-router/dev/config';
|
|
5
|
+
import type { NormalizedConfig } from '@rsbuild/core';
|
|
6
|
+
import type { RouteConfigEntry } from '@react-router/dev/routes';
|
|
7
|
+
|
|
8
|
+
export type BuildEndHook = {
|
|
9
|
+
bivarianceHack(args: {
|
|
10
|
+
buildManifest: ReactRouterBuildManifest | undefined;
|
|
11
|
+
reactRouterConfig: ResolvedReactRouterConfig;
|
|
12
|
+
viteConfig: NormalizedConfig;
|
|
13
|
+
}): void | Promise<void>;
|
|
14
|
+
}['bivarianceHack'];
|
|
15
|
+
|
|
16
|
+
type SplitRouteModulesConfig = boolean | 'enforce';
|
|
17
|
+
|
|
18
|
+
export type Config = Omit<
|
|
19
|
+
ReactRouterConfig,
|
|
20
|
+
'buildEnd' | 'future' | 'splitRouteModules' | 'subResourceIntegrity'
|
|
21
|
+
> & {
|
|
22
|
+
buildEnd?: BuildEndHook;
|
|
23
|
+
future?: Partial<FutureConfig>;
|
|
24
|
+
splitRouteModules?: SplitRouteModulesConfig;
|
|
25
|
+
subResourceIntegrity?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type FutureConfig = {
|
|
29
|
+
unstable_optimizeDeps: boolean;
|
|
30
|
+
unstable_subResourceIntegrity: boolean;
|
|
31
|
+
unstable_trailingSlashAwareDataRequests: boolean;
|
|
32
|
+
v8_middleware: boolean;
|
|
33
|
+
v8_splitRouteModules: boolean | 'enforce';
|
|
34
|
+
v8_viteEnvironmentApi: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type RouteManifestEntry = {
|
|
38
|
+
id: string;
|
|
39
|
+
parentId?: string;
|
|
40
|
+
path?: string;
|
|
41
|
+
index?: boolean;
|
|
42
|
+
caseSensitive?: boolean;
|
|
43
|
+
file: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type RouteManifest = Record<string, RouteManifestEntry>;
|
|
47
|
+
|
|
48
|
+
export type ResolvedReactRouterConfig = Readonly<{
|
|
49
|
+
appDirectory: string;
|
|
50
|
+
basename: string;
|
|
51
|
+
buildDirectory: string;
|
|
52
|
+
buildEnd?: Config['buildEnd'];
|
|
53
|
+
future: FutureConfig;
|
|
54
|
+
prerender: Config['prerender'];
|
|
55
|
+
routeDiscovery: Config['routeDiscovery'];
|
|
56
|
+
routes: RouteManifest;
|
|
57
|
+
serverBuildFile: NonNullable<ReactRouterConfig['serverBuildFile']>;
|
|
58
|
+
serverBundles?: Config['serverBundles'];
|
|
59
|
+
serverModuleFormat: NonNullable<ReactRouterConfig['serverModuleFormat']>;
|
|
60
|
+
splitRouteModules: SplitRouteModulesConfig;
|
|
61
|
+
subResourceIntegrity: boolean;
|
|
62
|
+
ssr: NonNullable<ReactRouterConfig['ssr']>;
|
|
63
|
+
allowedActionOrigins: string[] | false;
|
|
64
|
+
unstable_routeConfig: RouteConfigEntry[];
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
const DEFAULT_CONFIG = {
|
|
68
|
+
appDirectory: 'app',
|
|
69
|
+
basename: '/',
|
|
70
|
+
buildDirectory: 'build',
|
|
71
|
+
serverBuildFile: 'index.js',
|
|
72
|
+
serverModuleFormat: 'esm',
|
|
73
|
+
splitRouteModules: true,
|
|
74
|
+
subResourceIntegrity: false,
|
|
75
|
+
ssr: true,
|
|
76
|
+
future: {
|
|
77
|
+
unstable_optimizeDeps: false,
|
|
78
|
+
unstable_subResourceIntegrity: false,
|
|
79
|
+
unstable_trailingSlashAwareDataRequests: false,
|
|
80
|
+
v8_middleware: false,
|
|
81
|
+
v8_splitRouteModules: false,
|
|
82
|
+
v8_viteEnvironmentApi: false,
|
|
83
|
+
} satisfies FutureConfig,
|
|
84
|
+
routeDiscovery: undefined,
|
|
85
|
+
prerender: undefined,
|
|
86
|
+
serverBundles: undefined,
|
|
87
|
+
buildEnd: undefined,
|
|
88
|
+
allowedActionOrigins: false,
|
|
89
|
+
routes: {},
|
|
90
|
+
unstable_routeConfig: [],
|
|
91
|
+
} as const satisfies ResolvedReactRouterConfig;
|
|
92
|
+
|
|
93
|
+
const mergeReactRouterConfig = (...configs: Config[]): Config => {
|
|
94
|
+
const reducer = (configA: Config, configB: Config): Config => {
|
|
95
|
+
const mergeRequired = (key: keyof Config) =>
|
|
96
|
+
configA[key] !== undefined && configB[key] !== undefined;
|
|
97
|
+
return {
|
|
98
|
+
...configA,
|
|
99
|
+
...configB,
|
|
100
|
+
...(mergeRequired('buildEnd')
|
|
101
|
+
? {
|
|
102
|
+
buildEnd: async (
|
|
103
|
+
...args: Parameters<NonNullable<Config['buildEnd']>>
|
|
104
|
+
) => {
|
|
105
|
+
await Promise.all([
|
|
106
|
+
configA.buildEnd?.(...args),
|
|
107
|
+
configB.buildEnd?.(...args),
|
|
108
|
+
]);
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
...(mergeRequired('future')
|
|
113
|
+
? {
|
|
114
|
+
future: {
|
|
115
|
+
...configA.future,
|
|
116
|
+
...configB.future,
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
: {}),
|
|
120
|
+
...(mergeRequired('presets')
|
|
121
|
+
? {
|
|
122
|
+
presets: [...(configA.presets ?? []), ...(configB.presets ?? [])],
|
|
123
|
+
}
|
|
124
|
+
: {}),
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
return configs.reduce(reducer, {});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const normalizeSubResourceIntegrity = (config: Config): Config => {
|
|
131
|
+
const subResourceIntegrity =
|
|
132
|
+
config.subResourceIntegrity ?? config.future?.unstable_subResourceIntegrity;
|
|
133
|
+
|
|
134
|
+
if (subResourceIntegrity === undefined) {
|
|
135
|
+
return config;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...config,
|
|
140
|
+
subResourceIntegrity,
|
|
141
|
+
future: {
|
|
142
|
+
...(config.future ?? {}),
|
|
143
|
+
unstable_subResourceIntegrity: subResourceIntegrity,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const resolveReactRouterConfig = async (
|
|
149
|
+
reactRouterUserConfig: Config
|
|
150
|
+
): Promise<{
|
|
151
|
+
resolved: ResolvedReactRouterConfig;
|
|
152
|
+
presets: NonNullable<Config['presets']>;
|
|
153
|
+
hasConfiguredServerModuleFormat: boolean;
|
|
154
|
+
}> => {
|
|
155
|
+
const presets = await Promise.all(
|
|
156
|
+
(reactRouterUserConfig.presets ?? []).map(async preset => {
|
|
157
|
+
if (!preset.name) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'React Router presets must have a `name` property defined.'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!preset.reactRouterConfig) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const { buildEnd: _buildEnd, ...reactRouterUserConfigForPreset } =
|
|
166
|
+
reactRouterUserConfig;
|
|
167
|
+
const presetConfig = await preset.reactRouterConfig({
|
|
168
|
+
reactRouterUserConfig: reactRouterUserConfigForPreset,
|
|
169
|
+
});
|
|
170
|
+
if (!presetConfig) return null;
|
|
171
|
+
const { presets: _presets, ...rest } = presetConfig as Config;
|
|
172
|
+
return rest;
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const userAndPresetConfigs = mergeReactRouterConfig(
|
|
177
|
+
...(presets.filter(Boolean) as Config[]).map(normalizeSubResourceIntegrity),
|
|
178
|
+
normalizeSubResourceIntegrity(reactRouterUserConfig)
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const subResourceIntegrity =
|
|
182
|
+
userAndPresetConfigs.subResourceIntegrity ??
|
|
183
|
+
userAndPresetConfigs.future?.unstable_subResourceIntegrity ??
|
|
184
|
+
DEFAULT_CONFIG.subResourceIntegrity;
|
|
185
|
+
const resolvedFuture: FutureConfig = {
|
|
186
|
+
...DEFAULT_CONFIG.future,
|
|
187
|
+
...(userAndPresetConfigs.future ?? {}),
|
|
188
|
+
unstable_subResourceIntegrity: subResourceIntegrity,
|
|
189
|
+
};
|
|
190
|
+
const splitRouteModules =
|
|
191
|
+
userAndPresetConfigs.splitRouteModules ??
|
|
192
|
+
userAndPresetConfigs.future?.v8_splitRouteModules ??
|
|
193
|
+
DEFAULT_CONFIG.splitRouteModules;
|
|
194
|
+
|
|
195
|
+
let resolved: ResolvedReactRouterConfig = {
|
|
196
|
+
...DEFAULT_CONFIG,
|
|
197
|
+
...userAndPresetConfigs,
|
|
198
|
+
future: resolvedFuture,
|
|
199
|
+
splitRouteModules,
|
|
200
|
+
subResourceIntegrity,
|
|
201
|
+
allowedActionOrigins:
|
|
202
|
+
userAndPresetConfigs.allowedActionOrigins ??
|
|
203
|
+
DEFAULT_CONFIG.allowedActionOrigins,
|
|
204
|
+
routes: DEFAULT_CONFIG.routes,
|
|
205
|
+
unstable_routeConfig: DEFAULT_CONFIG.unstable_routeConfig,
|
|
206
|
+
};
|
|
207
|
+
if (!resolved.ssr) {
|
|
208
|
+
resolved = {
|
|
209
|
+
...resolved,
|
|
210
|
+
serverBundles: undefined,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
resolved,
|
|
216
|
+
presets: reactRouterUserConfig.presets ?? [],
|
|
217
|
+
hasConfiguredServerModuleFormat:
|
|
218
|
+
userAndPresetConfigs.serverModuleFormat !== undefined,
|
|
219
|
+
};
|
|
220
|
+
};
|