rsbuild-plugin-react-router 0.3.0 → 0.4.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 +88 -35
- package/dist/451.js +164 -30
- package/dist/build-manifest.d.ts +6 -3
- package/dist/build-output-transforms.d.ts +2 -1
- package/dist/concurrency.d.ts +2 -1
- package/dist/config-imports.d.ts +7 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +2 -2
- package/dist/dev-hmr.d.ts +45 -0
- package/dist/dev-runtime-artifacts.d.ts +9 -1
- package/dist/dev-runtime-compilation.d.ts +17 -2
- package/dist/dev-runtime-controller.d.ts +6 -1
- package/dist/dev-server.d.ts +5 -0
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +2 -2
- package/dist/index.cjs +11852 -848
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8289 -773
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +2 -1
- package/dist/manifest.d.ts +19 -4
- package/dist/parallel-route-transforms.d.ts +17 -2
- package/dist/prerender-build.d.ts +4 -0
- package/dist/prerender.d.ts +0 -1
- package/dist/react-router-config.d.ts +8 -5
- package/dist/route-artifacts.d.ts +10 -2
- package/dist/route-transform-tasks.d.ts +3 -0
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/typegen.d.ts +14 -1
- package/dist/types.d.ts +15 -6
- package/package.json +18 -10
- package/src/build-manifest.ts +110 -73
- package/src/build-output-transforms.ts +5 -0
- package/src/concurrency.ts +3 -22
- package/src/config-imports.ts +38 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +43 -53
- package/src/dev-hmr.ts +431 -0
- package/src/dev-runtime-artifacts.ts +50 -18
- package/src/dev-runtime-compilation.ts +80 -1
- package/src/dev-runtime-controller.ts +155 -31
- package/src/dev-runtime-session.ts +18 -11
- package/src/dev-server.ts +31 -1
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +82 -23
- package/src/index.ts +166 -153
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +12 -5
- package/src/manifest.ts +366 -255
- package/src/modify-browser-manifest.ts +2 -1
- package/src/parallel-route-transforms.ts +195 -69
- package/src/prerender-build.ts +147 -63
- package/src/prerender.ts +1 -19
- package/src/react-router-config.ts +98 -73
- package/src/route-artifacts.ts +122 -3
- package/src/route-export-resolution.ts +3 -3
- package/src/route-transform-tasks.ts +173 -2
- package/src/route-watch.ts +119 -84
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +7 -106
- package/src/ssr-externals.ts +1 -1
- package/src/typegen.ts +162 -33
- package/src/types.ts +16 -6
package/src/route-watch.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { watch, type FSWatcher } from 'node:fs';
|
|
2
2
|
import { access, mkdir, readdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import type { RsbuildConfig } from '@rsbuild/core';
|
|
4
|
+
import * as Effect from 'effect/Effect';
|
|
4
5
|
import { dirname, resolve } from 'pathe';
|
|
6
|
+
import { getCappedPluginConcurrency } from './concurrency.js';
|
|
7
|
+
import {
|
|
8
|
+
createDelayedPluginTask,
|
|
9
|
+
runPluginEffect,
|
|
10
|
+
tryPluginPromise,
|
|
11
|
+
} from './effect-runtime.js';
|
|
5
12
|
import type { Route } from './types.js';
|
|
6
13
|
|
|
7
14
|
const ROUTE_RESTART_MARKER_ASSET = '.react-router/route-watch';
|
|
8
15
|
const INITIAL_RESTART_MARKER_CONTENT = 'react-router-route-watch';
|
|
16
|
+
const ROUTE_TOPOLOGY_RESCAN_DEBOUNCE_MS = 100;
|
|
17
|
+
const ROUTE_DIRECTORY_SCAN_CONCURRENCY = getCappedPluginConcurrency();
|
|
9
18
|
|
|
10
19
|
type RouteManifestSnapshotEntry = Pick<
|
|
11
20
|
Route,
|
|
@@ -111,32 +120,32 @@ const areSetsEqual = <T>(left: Set<T>, right: Set<T>): boolean => {
|
|
|
111
120
|
return true;
|
|
112
121
|
};
|
|
113
122
|
|
|
114
|
-
const readRouteDirectories =
|
|
123
|
+
const readRouteDirectories = (watchDirectory: string): Promise<Set<string>> => {
|
|
124
|
+
return runPluginEffect(readRouteDirectoriesEffect(watchDirectory));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const readRouteDirectoriesEffect = (
|
|
115
128
|
watchDirectory: string
|
|
116
|
-
):
|
|
129
|
+
): Effect.Effect<Set<string>, Error, never> => {
|
|
117
130
|
const directories = new Set<string>();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
})
|
|
131
|
+
const walkDirectory = (directory: string): Effect.Effect<void> =>
|
|
132
|
+
tryPluginPromise(() => readdir(directory, { withFileTypes: true })).pipe(
|
|
133
|
+
Effect.catchAll(() => Effect.succeed([])),
|
|
134
|
+
Effect.map(entries => {
|
|
135
|
+
directories.add(directory);
|
|
136
|
+
return entries
|
|
137
|
+
.filter(entry => entry.isDirectory())
|
|
138
|
+
.map(entry => resolve(directory, entry.name));
|
|
139
|
+
}),
|
|
140
|
+
Effect.flatMap(childDirectories =>
|
|
141
|
+
Effect.forEach(childDirectories, walkDirectory, {
|
|
142
|
+
concurrency: ROUTE_DIRECTORY_SCAN_CONCURRENCY,
|
|
143
|
+
discard: true,
|
|
144
|
+
})
|
|
145
|
+
)
|
|
135
146
|
);
|
|
136
|
-
};
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
return directories;
|
|
148
|
+
return walkDirectory(watchDirectory).pipe(Effect.as(directories));
|
|
140
149
|
};
|
|
141
150
|
|
|
142
151
|
export const createRouteTopologyWatcher = async ({
|
|
@@ -178,14 +187,17 @@ export const createRouteTopologyWatcher = async ({
|
|
|
178
187
|
routeTopology: initialRouteTopology ?? discoveredState.routeTopology,
|
|
179
188
|
};
|
|
180
189
|
let closed = false;
|
|
181
|
-
let rescanTimer: ReturnType<typeof setTimeout> | undefined;
|
|
182
190
|
let rescanQueue = Promise.resolve();
|
|
183
191
|
const directoryWatchers = new Map<string, DirectoryWatcher>();
|
|
184
192
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
const touchRestartMarkerEffect = (): Effect.Effect<void, Error, never> =>
|
|
194
|
+
tryPluginPromise(() =>
|
|
195
|
+
mkdir(dirname(restartMarkerPath), { recursive: true })
|
|
196
|
+
).pipe(
|
|
197
|
+
Effect.zipRight(
|
|
198
|
+
tryPluginPromise(() => writeFile(restartMarkerPath, String(Date.now())))
|
|
199
|
+
)
|
|
200
|
+
);
|
|
189
201
|
|
|
190
202
|
const closeRemovedDirectoryWatchers = (
|
|
191
203
|
nextDirectories: Set<string>
|
|
@@ -205,13 +217,17 @@ export const createRouteTopologyWatcher = async ({
|
|
|
205
217
|
}
|
|
206
218
|
try {
|
|
207
219
|
let watcher: DirectoryWatcher;
|
|
208
|
-
watcher = watchDirectoryOverride(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
watcher = watchDirectoryOverride(
|
|
221
|
+
directory,
|
|
222
|
+
() => rescanTask.reschedule(),
|
|
223
|
+
error => {
|
|
224
|
+
if (directoryWatchers.get(directory) === watcher) {
|
|
225
|
+
watcher.close();
|
|
226
|
+
directoryWatchers.delete(directory);
|
|
227
|
+
}
|
|
228
|
+
onError(error);
|
|
212
229
|
}
|
|
213
|
-
|
|
214
|
-
});
|
|
230
|
+
);
|
|
215
231
|
directoryWatchers.set(directory, watcher);
|
|
216
232
|
} catch (error) {
|
|
217
233
|
onError(error);
|
|
@@ -224,74 +240,94 @@ export const createRouteTopologyWatcher = async ({
|
|
|
224
240
|
watchNewDirectories(nextDirectories);
|
|
225
241
|
};
|
|
226
242
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
syncDirectoryWatchers(nextState.directories);
|
|
232
|
-
if (!areSetsEqual(state.routeTopology, nextState.routeTopology)) {
|
|
233
|
-
if (onRouteTopologyChange) {
|
|
234
|
-
// This is a notification boundary, not part of the rescan
|
|
235
|
-
// transaction. A custom-server callback may close this watcher while
|
|
236
|
-
// replacing its compiler, so awaiting it here would deadlock close().
|
|
237
|
-
state = nextState;
|
|
238
|
-
try {
|
|
239
|
-
void Promise.resolve(onRouteTopologyChange()).catch(onError);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
onError(error);
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
244
|
-
} else {
|
|
245
|
-
await touchRestartMarker();
|
|
246
|
-
}
|
|
243
|
+
const applyNextStateEffect = (
|
|
244
|
+
nextState: RouteDirectoryState
|
|
245
|
+
): Effect.Effect<void, Error, never> =>
|
|
246
|
+
Effect.suspend(() => {
|
|
247
247
|
if (closed) {
|
|
248
|
-
return;
|
|
248
|
+
return Effect.void;
|
|
249
|
+
}
|
|
250
|
+
syncDirectoryWatchers(nextState.directories);
|
|
251
|
+
if (!areSetsEqual(state.routeTopology, nextState.routeTopology)) {
|
|
252
|
+
if (onRouteTopologyChange) {
|
|
253
|
+
// This is a notification boundary, not part of the rescan
|
|
254
|
+
// transaction. A custom-server callback may close this watcher while
|
|
255
|
+
// replacing its compiler, so awaiting it here would deadlock close().
|
|
256
|
+
state = nextState;
|
|
257
|
+
return Effect.sync(() => {
|
|
258
|
+
try {
|
|
259
|
+
void Promise.resolve(onRouteTopologyChange()).catch(onError);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
onError(error);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return touchRestartMarkerEffect().pipe(
|
|
266
|
+
Effect.zipRight(
|
|
267
|
+
Effect.sync(() => {
|
|
268
|
+
if (!closed) {
|
|
269
|
+
state = nextState;
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
)
|
|
273
|
+
);
|
|
249
274
|
}
|
|
250
275
|
state = nextState;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
state = nextState;
|
|
254
|
-
};
|
|
276
|
+
return Effect.void;
|
|
277
|
+
});
|
|
255
278
|
|
|
256
|
-
const
|
|
257
|
-
if (closed) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
279
|
+
const runRescanEffect = (): Effect.Effect<void, never, never> => {
|
|
260
280
|
let nextDirectories: Set<string> | undefined;
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
return Effect.gen(function* () {
|
|
282
|
+
if (closed) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
nextDirectories = yield* readRouteDirectoriesEffect(watchDirectory);
|
|
263
286
|
if (closed) {
|
|
264
287
|
return;
|
|
265
288
|
}
|
|
266
289
|
const nextState = {
|
|
267
290
|
directories: nextDirectories,
|
|
268
|
-
routeTopology:
|
|
291
|
+
routeTopology: yield* tryPluginPromise(getRouteTopology),
|
|
269
292
|
};
|
|
270
293
|
if (closed) {
|
|
271
294
|
return;
|
|
272
295
|
}
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
296
|
+
yield* applyNextStateEffect(nextState);
|
|
297
|
+
}).pipe(
|
|
298
|
+
Effect.catchAll(error =>
|
|
299
|
+
Effect.sync(() => {
|
|
300
|
+
if (nextDirectories && !closed) {
|
|
301
|
+
syncDirectoryWatchers(nextDirectories);
|
|
302
|
+
}
|
|
303
|
+
onError(error);
|
|
304
|
+
})
|
|
305
|
+
)
|
|
306
|
+
);
|
|
280
307
|
};
|
|
281
308
|
|
|
282
309
|
const rescan = (): Promise<void> => {
|
|
283
|
-
rescanQueue = rescanQueue.then(
|
|
310
|
+
rescanQueue = rescanQueue.then(
|
|
311
|
+
() => runPluginEffect(runRescanEffect()),
|
|
312
|
+
() => runPluginEffect(runRescanEffect())
|
|
313
|
+
);
|
|
284
314
|
return rescanQueue;
|
|
285
315
|
};
|
|
286
316
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
317
|
+
const rescanTask = createDelayedPluginTask({
|
|
318
|
+
delayMs: ROUTE_TOPOLOGY_RESCAN_DEBOUNCE_MS,
|
|
319
|
+
run: () =>
|
|
320
|
+
Effect.suspend(() =>
|
|
321
|
+
closed ? Effect.void : tryPluginPromise(rescan).pipe(Effect.asVoid)
|
|
322
|
+
),
|
|
323
|
+
onError,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const cancelScheduledRescan = (): Promise<void> =>
|
|
327
|
+
runPluginEffect(rescanTask.cancelEffect());
|
|
328
|
+
|
|
329
|
+
const applyNextState = async (nextState: RouteDirectoryState) => {
|
|
330
|
+
await runPluginEffect(applyNextStateEffect(nextState));
|
|
295
331
|
};
|
|
296
332
|
|
|
297
333
|
try {
|
|
@@ -302,13 +338,12 @@ export const createRouteTopologyWatcher = async ({
|
|
|
302
338
|
|
|
303
339
|
return async () => {
|
|
304
340
|
if (closed) {
|
|
341
|
+
await cancelScheduledRescan();
|
|
305
342
|
await rescanQueue;
|
|
306
343
|
return;
|
|
307
344
|
}
|
|
308
345
|
closed = true;
|
|
309
|
-
|
|
310
|
-
clearTimeout(rescanTimer);
|
|
311
|
-
}
|
|
346
|
+
await cancelScheduledRescan();
|
|
312
347
|
for (const watcher of directoryWatchers.values()) {
|
|
313
348
|
watcher.close();
|
|
314
349
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Internal module: exposes the Effect-based ServerBuild resolution used by
|
|
2
|
+
// dev-runtime code. Not re-exported from the package entry so the public
|
|
3
|
+
// declaration graph stays free of `effect` types; external callers go through
|
|
4
|
+
// the Promise wrappers in server-utils.ts.
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import type { ServerBuild } from 'react-router';
|
|
7
|
+
import { tryPluginPromise, tryPluginSync } from './effect-runtime.js';
|
|
8
|
+
|
|
9
|
+
const RESOLVABLE_BUILD_EXPORTS = new Set([
|
|
10
|
+
'allowedActionOrigins',
|
|
11
|
+
'assets',
|
|
12
|
+
'assetsBuildDirectory',
|
|
13
|
+
'basename',
|
|
14
|
+
'entry',
|
|
15
|
+
'future',
|
|
16
|
+
'isSpaMode',
|
|
17
|
+
'prerender',
|
|
18
|
+
'publicPath',
|
|
19
|
+
'routeDiscovery',
|
|
20
|
+
'routes',
|
|
21
|
+
'ssr',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
29
|
+
return isRecord(value) && typeof value.then === 'function';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isRouteDiscovery(value: unknown): boolean {
|
|
33
|
+
return (
|
|
34
|
+
value === undefined ||
|
|
35
|
+
(isRecord(value) &&
|
|
36
|
+
(value.mode === 'initial' ||
|
|
37
|
+
(value.mode === 'lazy' &&
|
|
38
|
+
(value.manifestPath === undefined ||
|
|
39
|
+
typeof value.manifestPath === 'string'))))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveBuildExportsEffect(
|
|
44
|
+
build: Record<string, unknown>
|
|
45
|
+
): Effect.Effect<Record<string, unknown>, Error, never> {
|
|
46
|
+
const resolved = { ...build };
|
|
47
|
+
return Effect.forEach(
|
|
48
|
+
Object.keys(build),
|
|
49
|
+
key =>
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
if (!RESOLVABLE_BUILD_EXPORTS.has(key)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const value = build[key];
|
|
55
|
+
if (typeof value === 'function' && value.length === 0) {
|
|
56
|
+
const result = yield* tryPluginSync(() => value());
|
|
57
|
+
resolved[key] = isPromiseLike(result)
|
|
58
|
+
? yield* tryPluginPromise(() => result)
|
|
59
|
+
: result;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (isPromiseLike(value)) {
|
|
63
|
+
resolved[key] = yield* tryPluginPromise(() => value);
|
|
64
|
+
}
|
|
65
|
+
}),
|
|
66
|
+
{ discard: true }
|
|
67
|
+
).pipe(Effect.as(resolved));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isServerBuild(value: unknown): value is ServerBuild {
|
|
71
|
+
return Boolean(
|
|
72
|
+
isRecord(value) &&
|
|
73
|
+
isRecord(value.entry) &&
|
|
74
|
+
isRecord(value.entry.module) &&
|
|
75
|
+
typeof value.entry.module.default === 'function' &&
|
|
76
|
+
isRecord(value.routes) &&
|
|
77
|
+
isRecord(value.assets) &&
|
|
78
|
+
typeof value.assetsBuildDirectory === 'string' &&
|
|
79
|
+
(value.basename === undefined || typeof value.basename === 'string') &&
|
|
80
|
+
isRecord(value.future) &&
|
|
81
|
+
typeof value.isSpaMode === 'boolean' &&
|
|
82
|
+
Array.isArray(value.prerender) &&
|
|
83
|
+
typeof value.publicPath === 'string' &&
|
|
84
|
+
isRouteDiscovery(value.routeDiscovery) &&
|
|
85
|
+
typeof value.ssr === 'boolean'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveServerBuildCandidateEffect(
|
|
90
|
+
candidate: unknown
|
|
91
|
+
): Effect.Effect<ServerBuild | undefined, Error, never> {
|
|
92
|
+
if (!isRecord(candidate)) {
|
|
93
|
+
return Effect.succeed(undefined);
|
|
94
|
+
}
|
|
95
|
+
return resolveBuildExportsEffect(candidate).pipe(
|
|
96
|
+
Effect.map(resolved => (isServerBuild(resolved) ? resolved : undefined))
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function resolveServerBuildModuleEffect(
|
|
101
|
+
buildModule: unknown,
|
|
102
|
+
source: string
|
|
103
|
+
): Effect.Effect<ServerBuild, Error, never> {
|
|
104
|
+
return Effect.gen(function* () {
|
|
105
|
+
const moduleValue = isPromiseLike(buildModule)
|
|
106
|
+
? yield* tryPluginPromise(() => buildModule)
|
|
107
|
+
: buildModule;
|
|
108
|
+
const candidates = [() => moduleValue];
|
|
109
|
+
if (isRecord(moduleValue)) {
|
|
110
|
+
if ('default' in moduleValue) {
|
|
111
|
+
candidates.push(() => moduleValue.default);
|
|
112
|
+
}
|
|
113
|
+
if ('module.exports' in moduleValue) {
|
|
114
|
+
candidates.push(() => moduleValue['module.exports']);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const getCandidate of candidates) {
|
|
119
|
+
const candidate = yield* tryPluginPromise(() => getCandidate());
|
|
120
|
+
const serverBuild = yield* resolveServerBuildCandidateEffect(candidate);
|
|
121
|
+
if (serverBuild) {
|
|
122
|
+
return serverBuild;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return yield* Effect.fail(
|
|
126
|
+
new Error(
|
|
127
|
+
`[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.`
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
}
|
package/src/server-utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'pathe';
|
|
2
2
|
import type { ServerBuild } from 'react-router';
|
|
3
|
+
import { runPluginEffect } from './effect-runtime.js';
|
|
4
|
+
import { resolveServerBuildModuleEffect } from './server-build-resolution.js';
|
|
3
5
|
import type { Route } from './types.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -91,120 +93,19 @@ function generateServerBuild(
|
|
|
91
93
|
return generateStaticTemplate(routes, options);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
'allowedActionOrigins',
|
|
96
|
-
'assets',
|
|
97
|
-
'assetsBuildDirectory',
|
|
98
|
-
'basename',
|
|
99
|
-
'entry',
|
|
100
|
-
'future',
|
|
101
|
-
'isSpaMode',
|
|
102
|
-
'prerender',
|
|
103
|
-
'publicPath',
|
|
104
|
-
'routeDiscovery',
|
|
105
|
-
'routes',
|
|
106
|
-
'ssr',
|
|
107
|
-
]);
|
|
108
|
-
|
|
109
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
110
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
114
|
-
return isRecord(value) && typeof value.then === 'function';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isRouteDiscovery(value: unknown): boolean {
|
|
118
|
-
return (
|
|
119
|
-
isRecord(value) &&
|
|
120
|
-
(value.mode === 'initial' ||
|
|
121
|
-
(value.mode === 'lazy' &&
|
|
122
|
-
(value.manifestPath === undefined ||
|
|
123
|
-
typeof value.manifestPath === 'string')))
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function resolveBuildExports(
|
|
128
|
-
build: Record<string, unknown>
|
|
129
|
-
): Promise<Record<string, unknown>> {
|
|
130
|
-
const resolved = { ...build };
|
|
131
|
-
for (const key of Object.keys(build)) {
|
|
132
|
-
if (!RESOLVABLE_BUILD_EXPORTS.has(key)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const value = build[key];
|
|
136
|
-
if (typeof value === 'function' && value.length === 0) {
|
|
137
|
-
const result = value();
|
|
138
|
-
resolved[key] = isPromiseLike(result) ? await result : result;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (isPromiseLike(value)) {
|
|
142
|
-
resolved[key] = await value;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return resolved;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function isServerBuild(value: unknown): value is ServerBuild {
|
|
149
|
-
return Boolean(
|
|
150
|
-
isRecord(value) &&
|
|
151
|
-
isRecord(value.entry) &&
|
|
152
|
-
isRecord(value.entry.module) &&
|
|
153
|
-
typeof value.entry.module.default === 'function' &&
|
|
154
|
-
isRecord(value.routes) &&
|
|
155
|
-
isRecord(value.assets) &&
|
|
156
|
-
typeof value.assetsBuildDirectory === 'string' &&
|
|
157
|
-
(value.basename === undefined || typeof value.basename === 'string') &&
|
|
158
|
-
isRecord(value.future) &&
|
|
159
|
-
typeof value.isSpaMode === 'boolean' &&
|
|
160
|
-
Array.isArray(value.prerender) &&
|
|
161
|
-
typeof value.publicPath === 'string' &&
|
|
162
|
-
isRouteDiscovery(value.routeDiscovery) &&
|
|
163
|
-
typeof value.ssr === 'boolean'
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function resolveServerBuildCandidate(
|
|
168
|
-
candidate: unknown
|
|
169
|
-
): Promise<ServerBuild | undefined> {
|
|
170
|
-
if (!isRecord(candidate)) {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
const resolved = await resolveBuildExports(candidate);
|
|
174
|
-
return isServerBuild(resolved) ? resolved : undefined;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function resolveServerBuildModule(
|
|
96
|
+
export function resolveServerBuildModule(
|
|
178
97
|
buildModule: unknown,
|
|
179
98
|
source: string
|
|
180
99
|
): Promise<ServerBuild> {
|
|
181
|
-
|
|
182
|
-
const candidates = [() => moduleValue];
|
|
183
|
-
if (isRecord(moduleValue)) {
|
|
184
|
-
if ('default' in moduleValue) {
|
|
185
|
-
candidates.push(() => moduleValue.default);
|
|
186
|
-
}
|
|
187
|
-
if ('module.exports' in moduleValue) {
|
|
188
|
-
candidates.push(() => moduleValue['module.exports']);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (const getCandidate of candidates) {
|
|
193
|
-
const candidate = await getCandidate();
|
|
194
|
-
const serverBuild = await resolveServerBuildCandidate(candidate);
|
|
195
|
-
if (serverBuild) {
|
|
196
|
-
return serverBuild;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
throw new Error(
|
|
200
|
-
`[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.`
|
|
201
|
-
);
|
|
100
|
+
return runPluginEffect(resolveServerBuildModuleEffect(buildModule, source));
|
|
202
101
|
}
|
|
203
102
|
|
|
204
103
|
export function resolveReactRouterServerBuild(
|
|
205
104
|
buildModule: unknown
|
|
206
105
|
): Promise<ServerBuild> {
|
|
207
|
-
return
|
|
106
|
+
return runPluginEffect(
|
|
107
|
+
resolveServerBuildModuleEffect(buildModule, 'Imported module')
|
|
108
|
+
);
|
|
208
109
|
}
|
|
209
110
|
|
|
210
111
|
export { generateServerBuild };
|
package/src/ssr-externals.ts
CHANGED