rsbuild-plugin-react-router 0.7.3 → 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 +13 -1
- package/dist/468.js +2 -2
- package/dist/511.js +57 -38
- 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 +5515 -5303
- package/dist/index.js +392 -200
- 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/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/build-output-transforms.ts +5 -0
- 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 +206 -151
- 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/route-chunks.ts +152 -63
- package/src/types.ts +7 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ReactRouterManifestStats = {
|
|
2
|
+
assetsByChunkName?: Record<string, string[]>;
|
|
3
|
+
entrypointFilesByName?: Record<string, string[]>;
|
|
4
|
+
assetTypesByName?: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
type ManifestStatsChunk = {
|
|
7
|
+
files?: Iterable<string>;
|
|
8
|
+
};
|
|
9
|
+
type ManifestStatsEntrypoint = {
|
|
10
|
+
getFiles?: () => Iterable<string>;
|
|
11
|
+
};
|
|
12
|
+
type ManifestStatsLookup<T> = Iterable<[string, T | null | undefined]> & {
|
|
13
|
+
get?: (name: string) => T | null | undefined;
|
|
14
|
+
};
|
|
15
|
+
type ManifestStatsCompilation = {
|
|
16
|
+
namedChunks: ManifestStatsLookup<ManifestStatsChunk>;
|
|
17
|
+
entrypoints?: ManifestStatsLookup<ManifestStatsEntrypoint>;
|
|
18
|
+
getAsset?: (name: string) => {
|
|
19
|
+
name?: string;
|
|
20
|
+
info?: {
|
|
21
|
+
assetType?: string;
|
|
22
|
+
javascriptModule?: boolean;
|
|
23
|
+
};
|
|
24
|
+
} | void;
|
|
25
|
+
};
|
|
26
|
+
export declare const stripAssetQuery: (name: string) => string;
|
|
27
|
+
export declare const getManifestAssetType: (name: string, assetTypesByName?: Readonly<Record<string, string>>) => string | undefined;
|
|
28
|
+
export declare const createReactRouterManifestStats: (compilation: ManifestStatsCompilation | undefined, chunkNames?: ReadonlySet<string>) => ReactRouterManifestStats | undefined;
|
|
29
|
+
type ChunkAssets = {
|
|
30
|
+
js: string[];
|
|
31
|
+
css: string[];
|
|
32
|
+
};
|
|
33
|
+
export declare const createChunkAssetResolver: (clientStats: ReactRouterManifestStats | undefined, includeEntrypointJavaScript: boolean) => ((chunkName: string) => ChunkAssets);
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactRouterManifestForDev, RouteManifestModuleExports } from './manifest.js';
|
|
2
|
+
import type { ReactRouterServerBuildPlan } from './server-build-plan.js';
|
|
3
|
+
import type { Route } from './types.js';
|
|
4
|
+
export type ReactRouterManifestSnapshot = {
|
|
5
|
+
browser: ReactRouterManifestForDev;
|
|
6
|
+
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
7
|
+
server: ReactRouterManifestForDev;
|
|
8
|
+
serverByBundleId: Readonly<Record<string, ReactRouterManifestForDev>>;
|
|
9
|
+
serverByEntryName: Readonly<Record<string, ReactRouterManifestForDev>>;
|
|
10
|
+
};
|
|
11
|
+
export declare const createReactRouterManifestSnapshot: ({ manifest, sri, moduleExportsByRouteId, serverBuildPlan, routesByServerBundleId, }: {
|
|
12
|
+
manifest: ReactRouterManifestForDev;
|
|
13
|
+
sri: ReactRouterManifestForDev["sri"];
|
|
14
|
+
moduleExportsByRouteId: RouteManifestModuleExports;
|
|
15
|
+
serverBuildPlan: Pick<ReactRouterServerBuildPlan, "defaultEntryName" | "serverBundleEntries">;
|
|
16
|
+
routesByServerBundleId: Readonly<Record<string, Readonly<Record<string, Route>>>>;
|
|
17
|
+
}) => ReactRouterManifestSnapshot;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
+
import type { ReactRouterManifestSnapshot } from './manifest-snapshot.js';
|
|
3
|
+
type ReactRouterManifestState = {
|
|
4
|
+
stage(compilation: Rspack.Compilation, snapshot: ReactRouterManifestSnapshot): void;
|
|
5
|
+
read(): ReactRouterManifestSnapshot | null;
|
|
6
|
+
};
|
|
7
|
+
export declare const createReactRouterManifestState: ({ api, isBuild, onPublish, }: {
|
|
8
|
+
api: Pick<RsbuildPluginAPI, "onBeforeEnvironmentCompile" | "onAfterEnvironmentCompile">;
|
|
9
|
+
isBuild: boolean;
|
|
10
|
+
onPublish: (compilation: Rspack.Compilation, snapshot: ReactRouterManifestSnapshot) => void;
|
|
11
|
+
}) => ReactRouterManifestState;
|
|
12
|
+
export {};
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ReactRouterManifestStats } from './manifest-assets.js';
|
|
2
|
+
export { createReactRouterManifestStats, type ReactRouterManifestStats, } from './manifest-assets.js';
|
|
1
3
|
import type { Route, PluginOptions, RouteManifestItem } from './types.js';
|
|
2
4
|
import { type RouteChunkCache } from './route-chunks.js';
|
|
3
5
|
import { type RouteModuleAnalysis } from './export-utils.js';
|
|
@@ -44,26 +46,6 @@ export type ReactRouterManifestForDev = {
|
|
|
44
46
|
sri?: Record<string, string> | true;
|
|
45
47
|
routes: Record<string, RouteManifestItem>;
|
|
46
48
|
};
|
|
47
|
-
export type ReactRouterManifestStats = {
|
|
48
|
-
assetsByChunkName?: Record<string, string[]>;
|
|
49
|
-
entrypointFilesByName?: Record<string, string[]>;
|
|
50
|
-
};
|
|
51
|
-
type ReactRouterManifestStatsChunk = {
|
|
52
|
-
files?: Iterable<string>;
|
|
53
|
-
};
|
|
54
|
-
type ReactRouterManifestStatsEntrypoint = {
|
|
55
|
-
getFiles?: () => Iterable<string>;
|
|
56
|
-
};
|
|
57
|
-
type ReactRouterManifestStatsLookup<T> = Iterable<[
|
|
58
|
-
string,
|
|
59
|
-
T | null | undefined
|
|
60
|
-
]> & {
|
|
61
|
-
get?: (name: string) => T | null | undefined;
|
|
62
|
-
};
|
|
63
|
-
type ReactRouterManifestStatsCompilation = {
|
|
64
|
-
namedChunks: ReactRouterManifestStatsLookup<ReactRouterManifestStatsChunk>;
|
|
65
|
-
entrypoints?: ReactRouterManifestStatsLookup<ReactRouterManifestStatsEntrypoint>;
|
|
66
|
-
};
|
|
67
49
|
export declare const isManifestJsAsset: (asset: string) => boolean;
|
|
68
50
|
export declare const isManifestCssAsset: (asset: string) => boolean;
|
|
69
51
|
/**
|
|
@@ -95,7 +77,6 @@ export type RscScriptAssetChunk = {
|
|
|
95
77
|
* emits it, so function templates and `tools.rspack` overrides are covered.
|
|
96
78
|
*/
|
|
97
79
|
export declare const collectUnsupportedRscScriptAssets: (compilation: RscScriptAssetCompilation) => string[];
|
|
98
|
-
export declare const createReactRouterManifestStats: (compilation: ReactRouterManifestStatsCompilation | undefined, chunkNames?: ReadonlySet<string>) => ReactRouterManifestStats | undefined;
|
|
99
80
|
export type RouteManifestModuleExports = Record<string, readonly string[]>;
|
|
100
81
|
export type ReactRouterManifestGenerationResult = {
|
|
101
82
|
manifest: ReactRouterManifestForDev;
|
|
@@ -109,4 +90,3 @@ export declare const getReactRouterManifestPath: ({ version, isBuild, entryModul
|
|
|
109
90
|
export declare const getReactRouterManifestChunkNames: (routes: Record<string, Route>, appDirectory: string, splitRouteModules?: boolean | "enforce") => Set<string>;
|
|
110
91
|
export declare function generateReactRouterManifestForDev(routes: Record<string, Route>, _options: PluginOptions, clientStats: ReactRouterManifestStats | undefined, context: string, assetPrefix?: string, manifestOptions?: ReactRouterManifestOptions): Promise<ReactRouterManifestGenerationResult>;
|
|
111
92
|
export declare function getReactRouterManifestForDev(...args: Parameters<typeof generateReactRouterManifestForDev>): Promise<ReactRouterManifestForDev>;
|
|
112
|
-
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RsbuildPluginAPI } from '@rsbuild/core';
|
|
2
|
+
import type { ReactRouterManifestSnapshot } from './manifest-snapshot.js';
|
|
3
|
+
import type { Route } from './types.js';
|
|
4
|
+
export declare const registerNodeOnlyManifestValidation: ({ api, routeByFilePath, getSnapshot, }: {
|
|
5
|
+
api: RsbuildPluginAPI;
|
|
6
|
+
routeByFilePath: ReadonlyMap<string, Route>;
|
|
7
|
+
getSnapshot: () => ReactRouterManifestSnapshot | null;
|
|
8
|
+
}) => void;
|
package/dist/plugin-utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const resolveAppPackagePath: (specifier: string) => string | undefined;
|
|
1
|
+
export declare const resolveAppPackagePath: (specifier: string, rootPath?: string) => string | undefined;
|
|
2
2
|
export declare const parseVersionMajorMinor: (version: string | undefined) => {
|
|
3
3
|
major: number;
|
|
4
4
|
minor: number;
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export type Route = {
|
|
|
9
9
|
children?: Route[];
|
|
10
10
|
};
|
|
11
11
|
export type PluginOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* Generate React Router route types during development and builds.
|
|
14
|
+
* Set to false when type generation is managed separately.
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
typegen?: boolean;
|
|
12
18
|
/**
|
|
13
19
|
* Whether to disable automatic middleware setup for custom server implementation.
|
|
14
20
|
* Use this when you want to handle server setup manually.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rsbuild-plugin-react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "React Router plugin for Rsbuild",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@react-router/fs-routes": "^8.3.1",
|
|
87
87
|
"@react-router/remix-routes-option-adapter": "^8.3.1",
|
|
88
88
|
"@react-router/serve": "^8.3.1",
|
|
89
|
-
"@rsbuild/core": "2.2.
|
|
89
|
+
"@rsbuild/core": "2.2.8",
|
|
90
90
|
"@rsbuild/plugin-less": "1.6.4",
|
|
91
91
|
"@rsbuild/plugin-mdx": "^1.1.3",
|
|
92
92
|
"@rsbuild/plugin-react": "2.1.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@rsbuild/plugin-svgr": "2.0.4",
|
|
95
95
|
"@rsbuild/plugin-tailwindcss": "2.0.3",
|
|
96
96
|
"@rslib/core": "^0.23.2",
|
|
97
|
-
"@rspack/core": "2.2.
|
|
97
|
+
"@rspack/core": "2.2.6",
|
|
98
98
|
"@rstest/core": "^0.11.4",
|
|
99
99
|
"@rstest/coverage-istanbul": "^0.11.4",
|
|
100
100
|
"@swc/helpers": "^0.5.23",
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
},
|
|
137
137
|
"peerDependencies": {
|
|
138
138
|
"@react-router/dev": "^7.13.0 || ^8.0.0",
|
|
139
|
-
"@rsbuild/core": "^2.2.
|
|
139
|
+
"@rsbuild/core": "^2.2.8",
|
|
140
140
|
"react-router": "^7.13.0 || ^8.0.0",
|
|
141
141
|
"react-server-dom-rspack": "0.1.0",
|
|
142
142
|
"rsbuild-plugin-rsc": "^0.1.1"
|
|
@@ -226,6 +226,11 @@ export const registerBuildOutputTransforms = ({
|
|
|
226
226
|
);
|
|
227
227
|
const bundleId = bundleMatch?.[1]?.replace(/\.js$/, '');
|
|
228
228
|
const latestServerManifest = getLatestServerManifest();
|
|
229
|
+
if (isBuild && !latestServerManifest) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`[${PLUGIN_NAME}] Production server manifest requested before the browser manifest was finalized.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
229
234
|
const manifest =
|
|
230
235
|
(latestServerManifest
|
|
231
236
|
? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ??
|
package/src/classic-mode.ts
CHANGED
|
@@ -125,7 +125,6 @@ export const createClassicBuildArtifacts = async ({
|
|
|
125
125
|
isBuild,
|
|
126
126
|
buildPlan: serverBuildPlan,
|
|
127
127
|
clientPatchesRouteMetadata: devHmr?.isEnabled,
|
|
128
|
-
onNodeRebuildCommitted: devHmr?.onNodeRebuildCommitted,
|
|
129
128
|
}),
|
|
130
129
|
prerenderPaths,
|
|
131
130
|
routesByServerBundleId,
|
package/src/constants.ts
CHANGED
|
@@ -93,3 +93,6 @@ export const CLIENT_EXPORTS = {
|
|
|
93
93
|
|
|
94
94
|
// SPA-mode prerender fallback document, served when no prerendered page matches.
|
|
95
95
|
export const SPA_FALLBACK_HTML_FILE = '__spa-fallback.html';
|
|
96
|
+
|
|
97
|
+
export const BROWSER_MANIFEST_ENTRY_NAME =
|
|
98
|
+
'virtual/react-router/browser-manifest';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { RsbuildDevServer } from '@rsbuild/core';
|
|
3
|
+
|
|
4
|
+
export const DEV_HDR_UPDATE_EVENT = 'react-router:hdr-update';
|
|
5
|
+
|
|
6
|
+
export const createDevHdrChannel = ({
|
|
7
|
+
hot,
|
|
8
|
+
isEnabled,
|
|
9
|
+
}: {
|
|
10
|
+
hot: RsbuildDevServer['environments'][string]['hot'];
|
|
11
|
+
isEnabled: () => boolean;
|
|
12
|
+
}) => {
|
|
13
|
+
const sessionId = randomUUID();
|
|
14
|
+
let revision = 0;
|
|
15
|
+
let closed = false;
|
|
16
|
+
const payload = () => ({
|
|
17
|
+
event: DEV_HDR_UPDATE_EVENT,
|
|
18
|
+
data: { sessionId, revision },
|
|
19
|
+
});
|
|
20
|
+
const unsubscribe = hot.onConnect(client => {
|
|
21
|
+
if (!closed && isEnabled() && revision > 0) {
|
|
22
|
+
client.send('custom', payload());
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
publish(): void {
|
|
28
|
+
if (closed || !isEnabled()) return;
|
|
29
|
+
revision += 1;
|
|
30
|
+
hot.send('custom', payload());
|
|
31
|
+
},
|
|
32
|
+
close(): void {
|
|
33
|
+
if (closed) return;
|
|
34
|
+
closed = true;
|
|
35
|
+
unsubscribe();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
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
|
};
|