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
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import fsExtra from 'fs-extra';
|
|
5
|
+
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
6
|
+
import {
|
|
7
|
+
createRequestHandler,
|
|
8
|
+
matchRoutes,
|
|
9
|
+
type ServerBuild,
|
|
10
|
+
} from 'react-router';
|
|
11
|
+
import { dirname, relative, resolve } from 'pathe';
|
|
12
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
13
|
+
import { getBuildManifest } from './build-manifest.js';
|
|
14
|
+
import {
|
|
15
|
+
generateReactRouterManifestForDev,
|
|
16
|
+
getReactRouterManifestForDev,
|
|
17
|
+
type ReactRouterManifestStats,
|
|
18
|
+
type RouteManifestModuleExports,
|
|
19
|
+
} from './manifest.js';
|
|
20
|
+
import {
|
|
21
|
+
createPrerenderRoutes,
|
|
22
|
+
getPrerenderConcurrency,
|
|
23
|
+
getSsrFalsePrerenderExportErrors,
|
|
24
|
+
normalizePrerenderMatchPath,
|
|
25
|
+
withBuildRequest,
|
|
26
|
+
} from './prerender.js';
|
|
27
|
+
import type {
|
|
28
|
+
Config,
|
|
29
|
+
ResolvedReactRouterConfig,
|
|
30
|
+
} from './react-router-config.js';
|
|
31
|
+
import { resolveServerBuildModule } from './server-utils.js';
|
|
32
|
+
import type { PluginOptions, Route } from './types.js';
|
|
33
|
+
|
|
34
|
+
type ReactRouterManifest = Awaited<
|
|
35
|
+
ReturnType<typeof getReactRouterManifestForDev>
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
type BuildRouteModule = {
|
|
39
|
+
loader?: unknown;
|
|
40
|
+
default?: unknown;
|
|
41
|
+
ErrorBoundary?: unknown;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type PrerenderServerBuild = ServerBuild & {
|
|
45
|
+
routes: Record<string, { id?: string; module?: BuildRouteModule }>;
|
|
46
|
+
assets?: {
|
|
47
|
+
routes?: Record<string, { hasLoader?: boolean }>;
|
|
48
|
+
};
|
|
49
|
+
prerender?: string[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type PrerenderBuildApi = Pick<
|
|
53
|
+
RsbuildPluginAPI,
|
|
54
|
+
'logger' | 'getNormalizedConfig'
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
type RunReactRouterPrerenderBuildOptions = {
|
|
58
|
+
api: PrerenderBuildApi;
|
|
59
|
+
hasWebEnvironment: boolean;
|
|
60
|
+
buildDirectory: string;
|
|
61
|
+
serverBuildFile?: string;
|
|
62
|
+
ssr: boolean;
|
|
63
|
+
isPrerenderEnabled: boolean;
|
|
64
|
+
prerenderConfig: Config['prerender'];
|
|
65
|
+
prerenderPaths: string[];
|
|
66
|
+
basename: string;
|
|
67
|
+
future: ResolvedReactRouterConfig['future'];
|
|
68
|
+
routes: Record<string, Route>;
|
|
69
|
+
latestBrowserManifest: ReactRouterManifest | null;
|
|
70
|
+
latestBrowserManifestModuleExports: RouteManifestModuleExports;
|
|
71
|
+
clientStats: ReactRouterManifestStats | undefined;
|
|
72
|
+
pluginOptions: PluginOptions;
|
|
73
|
+
appDirectory: string;
|
|
74
|
+
assetPrefix: string;
|
|
75
|
+
routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5];
|
|
76
|
+
buildManifest: Awaited<ReturnType<typeof getBuildManifest>>;
|
|
77
|
+
resolvedConfigWithRoutes: ResolvedReactRouterConfig;
|
|
78
|
+
buildEnd: Config['buildEnd'];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
|
|
82
|
+
|
|
83
|
+
const getServerBuildPath = async ({
|
|
84
|
+
buildDirectory,
|
|
85
|
+
serverBuildFile,
|
|
86
|
+
}: {
|
|
87
|
+
buildDirectory: string;
|
|
88
|
+
serverBuildFile?: string;
|
|
89
|
+
}): Promise<{ serverBuildDir: string; serverBuildPath: string }> => {
|
|
90
|
+
const serverBuildDir = resolve(buildDirectory, 'server');
|
|
91
|
+
const defaultServerBuildFile = 'static/js/app.js';
|
|
92
|
+
const configuredServerBuildFile = serverBuildFile || 'index.js';
|
|
93
|
+
const configuredServerBuildPath = resolve(
|
|
94
|
+
serverBuildDir,
|
|
95
|
+
configuredServerBuildFile
|
|
96
|
+
);
|
|
97
|
+
const defaultServerBuildPath = resolve(
|
|
98
|
+
serverBuildDir,
|
|
99
|
+
defaultServerBuildFile
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
configuredServerBuildFile !== defaultServerBuildFile &&
|
|
104
|
+
existsSync(defaultServerBuildPath) &&
|
|
105
|
+
!existsSync(configuredServerBuildPath)
|
|
106
|
+
) {
|
|
107
|
+
await mkdir(dirname(configuredServerBuildPath), { recursive: true });
|
|
108
|
+
await fsExtra.copy(defaultServerBuildPath, configuredServerBuildPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
serverBuildDir,
|
|
113
|
+
serverBuildPath: existsSync(configuredServerBuildPath)
|
|
114
|
+
? configuredServerBuildPath
|
|
115
|
+
: defaultServerBuildPath,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const createDataRequestPath = (
|
|
120
|
+
prerenderPath: string,
|
|
121
|
+
trailingSlashAwareDataRequests: boolean
|
|
122
|
+
): string => {
|
|
123
|
+
if (trailingSlashAwareDataRequests) {
|
|
124
|
+
return prerenderPath.endsWith('/')
|
|
125
|
+
? `${prerenderPath}_.data`
|
|
126
|
+
: `${prerenderPath}.data`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return prerenderPath === '/'
|
|
130
|
+
? '/_root.data'
|
|
131
|
+
: `${prerenderPath.replace(/\/$/, '')}.data`;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const prerenderData = async ({
|
|
135
|
+
handler,
|
|
136
|
+
prerenderPath,
|
|
137
|
+
onlyRoutes,
|
|
138
|
+
clientBuildDir,
|
|
139
|
+
basename,
|
|
140
|
+
trailingSlashAwareDataRequests,
|
|
141
|
+
api,
|
|
142
|
+
requestInit,
|
|
143
|
+
}: {
|
|
144
|
+
handler: (request: Request) => Promise<Response>;
|
|
145
|
+
prerenderPath: string;
|
|
146
|
+
onlyRoutes: string[] | null;
|
|
147
|
+
clientBuildDir: string;
|
|
148
|
+
basename: string;
|
|
149
|
+
trailingSlashAwareDataRequests: boolean;
|
|
150
|
+
api: PrerenderBuildApi;
|
|
151
|
+
requestInit?: RequestInit;
|
|
152
|
+
}): Promise<string> => {
|
|
153
|
+
const dataRequestPath = createDataRequestPath(
|
|
154
|
+
prerenderPath,
|
|
155
|
+
trailingSlashAwareDataRequests
|
|
156
|
+
);
|
|
157
|
+
const normalizedPath = `${basename}${dataRequestPath}`.replace(/\/\/+/g, '/');
|
|
158
|
+
const url = new URL(`http://localhost${normalizedPath}`);
|
|
159
|
+
if (onlyRoutes?.length) {
|
|
160
|
+
url.searchParams.set('_routes', onlyRoutes.join(','));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return withBuildRequest(url, requestInit, async request => {
|
|
164
|
+
const response = await handler(request);
|
|
165
|
+
const data = await response.text();
|
|
166
|
+
|
|
167
|
+
if (response.status !== 200 && response.status !== 202) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Prerender (data): Received a ${response.status} status code from ` +
|
|
170
|
+
`\`entry.server.tsx\` while prerendering the \`${prerenderPath}\` path.\n` +
|
|
171
|
+
`${normalizedPath}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/'));
|
|
176
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
177
|
+
await writeFile(outputPath, data);
|
|
178
|
+
api.logger.info(
|
|
179
|
+
`Prerender (data): ${prerenderPath} -> ${relative(
|
|
180
|
+
process.cwd(),
|
|
181
|
+
outputPath
|
|
182
|
+
)}`
|
|
183
|
+
);
|
|
184
|
+
return data;
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const prerenderRoute = async ({
|
|
189
|
+
handler,
|
|
190
|
+
prerenderPath,
|
|
191
|
+
clientBuildDir,
|
|
192
|
+
basename,
|
|
193
|
+
api,
|
|
194
|
+
requestInit,
|
|
195
|
+
}: {
|
|
196
|
+
handler: (request: Request) => Promise<Response>;
|
|
197
|
+
prerenderPath: string;
|
|
198
|
+
clientBuildDir: string;
|
|
199
|
+
basename: string;
|
|
200
|
+
api: PrerenderBuildApi;
|
|
201
|
+
requestInit?: RequestInit;
|
|
202
|
+
}): Promise<void> => {
|
|
203
|
+
const normalizedPath = `${basename}${prerenderPath}/`.replace(/\/\/+/g, '/');
|
|
204
|
+
await withBuildRequest(
|
|
205
|
+
`http://localhost${normalizedPath}`,
|
|
206
|
+
requestInit,
|
|
207
|
+
async request => {
|
|
208
|
+
const response = await handler(request);
|
|
209
|
+
let html = await response.text();
|
|
210
|
+
|
|
211
|
+
if (redirectStatusCodes.has(response.status)) {
|
|
212
|
+
const location = response.headers.get('Location');
|
|
213
|
+
const delay = response.status === 302 ? 2 : 0;
|
|
214
|
+
html = `<!doctype html>
|
|
215
|
+
<head>
|
|
216
|
+
<title>Redirecting to: ${location}</title>
|
|
217
|
+
<meta http-equiv="refresh" content="${delay};url=${location}">
|
|
218
|
+
<meta name="robots" content="noindex">
|
|
219
|
+
</head>
|
|
220
|
+
<body>
|
|
221
|
+
\t<a href="${location}">
|
|
222
|
+
Redirecting from <code>${normalizedPath}</code> to <code>${location}</code>
|
|
223
|
+
</a>
|
|
224
|
+
</body>
|
|
225
|
+
</html>`;
|
|
226
|
+
} else if (response.status !== 200) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Prerender (html): Received a ${response.status} status code from ` +
|
|
229
|
+
`\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` +
|
|
230
|
+
html
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const outputPath = resolve(
|
|
235
|
+
clientBuildDir,
|
|
236
|
+
...normalizedPath.split('/'),
|
|
237
|
+
'index.html'
|
|
238
|
+
);
|
|
239
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
240
|
+
await writeFile(outputPath, html);
|
|
241
|
+
api.logger.info(
|
|
242
|
+
`Prerender (html): ${prerenderPath} -> ${relative(
|
|
243
|
+
process.cwd(),
|
|
244
|
+
outputPath
|
|
245
|
+
)}`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const prerenderResourceRoute = async ({
|
|
252
|
+
handler,
|
|
253
|
+
prerenderPath,
|
|
254
|
+
clientBuildDir,
|
|
255
|
+
basename,
|
|
256
|
+
api,
|
|
257
|
+
requestInit,
|
|
258
|
+
}: {
|
|
259
|
+
handler: (request: Request) => Promise<Response>;
|
|
260
|
+
prerenderPath: string;
|
|
261
|
+
clientBuildDir: string;
|
|
262
|
+
basename: string;
|
|
263
|
+
api: PrerenderBuildApi;
|
|
264
|
+
requestInit?: RequestInit;
|
|
265
|
+
}): Promise<void> => {
|
|
266
|
+
const normalizedPath = `${basename}${prerenderPath}/`
|
|
267
|
+
.replace(/\/\/+/g, '/')
|
|
268
|
+
.replace(/\/$/g, '');
|
|
269
|
+
await withBuildRequest(
|
|
270
|
+
`http://localhost${normalizedPath}`,
|
|
271
|
+
requestInit,
|
|
272
|
+
async request => {
|
|
273
|
+
const response = await handler(request);
|
|
274
|
+
const content = Buffer.from(await response.arrayBuffer());
|
|
275
|
+
|
|
276
|
+
if (response.status !== 200) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Prerender (resource): Received a ${response.status} status code from ` +
|
|
279
|
+
`\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` +
|
|
280
|
+
content.toString('utf8')
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/'));
|
|
285
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
286
|
+
await writeFile(outputPath, content);
|
|
287
|
+
api.logger.info(
|
|
288
|
+
`Prerender (resource): ${prerenderPath} -> ${relative(
|
|
289
|
+
process.cwd(),
|
|
290
|
+
outputPath
|
|
291
|
+
)}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const handleSpaMode = async ({
|
|
298
|
+
handler,
|
|
299
|
+
build,
|
|
300
|
+
clientBuildDir,
|
|
301
|
+
basename,
|
|
302
|
+
api,
|
|
303
|
+
}: {
|
|
304
|
+
handler: (request: Request) => Promise<Response>;
|
|
305
|
+
build: PrerenderServerBuild;
|
|
306
|
+
clientBuildDir: string;
|
|
307
|
+
basename: string;
|
|
308
|
+
api: PrerenderBuildApi;
|
|
309
|
+
}): Promise<void> => {
|
|
310
|
+
await withBuildRequest(
|
|
311
|
+
`http://localhost${basename}`,
|
|
312
|
+
{
|
|
313
|
+
headers: {
|
|
314
|
+
'X-React-Router-SPA-Mode': 'yes',
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
async request => {
|
|
318
|
+
const response = await handler(request);
|
|
319
|
+
const html = await response.text();
|
|
320
|
+
const isPrerenderSpaFallback = build.prerender?.includes('/');
|
|
321
|
+
const filename = isPrerenderSpaFallback
|
|
322
|
+
? '__spa-fallback.html'
|
|
323
|
+
: 'index.html';
|
|
324
|
+
|
|
325
|
+
if (response.status !== 200) {
|
|
326
|
+
if (isPrerenderSpaFallback) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`Prerender: Received a ${response.status} status code from ` +
|
|
329
|
+
`\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` +
|
|
330
|
+
html
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
throw new Error(
|
|
334
|
+
`SPA Mode: Received a ${response.status} status code from ` +
|
|
335
|
+
`\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` +
|
|
336
|
+
html
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (
|
|
341
|
+
!html.includes('window.__reactRouterContext =') ||
|
|
342
|
+
!html.includes('window.__reactRouterRouteModules =')
|
|
343
|
+
) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
'SPA Mode: Did you forget to include `<Scripts/>` in your root route? ' +
|
|
346
|
+
'Your pre-rendered HTML cannot hydrate without `<Scripts />`.'
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const outputPath = resolve(clientBuildDir, filename);
|
|
351
|
+
await writeFile(outputPath, html);
|
|
352
|
+
const prettyPath = relative(process.cwd(), outputPath);
|
|
353
|
+
if (build.prerender?.length) {
|
|
354
|
+
api.logger.info(`Prerender (html): SPA Fallback -> ${prettyPath}`);
|
|
355
|
+
} else {
|
|
356
|
+
api.logger.info(`SPA Mode: Generated ${prettyPath}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const assertValidSsrFalsePrerenderExports = ({
|
|
363
|
+
routes,
|
|
364
|
+
manifestRoutes,
|
|
365
|
+
routeExports,
|
|
366
|
+
prerenderPaths,
|
|
367
|
+
api,
|
|
368
|
+
}: {
|
|
369
|
+
routes: Record<string, Route>;
|
|
370
|
+
manifestRoutes: ReactRouterManifest['routes'];
|
|
371
|
+
routeExports: RouteManifestModuleExports;
|
|
372
|
+
prerenderPaths: string[];
|
|
373
|
+
api: PrerenderBuildApi;
|
|
374
|
+
}) => {
|
|
375
|
+
const errors = getSsrFalsePrerenderExportErrors({
|
|
376
|
+
routes,
|
|
377
|
+
manifestRoutes,
|
|
378
|
+
routeExports,
|
|
379
|
+
prerenderPaths,
|
|
380
|
+
});
|
|
381
|
+
if (errors.length > 0) {
|
|
382
|
+
api.logger.error(errors.join('\n'));
|
|
383
|
+
throw new Error(
|
|
384
|
+
'Invalid route exports found when prerendering with `ssr:false`'
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const validatePrerenderPathMatches = (
|
|
390
|
+
routes: Record<string, Route>,
|
|
391
|
+
prerenderPaths: string[]
|
|
392
|
+
): void => {
|
|
393
|
+
const routeTree = createPrerenderRoutes(routes);
|
|
394
|
+
for (const path of prerenderPaths) {
|
|
395
|
+
const matches = matchRoutes(routeTree, normalizePrerenderMatchPath(path));
|
|
396
|
+
if (!matches) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`Unable to prerender path because it does not match any routes: ${path}`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const runPrerenderPaths = async ({
|
|
405
|
+
build,
|
|
406
|
+
requestHandler,
|
|
407
|
+
clientBuildDir,
|
|
408
|
+
options,
|
|
409
|
+
}: {
|
|
410
|
+
build: PrerenderServerBuild;
|
|
411
|
+
requestHandler: (request: Request) => Promise<Response>;
|
|
412
|
+
clientBuildDir: string;
|
|
413
|
+
options: RunReactRouterPrerenderBuildOptions;
|
|
414
|
+
}): Promise<void> => {
|
|
415
|
+
const { api, basename, future, prerenderConfig, prerenderPaths } = options;
|
|
416
|
+
const buildRoutes = createPrerenderRoutes(build.routes);
|
|
417
|
+
const concurrency = getPrerenderConcurrency(prerenderConfig);
|
|
418
|
+
const pending = new Set<Promise<void>>();
|
|
419
|
+
|
|
420
|
+
const enqueue = async (path: string) => {
|
|
421
|
+
const matches = matchRoutes(buildRoutes, normalizePrerenderMatchPath(path));
|
|
422
|
+
if (!matches) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const leafRoute = matches[matches.length - 1]?.route;
|
|
427
|
+
const routeId = leafRoute?.id;
|
|
428
|
+
const manifestRoute = routeId ? build.routes?.[routeId]?.module : null;
|
|
429
|
+
const isResourceRoute =
|
|
430
|
+
manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;
|
|
431
|
+
|
|
432
|
+
if (isResourceRoute) {
|
|
433
|
+
if (manifestRoute.loader && routeId) {
|
|
434
|
+
await prerenderData({
|
|
435
|
+
handler: requestHandler,
|
|
436
|
+
prerenderPath: path,
|
|
437
|
+
onlyRoutes: [routeId],
|
|
438
|
+
clientBuildDir,
|
|
439
|
+
basename,
|
|
440
|
+
trailingSlashAwareDataRequests:
|
|
441
|
+
future.unstable_trailingSlashAwareDataRequests,
|
|
442
|
+
api,
|
|
443
|
+
});
|
|
444
|
+
await prerenderResourceRoute({
|
|
445
|
+
handler: requestHandler,
|
|
446
|
+
prerenderPath: path,
|
|
447
|
+
clientBuildDir,
|
|
448
|
+
basename,
|
|
449
|
+
api,
|
|
450
|
+
});
|
|
451
|
+
} else {
|
|
452
|
+
api.logger.warn(
|
|
453
|
+
`⚠️ Skipping prerendering for resource route without a loader: ${routeId}`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const hasLoaders = matches.some(match => {
|
|
460
|
+
const matchedRouteId = match.route.id;
|
|
461
|
+
if (!matchedRouteId) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return build.assets?.routes?.[matchedRouteId]?.hasLoader;
|
|
465
|
+
});
|
|
466
|
+
let data: string | undefined;
|
|
467
|
+
if (hasLoaders) {
|
|
468
|
+
data = await prerenderData({
|
|
469
|
+
handler: requestHandler,
|
|
470
|
+
prerenderPath: path,
|
|
471
|
+
onlyRoutes: null,
|
|
472
|
+
clientBuildDir,
|
|
473
|
+
basename,
|
|
474
|
+
trailingSlashAwareDataRequests:
|
|
475
|
+
future.unstable_trailingSlashAwareDataRequests,
|
|
476
|
+
api,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
await prerenderRoute({
|
|
480
|
+
handler: requestHandler,
|
|
481
|
+
prerenderPath: path,
|
|
482
|
+
clientBuildDir,
|
|
483
|
+
basename,
|
|
484
|
+
api,
|
|
485
|
+
requestInit: data
|
|
486
|
+
? {
|
|
487
|
+
headers: {
|
|
488
|
+
'X-React-Router-Prerender-Data': encodeURI(data),
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
: undefined,
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
for (const path of prerenderPaths) {
|
|
496
|
+
const task = enqueue(path);
|
|
497
|
+
pending.add(task);
|
|
498
|
+
task.finally(() => pending.delete(task));
|
|
499
|
+
if (pending.size >= concurrency) {
|
|
500
|
+
await Promise.race(pending);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
await Promise.all(pending);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
export const runReactRouterPrerenderBuild = async (
|
|
507
|
+
options: RunReactRouterPrerenderBuildOptions
|
|
508
|
+
): Promise<void> => {
|
|
509
|
+
if (!options.hasWebEnvironment) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const {
|
|
514
|
+
api,
|
|
515
|
+
buildDirectory,
|
|
516
|
+
serverBuildFile,
|
|
517
|
+
ssr,
|
|
518
|
+
isPrerenderEnabled,
|
|
519
|
+
prerenderPaths,
|
|
520
|
+
routes,
|
|
521
|
+
latestBrowserManifest,
|
|
522
|
+
latestBrowserManifestModuleExports,
|
|
523
|
+
clientStats,
|
|
524
|
+
pluginOptions,
|
|
525
|
+
appDirectory,
|
|
526
|
+
assetPrefix,
|
|
527
|
+
routeChunkOptions,
|
|
528
|
+
buildManifest,
|
|
529
|
+
resolvedConfigWithRoutes,
|
|
530
|
+
buildEnd,
|
|
531
|
+
basename,
|
|
532
|
+
} = options;
|
|
533
|
+
const { serverBuildDir, serverBuildPath } = await getServerBuildPath({
|
|
534
|
+
buildDirectory,
|
|
535
|
+
serverBuildFile,
|
|
536
|
+
});
|
|
537
|
+
const clientBuildDir = resolve(buildDirectory, 'client');
|
|
538
|
+
|
|
539
|
+
if (!existsSync(serverBuildPath)) {
|
|
540
|
+
console.warn(
|
|
541
|
+
`[${PLUGIN_NAME}] Server build not found at ${serverBuildPath}. ` +
|
|
542
|
+
'Skipping prerendering.'
|
|
543
|
+
);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
await mkdir(clientBuildDir, { recursive: true });
|
|
548
|
+
|
|
549
|
+
if (!ssr || isPrerenderEnabled) {
|
|
550
|
+
process.env.IS_RR_BUILD_REQUEST = 'yes';
|
|
551
|
+
const buildModule = await import(pathToFileURL(serverBuildPath).toString());
|
|
552
|
+
const build = (await resolveServerBuildModule(
|
|
553
|
+
buildModule,
|
|
554
|
+
`Server build ${JSON.stringify(serverBuildPath)}`
|
|
555
|
+
)) as PrerenderServerBuild;
|
|
556
|
+
const requestHandler = createRequestHandler(build, 'production');
|
|
557
|
+
|
|
558
|
+
if (isPrerenderEnabled) {
|
|
559
|
+
if (!ssr) {
|
|
560
|
+
const generated = latestBrowserManifest
|
|
561
|
+
? {
|
|
562
|
+
manifest: latestBrowserManifest,
|
|
563
|
+
moduleExportsByRouteId: latestBrowserManifestModuleExports,
|
|
564
|
+
}
|
|
565
|
+
: await generateReactRouterManifestForDev(
|
|
566
|
+
routes,
|
|
567
|
+
pluginOptions,
|
|
568
|
+
clientStats,
|
|
569
|
+
appDirectory,
|
|
570
|
+
assetPrefix,
|
|
571
|
+
routeChunkOptions
|
|
572
|
+
);
|
|
573
|
+
assertValidSsrFalsePrerenderExports({
|
|
574
|
+
routes,
|
|
575
|
+
manifestRoutes: generated.manifest.routes,
|
|
576
|
+
routeExports: generated.moduleExportsByRouteId,
|
|
577
|
+
prerenderPaths,
|
|
578
|
+
api,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
validatePrerenderPathMatches(routes, prerenderPaths);
|
|
583
|
+
|
|
584
|
+
if (prerenderPaths.length > 0) {
|
|
585
|
+
api.logger.info(
|
|
586
|
+
`Prerender (html): ${prerenderPaths.length} path(s)...`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
await runPrerenderPaths({
|
|
591
|
+
build,
|
|
592
|
+
requestHandler,
|
|
593
|
+
clientBuildDir,
|
|
594
|
+
options,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (!ssr) {
|
|
599
|
+
await handleSpaMode({
|
|
600
|
+
handler: requestHandler,
|
|
601
|
+
build,
|
|
602
|
+
clientBuildDir,
|
|
603
|
+
basename,
|
|
604
|
+
api,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (!ssr) {
|
|
610
|
+
await fsExtra.remove(serverBuildDir);
|
|
611
|
+
api.logger.info(
|
|
612
|
+
`[${PLUGIN_NAME}] Removed server build (static deployment)`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (buildEnd) {
|
|
617
|
+
await buildEnd({
|
|
618
|
+
buildManifest,
|
|
619
|
+
reactRouterConfig: resolvedConfigWithRoutes,
|
|
620
|
+
viteConfig: api.getNormalizedConfig(),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
};
|