rsbuild-plugin-react-router 0.5.0 → 0.6.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 +58 -208
- package/dist/511.js +371 -332
- package/dist/build-output-transforms.d.ts +30 -6
- package/dist/classic-mode.d.ts +56 -0
- package/dist/config-imports.d.ts +8 -2
- package/dist/constants.d.ts +3 -0
- package/dist/dev-background-resources.d.ts +3 -1
- package/dist/dev-generation.d.ts +2 -3
- package/dist/dev-hmr.d.ts +8 -2
- package/dist/dev-runtime-controller.d.ts +6 -1
- package/dist/dev-source-maps.d.ts +4 -0
- package/dist/effect-runtime.d.ts +17 -5
- package/dist/entry-paths.d.ts +16 -0
- package/dist/environment-output.d.ts +6 -0
- package/dist/export-utils.d.ts +2 -1
- package/dist/index.cjs +3964 -2217
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3474 -1799
- package/dist/lazy-compilation-prewarm.d.ts +8 -5
- package/dist/manifest.d.ts +17 -5
- package/dist/mode-plan.d.ts +82 -0
- package/dist/modify-browser-manifest.d.ts +8 -4
- package/dist/plugin-utils.d.ts +27 -1
- package/dist/prerender-build.d.ts +5 -5
- package/dist/prerender.d.ts +1 -5
- package/dist/react-router-config.d.ts +11 -3
- package/dist/route-artifacts.d.ts +4 -2
- package/dist/route-chunks.d.ts +8 -2
- package/dist/route-component-transform.d.ts +0 -2
- package/dist/route-export-pruning.d.ts +4 -1
- package/dist/route-imports.d.ts +18 -0
- package/dist/route-transform-tasks.d.ts +5 -0
- package/dist/route-watch.d.ts +11 -6
- package/dist/rsc-dev-server.d.ts +26 -0
- package/dist/rsc-prerender.d.ts +54 -0
- package/dist/rsc-route-config.d.ts +6 -0
- package/dist/rsc-route-exports.d.ts +15 -0
- package/dist/rsc-route-transform-loader.cjs +43 -0
- package/dist/rsc-route-transform-loader.d.ts +30 -0
- package/dist/rsc-route-transform-loader.js +16 -0
- package/dist/rsc-route-transform-registration.d.ts +12 -0
- package/dist/rsc-route-transforms.d.ts +21 -0
- package/dist/rsc-support.d.ts +23 -0
- package/dist/rsc-virtual-modules.d.ts +19 -0
- package/dist/server-build-plan.d.ts +2 -1
- package/dist/server-build-resolution.d.ts +1 -2
- package/dist/server-utils.d.ts +4 -5
- package/dist/ssr-asset-relocation.d.ts +98 -0
- package/dist/templates/entry.rsc.client.d.ts +1 -0
- package/dist/templates/entry.rsc.client.js +61 -0
- package/dist/templates/entry.rsc.d.ts +9 -0
- package/dist/templates/entry.rsc.js +38 -0
- package/dist/templates/entry.rsc.ssr.d.ts +4 -0
- package/dist/templates/entry.rsc.ssr.js +24 -0
- package/dist/typegen.d.ts +4 -2
- package/dist/types.d.ts +30 -1
- package/package.json +69 -14
- package/src/build-output-transforms.ts +155 -21
- package/src/classic-mode.ts +258 -0
- package/src/config-imports.ts +153 -6
- package/src/constants.ts +6 -2
- package/src/dev-background-resources.ts +46 -85
- package/src/dev-generation.ts +52 -33
- package/src/dev-hmr.ts +112 -80
- package/src/dev-runtime-artifacts.ts +11 -12
- package/src/dev-runtime-controller.ts +75 -85
- package/src/dev-runtime-session.ts +14 -18
- package/src/dev-server.ts +2 -0
- package/src/dev-source-maps.ts +257 -0
- package/src/effect-runtime.ts +105 -57
- package/src/entry-paths.ts +80 -0
- package/src/environment-output.ts +55 -0
- package/src/export-utils.ts +15 -11
- package/src/index.ts +661 -496
- package/src/lazy-compilation-prewarm.ts +20 -4
- package/src/manifest.ts +186 -100
- package/src/mode-plan.ts +367 -0
- package/src/modify-browser-manifest.ts +131 -124
- package/src/plugin-utils.ts +103 -39
- package/src/prerender-build.ts +82 -104
- package/src/prerender.ts +23 -24
- package/src/react-router-config.ts +72 -26
- package/src/route-artifacts.ts +96 -66
- package/src/route-chunks.ts +196 -54
- package/src/route-component-transform.ts +14 -21
- package/src/route-export-pruning.ts +4 -3
- package/src/route-imports.ts +100 -0
- package/src/route-transform-tasks.ts +39 -25
- package/src/route-watch.ts +166 -188
- package/src/rsc-dev-server.ts +112 -0
- package/src/rsc-prerender.ts +362 -0
- package/src/rsc-route-config.ts +175 -0
- package/src/rsc-route-exports.ts +67 -0
- package/src/rsc-route-transform-loader.ts +65 -0
- package/src/rsc-route-transform-registration.ts +145 -0
- package/src/rsc-route-transforms.ts +995 -0
- package/src/rsc-runtime.d.ts +143 -0
- package/src/rsc-support.ts +116 -0
- package/src/rsc-virtual-modules.ts +113 -0
- package/src/server-build-plan.ts +14 -3
- package/src/server-build-resolution.ts +37 -47
- package/src/server-utils.ts +21 -35
- package/src/ssr-asset-relocation.ts +183 -0
- package/src/ssr-externals.ts +8 -26
- package/src/templates/entry.rsc.client.tsx +168 -0
- package/src/templates/entry.rsc.ssr.tsx +45 -0
- package/src/templates/entry.rsc.tsx +80 -0
- package/src/typegen.ts +40 -23
- package/src/types.ts +39 -1
- package/src/warnings/warn-on-client-source-maps.ts +6 -10
package/src/dev-hmr.ts
CHANGED
|
@@ -3,9 +3,14 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
import type { Rspack } from '@rsbuild/core';
|
|
4
4
|
import { dirname, join } from 'pathe';
|
|
5
5
|
|
|
6
|
-
import { HMR_PATCHABLE_ROUTE_FLAGS } from './route-artifacts.js';
|
|
7
|
-
|
|
8
6
|
export const DEV_HMR_RUNTIME_MODULE_ID = 'virtual/react-router/hmr-runtime';
|
|
7
|
+
export const DEV_MANIFEST_UPDATE_EVENT = 'react-router:manifest-update';
|
|
8
|
+
|
|
9
|
+
export type DevHmrPlanOptions = {
|
|
10
|
+
isEnabled: () => boolean;
|
|
11
|
+
runtimeModule: string;
|
|
12
|
+
onNodeRebuildCommitted: () => void;
|
|
13
|
+
};
|
|
9
14
|
|
|
10
15
|
type SwcLoaderOptions = {
|
|
11
16
|
jsc?: { transform?: { react?: { refresh?: boolean } } };
|
|
@@ -17,30 +22,21 @@ const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
|
17
22
|
const isSwcLoader = (loader: unknown): boolean =>
|
|
18
23
|
typeof loader === 'string' && loader.includes('builtin:swc-loader');
|
|
19
24
|
|
|
20
|
-
const hasReactRefresh = (options: unknown): boolean =>
|
|
21
|
-
(options as SwcLoaderOptions | undefined)?.jsc?.transform?.react?.refresh ===
|
|
22
|
-
true;
|
|
23
|
-
|
|
24
25
|
const readSwcLoaderRefresh = (value: unknown): boolean => {
|
|
25
|
-
if (Array.isArray(value))
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
if (!isObject(value)) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
26
|
+
if (Array.isArray(value)) return value.some(readSwcLoaderRefresh);
|
|
27
|
+
if (!isObject(value)) return false;
|
|
31
28
|
if (isSwcLoader(value.loader)) {
|
|
32
|
-
return
|
|
29
|
+
return (
|
|
30
|
+
(value.options as SwcLoaderOptions | undefined)?.jsc?.transform?.react
|
|
31
|
+
?.refresh === true
|
|
32
|
+
);
|
|
33
33
|
}
|
|
34
34
|
return Object.values(value).some(readSwcLoaderRefresh);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const readRuleSwcRefresh = (rule: unknown): boolean => {
|
|
38
|
-
if (!isObject(rule))
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
if (isSwcLoader(rule.loader)) {
|
|
42
|
-
return hasReactRefresh(rule.options);
|
|
43
|
-
}
|
|
38
|
+
if (!isObject(rule)) return false;
|
|
39
|
+
if (isSwcLoader(rule.loader)) return readSwcLoaderRefresh(rule);
|
|
44
40
|
return (
|
|
45
41
|
readSwcLoaderRefresh(rule.use) ||
|
|
46
42
|
readRuleSetSwcRefresh(rule.oneOf) ||
|
|
@@ -94,12 +90,11 @@ const hdrRevisionModuleContent = (revision: number): string =>
|
|
|
94
90
|
* whenever server code changes, which the client answers by revalidating
|
|
95
91
|
* React Router loader data.
|
|
96
92
|
*/
|
|
97
|
-
export const DEV_HDR_REVISION_RELATIVE_PATH = '.react-router/hdr-revision.mjs';
|
|
98
|
-
|
|
99
93
|
export const getDevHdrRevisionFilePath = (rootPath: string): string =>
|
|
100
|
-
join(rootPath,
|
|
94
|
+
join(rootPath, '.react-router', 'hdr-revision.mjs');
|
|
101
95
|
|
|
102
96
|
export type DevHdrRevisionSignal = {
|
|
97
|
+
filePath: string;
|
|
103
98
|
/** Writes the initial revision module so the first compile can resolve it. */
|
|
104
99
|
ensure: () => void;
|
|
105
100
|
/** Increments the revision, signaling hot data revalidation to the client. */
|
|
@@ -127,6 +122,7 @@ export const createDevHdrRevisionSignal = ({
|
|
|
127
122
|
}
|
|
128
123
|
};
|
|
129
124
|
return {
|
|
125
|
+
filePath,
|
|
130
126
|
ensure: write,
|
|
131
127
|
bump() {
|
|
132
128
|
revision += 1;
|
|
@@ -138,7 +134,7 @@ export const createDevHdrRevisionSignal = ({
|
|
|
138
134
|
/**
|
|
139
135
|
* Browser-side HMR runtime shared by all route client entries in development.
|
|
140
136
|
*
|
|
141
|
-
* This mirrors React Router
|
|
137
|
+
* This mirrors React Router framework HMR contract (see `refresh-utils.mjs` in
|
|
142
138
|
* `@react-router/dev`): route module updates are applied by patching
|
|
143
139
|
* `window.__reactRouterRouteModules` while preserving the previous component
|
|
144
140
|
* identities (React Fast Refresh swaps their implementations in place),
|
|
@@ -156,7 +152,11 @@ import * as __refreshRuntimeModule from ${JSON.stringify(reactRefreshRuntimePath
|
|
|
156
152
|
// Read revision so the import survives sideEffects: false tree-shaking.
|
|
157
153
|
import __hdrRevision from ${JSON.stringify(hdrRevisionFilePath)};
|
|
158
154
|
|
|
159
|
-
|
|
155
|
+
let latestHdrRevision = __hdrRevision;
|
|
156
|
+
|
|
157
|
+
export function getReactRouterHdrRevision() {
|
|
158
|
+
return latestHdrRevision;
|
|
159
|
+
}
|
|
160
160
|
|
|
161
161
|
const RefreshRuntime =
|
|
162
162
|
__refreshRuntimeModule && __refreshRuntimeModule.performReactRefresh
|
|
@@ -166,6 +166,7 @@ const RefreshRuntime =
|
|
|
166
166
|
const pendingRouteUpdates = new Map();
|
|
167
167
|
let flushTimeout;
|
|
168
168
|
let pendingRevalidation = false;
|
|
169
|
+
let pendingComponentRouteRevalidation = false;
|
|
169
170
|
|
|
170
171
|
function getCurrentRouterPath(router) {
|
|
171
172
|
const basename = router.basename || '/';
|
|
@@ -199,10 +200,10 @@ export function registerReactRouterRouteExports(routeId, moduleExports) {
|
|
|
199
200
|
|
|
200
201
|
export function scheduleReactRouterRouteUpdate(
|
|
201
202
|
routeId,
|
|
202
|
-
|
|
203
|
+
routeMetadata,
|
|
203
204
|
getRouteModuleExports
|
|
204
205
|
) {
|
|
205
|
-
pendingRouteUpdates.set(routeId, {
|
|
206
|
+
pendingRouteUpdates.set(routeId, { routeMetadata, getRouteModuleExports });
|
|
206
207
|
scheduleFlush();
|
|
207
208
|
}
|
|
208
209
|
|
|
@@ -228,16 +229,8 @@ function takePendingRouteUpdates() {
|
|
|
228
229
|
return updates;
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
function getRouteMetadata(routeFlags) {
|
|
232
|
-
return {
|
|
233
|
-
${HMR_PATCHABLE_ROUTE_FLAGS.map(
|
|
234
|
-
(flag, index) => ` ${flag}: Boolean(routeFlags & ${1 << index}),`
|
|
235
|
-
).join('\n')}
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
232
|
function applyRouteModuleUpdate(routeId, update, routeEntry, routeModules) {
|
|
240
|
-
Object.assign(routeEntry,
|
|
233
|
+
Object.assign(routeEntry, update.routeMetadata);
|
|
241
234
|
const imported = update.getRouteModuleExports();
|
|
242
235
|
registerReactRouterRouteExports(routeId, imported);
|
|
243
236
|
const current = routeModules[routeId];
|
|
@@ -294,11 +287,7 @@ function patchCurrentRouteMatches(router, routes) {
|
|
|
294
287
|
|
|
295
288
|
function applyPendingRouteUpdates(router, routeModules, manifest, context) {
|
|
296
289
|
if (pendingRouteUpdates.size === 0) {
|
|
297
|
-
return {
|
|
298
|
-
nextManifest: undefined,
|
|
299
|
-
shouldRefreshRouteState: false,
|
|
300
|
-
routesToRevalidate: new Set(),
|
|
301
|
-
};
|
|
290
|
+
return { nextManifest: undefined, hmrRoutes: undefined };
|
|
302
291
|
}
|
|
303
292
|
|
|
304
293
|
// Clone only entries mutated before the manifest is committed in flush().
|
|
@@ -309,8 +298,7 @@ function applyPendingRouteUpdates(router, routeModules, manifest, context) {
|
|
|
309
298
|
const existingEntry = nextManifest.routes[routeId];
|
|
310
299
|
if (!existingEntry) continue;
|
|
311
300
|
|
|
312
|
-
|
|
313
|
-
const routeEntry = { ...existingEntry };
|
|
301
|
+
const routeEntry = JSON.parse(JSON.stringify(existingEntry));
|
|
314
302
|
nextManifest.routes[routeId] = routeEntry;
|
|
315
303
|
applyRouteModuleUpdate(routeId, update, routeEntry, routeModules);
|
|
316
304
|
if (
|
|
@@ -332,54 +320,52 @@ function applyPendingRouteUpdates(router, routeModules, manifest, context) {
|
|
|
332
320
|
}
|
|
333
321
|
}
|
|
334
322
|
|
|
323
|
+
let hmrRoutes;
|
|
335
324
|
if (
|
|
336
325
|
typeof router.createRoutesForHMR === 'function' &&
|
|
337
326
|
typeof router._internalSetRoutes === 'function'
|
|
338
327
|
) {
|
|
339
|
-
|
|
328
|
+
hmrRoutes = router.createRoutesForHMR(
|
|
340
329
|
routesToRevalidate,
|
|
341
330
|
nextManifest.routes,
|
|
342
331
|
routeModules,
|
|
343
332
|
context.ssr,
|
|
344
333
|
context.isSpaMode
|
|
345
334
|
);
|
|
346
|
-
router._internalSetRoutes(
|
|
347
|
-
patchCurrentRouteMatches(router, routes);
|
|
335
|
+
router._internalSetRoutes(hmrRoutes);
|
|
348
336
|
}
|
|
349
337
|
|
|
350
|
-
return { nextManifest,
|
|
338
|
+
return { nextManifest, hmrRoutes, shouldRefreshRouteState };
|
|
351
339
|
}
|
|
352
340
|
|
|
353
|
-
async function
|
|
341
|
+
async function revalidateRouter(router) {
|
|
354
342
|
try {
|
|
355
343
|
window.__reactRouterHdrActive = true;
|
|
356
|
-
|
|
344
|
+
if (typeof router.revalidate === 'function') {
|
|
345
|
+
await router.revalidate();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (typeof router.navigate === 'function') {
|
|
349
|
+
await router.navigate(getCurrentRouterPath(router), {
|
|
350
|
+
replace: true,
|
|
351
|
+
preventScrollReset: true,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
357
354
|
} finally {
|
|
358
355
|
window.__reactRouterHdrActive = false;
|
|
359
356
|
}
|
|
360
357
|
}
|
|
361
358
|
|
|
362
|
-
async function
|
|
363
|
-
if (typeof router.revalidate
|
|
364
|
-
await withHdrActive(() => router.revalidate());
|
|
359
|
+
async function refreshRouteState(router) {
|
|
360
|
+
if (typeof router.revalidate !== 'function') {
|
|
365
361
|
return;
|
|
366
362
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
})
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async function refreshRouteState(router) {
|
|
378
|
-
if (typeof router.revalidate === 'function') {
|
|
379
|
-
await withHdrActive(() => router.revalidate());
|
|
380
|
-
return true;
|
|
363
|
+
try {
|
|
364
|
+
window.__reactRouterHdrActive = true;
|
|
365
|
+
await router.revalidate();
|
|
366
|
+
} finally {
|
|
367
|
+
window.__reactRouterHdrActive = false;
|
|
381
368
|
}
|
|
382
|
-
return false;
|
|
383
369
|
}
|
|
384
370
|
|
|
385
371
|
function performReactRefresh() {
|
|
@@ -391,6 +377,34 @@ function performReactRefresh() {
|
|
|
391
377
|
}
|
|
392
378
|
}
|
|
393
379
|
|
|
380
|
+
function applyManifestUpdate(nextRoutes) {
|
|
381
|
+
const router = window.__reactRouterDataRouter;
|
|
382
|
+
const routeModules = window.__reactRouterRouteModules;
|
|
383
|
+
const manifest = window.__reactRouterManifest;
|
|
384
|
+
const context = window.__reactRouterContext;
|
|
385
|
+
if (
|
|
386
|
+
!router ||
|
|
387
|
+
!routeModules ||
|
|
388
|
+
!manifest ||
|
|
389
|
+
!context ||
|
|
390
|
+
!nextRoutes ||
|
|
391
|
+
typeof router.createRoutesForHMR !== 'function' ||
|
|
392
|
+
typeof router._internalSetRoutes !== 'function'
|
|
393
|
+
) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const routes = router.createRoutesForHMR(
|
|
397
|
+
new Set(Object.keys(nextRoutes)),
|
|
398
|
+
nextRoutes,
|
|
399
|
+
routeModules,
|
|
400
|
+
context.ssr,
|
|
401
|
+
context.isSpaMode
|
|
402
|
+
);
|
|
403
|
+
manifest.routes = nextRoutes;
|
|
404
|
+
router._internalSetRoutes(routes);
|
|
405
|
+
patchCurrentRouteMatches(router, routes);
|
|
406
|
+
}
|
|
407
|
+
|
|
394
408
|
async function flush() {
|
|
395
409
|
const router = window.__reactRouterDataRouter;
|
|
396
410
|
const routeModules = window.__reactRouterRouteModules;
|
|
@@ -402,30 +416,48 @@ async function flush() {
|
|
|
402
416
|
|
|
403
417
|
let shouldRevalidate = pendingRevalidation;
|
|
404
418
|
pendingRevalidation = false;
|
|
405
|
-
const { nextManifest,
|
|
419
|
+
const { nextManifest, hmrRoutes, shouldRefreshRouteState } =
|
|
406
420
|
applyPendingRouteUpdates(router, routeModules, manifest, context);
|
|
407
|
-
|
|
421
|
+
// Loader updates must be visible during revalidation. Component-only routes
|
|
422
|
+
// stay staged until revalidation completes, matching React Router's HMR flow.
|
|
423
|
+
if (nextManifest && shouldRefreshRouteState) {
|
|
424
|
+
pendingComponentRouteRevalidation = false;
|
|
425
|
+
if (hmrRoutes) {
|
|
426
|
+
patchCurrentRouteMatches(router, hmrRoutes);
|
|
427
|
+
}
|
|
408
428
|
Object.assign(manifest, nextManifest);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
429
|
+
await refreshRouteState(router);
|
|
430
|
+
shouldRevalidate = false;
|
|
431
|
+
} else if (nextManifest) {
|
|
432
|
+
if (hmrRoutes) {
|
|
433
|
+
patchCurrentRouteMatches(router, hmrRoutes);
|
|
434
|
+
}
|
|
435
|
+
Object.assign(manifest, nextManifest);
|
|
436
|
+
// The node compiler also emits an HDR revision for this route edit. If it
|
|
437
|
+
// did not arrive in this flush, consume that redundant revalidation later.
|
|
438
|
+
pendingComponentRouteRevalidation = !shouldRevalidate;
|
|
439
|
+
shouldRevalidate = false;
|
|
440
|
+
} else if (shouldRevalidate) {
|
|
441
|
+
if (pendingComponentRouteRevalidation) {
|
|
442
|
+
pendingComponentRouteRevalidation = false;
|
|
443
|
+
} else {
|
|
444
|
+
await revalidateRouter(router);
|
|
417
445
|
}
|
|
418
|
-
}
|
|
419
|
-
if (shouldRevalidate) {
|
|
420
|
-
await revalidateRouter(router);
|
|
421
446
|
}
|
|
422
447
|
performReactRefresh();
|
|
423
448
|
}
|
|
424
449
|
|
|
425
450
|
if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
451
|
+
import.meta.webpackHot.on(
|
|
452
|
+
${JSON.stringify(DEV_MANIFEST_UPDATE_EVENT)},
|
|
453
|
+
applyManifestUpdate
|
|
454
|
+
);
|
|
426
455
|
import.meta.webpackHot.accept(
|
|
427
456
|
${JSON.stringify(hdrRevisionFilePath)},
|
|
428
|
-
|
|
457
|
+
() => {
|
|
458
|
+
latestHdrRevision = __hdrRevision;
|
|
459
|
+
scheduleReactRouterRevalidation();
|
|
460
|
+
}
|
|
429
461
|
);
|
|
430
462
|
}
|
|
431
463
|
`;
|
|
@@ -5,7 +5,7 @@ import type { ServerBuild } from 'react-router';
|
|
|
5
5
|
import type { ReactRouterManifestForDev } from './manifest.js';
|
|
6
6
|
import { getCappedPluginConcurrency } from './concurrency.js';
|
|
7
7
|
import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveServerBuildModule } from './server-build-resolution.js';
|
|
9
9
|
|
|
10
10
|
export type ReactRouterDevManifest = ReactRouterManifestForDev;
|
|
11
11
|
|
|
@@ -137,14 +137,17 @@ export const getEnvironmentStats = (
|
|
|
137
137
|
const startServerBuildEvaluationEffect = (
|
|
138
138
|
server: RsbuildDevServer,
|
|
139
139
|
entryName: string
|
|
140
|
-
): Effect.Effect<ServerBuild, Error, never> =>
|
|
140
|
+
): Effect.Effect<readonly [string, ServerBuild], Error, never> =>
|
|
141
141
|
tryPluginPromise(() => server.environments.node.loadBundle(entryName)).pipe(
|
|
142
142
|
Effect.flatMap(buildModule =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
tryPluginPromise(() =>
|
|
144
|
+
resolveServerBuildModule(
|
|
145
|
+
buildModule,
|
|
146
|
+
`Server entry ${JSON.stringify(entryName)}`
|
|
147
|
+
)
|
|
146
148
|
)
|
|
147
|
-
)
|
|
149
|
+
),
|
|
150
|
+
Effect.map(build => [entryName, build] as const)
|
|
148
151
|
);
|
|
149
152
|
|
|
150
153
|
const evaluateServerBuildsEffect = (
|
|
@@ -152,12 +155,8 @@ const evaluateServerBuildsEffect = (
|
|
|
152
155
|
entryNames: readonly string[]
|
|
153
156
|
): Effect.Effect<ReactRouterServerBuilds, Error, never> =>
|
|
154
157
|
Effect.forEach(
|
|
155
|
-
entryNames
|
|
156
|
-
|
|
157
|
-
Effect.map(build => [entryName, build] as const)
|
|
158
|
-
)
|
|
159
|
-
),
|
|
160
|
-
evaluation => evaluation,
|
|
158
|
+
entryNames,
|
|
159
|
+
entryName => startServerBuildEvaluationEffect(server, entryName),
|
|
161
160
|
{ concurrency: getCappedPluginConcurrency() }
|
|
162
161
|
).pipe(
|
|
163
162
|
Effect.map(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RsbuildConfig, RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
-
import * as Effect from 'effect/Effect';
|
|
3
2
|
import type { ServerBuild } from 'react-router';
|
|
4
3
|
import { PLUGIN_NAME } from './constants.js';
|
|
4
|
+
import { escapeHtml } from './plugin-utils.js';
|
|
5
5
|
import {
|
|
6
6
|
beginDevCompilerAttempt,
|
|
7
7
|
clearDevCompilerStart,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
registerReactRouterDevRuntime,
|
|
20
20
|
unregisterReactRouterDevRuntime,
|
|
21
21
|
} from './dev-generation.js';
|
|
22
|
+
import { DEV_MANIFEST_UPDATE_EVENT } from './dev-hmr.js';
|
|
22
23
|
import {
|
|
23
24
|
getEnvironmentStats,
|
|
24
25
|
snapshotDevChangedFiles,
|
|
@@ -27,17 +28,11 @@ import {
|
|
|
27
28
|
type ReactRouterDevBuildPlan,
|
|
28
29
|
type ReactRouterDevManifestSet,
|
|
29
30
|
} from './dev-runtime-artifacts.js';
|
|
30
|
-
import { DEV_HDR_REVISION_RELATIVE_PATH } from './dev-hmr.js';
|
|
31
31
|
import {
|
|
32
32
|
createDevRuntimeSessionManager,
|
|
33
33
|
type RuntimeBinding,
|
|
34
34
|
} from './dev-runtime-session.js';
|
|
35
|
-
import {
|
|
36
|
-
normalizeEffectError,
|
|
37
|
-
runPluginEffect,
|
|
38
|
-
tryPluginPromise,
|
|
39
|
-
tryPluginSync,
|
|
40
|
-
} from './effect-runtime.js';
|
|
35
|
+
import { normalizeEffectError } from './effect-runtime.js';
|
|
41
36
|
|
|
42
37
|
type ServerSetup = Exclude<
|
|
43
38
|
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
@@ -56,6 +51,11 @@ type CreateControllerOptions = {
|
|
|
56
51
|
api: RsbuildPluginAPI;
|
|
57
52
|
isBuild: boolean;
|
|
58
53
|
buildPlan: ReactRouterDevBuildPlan;
|
|
54
|
+
/**
|
|
55
|
+
* The browser HMR runtime patches route manifest metadata (loader/action
|
|
56
|
+
* flags) in place, so metadata-only changes no longer need a full reload.
|
|
57
|
+
*/
|
|
58
|
+
clientPatchesRouteMetadata?: boolean | (() => boolean);
|
|
59
59
|
/**
|
|
60
60
|
* Invoked after a development attempt commits a re-evaluated node build for
|
|
61
61
|
* changed server files. Used to signal hot data revalidation to the client.
|
|
@@ -63,36 +63,19 @@ type CreateControllerOptions = {
|
|
|
63
63
|
onNodeRebuildCommitted?: () => void;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const escapeHtml = (value: string): string =>
|
|
67
|
-
value
|
|
68
|
-
.replaceAll('&', '&')
|
|
69
|
-
.replaceAll('<', '<')
|
|
70
|
-
.replaceAll('>', '>');
|
|
71
|
-
|
|
72
66
|
const CSS_SOURCE_RELOAD_DELAY_MS = 1000;
|
|
73
67
|
|
|
74
68
|
const isHdrRevisionFile = (file: string): boolean =>
|
|
75
|
-
file.includes(
|
|
69
|
+
file.includes('.react-router/hdr-revision.mjs');
|
|
76
70
|
|
|
77
71
|
const isCssSourceFile = (file: string): boolean =>
|
|
78
72
|
/\.css(?:\.[cm]?[jt]s)?$/.test(file);
|
|
79
73
|
|
|
80
|
-
// A change that should bump the HDR revision: anything except the revision
|
|
81
|
-
// file itself (the bump's own echo) and CSS sources (styling never changes
|
|
82
|
-
// loader data).
|
|
83
|
-
const hasHdrTriggeringChange = (files: Iterable<string>): boolean => {
|
|
84
|
-
for (const file of files) {
|
|
85
|
-
if (!isHdrRevisionFile(file) && !isCssSourceFile(file)) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
74
|
export const createReactRouterDevRuntimeController = ({
|
|
93
75
|
api,
|
|
94
76
|
isBuild,
|
|
95
77
|
buildPlan,
|
|
78
|
+
clientPatchesRouteMetadata,
|
|
96
79
|
onNodeRebuildCommitted,
|
|
97
80
|
}: CreateControllerOptions): ReactRouterDevRuntimeController => {
|
|
98
81
|
if (isBuild) {
|
|
@@ -165,56 +148,44 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
165
148
|
NonNullable<DevGraphIdentity['node']>
|
|
166
149
|
>();
|
|
167
150
|
|
|
168
|
-
const
|
|
151
|
+
const finishRuntimeAttempt = async (
|
|
169
152
|
binding: RuntimeBinding,
|
|
170
153
|
pair: DevCompilerPair,
|
|
171
154
|
stats: DevRuntimeStats,
|
|
172
155
|
changes: Parameters<RuntimeBinding['runtime']['finishAttempt']>[1],
|
|
173
156
|
identity: Parameters<RuntimeBinding['runtime']['finishAttempt']>[2]
|
|
174
|
-
):
|
|
175
|
-
|
|
176
|
-
binding.runtime.finishAttempt(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const finishRuntimeAttempt = (
|
|
209
|
-
binding: RuntimeBinding,
|
|
210
|
-
pair: DevCompilerPair,
|
|
211
|
-
stats: DevRuntimeStats,
|
|
212
|
-
changes: Parameters<RuntimeBinding['runtime']['finishAttempt']>[1],
|
|
213
|
-
identity: Parameters<RuntimeBinding['runtime']['finishAttempt']>[2]
|
|
214
|
-
): Promise<void> =>
|
|
215
|
-
runPluginEffect(
|
|
216
|
-
finishRuntimeAttemptEffect(binding, pair, stats, changes, identity)
|
|
217
|
-
);
|
|
157
|
+
): Promise<void> => {
|
|
158
|
+
try {
|
|
159
|
+
const result = await binding.runtime.finishAttempt(
|
|
160
|
+
stats,
|
|
161
|
+
changes,
|
|
162
|
+
identity
|
|
163
|
+
);
|
|
164
|
+
if (sessions.getActiveBinding()?.id !== binding.id) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (result === 'retry-node') {
|
|
168
|
+
pair.node.watching?.invalidate();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
result === 'committed' &&
|
|
173
|
+
changes.node.known &&
|
|
174
|
+
identity.node !== undefined &&
|
|
175
|
+
hdrSignaledNodeIdentity.get(pair) !== identity.node &&
|
|
176
|
+
Array.from(changes.node.files).some(
|
|
177
|
+
file => !isHdrRevisionFile(file) && !isCssSourceFile(file)
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
hdrSignaledNodeIdentity.set(pair, identity.node);
|
|
181
|
+
onNodeRebuildCommitted?.();
|
|
182
|
+
}
|
|
183
|
+
} catch (cause) {
|
|
184
|
+
if (sessions.getActiveBinding()?.id === binding.id) {
|
|
185
|
+
binding.runtime.failAttempt(normalizeEffectError(cause));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
218
189
|
|
|
219
190
|
const flushSettledAttempt = (
|
|
220
191
|
binding: RuntimeBinding,
|
|
@@ -306,11 +277,22 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
306
277
|
reloadAfterCssAssetOwnershipRemoval = change === 'removed';
|
|
307
278
|
sendCssAssetOwnershipReload();
|
|
308
279
|
},
|
|
309
|
-
onRouteManifestChanged() {
|
|
280
|
+
onRouteManifestChanged(manifest) {
|
|
310
281
|
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
311
282
|
return;
|
|
312
283
|
}
|
|
313
|
-
|
|
284
|
+
const patchesRouteMetadata =
|
|
285
|
+
typeof clientPatchesRouteMetadata === 'function'
|
|
286
|
+
? clientPatchesRouteMetadata()
|
|
287
|
+
: clientPatchesRouteMetadata;
|
|
288
|
+
if (patchesRouteMetadata) {
|
|
289
|
+
server.sockWrite('custom', {
|
|
290
|
+
event: DEV_MANIFEST_UPDATE_EVENT,
|
|
291
|
+
data: manifest.routes,
|
|
292
|
+
});
|
|
293
|
+
} else {
|
|
294
|
+
server.sockWrite('full-reload', { path: '*' });
|
|
295
|
+
}
|
|
314
296
|
},
|
|
315
297
|
onWarning: message => api.logger.warn(message),
|
|
316
298
|
});
|
|
@@ -566,21 +548,29 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
566
548
|
},
|
|
567
549
|
|
|
568
550
|
createBuildLoader(entryName?: string): () => Promise<ServerBuild> {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
551
|
+
// Pin the loader to the dev-server session active at creation time. Once a
|
|
552
|
+
// loader is handed to React Router for session N it must keep serving N (or
|
|
553
|
+
// fail loudly with 'not registered' once N closes) and never silently migrate
|
|
554
|
+
// to a replacement session — this preserves SSR generation/session coherency.
|
|
555
|
+
// The live fallback below applies ONLY when no session exists yet at creation
|
|
556
|
+
// (boundServer === undefined), i.e. a loader built during config setup before
|
|
557
|
+
// the dev server has started; there is no session to stay coherent with yet.
|
|
558
|
+
const boundServer = sessions.getActiveBinding()?.server;
|
|
559
|
+
return () => {
|
|
560
|
+
const server = boundServer ?? sessions.getActiveBinding()?.server;
|
|
561
|
+
if (server) {
|
|
562
|
+
return loadReactRouterServerBuild(server, entryName);
|
|
563
|
+
}
|
|
564
|
+
const state = sessions.getState();
|
|
565
|
+
if (state.status === 'terminal') {
|
|
566
|
+
return Promise.reject(state.error);
|
|
567
|
+
}
|
|
568
|
+
return Promise.reject(
|
|
580
569
|
new Error(
|
|
581
570
|
`[${PLUGIN_NAME}] The development server runtime is not ready.`
|
|
582
571
|
)
|
|
583
572
|
);
|
|
573
|
+
};
|
|
584
574
|
},
|
|
585
575
|
};
|
|
586
576
|
};
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { RsbuildDevServer } from '@rsbuild/core';
|
|
2
|
-
import * as Effect from 'effect/Effect';
|
|
3
2
|
import { PLUGIN_NAME } from './constants.js';
|
|
4
3
|
import type { ReactRouterDevRuntime } from './dev-generation.js';
|
|
5
4
|
import type { DevCompilerPair } from './dev-runtime-compilation.js';
|
|
6
|
-
import {
|
|
7
|
-
runPluginEffect,
|
|
8
|
-
tryPluginPromise,
|
|
9
|
-
tryPluginSync,
|
|
10
|
-
} from './effect-runtime.js';
|
|
5
|
+
import { normalizeEffectError } from './effect-runtime.js';
|
|
11
6
|
|
|
12
7
|
export type RuntimeBinding = {
|
|
13
8
|
id: number;
|
|
@@ -115,18 +110,19 @@ export const createDevRuntimeSessionManager = (
|
|
|
115
110
|
if (observation.promise) {
|
|
116
111
|
return observation.promise;
|
|
117
112
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
let closePromise: Promise<void>;
|
|
114
|
+
try {
|
|
115
|
+
closePromise = Promise.resolve(close());
|
|
116
|
+
} catch (cause) {
|
|
117
|
+
closePromise = Promise.reject(normalizeEffectError(cause));
|
|
118
|
+
}
|
|
119
|
+
observation.promise = closePromise
|
|
120
|
+
.then(() => applyCloseOutcome(observation, { ok: true }))
|
|
121
|
+
.catch(cause => {
|
|
122
|
+
const error = normalizeEffectError(cause);
|
|
123
|
+
applyCloseOutcome(observation, { ok: false, cause: error });
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
130
126
|
return observation.promise;
|
|
131
127
|
};
|
|
132
128
|
closeObservationByServer.set(server, observation);
|
package/src/dev-server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import type { RsbuildConfig } from '@rsbuild/core';
|
|
3
3
|
import type { ServerBuild } from 'react-router';
|
|
4
|
+
import { installDevServerSourceMapSupport } from './dev-source-maps.js';
|
|
4
5
|
|
|
5
6
|
export type ServerSetup = Exclude<
|
|
6
7
|
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
@@ -83,6 +84,7 @@ export const createReactRouterDevServerSetup = ({
|
|
|
83
84
|
// middlewares, so static assets are served before the React Router
|
|
84
85
|
// request handler.
|
|
85
86
|
return () => {
|
|
87
|
+
installDevServerSourceMapSupport();
|
|
86
88
|
context.server.middlewares.use(createDevServerMiddleware({ loadBuild }));
|
|
87
89
|
};
|
|
88
90
|
};
|