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
|
@@ -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
|
+
};
|
package/src/effect-runtime.ts
CHANGED
|
@@ -1,9 +1,80 @@
|
|
|
1
1
|
import * as Cause from 'effect/Cause';
|
|
2
|
+
import * as Context from 'effect/Context';
|
|
2
3
|
import * as Duration from 'effect/Duration';
|
|
3
4
|
import * as Effect from 'effect/Effect';
|
|
5
|
+
import * as ExecutionStrategy from 'effect/ExecutionStrategy';
|
|
4
6
|
import * as Exit from 'effect/Exit';
|
|
5
7
|
import * as Fiber from 'effect/Fiber';
|
|
8
|
+
import * as FiberSet from 'effect/FiberSet';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
import * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
6
11
|
import * as Option from 'effect/Option';
|
|
12
|
+
import * as Scope from 'effect/Scope';
|
|
13
|
+
|
|
14
|
+
const PluginFibers = Context.GenericTag<FiberSet.FiberSet>(
|
|
15
|
+
'rsbuild-plugin-react-router/PluginFibers'
|
|
16
|
+
);
|
|
17
|
+
type PluginResources = { readonly scope: Scope.CloseableScope };
|
|
18
|
+
const PluginResources: Context.Tag<PluginResources, PluginResources> =
|
|
19
|
+
Context.GenericTag<PluginResources>(
|
|
20
|
+
'rsbuild-plugin-react-router/PluginResources'
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
type PluginRuntimeContext = Scope.Scope | FiberSet.FiberSet | PluginResources;
|
|
24
|
+
|
|
25
|
+
const PluginRuntimeLive = Layer.scopedContext(
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
const scope = yield* Effect.scope;
|
|
28
|
+
const resources = yield* Scope.fork(scope, ExecutionStrategy.sequential);
|
|
29
|
+
const fiberScope = yield* Scope.fork(scope, ExecutionStrategy.sequential);
|
|
30
|
+
const fibers = yield* FiberSet.make().pipe(
|
|
31
|
+
Effect.provideService(Scope.Scope, fiberScope)
|
|
32
|
+
);
|
|
33
|
+
return Context.make(Scope.Scope, resources).pipe(
|
|
34
|
+
Context.add(PluginFibers, fibers),
|
|
35
|
+
Context.add(PluginResources, { scope: resources })
|
|
36
|
+
);
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export type PluginEffectRuntime = Pick<
|
|
41
|
+
ManagedRuntime.ManagedRuntime<PluginRuntimeContext, never>,
|
|
42
|
+
'runFork' | 'runPromise'
|
|
43
|
+
> & {
|
|
44
|
+
readonly dispose: () => Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const createPluginEffectRuntime = (): PluginEffectRuntime => {
|
|
48
|
+
const runtime = ManagedRuntime.make(PluginRuntimeLive);
|
|
49
|
+
let fiberRunFork: typeof runtime.runFork | undefined;
|
|
50
|
+
|
|
51
|
+
let disposePromise: Promise<void> | undefined;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
runPromise: runtime.runPromise,
|
|
55
|
+
runFork: (effect, options) => {
|
|
56
|
+
if (disposePromise) return Effect.runFork(Effect.interrupt);
|
|
57
|
+
return (fiberRunFork ??= runtime.runSync(
|
|
58
|
+
Effect.flatMap(PluginFibers, fibers =>
|
|
59
|
+
FiberSet.runtime(fibers)<PluginRuntimeContext>()
|
|
60
|
+
)
|
|
61
|
+
))(effect, options);
|
|
62
|
+
},
|
|
63
|
+
// Resource finalizers cancel their owned fibers before runtime disposal
|
|
64
|
+
// closes the fiber scope and interrupts any stragglers. Deferring by one
|
|
65
|
+
// microtask also lets a managed fiber request its own shutdown safely.
|
|
66
|
+
dispose: (): Promise<void> =>
|
|
67
|
+
(disposePromise ??= Promise.resolve()
|
|
68
|
+
.then(() =>
|
|
69
|
+
runtime.runPromise(
|
|
70
|
+
Effect.flatMap(PluginResources, ({ scope }) =>
|
|
71
|
+
Scope.close(scope, Exit.void)
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
.finally(() => runtime.dispose())),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
7
78
|
|
|
8
79
|
export const DEV_BACKGROUND_STARTUP_DELAY_MS = 3_000;
|
|
9
80
|
|
|
@@ -27,14 +98,6 @@ export const runPluginEffect = async <A, E>(
|
|
|
27
98
|
throw normalizeEffectCause(exit.cause);
|
|
28
99
|
};
|
|
29
100
|
|
|
30
|
-
export const tryPluginSync = <A>(
|
|
31
|
-
evaluate: () => A
|
|
32
|
-
): Effect.Effect<A, Error, never> =>
|
|
33
|
-
Effect.try({
|
|
34
|
-
try: evaluate,
|
|
35
|
-
catch: normalizeEffectError,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
101
|
export const tryPluginPromise = <A>(
|
|
39
102
|
evaluate: () => PromiseLike<A> | A
|
|
40
103
|
): Effect.Effect<A, Error, never> =>
|
|
@@ -46,45 +109,38 @@ export const tryPluginPromise = <A>(
|
|
|
46
109
|
type DelayedPluginTask = {
|
|
47
110
|
schedule(): void;
|
|
48
111
|
reschedule(): void;
|
|
49
|
-
cancelEffect(): Effect.Effect<void
|
|
50
|
-
cancel(): Promise<void>;
|
|
112
|
+
cancelEffect(): Effect.Effect<void>;
|
|
51
113
|
};
|
|
52
114
|
|
|
53
115
|
export const createDelayedPluginTask = ({
|
|
116
|
+
runtime,
|
|
54
117
|
delayMs,
|
|
55
118
|
run,
|
|
56
119
|
onError,
|
|
57
120
|
}: {
|
|
121
|
+
runtime: PluginEffectRuntime;
|
|
58
122
|
delayMs: number;
|
|
59
|
-
run: () => Effect.Effect<void, Error,
|
|
123
|
+
run: () => Effect.Effect<void, Error, Scope.Scope>;
|
|
60
124
|
onError: (error: Error) => void;
|
|
61
125
|
}): DelayedPluginTask => {
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
activeFiber
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
const cancelEffect = (): Effect.Effect<void, Error, never> =>
|
|
77
|
-
Effect.sync(() => {
|
|
78
|
-
version += 1;
|
|
79
|
-
}).pipe(Effect.zipRight(cancelActiveEffect()));
|
|
80
|
-
|
|
81
|
-
const start = (taskVersion: number): void => {
|
|
82
|
-
if (activeFiber || version !== taskVersion) {
|
|
126
|
+
let fiber: ReturnType<PluginEffectRuntime['runFork']> | null | undefined;
|
|
127
|
+
|
|
128
|
+
const cancelEffect = (): Effect.Effect<void> =>
|
|
129
|
+
Effect.suspend(() => {
|
|
130
|
+
const activeFiber = fiber;
|
|
131
|
+
fiber = null;
|
|
132
|
+
return activeFiber
|
|
133
|
+
? Fiber.interrupt(activeFiber).pipe(Effect.asVoid)
|
|
134
|
+
: Effect.void;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const start = (): void => {
|
|
138
|
+
if (fiber !== undefined) {
|
|
83
139
|
return;
|
|
84
140
|
}
|
|
85
141
|
|
|
86
|
-
let
|
|
87
|
-
|
|
142
|
+
let activeFiber: ReturnType<PluginEffectRuntime['runFork']>;
|
|
143
|
+
activeFiber = runtime.runFork(
|
|
88
144
|
Effect.sleep(Duration.millis(delayMs)).pipe(
|
|
89
145
|
Effect.zipRight(Effect.suspend(run)),
|
|
90
146
|
Effect.catchAll(error =>
|
|
@@ -94,37 +150,29 @@ export const createDelayedPluginTask = ({
|
|
|
94
150
|
),
|
|
95
151
|
Effect.ensuring(
|
|
96
152
|
Effect.sync(() => {
|
|
97
|
-
if (
|
|
98
|
-
|
|
153
|
+
if (fiber === activeFiber) {
|
|
154
|
+
fiber = undefined;
|
|
99
155
|
}
|
|
100
156
|
})
|
|
101
157
|
)
|
|
102
158
|
)
|
|
103
159
|
);
|
|
104
|
-
|
|
160
|
+
fiber = activeFiber;
|
|
105
161
|
};
|
|
106
162
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
start(
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
reschedule(): void {
|
|
117
|
-
version += 1;
|
|
118
|
-
const taskVersion = version;
|
|
119
|
-
void runPluginEffect(cancelActiveEffect())
|
|
120
|
-
.then(() => start(taskVersion))
|
|
121
|
-
.catch(onError);
|
|
122
|
-
},
|
|
163
|
+
const reschedule = (): void => {
|
|
164
|
+
if (fiber) {
|
|
165
|
+
runtime.runFork(
|
|
166
|
+
Fiber.interrupt(fiber).pipe(Effect.ensuring(Effect.sync(start)))
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
start();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
123
172
|
|
|
173
|
+
return {
|
|
174
|
+
schedule: start,
|
|
175
|
+
reschedule,
|
|
124
176
|
cancelEffect,
|
|
125
|
-
|
|
126
|
-
async cancel(): Promise<void> {
|
|
127
|
-
await runPluginEffect(cancelEffect());
|
|
128
|
-
},
|
|
129
177
|
};
|
|
130
178
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'pathe';
|
|
3
|
+
import { findEntryFile } from './plugin-utils.js';
|
|
4
|
+
|
|
5
|
+
type ReactRouterEntryPathsOptions = {
|
|
6
|
+
appDirectory: string;
|
|
7
|
+
templatesDirectory: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ReactRouterEntryPaths = {
|
|
11
|
+
devServerBuildEntryName: string;
|
|
12
|
+
finalEntryClientPath: string;
|
|
13
|
+
finalEntryRscClientPath: string;
|
|
14
|
+
finalEntryRscPath: string;
|
|
15
|
+
finalEntryRscSsrPath: string;
|
|
16
|
+
finalEntryServerPath: string;
|
|
17
|
+
hasServerApp: boolean;
|
|
18
|
+
serverAppPath: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const resolveEntryWithTemplate = ({
|
|
22
|
+
appDirectory,
|
|
23
|
+
entryName,
|
|
24
|
+
templateName,
|
|
25
|
+
templatesDirectory,
|
|
26
|
+
}: ReactRouterEntryPathsOptions & {
|
|
27
|
+
entryName: string;
|
|
28
|
+
templateName: string;
|
|
29
|
+
}): string => {
|
|
30
|
+
const userEntryPath = findEntryFile(resolve(appDirectory, entryName));
|
|
31
|
+
return existsSync(userEntryPath)
|
|
32
|
+
? userEntryPath
|
|
33
|
+
: resolve(templatesDirectory, templateName);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const resolveReactRouterEntryPaths = ({
|
|
37
|
+
appDirectory,
|
|
38
|
+
templatesDirectory,
|
|
39
|
+
}: ReactRouterEntryPathsOptions): ReactRouterEntryPaths => {
|
|
40
|
+
const serverAppPath = findEntryFile(resolve(appDirectory, '../server/index'));
|
|
41
|
+
const hasServerApp = existsSync(serverAppPath);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
devServerBuildEntryName: hasServerApp
|
|
45
|
+
? 'static/js/react-router-server-build'
|
|
46
|
+
: 'static/js/app',
|
|
47
|
+
finalEntryClientPath: resolveEntryWithTemplate({
|
|
48
|
+
appDirectory,
|
|
49
|
+
entryName: 'entry.client',
|
|
50
|
+
templateName: 'entry.client.js',
|
|
51
|
+
templatesDirectory,
|
|
52
|
+
}),
|
|
53
|
+
finalEntryServerPath: resolveEntryWithTemplate({
|
|
54
|
+
appDirectory,
|
|
55
|
+
entryName: 'entry.server',
|
|
56
|
+
templateName: 'entry.server.js',
|
|
57
|
+
templatesDirectory,
|
|
58
|
+
}),
|
|
59
|
+
finalEntryRscClientPath: resolveEntryWithTemplate({
|
|
60
|
+
appDirectory,
|
|
61
|
+
entryName: 'entry.rsc.client',
|
|
62
|
+
templateName: 'entry.rsc.client.js',
|
|
63
|
+
templatesDirectory,
|
|
64
|
+
}),
|
|
65
|
+
finalEntryRscPath: resolveEntryWithTemplate({
|
|
66
|
+
appDirectory,
|
|
67
|
+
entryName: 'entry.rsc',
|
|
68
|
+
templateName: 'entry.rsc.js',
|
|
69
|
+
templatesDirectory,
|
|
70
|
+
}),
|
|
71
|
+
finalEntryRscSsrPath: resolveEntryWithTemplate({
|
|
72
|
+
appDirectory,
|
|
73
|
+
entryName: 'entry.ssr',
|
|
74
|
+
templateName: 'entry.rsc.ssr.js',
|
|
75
|
+
templatesDirectory,
|
|
76
|
+
}),
|
|
77
|
+
hasServerApp,
|
|
78
|
+
serverAppPath,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
2
|
+
import { ensureFederationAsyncStartup } from './federation.js';
|
|
3
|
+
|
|
4
|
+
export const registerReactRouterEnvironmentOutput = ({
|
|
5
|
+
api,
|
|
6
|
+
federation,
|
|
7
|
+
resolvedServerOutput,
|
|
8
|
+
}: {
|
|
9
|
+
api: RsbuildPluginAPI;
|
|
10
|
+
federation: boolean | undefined;
|
|
11
|
+
resolvedServerOutput: 'commonjs' | 'module';
|
|
12
|
+
}): void => {
|
|
13
|
+
api.modifyEnvironmentConfig(
|
|
14
|
+
async (config, { name, mergeEnvironmentConfig }) => {
|
|
15
|
+
if (name !== 'web' && name !== 'node') {
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return mergeEnvironmentConfig(config, {
|
|
20
|
+
tools: {
|
|
21
|
+
rspack: rspackConfig => {
|
|
22
|
+
if (federation) {
|
|
23
|
+
ensureFederationAsyncStartup(rspackConfig);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (name === 'node') {
|
|
27
|
+
const output = rspackConfig.output;
|
|
28
|
+
if (output) {
|
|
29
|
+
const library = output.library;
|
|
30
|
+
const libraryOptions =
|
|
31
|
+
library &&
|
|
32
|
+
typeof library === 'object' &&
|
|
33
|
+
!Array.isArray(library)
|
|
34
|
+
? library
|
|
35
|
+
: {};
|
|
36
|
+
rspackConfig.output = {
|
|
37
|
+
...output,
|
|
38
|
+
library: {
|
|
39
|
+
...libraryOptions,
|
|
40
|
+
type:
|
|
41
|
+
resolvedServerOutput === 'module'
|
|
42
|
+
? 'module'
|
|
43
|
+
: 'commonjs2',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return rspackConfig;
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
};
|
package/src/export-utils.ts
CHANGED
|
@@ -15,7 +15,7 @@ type ExportInfo = {
|
|
|
15
15
|
readonly exportAllModules: readonly string[];
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
type RouteModuleAnalysis = {
|
|
18
|
+
export type RouteModuleAnalysis = {
|
|
19
19
|
readonly code: string;
|
|
20
20
|
readonly exports: readonly string[];
|
|
21
21
|
readonly exportAllModules: readonly string[];
|
|
@@ -94,10 +94,7 @@ const parseProgram = (code: string, resourcePath?: string): ProgramNode => {
|
|
|
94
94
|
return getProgram(normalizedResult);
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
const getExportInfoCacheKey = (
|
|
98
|
-
code: string,
|
|
99
|
-
resourcePath?: string
|
|
100
|
-
): string => {
|
|
97
|
+
const getExportInfoCacheKey = (code: string, resourcePath?: string): string => {
|
|
101
98
|
const lang = resourcePath
|
|
102
99
|
? langFromPath(stripResourcePathQuery(resourcePath))
|
|
103
100
|
: 'inline';
|
|
@@ -236,6 +233,18 @@ export const getExportNamesAndExportAll = async (
|
|
|
236
233
|
return trackedExportInfo;
|
|
237
234
|
};
|
|
238
235
|
|
|
236
|
+
export const analyzeRouteModuleCode = (
|
|
237
|
+
code: string,
|
|
238
|
+
resourcePath?: string
|
|
239
|
+
): RouteModuleAnalysis => {
|
|
240
|
+
const program = parseProgram(code, resourcePath);
|
|
241
|
+
return {
|
|
242
|
+
code,
|
|
243
|
+
exports: collectProgramExportNames(program),
|
|
244
|
+
exportAllModules: collectExportAllModules(program),
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
|
|
239
248
|
export const getRouteModuleAnalysis = async (
|
|
240
249
|
resourcePath: string
|
|
241
250
|
): Promise<RouteModuleAnalysis> => {
|
|
@@ -247,12 +256,7 @@ export const getRouteModuleAnalysis = async (
|
|
|
247
256
|
|
|
248
257
|
const analysis = (async () => {
|
|
249
258
|
const source = await readFile(resourcePath, 'utf8');
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
code: source,
|
|
253
|
-
exports: collectProgramExportNames(program),
|
|
254
|
-
exportAllModules: collectExportAllModules(program),
|
|
255
|
-
};
|
|
259
|
+
return analyzeRouteModuleCode(source, resourcePath);
|
|
256
260
|
})();
|
|
257
261
|
|
|
258
262
|
let trackedAnalysis: Promise<RouteModuleAnalysis>;
|