rsbuild-plugin-react-router 0.4.1 → 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 +72 -240
- package/dist/{451.js → 511.js} +461 -331
- package/dist/build-output-transforms.d.ts +32 -7
- 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 -3
- 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/federation.d.ts +2 -0
- package/dist/index.cjs +3675 -2107
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3614 -2214
- 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/performance.d.ts +4 -1
- 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-module-transform-loader.d.ts +14 -0
- package/dist/route-module-transform-loader.js +44 -0
- package/dist/route-module-transform-rules.d.ts +14 -0
- package/dist/route-transform-tasks.d.ts +6 -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 +34 -6
- package/package.json +76 -22
- package/src/build-output-transforms.ts +181 -43
- 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 -100
- 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/federation.ts +38 -0
- package/src/index.ts +666 -519
- 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/performance.ts +11 -2
- 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-module-transform-loader.ts +149 -0
- package/src/route-module-transform-rules.ts +115 -0
- package/src/route-transform-tasks.ts +44 -29
- 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 +43 -6
- package/src/warnings/warn-on-client-source-maps.ts +6 -10
- package/dist/parallel-route-transform-protocol.d.ts +0 -25
- package/dist/parallel-route-transform-worker.d.ts +0 -1
- package/dist/parallel-route-transform-worker.js +0 -38
- package/dist/parallel-route-transforms.d.ts +0 -29
- package/src/parallel-route-transform-protocol.ts +0 -35
- package/src/parallel-route-transform-worker.ts +0 -82
- package/src/parallel-route-transforms.ts +0 -458
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RsbuildConfig, RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
2
|
-
import * as Effect from 'effect/Effect';
|
|
3
2
|
import type { ServerBuild } from 'react-router';
|
|
4
3
|
import { PLUGIN_NAME } from './constants.js';
|
|
4
|
+
import { escapeHtml } from './plugin-utils.js';
|
|
5
5
|
import {
|
|
6
6
|
beginDevCompilerAttempt,
|
|
7
7
|
clearDevCompilerStart,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
registerReactRouterDevRuntime,
|
|
20
20
|
unregisterReactRouterDevRuntime,
|
|
21
21
|
} from './dev-generation.js';
|
|
22
|
+
import { DEV_MANIFEST_UPDATE_EVENT } from './dev-hmr.js';
|
|
22
23
|
import {
|
|
23
24
|
getEnvironmentStats,
|
|
24
25
|
snapshotDevChangedFiles,
|
|
@@ -27,17 +28,11 @@ import {
|
|
|
27
28
|
type ReactRouterDevBuildPlan,
|
|
28
29
|
type ReactRouterDevManifestSet,
|
|
29
30
|
} from './dev-runtime-artifacts.js';
|
|
30
|
-
import { DEV_HDR_REVISION_RELATIVE_PATH } from './dev-hmr.js';
|
|
31
31
|
import {
|
|
32
32
|
createDevRuntimeSessionManager,
|
|
33
33
|
type RuntimeBinding,
|
|
34
34
|
} from './dev-runtime-session.js';
|
|
35
|
-
import {
|
|
36
|
-
normalizeEffectError,
|
|
37
|
-
runPluginEffect,
|
|
38
|
-
tryPluginPromise,
|
|
39
|
-
tryPluginSync,
|
|
40
|
-
} from './effect-runtime.js';
|
|
35
|
+
import { normalizeEffectError } from './effect-runtime.js';
|
|
41
36
|
|
|
42
37
|
type ServerSetup = Exclude<
|
|
43
38
|
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
@@ -56,6 +51,11 @@ type CreateControllerOptions = {
|
|
|
56
51
|
api: RsbuildPluginAPI;
|
|
57
52
|
isBuild: boolean;
|
|
58
53
|
buildPlan: ReactRouterDevBuildPlan;
|
|
54
|
+
/**
|
|
55
|
+
* The browser HMR runtime patches route manifest metadata (loader/action
|
|
56
|
+
* flags) in place, so metadata-only changes no longer need a full reload.
|
|
57
|
+
*/
|
|
58
|
+
clientPatchesRouteMetadata?: boolean | (() => boolean);
|
|
59
59
|
/**
|
|
60
60
|
* Invoked after a development attempt commits a re-evaluated node build for
|
|
61
61
|
* changed server files. Used to signal hot data revalidation to the client.
|
|
@@ -63,36 +63,19 @@ type CreateControllerOptions = {
|
|
|
63
63
|
onNodeRebuildCommitted?: () => void;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const escapeHtml = (value: string): string =>
|
|
67
|
-
value
|
|
68
|
-
.replaceAll('&', '&')
|
|
69
|
-
.replaceAll('<', '<')
|
|
70
|
-
.replaceAll('>', '>');
|
|
71
|
-
|
|
72
66
|
const CSS_SOURCE_RELOAD_DELAY_MS = 1000;
|
|
73
67
|
|
|
74
68
|
const isHdrRevisionFile = (file: string): boolean =>
|
|
75
|
-
file.includes(
|
|
69
|
+
file.includes('.react-router/hdr-revision.mjs');
|
|
76
70
|
|
|
77
71
|
const isCssSourceFile = (file: string): boolean =>
|
|
78
72
|
/\.css(?:\.[cm]?[jt]s)?$/.test(file);
|
|
79
73
|
|
|
80
|
-
// A change that should bump the HDR revision: anything except the revision
|
|
81
|
-
// file itself (the bump's own echo) and CSS sources (styling never changes
|
|
82
|
-
// loader data).
|
|
83
|
-
const hasHdrTriggeringChange = (files: Iterable<string>): boolean => {
|
|
84
|
-
for (const file of files) {
|
|
85
|
-
if (!isHdrRevisionFile(file) && !isCssSourceFile(file)) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
74
|
export const createReactRouterDevRuntimeController = ({
|
|
93
75
|
api,
|
|
94
76
|
isBuild,
|
|
95
77
|
buildPlan,
|
|
78
|
+
clientPatchesRouteMetadata,
|
|
96
79
|
onNodeRebuildCommitted,
|
|
97
80
|
}: CreateControllerOptions): ReactRouterDevRuntimeController => {
|
|
98
81
|
if (isBuild) {
|
|
@@ -165,56 +148,44 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
165
148
|
NonNullable<DevGraphIdentity['node']>
|
|
166
149
|
>();
|
|
167
150
|
|
|
168
|
-
const
|
|
151
|
+
const finishRuntimeAttempt = async (
|
|
169
152
|
binding: RuntimeBinding,
|
|
170
153
|
pair: DevCompilerPair,
|
|
171
154
|
stats: DevRuntimeStats,
|
|
172
155
|
changes: Parameters<RuntimeBinding['runtime']['finishAttempt']>[1],
|
|
173
156
|
identity: Parameters<RuntimeBinding['runtime']['finishAttempt']>[2]
|
|
174
|
-
):
|
|
175
|
-
|
|
176
|
-
binding.runtime.finishAttempt(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const finishRuntimeAttempt = (
|
|
209
|
-
binding: RuntimeBinding,
|
|
210
|
-
pair: DevCompilerPair,
|
|
211
|
-
stats: DevRuntimeStats,
|
|
212
|
-
changes: Parameters<RuntimeBinding['runtime']['finishAttempt']>[1],
|
|
213
|
-
identity: Parameters<RuntimeBinding['runtime']['finishAttempt']>[2]
|
|
214
|
-
): Promise<void> =>
|
|
215
|
-
runPluginEffect(
|
|
216
|
-
finishRuntimeAttemptEffect(binding, pair, stats, changes, identity)
|
|
217
|
-
);
|
|
157
|
+
): Promise<void> => {
|
|
158
|
+
try {
|
|
159
|
+
const result = await binding.runtime.finishAttempt(
|
|
160
|
+
stats,
|
|
161
|
+
changes,
|
|
162
|
+
identity
|
|
163
|
+
);
|
|
164
|
+
if (sessions.getActiveBinding()?.id !== binding.id) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (result === 'retry-node') {
|
|
168
|
+
pair.node.watching?.invalidate();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
result === 'committed' &&
|
|
173
|
+
changes.node.known &&
|
|
174
|
+
identity.node !== undefined &&
|
|
175
|
+
hdrSignaledNodeIdentity.get(pair) !== identity.node &&
|
|
176
|
+
Array.from(changes.node.files).some(
|
|
177
|
+
file => !isHdrRevisionFile(file) && !isCssSourceFile(file)
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
hdrSignaledNodeIdentity.set(pair, identity.node);
|
|
181
|
+
onNodeRebuildCommitted?.();
|
|
182
|
+
}
|
|
183
|
+
} catch (cause) {
|
|
184
|
+
if (sessions.getActiveBinding()?.id === binding.id) {
|
|
185
|
+
binding.runtime.failAttempt(normalizeEffectError(cause));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
218
189
|
|
|
219
190
|
const flushSettledAttempt = (
|
|
220
191
|
binding: RuntimeBinding,
|
|
@@ -306,11 +277,22 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
306
277
|
reloadAfterCssAssetOwnershipRemoval = change === 'removed';
|
|
307
278
|
sendCssAssetOwnershipReload();
|
|
308
279
|
},
|
|
309
|
-
onRouteManifestChanged() {
|
|
280
|
+
onRouteManifestChanged(manifest) {
|
|
310
281
|
if (sessions.getActiveBinding()?.runtime !== runtime) {
|
|
311
282
|
return;
|
|
312
283
|
}
|
|
313
|
-
|
|
284
|
+
const patchesRouteMetadata =
|
|
285
|
+
typeof clientPatchesRouteMetadata === 'function'
|
|
286
|
+
? clientPatchesRouteMetadata()
|
|
287
|
+
: clientPatchesRouteMetadata;
|
|
288
|
+
if (patchesRouteMetadata) {
|
|
289
|
+
server.sockWrite('custom', {
|
|
290
|
+
event: DEV_MANIFEST_UPDATE_EVENT,
|
|
291
|
+
data: manifest.routes,
|
|
292
|
+
});
|
|
293
|
+
} else {
|
|
294
|
+
server.sockWrite('full-reload', { path: '*' });
|
|
295
|
+
}
|
|
314
296
|
},
|
|
315
297
|
onWarning: message => api.logger.warn(message),
|
|
316
298
|
});
|
|
@@ -566,21 +548,29 @@ export const createReactRouterDevRuntimeController = ({
|
|
|
566
548
|
},
|
|
567
549
|
|
|
568
550
|
createBuildLoader(entryName?: string): () => Promise<ServerBuild> {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
551
|
+
// Pin the loader to the dev-server session active at creation time. Once a
|
|
552
|
+
// loader is handed to React Router for session N it must keep serving N (or
|
|
553
|
+
// fail loudly with 'not registered' once N closes) and never silently migrate
|
|
554
|
+
// to a replacement session — this preserves SSR generation/session coherency.
|
|
555
|
+
// The live fallback below applies ONLY when no session exists yet at creation
|
|
556
|
+
// (boundServer === undefined), i.e. a loader built during config setup before
|
|
557
|
+
// the dev server has started; there is no session to stay coherent with yet.
|
|
558
|
+
const boundServer = sessions.getActiveBinding()?.server;
|
|
559
|
+
return () => {
|
|
560
|
+
const server = boundServer ?? sessions.getActiveBinding()?.server;
|
|
561
|
+
if (server) {
|
|
562
|
+
return loadReactRouterServerBuild(server, entryName);
|
|
563
|
+
}
|
|
564
|
+
const state = sessions.getState();
|
|
565
|
+
if (state.status === 'terminal') {
|
|
566
|
+
return Promise.reject(state.error);
|
|
567
|
+
}
|
|
568
|
+
return Promise.reject(
|
|
580
569
|
new Error(
|
|
581
570
|
`[${PLUGIN_NAME}] The development server runtime is not ready.`
|
|
582
571
|
)
|
|
583
572
|
);
|
|
573
|
+
};
|
|
584
574
|
},
|
|
585
575
|
};
|
|
586
576
|
};
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { RsbuildDevServer } from '@rsbuild/core';
|
|
2
|
-
import * as Effect from 'effect/Effect';
|
|
3
2
|
import { PLUGIN_NAME } from './constants.js';
|
|
4
3
|
import type { ReactRouterDevRuntime } from './dev-generation.js';
|
|
5
4
|
import type { DevCompilerPair } from './dev-runtime-compilation.js';
|
|
6
|
-
import {
|
|
7
|
-
runPluginEffect,
|
|
8
|
-
tryPluginPromise,
|
|
9
|
-
tryPluginSync,
|
|
10
|
-
} from './effect-runtime.js';
|
|
5
|
+
import { normalizeEffectError } from './effect-runtime.js';
|
|
11
6
|
|
|
12
7
|
export type RuntimeBinding = {
|
|
13
8
|
id: number;
|
|
@@ -115,18 +110,19 @@ export const createDevRuntimeSessionManager = (
|
|
|
115
110
|
if (observation.promise) {
|
|
116
111
|
return observation.promise;
|
|
117
112
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
let closePromise: Promise<void>;
|
|
114
|
+
try {
|
|
115
|
+
closePromise = Promise.resolve(close());
|
|
116
|
+
} catch (cause) {
|
|
117
|
+
closePromise = Promise.reject(normalizeEffectError(cause));
|
|
118
|
+
}
|
|
119
|
+
observation.promise = closePromise
|
|
120
|
+
.then(() => applyCloseOutcome(observation, { ok: true }))
|
|
121
|
+
.catch(cause => {
|
|
122
|
+
const error = normalizeEffectError(cause);
|
|
123
|
+
applyCloseOutcome(observation, { ok: false, cause: error });
|
|
124
|
+
throw error;
|
|
125
|
+
});
|
|
130
126
|
return observation.promise;
|
|
131
127
|
};
|
|
132
128
|
closeObservationByServer.set(server, observation);
|
package/src/dev-server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import type { RsbuildConfig } from '@rsbuild/core';
|
|
3
3
|
import type { ServerBuild } from 'react-router';
|
|
4
|
+
import { installDevServerSourceMapSupport } from './dev-source-maps.js';
|
|
4
5
|
|
|
5
6
|
export type ServerSetup = Exclude<
|
|
6
7
|
NonNullable<NonNullable<RsbuildConfig['server']>['setup']>,
|
|
@@ -83,6 +84,7 @@ export const createReactRouterDevServerSetup = ({
|
|
|
83
84
|
// middlewares, so static assets are served before the React Router
|
|
84
85
|
// request handler.
|
|
85
86
|
return () => {
|
|
87
|
+
installDevServerSourceMapSupport();
|
|
86
88
|
context.server.middlewares.use(createDevServerMiddleware({ loadBuild }));
|
|
87
89
|
};
|
|
88
90
|
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { SourceMap, type SourceMapPayload } from 'node:module';
|
|
3
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import type { Rspack } from '@rsbuild/core';
|
|
6
|
+
|
|
7
|
+
type CachedSourceMap = {
|
|
8
|
+
cacheKey: string;
|
|
9
|
+
sourceMap: SourceMap | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type InMemorySourceMap = {
|
|
13
|
+
load: () => SourceMapPayload | null;
|
|
14
|
+
sourceMap?: SourceMap | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SOURCE_MAPPING_URL_MARKER = '//# sourceMappingURL=';
|
|
18
|
+
const GENERATED_JS_FRAME_RE =
|
|
19
|
+
/(file:\/\/\/[^\s()]+\.m?js|\/[^\s()]+\.m?js):(\d+):(\d+)/g;
|
|
20
|
+
|
|
21
|
+
let installed = false;
|
|
22
|
+
let previousPrepareStackTrace: typeof Error.prepareStackTrace;
|
|
23
|
+
const sourceMapCache = new Map<string, CachedSourceMap>();
|
|
24
|
+
const inMemorySourceMaps = new Map<string, InMemorySourceMap>();
|
|
25
|
+
const generatedFilesByOutputPath = new Map<string, Set<string>>();
|
|
26
|
+
|
|
27
|
+
const getGeneratedFilePath = (fileName: string): string => {
|
|
28
|
+
if (fileName.startsWith('file://')) {
|
|
29
|
+
return fileURLToPath(fileName);
|
|
30
|
+
}
|
|
31
|
+
return fileName;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getCacheKey = (filePath: string): string | null => {
|
|
35
|
+
try {
|
|
36
|
+
const generatedStats = statSync(filePath);
|
|
37
|
+
const mapPath = `${filePath}.map`;
|
|
38
|
+
const mapStats = existsSync(mapPath) ? statSync(mapPath) : undefined;
|
|
39
|
+
return [
|
|
40
|
+
generatedStats.mtimeMs,
|
|
41
|
+
generatedStats.size,
|
|
42
|
+
mapStats?.mtimeMs ?? 0,
|
|
43
|
+
mapStats?.size ?? 0,
|
|
44
|
+
].join(':');
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const parseSourceMapPayload = (sourceMapJson: string): SourceMapPayload => {
|
|
51
|
+
const payload = JSON.parse(sourceMapJson) as Partial<SourceMapPayload>;
|
|
52
|
+
return {
|
|
53
|
+
file: payload.file ?? '',
|
|
54
|
+
version: payload.version ?? 3,
|
|
55
|
+
sources: payload.sources ?? [],
|
|
56
|
+
sourcesContent: payload.sourcesContent ?? [],
|
|
57
|
+
names: payload.names ?? [],
|
|
58
|
+
mappings: payload.mappings ?? '',
|
|
59
|
+
sourceRoot: payload.sourceRoot ?? '',
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const toSourceMapPayload = (value: unknown): SourceMapPayload | null => {
|
|
64
|
+
if (!value || typeof value !== 'object') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const payload = value as Partial<SourceMapPayload>;
|
|
68
|
+
return {
|
|
69
|
+
file: payload.file ?? '',
|
|
70
|
+
version: payload.version ?? 3,
|
|
71
|
+
sources: payload.sources ?? [],
|
|
72
|
+
sourcesContent: payload.sourcesContent ?? [],
|
|
73
|
+
names: payload.names ?? [],
|
|
74
|
+
mappings: payload.mappings ?? '',
|
|
75
|
+
sourceRoot: payload.sourceRoot ?? '',
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const readInlineSourceMap = (filePath: string): string | null => {
|
|
80
|
+
const source = readFileSync(filePath, 'utf8');
|
|
81
|
+
const markerIndex = source.lastIndexOf(SOURCE_MAPPING_URL_MARKER);
|
|
82
|
+
if (markerIndex === -1) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const sourceMapUrlStart = markerIndex + SOURCE_MAPPING_URL_MARKER.length;
|
|
87
|
+
const nextLineIndex = source.indexOf('\n', sourceMapUrlStart);
|
|
88
|
+
const sourceMapUrl = source
|
|
89
|
+
.slice(sourceMapUrlStart, nextLineIndex === -1 ? undefined : nextLineIndex)
|
|
90
|
+
.trim();
|
|
91
|
+
if (!sourceMapUrl.startsWith('data:')) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const commaIndex = sourceMapUrl.indexOf(',');
|
|
96
|
+
if (commaIndex === -1) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const metadata = sourceMapUrl.slice(0, commaIndex);
|
|
101
|
+
const data = sourceMapUrl.slice(commaIndex + 1);
|
|
102
|
+
return metadata.endsWith(';base64')
|
|
103
|
+
? Buffer.from(data, 'base64').toString('utf8')
|
|
104
|
+
: decodeURIComponent(data);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const readSourceMapPayload = (filePath: string): SourceMapPayload | null => {
|
|
108
|
+
const inlineSourceMap = readInlineSourceMap(filePath);
|
|
109
|
+
if (inlineSourceMap) {
|
|
110
|
+
return parseSourceMapPayload(inlineSourceMap);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const externalSourceMapPath = `${filePath}.map`;
|
|
114
|
+
if (!existsSync(externalSourceMapPath)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parseSourceMapPayload(readFileSync(externalSourceMapPath, 'utf8'));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getSourceMap = (filePath: string): SourceMap | null => {
|
|
122
|
+
const inMemory = inMemorySourceMaps.get(filePath);
|
|
123
|
+
if (inMemory) {
|
|
124
|
+
if (inMemory.sourceMap !== undefined) {
|
|
125
|
+
return inMemory.sourceMap;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const payload = inMemory.load();
|
|
129
|
+
inMemory.sourceMap = payload ? new SourceMap(payload) : null;
|
|
130
|
+
} catch {
|
|
131
|
+
inMemory.sourceMap = null;
|
|
132
|
+
}
|
|
133
|
+
return inMemory.sourceMap;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const cacheKey = getCacheKey(filePath);
|
|
137
|
+
if (!cacheKey) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const cached = sourceMapCache.get(filePath);
|
|
142
|
+
if (cached?.cacheKey === cacheKey) {
|
|
143
|
+
return cached.sourceMap;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let sourceMap: SourceMap | null = null;
|
|
147
|
+
try {
|
|
148
|
+
const payload = readSourceMapPayload(filePath);
|
|
149
|
+
sourceMap = payload ? new SourceMap(payload) : null;
|
|
150
|
+
} catch {
|
|
151
|
+
sourceMap = null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
sourceMapCache.set(filePath, { cacheKey, sourceMap });
|
|
155
|
+
return sourceMap;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const registerDevServerSourceMaps = (
|
|
159
|
+
compilation: Pick<Rspack.Compilation, 'getAssets' | 'outputOptions'>
|
|
160
|
+
): void => {
|
|
161
|
+
const outputPath = compilation.outputOptions.path;
|
|
162
|
+
if (!outputPath) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const previousFiles = generatedFilesByOutputPath.get(outputPath);
|
|
166
|
+
if (previousFiles) {
|
|
167
|
+
for (const filePath of previousFiles) {
|
|
168
|
+
inMemorySourceMaps.delete(filePath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const assets = compilation.getAssets();
|
|
173
|
+
const assetsByName = new Map(assets.map(asset => [asset.name, asset]));
|
|
174
|
+
const generatedFiles = new Set<string>();
|
|
175
|
+
|
|
176
|
+
for (const asset of assets) {
|
|
177
|
+
if (!/\.m?js$/.test(asset.name)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const generatedFilePath = resolve(outputPath, asset.name);
|
|
182
|
+
const externalMap = assetsByName.get(`${asset.name}.map`);
|
|
183
|
+
const load = externalMap
|
|
184
|
+
? () =>
|
|
185
|
+
parseSourceMapPayload(externalMap.source.buffer().toString('utf8'))
|
|
186
|
+
: () => toSourceMapPayload(asset.source.map());
|
|
187
|
+
|
|
188
|
+
generatedFiles.add(generatedFilePath);
|
|
189
|
+
inMemorySourceMaps.set(generatedFilePath, { load });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
generatedFilesByOutputPath.set(outputPath, generatedFiles);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const resolveOriginalFileName = (
|
|
196
|
+
generatedFilePath: string,
|
|
197
|
+
originalFileName: string
|
|
198
|
+
): string => {
|
|
199
|
+
if (
|
|
200
|
+
isAbsolute(originalFileName) ||
|
|
201
|
+
originalFileName.startsWith('node:') ||
|
|
202
|
+
originalFileName.includes('://')
|
|
203
|
+
) {
|
|
204
|
+
return originalFileName;
|
|
205
|
+
}
|
|
206
|
+
return resolve(dirname(generatedFilePath), originalFileName);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const remapDevServerStack = (stack: string): string =>
|
|
210
|
+
stack.replace(GENERATED_JS_FRAME_RE, (match, fileName, line, column) => {
|
|
211
|
+
const generatedFilePath = getGeneratedFilePath(fileName);
|
|
212
|
+
const sourceMap = getSourceMap(generatedFilePath);
|
|
213
|
+
const origin = sourceMap?.findOrigin(Number(line), Number(column));
|
|
214
|
+
if (!origin || !('fileName' in origin)) {
|
|
215
|
+
return match;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return `${resolveOriginalFileName(
|
|
219
|
+
generatedFilePath,
|
|
220
|
+
origin.fileName
|
|
221
|
+
)}:${origin.lineNumber}:${origin.columnNumber}`;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const formatStackTrace = (
|
|
225
|
+
error: Error,
|
|
226
|
+
callSites: NodeJS.CallSite[]
|
|
227
|
+
): string => {
|
|
228
|
+
const message = error.message
|
|
229
|
+
? `${error.name}: ${error.message}`
|
|
230
|
+
: error.name;
|
|
231
|
+
const frames = callSites.map(callSite => ` at ${callSite.toString()}`);
|
|
232
|
+
return [message, ...frames].join('\n');
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const prepareStackTrace: NonNullable<typeof Error.prepareStackTrace> = (
|
|
236
|
+
error,
|
|
237
|
+
callSites
|
|
238
|
+
) => {
|
|
239
|
+
const formattedStack =
|
|
240
|
+
previousPrepareStackTrace && previousPrepareStackTrace !== prepareStackTrace
|
|
241
|
+
? previousPrepareStackTrace(error, callSites)
|
|
242
|
+
: formatStackTrace(error, callSites);
|
|
243
|
+
|
|
244
|
+
return typeof formattedStack === 'string'
|
|
245
|
+
? remapDevServerStack(formattedStack)
|
|
246
|
+
: formattedStack;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const installDevServerSourceMapSupport = (): void => {
|
|
250
|
+
if (installed) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
installed = true;
|
|
255
|
+
previousPrepareStackTrace = Error.prepareStackTrace;
|
|
256
|
+
Error.prepareStackTrace = prepareStackTrace;
|
|
257
|
+
};
|