rsbuild-plugin-react-router 0.5.0 → 0.6.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 +58 -208
- package/dist/511.js +370 -331
- 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 +3957 -2208
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3470 -1793
- package/dist/lazy-compilation-prewarm.d.ts +8 -5
- package/dist/manifest.d.ts +16 -4
- 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 +2 -1
- 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 +253 -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 +157 -82
- package/src/mode-plan.ts +367 -0
- package/src/modify-browser-manifest.ts +130 -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 +167 -51
- 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/config-imports.ts
CHANGED
|
@@ -1,14 +1,162 @@
|
|
|
1
|
-
import type { ModuleCache } from 'jiti';
|
|
1
|
+
import type { JitiOptions, ModuleCache, TransformOptions } from 'jiti';
|
|
2
2
|
import { createJiti } from 'jiti';
|
|
3
3
|
import { resolve } from 'pathe';
|
|
4
|
+
import { parse } from './yuku.js';
|
|
4
5
|
|
|
5
|
-
type ConfigImporter = Pick<ReturnType<typeof createJiti>, 'import'>;
|
|
6
|
+
export type ConfigImporter = Pick<ReturnType<typeof createJiti>, 'import'>;
|
|
7
|
+
|
|
8
|
+
type ConfigImportOptions = {
|
|
9
|
+
define?: Record<string, unknown>;
|
|
10
|
+
moduleCache?: boolean;
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
const normalizePath = (filePath: string): string => resolve(filePath);
|
|
8
14
|
|
|
9
15
|
const isNodeModulePath = (filePath: string): boolean =>
|
|
10
16
|
filePath.split(/[\\/]/).includes('node_modules');
|
|
11
17
|
|
|
18
|
+
type AstNode = {
|
|
19
|
+
type?: string;
|
|
20
|
+
start?: number;
|
|
21
|
+
end?: number;
|
|
22
|
+
object?: AstNode;
|
|
23
|
+
property?: AstNode;
|
|
24
|
+
meta?: AstNode;
|
|
25
|
+
name?: string;
|
|
26
|
+
value?: unknown;
|
|
27
|
+
computed?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const normalizeDefineValue = (value: unknown): string | undefined => {
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getMemberExpressionName = (node: AstNode | undefined): string | null => {
|
|
41
|
+
if (!node) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (node.type === 'Identifier') {
|
|
45
|
+
return node.name ?? null;
|
|
46
|
+
}
|
|
47
|
+
if (node.type === 'MetaProperty') {
|
|
48
|
+
const meta = getMemberExpressionName(node.meta);
|
|
49
|
+
const property = getMemberExpressionName(node.property);
|
|
50
|
+
return meta && property ? `${meta}.${property}` : null;
|
|
51
|
+
}
|
|
52
|
+
if (node.type !== 'MemberExpression') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const object = getMemberExpressionName(node.object);
|
|
56
|
+
const property =
|
|
57
|
+
node.computed && typeof node.property?.value === 'string'
|
|
58
|
+
? node.property.value
|
|
59
|
+
: getMemberExpressionName(node.property);
|
|
60
|
+
return object && property ? `${object}.${property}` : null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const collectDefineReplacements = (
|
|
64
|
+
node: unknown,
|
|
65
|
+
define: ReadonlyMap<string, string>,
|
|
66
|
+
replacements: Array<{ start: number; end: number; value: string }>
|
|
67
|
+
): void => {
|
|
68
|
+
if (!node || typeof node !== 'object') {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const astNode = node as AstNode;
|
|
73
|
+
const memberName = getMemberExpressionName(astNode);
|
|
74
|
+
const value = memberName ? define.get(memberName) : undefined;
|
|
75
|
+
if (
|
|
76
|
+
value !== undefined &&
|
|
77
|
+
typeof astNode.start === 'number' &&
|
|
78
|
+
typeof astNode.end === 'number'
|
|
79
|
+
) {
|
|
80
|
+
replacements.push({
|
|
81
|
+
start: astNode.start,
|
|
82
|
+
end: astNode.end,
|
|
83
|
+
value,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const child of Object.values(node)) {
|
|
89
|
+
if (Array.isArray(child)) {
|
|
90
|
+
for (const item of child) {
|
|
91
|
+
collectDefineReplacements(item, define, replacements);
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
collectDefineReplacements(child, define, replacements);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const applyConfigDefines = (
|
|
100
|
+
source: string,
|
|
101
|
+
define: Record<string, unknown> | undefined
|
|
102
|
+
): string => {
|
|
103
|
+
if (!define) {
|
|
104
|
+
return source;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const normalizedDefine = new Map(
|
|
108
|
+
Object.entries(define).flatMap(([key, value]) => {
|
|
109
|
+
const normalized = normalizeDefineValue(value);
|
|
110
|
+
return normalized === undefined ? [] : [[key, normalized]];
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
if (normalizedDefine.size === 0) {
|
|
114
|
+
return source;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const replacements: Array<{ start: number; end: number; value: string }> = [];
|
|
118
|
+
collectDefineReplacements(
|
|
119
|
+
parse(source, { sourceType: 'module' }),
|
|
120
|
+
normalizedDefine,
|
|
121
|
+
replacements
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return replacements
|
|
125
|
+
.sort((left, right) => right.start - left.start)
|
|
126
|
+
.reduce(
|
|
127
|
+
(contents, replacement) =>
|
|
128
|
+
`${contents.slice(0, replacement.start)}${replacement.value}${contents.slice(
|
|
129
|
+
replacement.end
|
|
130
|
+
)}`,
|
|
131
|
+
source
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const createConfigImporter = ({
|
|
136
|
+
define,
|
|
137
|
+
moduleCache = true,
|
|
138
|
+
}: ConfigImportOptions = {}): ReturnType<typeof createJiti> => {
|
|
139
|
+
const defaultJiti = createJiti(process.cwd(), { moduleCache });
|
|
140
|
+
if (!define || Object.keys(define).length === 0) {
|
|
141
|
+
return defaultJiti;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const defaultTransform = defaultJiti.transform;
|
|
145
|
+
const options = {
|
|
146
|
+
moduleCache,
|
|
147
|
+
transform(transformOptions: TransformOptions) {
|
|
148
|
+
return {
|
|
149
|
+
code: defaultTransform({
|
|
150
|
+
...transformOptions,
|
|
151
|
+
source: applyConfigDefines(transformOptions.source, define),
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
} satisfies JitiOptions;
|
|
156
|
+
|
|
157
|
+
return createJiti(process.cwd(), options);
|
|
158
|
+
};
|
|
159
|
+
|
|
12
160
|
export const collectConfigImportWatchPaths = (
|
|
13
161
|
configPath: string,
|
|
14
162
|
moduleCache: ModuleCache,
|
|
@@ -50,11 +198,10 @@ export const clearConfigImportCache = (
|
|
|
50
198
|
export const importConfigWithWatchPaths = async <T>(
|
|
51
199
|
configPath: string,
|
|
52
200
|
load: (importer: ConfigImporter) => PromiseLike<T> | T = async importer =>
|
|
53
|
-
importer.import<T>(configPath, { default: true })
|
|
201
|
+
importer.import<T>(configPath, { default: true }),
|
|
202
|
+
options?: ConfigImportOptions
|
|
54
203
|
): Promise<{ value: Awaited<T>; watchPaths: string | string[] }> => {
|
|
55
|
-
const jiti =
|
|
56
|
-
moduleCache: true,
|
|
57
|
-
});
|
|
204
|
+
const jiti = createConfigImporter({ moduleCache: true, ...options });
|
|
58
205
|
const previousCacheKeys = new Set(Object.keys(jiti.cache));
|
|
59
206
|
let importPaths: string[] = [];
|
|
60
207
|
|
package/src/constants.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const PLUGIN_NAME = 'rsbuild:react-router';
|
|
2
2
|
|
|
3
|
+
/** Default web `output.distPath.js` segment when the user has not customized it. */
|
|
4
|
+
export const DEFAULT_JS_DIST_PATH = 'static/js';
|
|
5
|
+
|
|
3
6
|
export const JS_EXTENSIONS = [
|
|
4
7
|
'.tsx',
|
|
5
8
|
'.ts',
|
|
@@ -32,8 +35,6 @@ export const SERVER_ONLY_ROUTE_EXPORTS_SET: ReadonlySet<string> = new Set(
|
|
|
32
35
|
SERVER_ONLY_ROUTE_EXPORTS
|
|
33
36
|
);
|
|
34
37
|
|
|
35
|
-
// Client route exports are split into non-component exports and component exports.
|
|
36
|
-
// This mirrors upstream React Router Vite plugin intent and is used for export filtering.
|
|
37
38
|
export const CLIENT_NON_COMPONENT_EXPORTS = [
|
|
38
39
|
'clientAction',
|
|
39
40
|
'clientLoader',
|
|
@@ -89,3 +90,6 @@ export const CLIENT_EXPORTS = {
|
|
|
89
90
|
meta: 'meta',
|
|
90
91
|
shouldRevalidate: 'shouldRevalidate',
|
|
91
92
|
} as const;
|
|
93
|
+
|
|
94
|
+
// SPA-mode prerender fallback document, served when no prerendered page matches.
|
|
95
|
+
export const SPA_FALLBACK_HTML_FILE = '__spa-fallback.html';
|
|
@@ -7,11 +7,10 @@ import {
|
|
|
7
7
|
createDelayedPluginTask,
|
|
8
8
|
DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
9
9
|
normalizeEffectError,
|
|
10
|
-
|
|
11
|
-
tryPluginPromise,
|
|
10
|
+
type PluginEffectRuntime,
|
|
12
11
|
} from './effect-runtime.js';
|
|
13
12
|
import {
|
|
14
|
-
|
|
13
|
+
acquireLazyCompilationPrewarm,
|
|
15
14
|
normalizeLazyCompilationPrewarmOptions,
|
|
16
15
|
} from './lazy-compilation-prewarm.js';
|
|
17
16
|
import {
|
|
@@ -19,8 +18,8 @@ import {
|
|
|
19
18
|
type ReactRouterManifestForDev,
|
|
20
19
|
} from './manifest.js';
|
|
21
20
|
import {
|
|
21
|
+
acquireRouteTopologyWatcher,
|
|
22
22
|
createRouteManifestSnapshot,
|
|
23
|
-
createRouteTopologyWatcher,
|
|
24
23
|
ensureDevRestartMarker,
|
|
25
24
|
type WatchFileConfig,
|
|
26
25
|
} from './route-watch.js';
|
|
@@ -28,6 +27,7 @@ import type { PluginOptions } from './types.js';
|
|
|
28
27
|
|
|
29
28
|
type RegisterReactRouterDevBackgroundResourcesOptions = {
|
|
30
29
|
api: RsbuildPluginAPI;
|
|
30
|
+
runtime: PluginEffectRuntime;
|
|
31
31
|
isBuild: boolean;
|
|
32
32
|
lazyCompilationPrewarm: PluginOptions['unstableLazyCompilationPrewarm'];
|
|
33
33
|
routeRestartMarkerPath: string;
|
|
@@ -98,7 +98,10 @@ export const createReactRouterRouteWatchFiles = ({
|
|
|
98
98
|
watchFiles.push(
|
|
99
99
|
{
|
|
100
100
|
paths: routeConfigWatchPaths,
|
|
101
|
-
|
|
101
|
+
// Keep the current compiler alive while a route config is temporarily
|
|
102
|
+
// invalid. The topology watcher touches the dedicated restart marker
|
|
103
|
+
// once a valid edit actually changes the route graph.
|
|
104
|
+
type: 'reload-page',
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
107
|
paths: routeRestartMarkerPath,
|
|
@@ -110,27 +113,9 @@ export const createReactRouterRouteWatchFiles = ({
|
|
|
110
113
|
return watchFiles;
|
|
111
114
|
};
|
|
112
115
|
|
|
113
|
-
const
|
|
114
|
-
message: string,
|
|
115
|
-
closers: Array<() => Promise<void>>
|
|
116
|
-
): Promise<void> => {
|
|
117
|
-
const results = await Promise.allSettled(closers.map(closer => closer()));
|
|
118
|
-
const errors = results
|
|
119
|
-
.filter(
|
|
120
|
-
(result): result is PromiseRejectedResult => result.status === 'rejected'
|
|
121
|
-
)
|
|
122
|
-
.map(result => normalizeEffectError(result.reason));
|
|
123
|
-
|
|
124
|
-
if (errors.length === 1) {
|
|
125
|
-
throw errors[0];
|
|
126
|
-
}
|
|
127
|
-
if (errors.length > 1) {
|
|
128
|
-
throw new AggregateError(errors, message);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export const registerReactRouterDevBackgroundResources = ({
|
|
116
|
+
export const registerReactRouterDevBackgroundResources = async ({
|
|
133
117
|
api,
|
|
118
|
+
runtime,
|
|
134
119
|
isBuild,
|
|
135
120
|
lazyCompilationPrewarm,
|
|
136
121
|
routeRestartMarkerPath,
|
|
@@ -138,9 +123,8 @@ export const registerReactRouterDevBackgroundResources = ({
|
|
|
138
123
|
getRouteTopology,
|
|
139
124
|
initialRouteTopology,
|
|
140
125
|
onRouteTopologyChange,
|
|
141
|
-
}: RegisterReactRouterDevBackgroundResourcesOptions): ReactRouterDevBackgroundResources => {
|
|
142
|
-
let
|
|
143
|
-
let routeTopologyWatcherClosed = false;
|
|
126
|
+
}: RegisterReactRouterDevBackgroundResourcesOptions): Promise<ReactRouterDevBackgroundResources> => {
|
|
127
|
+
let routeTopologyWatcherStarted = false;
|
|
144
128
|
|
|
145
129
|
const reportRouteTopologyWatcherError = (error: unknown): void => {
|
|
146
130
|
api.logger.warn(
|
|
@@ -149,54 +133,49 @@ export const registerReactRouterDevBackgroundResources = ({
|
|
|
149
133
|
};
|
|
150
134
|
|
|
151
135
|
const routeTopologyWatcherTask = createDelayedPluginTask({
|
|
136
|
+
runtime,
|
|
152
137
|
delayMs: DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
153
138
|
run: () =>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (routeTopologyWatcherClosed) {
|
|
169
|
-
yield* tryPluginPromise(() => closeWatcher());
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
closeActiveRouteTopologyWatcher = closeWatcher;
|
|
173
|
-
}),
|
|
139
|
+
acquireRouteTopologyWatcher({
|
|
140
|
+
runtime,
|
|
141
|
+
watchDirectory,
|
|
142
|
+
getRouteTopology,
|
|
143
|
+
initialRouteTopology,
|
|
144
|
+
restartMarkerPath: routeRestartMarkerPath,
|
|
145
|
+
onRouteTopologyChange,
|
|
146
|
+
onError: reportRouteTopologyWatcherError,
|
|
147
|
+
}).pipe(
|
|
148
|
+
Effect.tap(() =>
|
|
149
|
+
Effect.sync(() => (routeTopologyWatcherStarted = true))
|
|
150
|
+
),
|
|
151
|
+
Effect.asVoid
|
|
152
|
+
),
|
|
174
153
|
onError: reportRouteTopologyWatcherError,
|
|
175
154
|
});
|
|
176
155
|
|
|
177
|
-
const scheduleRouteTopologyWatcher = (): void => {
|
|
178
|
-
if (routeTopologyWatcherClosed || closeActiveRouteTopologyWatcher) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
routeTopologyWatcherTask.schedule();
|
|
182
|
-
};
|
|
183
|
-
|
|
184
156
|
const lazyCompilationPrewarmConfig = normalizeLazyCompilationPrewarmOptions(
|
|
185
157
|
lazyCompilationPrewarm
|
|
186
158
|
);
|
|
187
159
|
const lazyCompilationPrewarmController = lazyCompilationPrewarmConfig
|
|
188
|
-
?
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
160
|
+
? await runtime.runPromise(
|
|
161
|
+
acquireLazyCompilationPrewarm({
|
|
162
|
+
runtime,
|
|
163
|
+
config: lazyCompilationPrewarmConfig,
|
|
164
|
+
onError: error =>
|
|
165
|
+
api.logger.warn(
|
|
166
|
+
`[${PLUGIN_NAME}] Lazy compilation prewarm skipped: ${error.message}`
|
|
167
|
+
),
|
|
168
|
+
})
|
|
169
|
+
)
|
|
195
170
|
: null;
|
|
196
171
|
|
|
197
172
|
if (!isBuild) {
|
|
198
|
-
|
|
199
|
-
|
|
173
|
+
await runtime.runPromise(
|
|
174
|
+
Effect.acquireRelease(Effect.void, routeTopologyWatcherTask.cancelEffect)
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
api.onBeforeStartDevServer(async () => {
|
|
178
|
+
await ensureDevRestartMarker(routeRestartMarkerPath);
|
|
200
179
|
});
|
|
201
180
|
|
|
202
181
|
api.onAfterStartDevServer(({ port }) => {
|
|
@@ -207,31 +186,13 @@ export const registerReactRouterDevBackgroundResources = ({
|
|
|
207
186
|
});
|
|
208
187
|
|
|
209
188
|
api.onAfterDevCompile(() => {
|
|
210
|
-
|
|
189
|
+
if (!routeTopologyWatcherStarted) {
|
|
190
|
+
routeTopologyWatcherTask.schedule();
|
|
191
|
+
}
|
|
211
192
|
lazyCompilationPrewarmController?.schedule();
|
|
212
193
|
});
|
|
213
194
|
}
|
|
214
195
|
|
|
215
|
-
const closeRouteTopologyWatcher = async (): Promise<void> => {
|
|
216
|
-
routeTopologyWatcherClosed = true;
|
|
217
|
-
await runPluginEffect(routeTopologyWatcherTask.cancelEffect());
|
|
218
|
-
await closeActiveRouteTopologyWatcher?.();
|
|
219
|
-
closeActiveRouteTopologyWatcher = undefined;
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const closeLazyCompilationPrewarm = async (): Promise<void> => {
|
|
223
|
-
await runPluginEffect(
|
|
224
|
-
lazyCompilationPrewarmController?.cancelEffect() ?? Effect.void
|
|
225
|
-
);
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
api.onCloseDevServer(() =>
|
|
229
|
-
closeAll(
|
|
230
|
-
'[rsbuild-plugin-react-router] Failed to close dev server resources.',
|
|
231
|
-
[closeRouteTopologyWatcher, closeLazyCompilationPrewarm]
|
|
232
|
-
)
|
|
233
|
-
);
|
|
234
|
-
|
|
235
196
|
return {
|
|
236
197
|
setManifest(manifest) {
|
|
237
198
|
lazyCompilationPrewarmController?.setManifest(manifest);
|
package/src/dev-generation.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RsbuildDevServer, Rspack } from '@rsbuild/core';
|
|
2
2
|
import * as EffectDeferred from 'effect/Deferred';
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
|
+
import * as FiberId from 'effect/FiberId';
|
|
4
5
|
import type { ServerBuild } from 'react-router';
|
|
6
|
+
import { HMR_PATCHABLE_ROUTE_FLAGS } from './route-artifacts.js';
|
|
5
7
|
import {
|
|
6
8
|
evaluateServerBuilds,
|
|
7
9
|
getEnvironmentStats,
|
|
@@ -20,14 +22,10 @@ import {
|
|
|
20
22
|
} from './dev-runtime-artifacts.js';
|
|
21
23
|
import { normalizeEffectError, runPluginEffect } from './effect-runtime.js';
|
|
22
24
|
|
|
23
|
-
export { snapshotDevChangedFiles } from './dev-runtime-artifacts.js';
|
|
24
25
|
export type {
|
|
25
|
-
DevChangedFiles,
|
|
26
26
|
DevGraphChanges,
|
|
27
27
|
DevGraphIdentity,
|
|
28
|
-
ReactRouterDevBuildPlan,
|
|
29
28
|
ReactRouterDevManifest,
|
|
30
|
-
ReactRouterDevManifestSet,
|
|
31
29
|
} from './dev-runtime-artifacts.js';
|
|
32
30
|
|
|
33
31
|
type CommittedGeneration = {
|
|
@@ -73,7 +71,9 @@ type CreateReactRouterDevRuntimeOptions = {
|
|
|
73
71
|
buildPlan: ReactRouterDevBuildPlan;
|
|
74
72
|
onEvaluationError: (error: Error) => void;
|
|
75
73
|
onCssAssetOwnershipChanged?: (change: 'removed' | 'restored') => void;
|
|
76
|
-
onRouteManifestChanged?: (
|
|
74
|
+
onRouteManifestChanged?: (
|
|
75
|
+
manifest: ReactRouterDevManifestSet[string]
|
|
76
|
+
) => void;
|
|
77
77
|
onWarning?: (message: string) => void;
|
|
78
78
|
};
|
|
79
79
|
|
|
@@ -199,28 +199,35 @@ type DevRouteManifestEntry = NonNullable<
|
|
|
199
199
|
|
|
200
200
|
const hasSameRouteMetadata = (
|
|
201
201
|
previous: DevRouteManifestEntry,
|
|
202
|
-
next: DevRouteManifestEntry
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
202
|
+
next: DevRouteManifestEntry,
|
|
203
|
+
includeHmrPatchableFlags: boolean
|
|
204
|
+
): boolean => {
|
|
205
|
+
const structuralEqual =
|
|
206
|
+
previous.caseSensitive === next.caseSensitive &&
|
|
207
|
+
previous.clientActionModule === next.clientActionModule &&
|
|
208
|
+
previous.clientLoaderModule === next.clientLoaderModule &&
|
|
209
|
+
previous.clientMiddlewareModule === next.clientMiddlewareModule &&
|
|
210
|
+
previous.hasDefaultExport === next.hasDefaultExport &&
|
|
211
|
+
previous.hydrateFallbackModule === next.hydrateFallbackModule &&
|
|
212
|
+
previous.id === next.id &&
|
|
213
|
+
previous.index === next.index &&
|
|
214
|
+
previous.parentId === next.parentId &&
|
|
215
|
+
previous.path === next.path;
|
|
216
|
+
if (!structuralEqual) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (!includeHmrPatchableFlags) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
// Without a client-side HMR runtime these flags can only reach the browser
|
|
223
|
+
// through a full reload.
|
|
224
|
+
return HMR_PATCHABLE_ROUTE_FLAGS.every(flag => previous[flag] === next[flag]);
|
|
225
|
+
};
|
|
220
226
|
|
|
221
227
|
const hasRouteManifestMetadataChanges = (
|
|
222
228
|
previous: ReactRouterDevManifestSet,
|
|
223
|
-
next: ReactRouterDevManifestSet
|
|
229
|
+
next: ReactRouterDevManifestSet,
|
|
230
|
+
includeHmrPatchableFlags: boolean
|
|
224
231
|
): boolean => {
|
|
225
232
|
const previousEntryNames = Object.keys(previous);
|
|
226
233
|
if (previousEntryNames.length !== Object.keys(next).length) {
|
|
@@ -240,7 +247,14 @@ const hasRouteManifestMetadataChanges = (
|
|
|
240
247
|
for (const routeId of previousRouteIds) {
|
|
241
248
|
const previousRoute = previousRoutes[routeId];
|
|
242
249
|
const nextRoute = nextRoutes[routeId];
|
|
243
|
-
if (
|
|
250
|
+
if (
|
|
251
|
+
!nextRoute ||
|
|
252
|
+
!hasSameRouteMetadata(
|
|
253
|
+
previousRoute,
|
|
254
|
+
nextRoute,
|
|
255
|
+
includeHmrPatchableFlags
|
|
256
|
+
)
|
|
257
|
+
) {
|
|
244
258
|
return true;
|
|
245
259
|
}
|
|
246
260
|
}
|
|
@@ -251,7 +265,7 @@ const hasRouteManifestMetadataChanges = (
|
|
|
251
265
|
const createReadinessDeferred = (): EffectDeferred.Deferred<
|
|
252
266
|
CommittedGeneration,
|
|
253
267
|
Error
|
|
254
|
-
> =>
|
|
268
|
+
> => EffectDeferred.unsafeMake<CommittedGeneration, Error>(FiberId.none);
|
|
255
269
|
|
|
256
270
|
export const createReactRouterDevRuntime = ({
|
|
257
271
|
server,
|
|
@@ -285,9 +299,11 @@ export const createReactRouterDevRuntime = ({
|
|
|
285
299
|
}
|
|
286
300
|
};
|
|
287
301
|
|
|
288
|
-
const notifyRouteManifestChanged = (
|
|
302
|
+
const notifyRouteManifestChanged = (
|
|
303
|
+
manifest: ReactRouterDevManifestSet[string]
|
|
304
|
+
): void => {
|
|
289
305
|
try {
|
|
290
|
-
onRouteManifestChanged();
|
|
306
|
+
onRouteManifestChanged(manifest);
|
|
291
307
|
} catch (cause) {
|
|
292
308
|
onWarning(
|
|
293
309
|
`[rsbuild-plugin-react-router] Failed to notify the browser after route manifest metadata changed: ${normalizeEffectError(cause).message}`
|
|
@@ -340,7 +356,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
340
356
|
if (state.kind === 'starting') {
|
|
341
357
|
const { readiness } = state;
|
|
342
358
|
state = { kind: 'failed', attemptId, error };
|
|
343
|
-
|
|
359
|
+
EffectDeferred.unsafeDone(readiness, Effect.fail(error));
|
|
344
360
|
} else if (state.kind === 'ready') {
|
|
345
361
|
state = { ...state, pendingAttemptId: null };
|
|
346
362
|
}
|
|
@@ -359,7 +375,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
359
375
|
if (state.kind === 'starting') {
|
|
360
376
|
const { readiness } = state;
|
|
361
377
|
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
362
|
-
|
|
378
|
+
EffectDeferred.unsafeDone(readiness, Effect.succeed(committed));
|
|
363
379
|
} else if (state.kind === 'ready') {
|
|
364
380
|
state = { kind: 'ready', committed, pendingAttemptId: null };
|
|
365
381
|
}
|
|
@@ -513,7 +529,8 @@ export const createReactRouterDevRuntime = ({
|
|
|
513
529
|
webChanged &&
|
|
514
530
|
hasRouteManifestMetadataChanges(
|
|
515
531
|
previous.web.manifestsByEntryName,
|
|
516
|
-
manifestsByEntryName
|
|
532
|
+
manifestsByEntryName,
|
|
533
|
+
true
|
|
517
534
|
);
|
|
518
535
|
const reusePreviousNodeBuild =
|
|
519
536
|
!!previous &&
|
|
@@ -587,7 +604,9 @@ export const createReactRouterDevRuntime = ({
|
|
|
587
604
|
reloadAfterCssRemoval = false;
|
|
588
605
|
}
|
|
589
606
|
if (routeManifestMetadataChanged) {
|
|
590
|
-
notifyRouteManifestChanged(
|
|
607
|
+
notifyRouteManifestChanged(
|
|
608
|
+
web.manifestsByEntryName[buildPlan.defaultEntryName]
|
|
609
|
+
);
|
|
591
610
|
}
|
|
592
611
|
return 'committed';
|
|
593
612
|
} catch (cause) {
|
|
@@ -636,7 +655,7 @@ export const createReactRouterDevRuntime = ({
|
|
|
636
655
|
'[rsbuild-plugin-react-router] The development server closed before a React Router build was ready.'
|
|
637
656
|
);
|
|
638
657
|
if (state.kind === 'starting') {
|
|
639
|
-
|
|
658
|
+
EffectDeferred.unsafeDone(state.readiness, Effect.fail(closeError));
|
|
640
659
|
}
|
|
641
660
|
state = { kind: 'closed', error: closeError };
|
|
642
661
|
},
|