rsbuild-plugin-react-router 0.7.2 → 0.8.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 +14 -2
- package/dist/468.js +48 -0
- package/dist/511.js +60 -86
- package/dist/819.js +64 -0
- package/dist/build-output-transforms.d.ts +3 -1
- package/dist/constants.d.ts +1 -0
- package/dist/dev-hdr-channel.d.ts +9 -0
- package/dist/dev-hmr.d.ts +1 -22
- package/dist/dev-runtime-controller.d.ts +1 -6
- package/dist/dev-server.d.ts +3 -1
- package/dist/index.cjs +5417 -5080
- package/dist/index.js +1701 -1441
- package/dist/manifest-assets.d.ts +34 -0
- package/dist/manifest-snapshot.d.ts +17 -0
- package/dist/manifest-state.d.ts +12 -0
- package/dist/manifest.d.ts +2 -22
- package/dist/modify-browser-manifest.d.ts +2 -0
- package/dist/node-only-manifest.d.ts +8 -0
- package/dist/plugin-utils.d.ts +1 -1
- package/dist/rsc-prerender.d.ts +3 -2
- package/dist/server-build-worker-client.d.ts +20 -0
- package/dist/server-build-worker-protocol.d.ts +84 -0
- package/dist/server-build-worker.d.ts +1 -0
- package/dist/server-build-worker.js +123 -0
- package/dist/server-utils.d.ts +1 -2
- package/dist/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/build-output-transforms.ts +22 -1
- package/src/classic-mode.ts +0 -1
- package/src/constants.ts +3 -0
- package/src/dev-hdr-channel.ts +38 -0
- package/src/dev-hmr.ts +57 -100
- package/src/dev-runtime-controller.ts +23 -18
- package/src/dev-server.ts +24 -4
- package/src/index.ts +229 -144
- package/src/lazy-compilation.ts +7 -2
- package/src/manifest-assets.ts +228 -0
- package/src/manifest-snapshot.ts +80 -0
- package/src/manifest-state.ts +71 -0
- package/src/manifest.ts +23 -161
- package/src/mode-plan.ts +12 -4
- package/src/modify-browser-manifest.ts +47 -18
- package/src/node-only-manifest.ts +52 -0
- package/src/plugin-utils.ts +6 -2
- package/src/prerender-build.ts +76 -86
- package/src/route-chunks.ts +152 -63
- package/src/rsc-prerender.ts +7 -35
- package/src/server-build-resolution.ts +1 -2
- package/src/server-build-worker-client.ts +221 -0
- package/src/server-build-worker-protocol.ts +69 -0
- package/src/server-build-worker.ts +192 -0
- package/src/server-utils.ts +0 -2
- package/src/types.ts +7 -0
package/src/dev-hmr.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
1
|
import { createRequire } from 'node:module';
|
|
3
2
|
import type { Rspack } from '@rsbuild/core';
|
|
4
|
-
import {
|
|
3
|
+
import { join } from 'pathe';
|
|
4
|
+
import { DEV_HDR_UPDATE_EVENT } from './dev-hdr-channel.js';
|
|
5
5
|
|
|
6
6
|
export const DEV_HMR_RUNTIME_MODULE_ID = 'virtual/react-router/hmr-runtime';
|
|
7
7
|
export const DEV_MANIFEST_UPDATE_EVENT = 'react-router:manifest-update';
|
|
@@ -9,7 +9,6 @@ export const DEV_MANIFEST_UPDATE_EVENT = 'react-router:manifest-update';
|
|
|
9
9
|
export type DevHmrPlanOptions = {
|
|
10
10
|
isEnabled: () => boolean;
|
|
11
11
|
runtimeModule: string;
|
|
12
|
-
onNodeRebuildCommitted: () => void;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
type SwcLoaderOptions = {
|
|
@@ -80,57 +79,6 @@ export const resolveReactRefreshRuntimePath = (
|
|
|
80
79
|
}
|
|
81
80
|
};
|
|
82
81
|
|
|
83
|
-
const hdrRevisionModuleContent = (revision: number): string =>
|
|
84
|
-
`export default ${revision};\n`;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The HDR revision module is a real file (not a virtual module) because it
|
|
88
|
-
* must wake the web compiler through the regular file watcher: the browser
|
|
89
|
-
* HMR runtime imports it, so bumping the revision produces a web hot update
|
|
90
|
-
* whenever server code changes, which the client answers by revalidating
|
|
91
|
-
* React Router loader data.
|
|
92
|
-
*/
|
|
93
|
-
export const getDevHdrRevisionFilePath = (rootPath: string): string =>
|
|
94
|
-
join(rootPath, '.react-router', 'hdr-revision.mjs');
|
|
95
|
-
|
|
96
|
-
export type DevHdrRevisionSignal = {
|
|
97
|
-
filePath: string;
|
|
98
|
-
/** Writes the initial revision module so the first compile can resolve it. */
|
|
99
|
-
ensure: () => void;
|
|
100
|
-
/** Increments the revision, signaling hot data revalidation to the client. */
|
|
101
|
-
bump: () => void;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export const createDevHdrRevisionSignal = ({
|
|
105
|
-
filePath,
|
|
106
|
-
onError,
|
|
107
|
-
}: {
|
|
108
|
-
filePath: string;
|
|
109
|
-
onError?: (error: Error) => void;
|
|
110
|
-
}): DevHdrRevisionSignal => {
|
|
111
|
-
let revision = 0;
|
|
112
|
-
let dirEnsured = false;
|
|
113
|
-
const write = (): void => {
|
|
114
|
-
try {
|
|
115
|
-
if (!dirEnsured) {
|
|
116
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
117
|
-
dirEnsured = true;
|
|
118
|
-
}
|
|
119
|
-
writeFileSync(filePath, hdrRevisionModuleContent(revision));
|
|
120
|
-
} catch (error) {
|
|
121
|
-
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
return {
|
|
125
|
-
filePath,
|
|
126
|
-
ensure: write,
|
|
127
|
-
bump() {
|
|
128
|
-
revision += 1;
|
|
129
|
-
write();
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
};
|
|
133
|
-
|
|
134
82
|
/**
|
|
135
83
|
* Browser-side HMR runtime shared by all route client entries in development.
|
|
136
84
|
*
|
|
@@ -143,20 +91,10 @@ export const createDevHdrRevisionSignal = ({
|
|
|
143
91
|
*/
|
|
144
92
|
export const generateDevHmrRuntimeModule = ({
|
|
145
93
|
reactRefreshRuntimePath,
|
|
146
|
-
hdrRevisionFilePath,
|
|
147
94
|
}: {
|
|
148
95
|
reactRefreshRuntimePath: string;
|
|
149
|
-
hdrRevisionFilePath: string;
|
|
150
96
|
}): string => `
|
|
151
97
|
import * as __refreshRuntimeModule from ${JSON.stringify(reactRefreshRuntimePath)};
|
|
152
|
-
// Read revision so the import survives sideEffects: false tree-shaking.
|
|
153
|
-
import __hdrRevision from ${JSON.stringify(hdrRevisionFilePath)};
|
|
154
|
-
|
|
155
|
-
let latestHdrRevision = __hdrRevision;
|
|
156
|
-
|
|
157
|
-
export function getReactRouterHdrRevision() {
|
|
158
|
-
return latestHdrRevision;
|
|
159
|
-
}
|
|
160
98
|
|
|
161
99
|
const RefreshRuntime =
|
|
162
100
|
__refreshRuntimeModule && __refreshRuntimeModule.performReactRefresh
|
|
@@ -166,7 +104,24 @@ const RefreshRuntime =
|
|
|
166
104
|
const pendingRouteUpdates = new Map();
|
|
167
105
|
let flushTimeout;
|
|
168
106
|
let pendingRevalidation = false;
|
|
169
|
-
let
|
|
107
|
+
let flushing = false;
|
|
108
|
+
let hdrSession;
|
|
109
|
+
let latestHdrRevision = 0;
|
|
110
|
+
const retiredHdrSessions = new Set();
|
|
111
|
+
|
|
112
|
+
function receiveHdrRevision(message) {
|
|
113
|
+
if (!message || typeof message.sessionId !== 'string' ||
|
|
114
|
+
!Number.isSafeInteger(message.revision) || message.revision < 1 ||
|
|
115
|
+
retiredHdrSessions.has(message.sessionId)) return;
|
|
116
|
+
if (hdrSession !== message.sessionId) {
|
|
117
|
+
if (hdrSession) retiredHdrSessions.add(hdrSession);
|
|
118
|
+
hdrSession = message.sessionId;
|
|
119
|
+
latestHdrRevision = 0;
|
|
120
|
+
}
|
|
121
|
+
if (message.revision <= latestHdrRevision) return;
|
|
122
|
+
latestHdrRevision = message.revision;
|
|
123
|
+
scheduleReactRouterRevalidation();
|
|
124
|
+
}
|
|
170
125
|
|
|
171
126
|
function getCurrentRouterPath(router) {
|
|
172
127
|
const basename = router.basename || '/';
|
|
@@ -207,7 +162,7 @@ export function scheduleReactRouterRouteUpdate(
|
|
|
207
162
|
scheduleFlush();
|
|
208
163
|
}
|
|
209
164
|
|
|
210
|
-
|
|
165
|
+
function scheduleReactRouterRevalidation() {
|
|
211
166
|
pendingRevalidation = true;
|
|
212
167
|
scheduleFlush();
|
|
213
168
|
}
|
|
@@ -406,45 +361,50 @@ function applyManifestUpdate(nextRoutes) {
|
|
|
406
361
|
}
|
|
407
362
|
|
|
408
363
|
async function flush() {
|
|
364
|
+
if (flushing) return;
|
|
365
|
+
if (import.meta.webpackHot && import.meta.webpackHot.status() !== 'idle') {
|
|
366
|
+
scheduleFlush();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
409
369
|
const router = window.__reactRouterDataRouter;
|
|
410
370
|
const routeModules = window.__reactRouterRouteModules;
|
|
411
371
|
const manifest = window.__reactRouterManifest;
|
|
412
372
|
const context = window.__reactRouterContext;
|
|
413
373
|
if (!router || !routeModules || !manifest || !context) {
|
|
374
|
+
scheduleFlush();
|
|
414
375
|
return;
|
|
415
376
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
377
|
+
flushing = true;
|
|
378
|
+
try {
|
|
379
|
+
let shouldRevalidate = pendingRevalidation;
|
|
380
|
+
pendingRevalidation = false;
|
|
381
|
+
const { nextManifest, hmrRoutes, shouldRefreshRouteState } =
|
|
382
|
+
applyPendingRouteUpdates(router, routeModules, manifest, context);
|
|
383
|
+
// Loader updates must be visible during revalidation. Component-only routes
|
|
384
|
+
// stay staged until revalidation completes, matching React Router's HMR flow.
|
|
385
|
+
if (nextManifest && shouldRefreshRouteState) {
|
|
386
|
+
if (hmrRoutes) {
|
|
387
|
+
patchCurrentRouteMatches(router, hmrRoutes);
|
|
388
|
+
}
|
|
389
|
+
Object.assign(manifest, nextManifest);
|
|
390
|
+
await refreshRouteState(router);
|
|
391
|
+
shouldRevalidate = false;
|
|
392
|
+
} else if (nextManifest) {
|
|
393
|
+
if (hmrRoutes) {
|
|
394
|
+
patchCurrentRouteMatches(router, hmrRoutes);
|
|
395
|
+
}
|
|
396
|
+
Object.assign(manifest, nextManifest);
|
|
434
397
|
}
|
|
435
|
-
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
pendingComponentRouteRevalidation = !shouldRevalidate;
|
|
439
|
-
shouldRevalidate = false;
|
|
440
|
-
} else if (shouldRevalidate) {
|
|
441
|
-
if (pendingComponentRouteRevalidation) {
|
|
442
|
-
pendingComponentRouteRevalidation = false;
|
|
443
|
-
} else {
|
|
398
|
+
// A replay may represent several server edits, not just the component
|
|
399
|
+
// update in this batch. Only an actual data revalidation can consume it.
|
|
400
|
+
if (shouldRevalidate) {
|
|
444
401
|
await revalidateRouter(router);
|
|
445
402
|
}
|
|
403
|
+
performReactRefresh();
|
|
404
|
+
} finally {
|
|
405
|
+
flushing = false;
|
|
406
|
+
if (pendingRevalidation || pendingRouteUpdates.size > 0) scheduleFlush();
|
|
446
407
|
}
|
|
447
|
-
performReactRefresh();
|
|
448
408
|
}
|
|
449
409
|
|
|
450
410
|
if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
@@ -452,12 +412,9 @@ if (typeof window !== 'undefined' && import.meta.webpackHot) {
|
|
|
452
412
|
${JSON.stringify(DEV_MANIFEST_UPDATE_EVENT)},
|
|
453
413
|
applyManifestUpdate
|
|
454
414
|
);
|
|
455
|
-
import.meta.webpackHot.
|
|
456
|
-
${JSON.stringify(
|
|
457
|
-
|
|
458
|
-
latestHdrRevision = __hdrRevision;
|
|
459
|
-
scheduleReactRouterRevalidation();
|
|
460
|
-
}
|
|
415
|
+
import.meta.webpackHot.on(
|
|
416
|
+
${JSON.stringify(DEV_HDR_UPDATE_EVENT)},
|
|
417
|
+
receiveHdrRevision
|
|
461
418
|
);
|
|
462
419
|
}
|
|
463
420
|
`;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
unregisterReactRouterDevRuntime,
|
|
21
21
|
} from './dev-generation.js';
|
|
22
22
|
import { createDevHdrIntentTracker } from './dev-hdr-intent.js';
|
|
23
|
+
import { createDevHdrChannel } from './dev-hdr-channel.js';
|
|
23
24
|
import { DEV_MANIFEST_UPDATE_EVENT } from './dev-hmr.js';
|
|
24
25
|
import {
|
|
25
26
|
getEnvironmentStats,
|
|
@@ -57,18 +58,10 @@ type CreateControllerOptions = {
|
|
|
57
58
|
* flags) in place, so metadata-only changes no longer need a full reload.
|
|
58
59
|
*/
|
|
59
60
|
clientPatchesRouteMetadata?: boolean | (() => boolean);
|
|
60
|
-
/**
|
|
61
|
-
* Invoked after a development attempt commits a re-evaluated node build for
|
|
62
|
-
* changed server files. Used to signal hot data revalidation to the client.
|
|
63
|
-
*/
|
|
64
|
-
onNodeRebuildCommitted?: () => void;
|
|
65
61
|
};
|
|
66
62
|
|
|
67
63
|
const CSS_SOURCE_RELOAD_DELAY_MS = 1000;
|
|
68
64
|
|
|
69
|
-
const isHdrRevisionFile = (file: string): boolean =>
|
|
70
|
-
file.includes('.react-router/hdr-revision.mjs');
|
|
71
|
-
|
|
72
65
|
const isCssSourceFile = (file: string): boolean =>
|
|
73
66
|
/\.css(?:\.[cm]?[jt]s)?$/.test(file);
|
|
74
67
|
|
|
@@ -77,7 +70,6 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
77
70
|
isBuild,
|
|
78
71
|
buildPlan,
|
|
79
72
|
clientPatchesRouteMetadata,
|
|
80
|
-
onNodeRebuildCommitted,
|
|
81
73
|
}: CreateControllerOptions): ReactRouterDevRuntimeController => {
|
|
82
74
|
if (isBuild) {
|
|
83
75
|
return {
|
|
@@ -122,7 +114,18 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
122
114
|
}, CSS_SOURCE_RELOAD_DELAY_MS);
|
|
123
115
|
};
|
|
124
116
|
|
|
117
|
+
const hdrChannels = new WeakMap<
|
|
118
|
+
RuntimeBinding,
|
|
119
|
+
ReturnType<typeof createDevHdrChannel>
|
|
120
|
+
>();
|
|
121
|
+
const isHmrEnabled = () =>
|
|
122
|
+
typeof clientPatchesRouteMetadata === 'function'
|
|
123
|
+
? clientPatchesRouteMetadata()
|
|
124
|
+
: clientPatchesRouteMetadata === true;
|
|
125
|
+
|
|
125
126
|
const closeBinding = (binding: RuntimeBinding, error?: Error): void => {
|
|
127
|
+
hdrChannels.get(binding)?.close();
|
|
128
|
+
hdrChannels.delete(binding);
|
|
126
129
|
if (scheduledCssAssetOwnershipReload) {
|
|
127
130
|
clearTimeout(scheduledCssAssetOwnershipReload);
|
|
128
131
|
scheduledCssAssetOwnershipReload = undefined;
|
|
@@ -174,7 +177,7 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
174
177
|
identity.node === binding.runtime.getCommittedNodeIdentity()
|
|
175
178
|
) {
|
|
176
179
|
hdrIntentsByPair.get(pair)?.signalCommitted(nodeCompilation, () => {
|
|
177
|
-
|
|
180
|
+
hdrChannels.get(binding)?.publish();
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
183
|
} catch (cause) {
|
|
@@ -278,11 +281,7 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
278
281
|
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
279
282
|
return;
|
|
280
283
|
}
|
|
281
|
-
|
|
282
|
-
typeof clientPatchesRouteMetadata === 'function'
|
|
283
|
-
? clientPatchesRouteMetadata()
|
|
284
|
-
: clientPatchesRouteMetadata;
|
|
285
|
-
if (patchesRouteMetadata) {
|
|
284
|
+
if (isHmrEnabled()) {
|
|
286
285
|
server.sockWrite('custom', {
|
|
287
286
|
event: DEV_MANIFEST_UPDATE_EVENT,
|
|
288
287
|
data: manifest.routes,
|
|
@@ -294,6 +293,14 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
294
293
|
onWarning: message => api.logger.warn(message),
|
|
295
294
|
});
|
|
296
295
|
const binding = sessions.createBinding(server, runtime);
|
|
296
|
+
hdrChannels.set(
|
|
297
|
+
binding,
|
|
298
|
+
createDevHdrChannel({
|
|
299
|
+
hot: server.environments.web.hot,
|
|
300
|
+
isEnabled: () =>
|
|
301
|
+
sessions.getActiveBinding() === binding && isHmrEnabled(),
|
|
302
|
+
})
|
|
303
|
+
);
|
|
297
304
|
registerReactRouterDevRuntime(server, runtime);
|
|
298
305
|
sessions.bindCloseObservation(binding);
|
|
299
306
|
},
|
|
@@ -430,9 +437,7 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
430
437
|
hdrIntents.capture(
|
|
431
438
|
compilation,
|
|
432
439
|
changes.known &&
|
|
433
|
-
Array.from(changes.files).some(
|
|
434
|
-
file => !isHdrRevisionFile(file) && !isCssSourceFile(file)
|
|
435
|
-
)
|
|
440
|
+
Array.from(changes.files).some(file => !isCssSourceFile(file))
|
|
436
441
|
);
|
|
437
442
|
pair.latestNodeStart = {
|
|
438
443
|
status: 'started',
|
package/src/dev-server.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
2
3
|
import type { RsbuildConfig } from '@rsbuild/core';
|
|
3
4
|
import type { ServerBuild } from 'react-router';
|
|
4
5
|
import { installDevServerSourceMapSupport } from './dev-source-maps.js';
|
|
6
|
+
import { resolveAppPackagePath } from './plugin-utils.js';
|
|
5
7
|
|
|
6
8
|
export type ServerSetup = Exclude<
|
|
7
9
|
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
@@ -18,6 +20,7 @@ type RequestHandler = (request: Request) => Response | Promise<Response>;
|
|
|
18
20
|
type BuildProvider = () => Promise<ServerBuild>;
|
|
19
21
|
|
|
20
22
|
export type DevServerMiddlewareDependencies = {
|
|
23
|
+
rootPath: string;
|
|
21
24
|
loadBuild: BuildProvider;
|
|
22
25
|
createRequestHandler?: (
|
|
23
26
|
build: BuildProvider,
|
|
@@ -39,9 +42,22 @@ export const createDevServerMiddleware = (
|
|
|
39
42
|
|
|
40
43
|
const getListener = () => {
|
|
41
44
|
listenerPromise ??= (async () => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
let createRequestHandler = dependencies.createRequestHandler;
|
|
46
|
+
if (!createRequestHandler) {
|
|
47
|
+
const reactRouterPath = resolveAppPackagePath(
|
|
48
|
+
'react-router',
|
|
49
|
+
dependencies.rootPath
|
|
50
|
+
);
|
|
51
|
+
if (!reactRouterPath) {
|
|
52
|
+
throw new Error('Cannot resolve react-router from the application.');
|
|
53
|
+
}
|
|
54
|
+
// Match the application's browser/server bundles, including when the
|
|
55
|
+
// plugin is linked from a workspace with a different Router version.
|
|
56
|
+
const routerModule: typeof import('react-router') = await import(
|
|
57
|
+
pathToFileURL(reactRouterPath).href
|
|
58
|
+
);
|
|
59
|
+
createRequestHandler = routerModule.createRequestHandler;
|
|
60
|
+
}
|
|
45
61
|
const createRequestListener =
|
|
46
62
|
dependencies.createRequestListener ??
|
|
47
63
|
(await import('@remix-run/node-fetch-server')).createRequestListener;
|
|
@@ -73,8 +89,10 @@ export const createDevServerMiddleware = (
|
|
|
73
89
|
|
|
74
90
|
export const createReactRouterDevServerSetup = ({
|
|
75
91
|
loadBuild,
|
|
92
|
+
rootPath,
|
|
76
93
|
}: {
|
|
77
94
|
loadBuild: BuildProvider;
|
|
95
|
+
rootPath: string;
|
|
78
96
|
}): ServerSetup =>
|
|
79
97
|
function reactRouterDevServerSetup(context) {
|
|
80
98
|
if (context.action !== 'dev') {
|
|
@@ -85,6 +103,8 @@ export const createReactRouterDevServerSetup = ({
|
|
|
85
103
|
// request handler.
|
|
86
104
|
return () => {
|
|
87
105
|
installDevServerSourceMapSupport();
|
|
88
|
-
context.server.middlewares.use(
|
|
106
|
+
context.server.middlewares.use(
|
|
107
|
+
createDevServerMiddleware({ loadBuild, rootPath })
|
|
108
|
+
);
|
|
89
109
|
};
|
|
90
110
|
};
|