rsbuild-plugin-react-router 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -204
- package/dist/451.js +1087 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +1 -1
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/config-imports.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +39 -0
- package/dist/dev-runtime-compilation.d.ts +32 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +11 -2
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +3085 -1272
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2039 -1299
- package/dist/lazy-compilation.d.ts +5 -0
- package/dist/manifest.d.ts +32 -9
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +14 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +32 -0
- package/dist/prerender.d.ts +28 -2
- package/dist/react-router-config.d.ts +8 -1
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/templates/entry.client.js +3 -3
- package/dist/templates/entry.server.cjs +11 -8
- package/dist/templates/entry.server.js +5 -5
- package/dist/typegen.d.ts +2 -0
- package/dist/types.d.ts +30 -12
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +26 -22
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +168 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +34 -0
- package/src/config-imports.ts +45 -0
- package/src/constants.ts +91 -0
- package/src/dev-generation.ts +700 -0
- package/src/dev-runtime-artifacts.ts +207 -0
- package/src/dev-runtime-compilation.ts +92 -0
- package/src/dev-runtime-controller.ts +462 -0
- package/src/dev-runtime-session.ts +184 -0
- package/src/dev-server.ts +58 -0
- package/src/export-utils.ts +219 -0
- package/src/index.ts +1025 -0
- package/src/lazy-compilation.ts +94 -0
- package/src/manifest.ts +480 -0
- package/src/modify-browser-manifest.ts +245 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +332 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +623 -0
- package/src/prerender.ts +367 -0
- package/src/react-router-config.ts +220 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +322 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-utils.ts +210 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +49 -0
- package/src/types.ts +82 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/906.js +0 -1
- package/dist/946.js +0 -1
- package/dist/babel.d.ts +0 -8
- package/dist/rslib-runtime.js +0 -23
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import type { RsbuildConfig, RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
+
import type { ServerBuild } from 'react-router';
|
|
3
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
4
|
+
import {
|
|
5
|
+
createCompilationIdentityTracker,
|
|
6
|
+
hasPendingCompilation,
|
|
7
|
+
isLatestStartedCompilation,
|
|
8
|
+
type DevCompilerPair,
|
|
9
|
+
} from './dev-runtime-compilation.js';
|
|
10
|
+
import {
|
|
11
|
+
createReactRouterDevRuntime,
|
|
12
|
+
loadReactRouterServerBuild,
|
|
13
|
+
registerReactRouterDevRuntime,
|
|
14
|
+
unregisterReactRouterDevRuntime,
|
|
15
|
+
} from './dev-generation.js';
|
|
16
|
+
import {
|
|
17
|
+
getEnvironmentStats,
|
|
18
|
+
snapshotDevChangedFiles,
|
|
19
|
+
type ReactRouterDevBuildPlan,
|
|
20
|
+
type ReactRouterDevManifestSet,
|
|
21
|
+
} from './dev-runtime-artifacts.js';
|
|
22
|
+
import {
|
|
23
|
+
createDevRuntimeSessionManager,
|
|
24
|
+
type RuntimeBinding,
|
|
25
|
+
} from './dev-runtime-session.js';
|
|
26
|
+
|
|
27
|
+
type ServerSetup = Exclude<
|
|
28
|
+
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
29
|
+
unknown[]
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export type ReactRouterDevRuntimeController = {
|
|
33
|
+
captureWeb: (
|
|
34
|
+
compilation: Rspack.Compilation,
|
|
35
|
+
manifestsByEntryName: ReactRouterDevManifestSet
|
|
36
|
+
) => void;
|
|
37
|
+
createBuildLoader: (entryName?: string) => () => Promise<ServerBuild>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type CreateControllerOptions = {
|
|
41
|
+
api: RsbuildPluginAPI;
|
|
42
|
+
isBuild: boolean;
|
|
43
|
+
buildPlan: ReactRouterDevBuildPlan;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const escapeHtml = (value: string): string =>
|
|
47
|
+
value
|
|
48
|
+
.replaceAll('&', '&')
|
|
49
|
+
.replaceAll('<', '<')
|
|
50
|
+
.replaceAll('>', '>');
|
|
51
|
+
|
|
52
|
+
const CSS_SOURCE_RELOAD_DELAY_MS = 1000;
|
|
53
|
+
|
|
54
|
+
export const createReactRouterDevRuntimeController = ({
|
|
55
|
+
api,
|
|
56
|
+
isBuild,
|
|
57
|
+
buildPlan,
|
|
58
|
+
}: CreateControllerOptions): ReactRouterDevRuntimeController => {
|
|
59
|
+
if (isBuild) {
|
|
60
|
+
return {
|
|
61
|
+
captureWeb() {},
|
|
62
|
+
createBuildLoader() {
|
|
63
|
+
return () =>
|
|
64
|
+
Promise.reject(
|
|
65
|
+
new Error(
|
|
66
|
+
`[${PLUGIN_NAME}] The development server runtime is unavailable during a production build.`
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let scheduledCssAssetOwnershipReload:
|
|
74
|
+
| ReturnType<typeof setTimeout>
|
|
75
|
+
| undefined;
|
|
76
|
+
let lastCssAssetOwnershipReloadAt = 0;
|
|
77
|
+
let reloadAfterCssAssetOwnershipRemoval = false;
|
|
78
|
+
|
|
79
|
+
const sendCssAssetOwnershipReload = (): void => {
|
|
80
|
+
const binding = sessions.getActiveBinding();
|
|
81
|
+
if (!binding) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
lastCssAssetOwnershipReloadAt = Date.now();
|
|
85
|
+
binding.server.sockWrite('full-reload', { path: '*' });
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const scheduleCssAssetOwnershipReload = (): void => {
|
|
89
|
+
if (scheduledCssAssetOwnershipReload) {
|
|
90
|
+
clearTimeout(scheduledCssAssetOwnershipReload);
|
|
91
|
+
}
|
|
92
|
+
const scheduledAt = Date.now();
|
|
93
|
+
scheduledCssAssetOwnershipReload = setTimeout(() => {
|
|
94
|
+
scheduledCssAssetOwnershipReload = undefined;
|
|
95
|
+
if (lastCssAssetOwnershipReloadAt > scheduledAt) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
sendCssAssetOwnershipReload();
|
|
99
|
+
}, CSS_SOURCE_RELOAD_DELAY_MS);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const closeBinding = (binding: RuntimeBinding, error?: Error): void => {
|
|
103
|
+
if (scheduledCssAssetOwnershipReload) {
|
|
104
|
+
clearTimeout(scheduledCssAssetOwnershipReload);
|
|
105
|
+
scheduledCssAssetOwnershipReload = undefined;
|
|
106
|
+
}
|
|
107
|
+
reloadAfterCssAssetOwnershipRemoval = false;
|
|
108
|
+
const pair = binding.compilers;
|
|
109
|
+
if (pair) {
|
|
110
|
+
pair.pendingAttempt = undefined;
|
|
111
|
+
pair.latestCompletedWebIdentity = undefined;
|
|
112
|
+
pair.latestWebStart = undefined;
|
|
113
|
+
pair.latestNodeStart = undefined;
|
|
114
|
+
}
|
|
115
|
+
binding.compilers = undefined;
|
|
116
|
+
binding.runtime.close(error);
|
|
117
|
+
unregisterReactRouterDevRuntime(binding.server, binding.runtime);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const sessions = createDevRuntimeSessionManager(closeBinding);
|
|
121
|
+
const compilationIdentities = createCompilationIdentityTracker();
|
|
122
|
+
const { getCompilationIdentity } = compilationIdentities;
|
|
123
|
+
|
|
124
|
+
const flushSettledAttempt = (
|
|
125
|
+
binding: RuntimeBinding,
|
|
126
|
+
pair: DevCompilerPair
|
|
127
|
+
): void => {
|
|
128
|
+
const pending = pair.pendingAttempt;
|
|
129
|
+
if (
|
|
130
|
+
!pending ||
|
|
131
|
+
sessions.getActiveBinding()?.id !== binding.id ||
|
|
132
|
+
!pair.settledCompilations.has(pending.webCompilation) ||
|
|
133
|
+
!pair.settledCompilations.has(pending.nodeCompilation)
|
|
134
|
+
) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
pair.pendingAttempt = undefined;
|
|
138
|
+
if (
|
|
139
|
+
!isLatestStartedCompilation(pending.identity.web, pair.latestWebStart) ||
|
|
140
|
+
!isLatestStartedCompilation(pending.identity.node, pair.latestNodeStart)
|
|
141
|
+
) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
void binding.runtime
|
|
145
|
+
.finishAttempt(pending.stats, pending.changes, pending.identity)
|
|
146
|
+
.catch(cause => {
|
|
147
|
+
if (sessions.getActiveBinding()?.id === binding.id) {
|
|
148
|
+
binding.runtime.failAttempt(
|
|
149
|
+
cause instanceof Error ? cause : new Error(String(cause))
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const rejectUnsupportedCompiler = (reason: string): void => {
|
|
156
|
+
const message = `[${PLUGIN_NAME}] Could not coordinate React Router development output because ${reason}.`;
|
|
157
|
+
api.logger.warn(message);
|
|
158
|
+
const binding = sessions.getActiveBinding();
|
|
159
|
+
if (!binding) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const error = new Error(message);
|
|
163
|
+
sessions.terminate(binding, error);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Rsbuild runs server.setup before onBeforeStartDevServer. Prepending the
|
|
167
|
+
// observer here ensures setup callbacks cannot capture an unobserved close.
|
|
168
|
+
api.modifyRsbuildConfig({
|
|
169
|
+
order: 'post',
|
|
170
|
+
handler(config) {
|
|
171
|
+
const existingSetup = config.server?.setup;
|
|
172
|
+
const setup = existingSetup
|
|
173
|
+
? Array.isArray(existingSetup)
|
|
174
|
+
? existingSetup
|
|
175
|
+
: [existingSetup]
|
|
176
|
+
: [];
|
|
177
|
+
const observeServer: ServerSetup = context => {
|
|
178
|
+
if (context.action === 'dev') {
|
|
179
|
+
sessions.observeClose(context.server);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
...config,
|
|
184
|
+
server: {
|
|
185
|
+
...config.server,
|
|
186
|
+
setup: [observeServer, ...setup],
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
api.onBeforeStartDevServer({
|
|
193
|
+
order: 'pre',
|
|
194
|
+
async handler({ server }) {
|
|
195
|
+
sessions.assertCanStart();
|
|
196
|
+
const runtime = createReactRouterDevRuntime({
|
|
197
|
+
server,
|
|
198
|
+
buildPlan,
|
|
199
|
+
onEvaluationError(error) {
|
|
200
|
+
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
api.logger.error(error.message);
|
|
204
|
+
server.sockWrite('errors', {
|
|
205
|
+
text: [error.message],
|
|
206
|
+
html: escapeHtml(error.message),
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
onCssAssetOwnershipChanged(change) {
|
|
210
|
+
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
reloadAfterCssAssetOwnershipRemoval = change === 'removed';
|
|
214
|
+
sendCssAssetOwnershipReload();
|
|
215
|
+
},
|
|
216
|
+
onRouteManifestChanged() {
|
|
217
|
+
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
server.sockWrite('full-reload', { path: '*' });
|
|
221
|
+
},
|
|
222
|
+
onWarning: message => api.logger.warn(message),
|
|
223
|
+
});
|
|
224
|
+
const binding = sessions.createBinding(server, runtime);
|
|
225
|
+
registerReactRouterDevRuntime(server, runtime);
|
|
226
|
+
sessions.bindCloseObservation(binding);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
api.onCloseDevServer({
|
|
231
|
+
order: 'pre',
|
|
232
|
+
handler() {
|
|
233
|
+
const binding = sessions.getActiveBinding();
|
|
234
|
+
if (!binding) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
closeBinding(binding);
|
|
238
|
+
sessions.markClosing(binding);
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
api.onBeforeDevCompile({
|
|
243
|
+
order: 'pre',
|
|
244
|
+
handler() {
|
|
245
|
+
const binding = sessions.getActiveBinding();
|
|
246
|
+
const pair = binding?.compilers;
|
|
247
|
+
if (!binding || !pair || hasPendingCompilation(pair)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
pair.pendingAttempt = undefined;
|
|
251
|
+
binding.runtime.beginAttempt();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
api.onAfterCreateCompiler(({ compiler }) => {
|
|
256
|
+
if (!('compilers' in compiler)) {
|
|
257
|
+
rejectUnsupportedCompiler('Rsbuild did not create a multi-compiler');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const web = compiler.compilers.find(item => item.name === 'web');
|
|
261
|
+
const node = compiler.compilers.find(item => item.name === 'node');
|
|
262
|
+
if (!web || !node) {
|
|
263
|
+
rejectUnsupportedCompiler('the web or node compiler was missing');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const binding = sessions.getActiveBinding();
|
|
267
|
+
if (!binding) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const pair: DevCompilerPair = {
|
|
271
|
+
web,
|
|
272
|
+
node,
|
|
273
|
+
settledCompilations: new WeakSet(),
|
|
274
|
+
};
|
|
275
|
+
binding.compilers = pair;
|
|
276
|
+
const sessionId = binding.id;
|
|
277
|
+
const runtime = binding.runtime;
|
|
278
|
+
const failCurrentAttempt = (side: 'web' | 'node', error: Error): void => {
|
|
279
|
+
if (sessions.getActiveBinding()?.id === sessionId) {
|
|
280
|
+
if (side === 'web') {
|
|
281
|
+
pair.latestWebStart = undefined;
|
|
282
|
+
} else {
|
|
283
|
+
pair.latestNodeStart = undefined;
|
|
284
|
+
}
|
|
285
|
+
pair.pendingAttempt = undefined;
|
|
286
|
+
runtime.failAttempt(error);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const beginCompilerAttempt = (
|
|
290
|
+
side: 'latestWebStart' | 'latestNodeStart'
|
|
291
|
+
): void => {
|
|
292
|
+
if (
|
|
293
|
+
sessions.getActiveBinding()?.id === sessionId &&
|
|
294
|
+
pair[side]?.status !== 'pending'
|
|
295
|
+
) {
|
|
296
|
+
const attemptAlreadyPending = hasPendingCompilation(pair);
|
|
297
|
+
// Invalidation can arrive before the aggregate before-compile hook.
|
|
298
|
+
// Supersede any evaluation that could resolve in that gap immediately.
|
|
299
|
+
pair[side] = { status: 'pending' };
|
|
300
|
+
pair.pendingAttempt = undefined;
|
|
301
|
+
if (!attemptAlreadyPending) {
|
|
302
|
+
runtime.beginAttempt();
|
|
303
|
+
}
|
|
304
|
+
if (side === 'latestWebStart' && reloadAfterCssAssetOwnershipRemoval) {
|
|
305
|
+
reloadAfterCssAssetOwnershipRemoval = false;
|
|
306
|
+
scheduleCssAssetOwnershipReload();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
web.hooks.invalid.tap(`${PLUGIN_NAME}:dev-web-invalid`, () =>
|
|
311
|
+
beginCompilerAttempt('latestWebStart')
|
|
312
|
+
);
|
|
313
|
+
node.hooks.invalid.tap(`${PLUGIN_NAME}:dev-node-invalid`, () =>
|
|
314
|
+
beginCompilerAttempt('latestNodeStart')
|
|
315
|
+
);
|
|
316
|
+
web.hooks.done.tap(
|
|
317
|
+
{ name: `${PLUGIN_NAME}:dev-web-complete`, stage: -1000 },
|
|
318
|
+
stats => {
|
|
319
|
+
if (sessions.getActiveBinding()?.id !== sessionId) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
pair.latestCompletedWebIdentity = getCompilationIdentity(
|
|
323
|
+
stats.compilation
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
web.hooks.thisCompilation.tap(
|
|
328
|
+
`${PLUGIN_NAME}:dev-web-compilation`,
|
|
329
|
+
compilation => {
|
|
330
|
+
if (sessions.getActiveBinding()?.id === sessionId) {
|
|
331
|
+
pair.latestWebStart = {
|
|
332
|
+
status: 'started',
|
|
333
|
+
identity: getCompilationIdentity(compilation),
|
|
334
|
+
};
|
|
335
|
+
if (reloadAfterCssAssetOwnershipRemoval) {
|
|
336
|
+
reloadAfterCssAssetOwnershipRemoval = false;
|
|
337
|
+
scheduleCssAssetOwnershipReload();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
node.hooks.thisCompilation.tap(
|
|
343
|
+
`${PLUGIN_NAME}:dev-node-web-compilation`,
|
|
344
|
+
compilation => {
|
|
345
|
+
if (sessions.getActiveBinding()?.id !== sessionId) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
pair.latestNodeStart = {
|
|
349
|
+
status: 'started',
|
|
350
|
+
identity: getCompilationIdentity(compilation),
|
|
351
|
+
};
|
|
352
|
+
if (pair.latestCompletedWebIdentity) {
|
|
353
|
+
compilationIdentities.setWebIdentityForNodeCompilation(
|
|
354
|
+
compilation,
|
|
355
|
+
pair.latestCompletedWebIdentity
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
const settleCompilation = (stats: Rspack.Stats): void => {
|
|
361
|
+
if (sessions.getActiveBinding()?.id !== sessionId) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
pair.settledCompilations.add(stats.compilation);
|
|
365
|
+
flushSettledAttempt(binding, pair);
|
|
366
|
+
};
|
|
367
|
+
web.hooks.afterDone.tap(
|
|
368
|
+
`${PLUGIN_NAME}:dev-web-settled`,
|
|
369
|
+
settleCompilation
|
|
370
|
+
);
|
|
371
|
+
node.hooks.afterDone.tap(
|
|
372
|
+
`${PLUGIN_NAME}:dev-node-settled`,
|
|
373
|
+
settleCompilation
|
|
374
|
+
);
|
|
375
|
+
web.hooks.failed.tap(`${PLUGIN_NAME}:dev-web-failed`, error =>
|
|
376
|
+
failCurrentAttempt('web', error)
|
|
377
|
+
);
|
|
378
|
+
node.hooks.failed.tap(`${PLUGIN_NAME}:dev-node-failed`, error =>
|
|
379
|
+
failCurrentAttempt('node', error)
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
api.onAfterDevCompile(async ({ stats }) => {
|
|
384
|
+
const binding = sessions.getActiveBinding();
|
|
385
|
+
const pair = binding?.compilers;
|
|
386
|
+
if (!binding || !pair) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const webStats = getEnvironmentStats(stats, 'web');
|
|
390
|
+
const nodeStats = getEnvironmentStats(stats, 'node');
|
|
391
|
+
if (
|
|
392
|
+
(webStats && webStats.compilation.compiler !== pair.web) ||
|
|
393
|
+
(nodeStats && nodeStats.compilation.compiler !== pair.node)
|
|
394
|
+
) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const webIdentity = webStats
|
|
398
|
+
? getCompilationIdentity(webStats.compilation)
|
|
399
|
+
: undefined;
|
|
400
|
+
const nodeIdentity = nodeStats
|
|
401
|
+
? getCompilationIdentity(nodeStats.compilation)
|
|
402
|
+
: undefined;
|
|
403
|
+
if (
|
|
404
|
+
!isLatestStartedCompilation(webIdentity, pair.latestWebStart) ||
|
|
405
|
+
!isLatestStartedCompilation(nodeIdentity, pair.latestNodeStart)
|
|
406
|
+
) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const changes = {
|
|
410
|
+
web: snapshotDevChangedFiles(pair.web),
|
|
411
|
+
node: snapshotDevChangedFiles(pair.node),
|
|
412
|
+
};
|
|
413
|
+
const identity = {
|
|
414
|
+
web: webIdentity,
|
|
415
|
+
node: nodeIdentity,
|
|
416
|
+
nodeWeb: nodeStats
|
|
417
|
+
? compilationIdentities.getWebIdentityForNodeCompilation(
|
|
418
|
+
nodeStats.compilation
|
|
419
|
+
)
|
|
420
|
+
: undefined,
|
|
421
|
+
};
|
|
422
|
+
if (!webStats || !nodeStats) {
|
|
423
|
+
await binding.runtime.finishAttempt(stats, changes, identity);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
pair.pendingAttempt = {
|
|
427
|
+
stats,
|
|
428
|
+
changes,
|
|
429
|
+
identity,
|
|
430
|
+
webCompilation: webStats.compilation,
|
|
431
|
+
nodeCompilation: nodeStats.compilation,
|
|
432
|
+
};
|
|
433
|
+
flushSettledAttempt(binding, pair);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
captureWeb(compilation, manifestsByEntryName): void {
|
|
438
|
+
const binding = sessions.getActiveBinding();
|
|
439
|
+
if (binding?.compilers?.web === compilation.compiler) {
|
|
440
|
+
binding.runtime.captureWeb(compilation, manifestsByEntryName);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
createBuildLoader(entryName?: string): () => Promise<ServerBuild> {
|
|
445
|
+
const server = sessions.getActiveBinding()?.server;
|
|
446
|
+
if (server) {
|
|
447
|
+
return () => loadReactRouterServerBuild(server, entryName);
|
|
448
|
+
}
|
|
449
|
+
const state = sessions.getState();
|
|
450
|
+
if (state.status === 'terminal') {
|
|
451
|
+
const { error } = state;
|
|
452
|
+
return () => Promise.reject(error);
|
|
453
|
+
}
|
|
454
|
+
return () =>
|
|
455
|
+
Promise.reject(
|
|
456
|
+
new Error(
|
|
457
|
+
`[${PLUGIN_NAME}] The development server runtime is not ready.`
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { RsbuildDevServer } from '@rsbuild/core';
|
|
2
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
3
|
+
import type { ReactRouterDevRuntime } from './dev-generation.js';
|
|
4
|
+
import type { DevCompilerPair } from './dev-runtime-compilation.js';
|
|
5
|
+
|
|
6
|
+
export type RuntimeBinding = {
|
|
7
|
+
id: number;
|
|
8
|
+
server: RsbuildDevServer;
|
|
9
|
+
runtime: ReactRouterDevRuntime;
|
|
10
|
+
compilers?: DevCompilerPair;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type CloseOutcome = { ok: true } | { ok: false; cause: unknown };
|
|
14
|
+
|
|
15
|
+
type CloseObservation = {
|
|
16
|
+
binding?: RuntimeBinding;
|
|
17
|
+
promise?: Promise<void>;
|
|
18
|
+
outcome?: CloseOutcome;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ControllerState =
|
|
22
|
+
| { status: 'idle' }
|
|
23
|
+
| { status: 'active'; binding: RuntimeBinding }
|
|
24
|
+
| { status: 'closing'; binding: RuntimeBinding }
|
|
25
|
+
| { status: 'terminal'; error: Error };
|
|
26
|
+
|
|
27
|
+
type CloseBinding = (binding: RuntimeBinding, error?: Error) => void;
|
|
28
|
+
|
|
29
|
+
export type DevRuntimeSessionManager = {
|
|
30
|
+
getState(): ControllerState;
|
|
31
|
+
getActiveBinding(): RuntimeBinding | undefined;
|
|
32
|
+
observeClose(server: RsbuildDevServer): void;
|
|
33
|
+
assertCanStart(): void;
|
|
34
|
+
createBinding(
|
|
35
|
+
server: RsbuildDevServer,
|
|
36
|
+
runtime: ReactRouterDevRuntime
|
|
37
|
+
): RuntimeBinding;
|
|
38
|
+
bindCloseObservation(binding: RuntimeBinding): void;
|
|
39
|
+
markClosing(binding: RuntimeBinding): void;
|
|
40
|
+
terminate(binding: RuntimeBinding, error: Error): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const createDevRuntimeSessionManager = (
|
|
44
|
+
closeBinding: CloseBinding
|
|
45
|
+
): DevRuntimeSessionManager => {
|
|
46
|
+
let state: ControllerState = { status: 'idle' };
|
|
47
|
+
let nextSessionId = 1;
|
|
48
|
+
const closeObservationByServer = new WeakMap<
|
|
49
|
+
RsbuildDevServer,
|
|
50
|
+
CloseObservation
|
|
51
|
+
>();
|
|
52
|
+
|
|
53
|
+
const getState = (): ControllerState => state;
|
|
54
|
+
|
|
55
|
+
const getActiveBinding = (): RuntimeBinding | undefined =>
|
|
56
|
+
state.status === 'active' ? state.binding : undefined;
|
|
57
|
+
|
|
58
|
+
const isCurrentBinding = (binding: RuntimeBinding): boolean =>
|
|
59
|
+
(state.status === 'active' || state.status === 'closing') &&
|
|
60
|
+
state.binding === binding;
|
|
61
|
+
|
|
62
|
+
const completeClose = (binding: RuntimeBinding): void => {
|
|
63
|
+
if (!isCurrentBinding(binding)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (state.status === 'active') {
|
|
67
|
+
closeBinding(binding);
|
|
68
|
+
}
|
|
69
|
+
state = { status: 'idle' };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const failClose = (binding: RuntimeBinding, cause: unknown): void => {
|
|
73
|
+
if (!isCurrentBinding(binding)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const error = new Error(
|
|
77
|
+
`[${PLUGIN_NAME}] The previous development server failed to close. Restart the process before retrying because Rsbuild may not have finished tearing down its compiler and watchers.`,
|
|
78
|
+
{ cause }
|
|
79
|
+
);
|
|
80
|
+
closeBinding(binding, error);
|
|
81
|
+
state = { status: 'terminal', error };
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const applyCloseOutcome = (
|
|
85
|
+
observation: CloseObservation,
|
|
86
|
+
outcome: CloseOutcome
|
|
87
|
+
): void => {
|
|
88
|
+
observation.outcome = outcome;
|
|
89
|
+
const { binding } = observation;
|
|
90
|
+
if (!binding) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (outcome.ok) {
|
|
94
|
+
completeClose(binding);
|
|
95
|
+
} else {
|
|
96
|
+
failClose(binding, outcome.cause);
|
|
97
|
+
}
|
|
98
|
+
observation.binding = undefined;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const observeClose = (server: RsbuildDevServer): CloseObservation => {
|
|
102
|
+
const existing = closeObservationByServer.get(server);
|
|
103
|
+
if (existing) {
|
|
104
|
+
return existing;
|
|
105
|
+
}
|
|
106
|
+
const observation: CloseObservation = {};
|
|
107
|
+
const close = server.close.bind(server);
|
|
108
|
+
server.close = () => {
|
|
109
|
+
if (observation.promise) {
|
|
110
|
+
return observation.promise;
|
|
111
|
+
}
|
|
112
|
+
let closePromise: Promise<void>;
|
|
113
|
+
try {
|
|
114
|
+
closePromise = close();
|
|
115
|
+
} catch (cause) {
|
|
116
|
+
closePromise = Promise.reject(cause);
|
|
117
|
+
}
|
|
118
|
+
observation.promise = closePromise;
|
|
119
|
+
void closePromise.then(
|
|
120
|
+
() => applyCloseOutcome(observation, { ok: true }),
|
|
121
|
+
cause => applyCloseOutcome(observation, { ok: false, cause })
|
|
122
|
+
);
|
|
123
|
+
return closePromise;
|
|
124
|
+
};
|
|
125
|
+
closeObservationByServer.set(server, observation);
|
|
126
|
+
return observation;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
getState,
|
|
131
|
+
getActiveBinding,
|
|
132
|
+
|
|
133
|
+
observeClose(server: RsbuildDevServer): void {
|
|
134
|
+
observeClose(server);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
assertCanStart(): void {
|
|
138
|
+
if (state.status === 'terminal') {
|
|
139
|
+
throw state.error;
|
|
140
|
+
}
|
|
141
|
+
if (state.status === 'active') {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`[${PLUGIN_NAME}] A development server is already active. Await its close() before calling createDevServer() again. If startup failed before returning the server, restart the process before retrying.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (state.status === 'closing') {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`[${PLUGIN_NAME}] The previous development server is still closing. Await its close() before calling createDevServer() again.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
createBinding(
|
|
154
|
+
server: RsbuildDevServer,
|
|
155
|
+
runtime: ReactRouterDevRuntime
|
|
156
|
+
): RuntimeBinding {
|
|
157
|
+
const binding = { id: nextSessionId++, server, runtime };
|
|
158
|
+
state = { status: 'active', binding };
|
|
159
|
+
return binding;
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
bindCloseObservation(binding: RuntimeBinding): void {
|
|
163
|
+
const observation = observeClose(binding.server);
|
|
164
|
+
observation.binding = binding;
|
|
165
|
+
if (observation.outcome) {
|
|
166
|
+
applyCloseOutcome(observation, observation.outcome);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
markClosing(binding: RuntimeBinding): void {
|
|
171
|
+
if (isCurrentBinding(binding)) {
|
|
172
|
+
state = { status: 'closing', binding };
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
terminate(binding: RuntimeBinding, error: Error): void {
|
|
177
|
+
if (!isCurrentBinding(binding)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
closeBinding(binding, error);
|
|
181
|
+
state = { status: 'terminal', error };
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { ServerBuild } from 'react-router';
|
|
3
|
+
|
|
4
|
+
export type DevServerMiddleware = (
|
|
5
|
+
req: IncomingMessage,
|
|
6
|
+
res: ServerResponse,
|
|
7
|
+
next: (err?: unknown) => void
|
|
8
|
+
) => Promise<void>;
|
|
9
|
+
|
|
10
|
+
type RequestHandler = (request: Request) => Response | Promise<Response>;
|
|
11
|
+
type BuildProvider = () => Promise<ServerBuild>;
|
|
12
|
+
|
|
13
|
+
export type DevServerMiddlewareDependencies = {
|
|
14
|
+
loadBuild: BuildProvider;
|
|
15
|
+
createRequestHandler?: (
|
|
16
|
+
build: BuildProvider,
|
|
17
|
+
mode: 'development'
|
|
18
|
+
) => RequestHandler;
|
|
19
|
+
createRequestListener?: (
|
|
20
|
+
handler: RequestHandler
|
|
21
|
+
) => (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const createDevServerMiddleware = (
|
|
25
|
+
dependencies: DevServerMiddlewareDependencies
|
|
26
|
+
): DevServerMiddleware => {
|
|
27
|
+
let listenerPromise:
|
|
28
|
+
| Promise<
|
|
29
|
+
(req: IncomingMessage, res: ServerResponse) => void | Promise<void>
|
|
30
|
+
>
|
|
31
|
+
| undefined;
|
|
32
|
+
|
|
33
|
+
const getListener = () => {
|
|
34
|
+
listenerPromise ??= (async () => {
|
|
35
|
+
const createRequestHandler =
|
|
36
|
+
dependencies.createRequestHandler ??
|
|
37
|
+
(await import('react-router')).createRequestHandler;
|
|
38
|
+
const createRequestListener =
|
|
39
|
+
dependencies.createRequestListener ??
|
|
40
|
+
(await import('@remix-run/node-fetch-server')).createRequestListener;
|
|
41
|
+
const requestHandler = createRequestHandler(
|
|
42
|
+
dependencies.loadBuild,
|
|
43
|
+
'development'
|
|
44
|
+
);
|
|
45
|
+
return createRequestListener(requestHandler);
|
|
46
|
+
})();
|
|
47
|
+
return listenerPromise;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return async (req, res, next): Promise<void> => {
|
|
51
|
+
try {
|
|
52
|
+
const listener = await getListener();
|
|
53
|
+
await listener(req, res);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
next(error);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|