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.
Files changed (110) hide show
  1. package/README.md +58 -208
  2. package/dist/511.js +370 -331
  3. package/dist/build-output-transforms.d.ts +30 -6
  4. package/dist/classic-mode.d.ts +56 -0
  5. package/dist/config-imports.d.ts +8 -2
  6. package/dist/constants.d.ts +3 -0
  7. package/dist/dev-background-resources.d.ts +3 -1
  8. package/dist/dev-generation.d.ts +2 -3
  9. package/dist/dev-hmr.d.ts +8 -2
  10. package/dist/dev-runtime-controller.d.ts +6 -1
  11. package/dist/dev-source-maps.d.ts +4 -0
  12. package/dist/effect-runtime.d.ts +17 -5
  13. package/dist/entry-paths.d.ts +16 -0
  14. package/dist/environment-output.d.ts +6 -0
  15. package/dist/export-utils.d.ts +2 -1
  16. package/dist/index.cjs +3957 -2208
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +3470 -1793
  19. package/dist/lazy-compilation-prewarm.d.ts +8 -5
  20. package/dist/manifest.d.ts +16 -4
  21. package/dist/mode-plan.d.ts +82 -0
  22. package/dist/modify-browser-manifest.d.ts +8 -4
  23. package/dist/plugin-utils.d.ts +27 -1
  24. package/dist/prerender-build.d.ts +5 -5
  25. package/dist/prerender.d.ts +1 -5
  26. package/dist/react-router-config.d.ts +11 -3
  27. package/dist/route-artifacts.d.ts +4 -2
  28. package/dist/route-chunks.d.ts +2 -1
  29. package/dist/route-component-transform.d.ts +0 -2
  30. package/dist/route-export-pruning.d.ts +4 -1
  31. package/dist/route-imports.d.ts +18 -0
  32. package/dist/route-transform-tasks.d.ts +5 -0
  33. package/dist/route-watch.d.ts +11 -6
  34. package/dist/rsc-dev-server.d.ts +26 -0
  35. package/dist/rsc-prerender.d.ts +54 -0
  36. package/dist/rsc-route-config.d.ts +6 -0
  37. package/dist/rsc-route-exports.d.ts +15 -0
  38. package/dist/rsc-route-transform-loader.cjs +43 -0
  39. package/dist/rsc-route-transform-loader.d.ts +30 -0
  40. package/dist/rsc-route-transform-loader.js +16 -0
  41. package/dist/rsc-route-transform-registration.d.ts +12 -0
  42. package/dist/rsc-route-transforms.d.ts +21 -0
  43. package/dist/rsc-support.d.ts +23 -0
  44. package/dist/rsc-virtual-modules.d.ts +19 -0
  45. package/dist/server-build-plan.d.ts +2 -1
  46. package/dist/server-build-resolution.d.ts +1 -2
  47. package/dist/server-utils.d.ts +4 -5
  48. package/dist/ssr-asset-relocation.d.ts +98 -0
  49. package/dist/templates/entry.rsc.client.d.ts +1 -0
  50. package/dist/templates/entry.rsc.client.js +61 -0
  51. package/dist/templates/entry.rsc.d.ts +9 -0
  52. package/dist/templates/entry.rsc.js +38 -0
  53. package/dist/templates/entry.rsc.ssr.d.ts +4 -0
  54. package/dist/templates/entry.rsc.ssr.js +24 -0
  55. package/dist/typegen.d.ts +4 -2
  56. package/dist/types.d.ts +30 -1
  57. package/package.json +69 -14
  58. package/src/build-output-transforms.ts +155 -21
  59. package/src/classic-mode.ts +253 -0
  60. package/src/config-imports.ts +153 -6
  61. package/src/constants.ts +6 -2
  62. package/src/dev-background-resources.ts +46 -85
  63. package/src/dev-generation.ts +52 -33
  64. package/src/dev-hmr.ts +112 -80
  65. package/src/dev-runtime-artifacts.ts +11 -12
  66. package/src/dev-runtime-controller.ts +75 -85
  67. package/src/dev-runtime-session.ts +14 -18
  68. package/src/dev-server.ts +2 -0
  69. package/src/dev-source-maps.ts +257 -0
  70. package/src/effect-runtime.ts +105 -57
  71. package/src/entry-paths.ts +80 -0
  72. package/src/environment-output.ts +55 -0
  73. package/src/export-utils.ts +15 -11
  74. package/src/index.ts +661 -496
  75. package/src/lazy-compilation-prewarm.ts +20 -4
  76. package/src/manifest.ts +157 -82
  77. package/src/mode-plan.ts +367 -0
  78. package/src/modify-browser-manifest.ts +130 -124
  79. package/src/plugin-utils.ts +103 -39
  80. package/src/prerender-build.ts +82 -104
  81. package/src/prerender.ts +23 -24
  82. package/src/react-router-config.ts +72 -26
  83. package/src/route-artifacts.ts +96 -66
  84. package/src/route-chunks.ts +167 -51
  85. package/src/route-component-transform.ts +14 -21
  86. package/src/route-export-pruning.ts +4 -3
  87. package/src/route-imports.ts +100 -0
  88. package/src/route-transform-tasks.ts +39 -25
  89. package/src/route-watch.ts +166 -188
  90. package/src/rsc-dev-server.ts +112 -0
  91. package/src/rsc-prerender.ts +362 -0
  92. package/src/rsc-route-config.ts +175 -0
  93. package/src/rsc-route-exports.ts +67 -0
  94. package/src/rsc-route-transform-loader.ts +65 -0
  95. package/src/rsc-route-transform-registration.ts +145 -0
  96. package/src/rsc-route-transforms.ts +995 -0
  97. package/src/rsc-runtime.d.ts +143 -0
  98. package/src/rsc-support.ts +116 -0
  99. package/src/rsc-virtual-modules.ts +113 -0
  100. package/src/server-build-plan.ts +14 -3
  101. package/src/server-build-resolution.ts +37 -47
  102. package/src/server-utils.ts +21 -35
  103. package/src/ssr-asset-relocation.ts +183 -0
  104. package/src/ssr-externals.ts +8 -26
  105. package/src/templates/entry.rsc.client.tsx +168 -0
  106. package/src/templates/entry.rsc.ssr.tsx +45 -0
  107. package/src/templates/entry.rsc.tsx +80 -0
  108. package/src/typegen.ts +40 -23
  109. package/src/types.ts +39 -1
  110. 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
+ };
@@ -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, Error, never>;
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, never>;
123
+ run: () => Effect.Effect<void, Error, Scope.Scope>;
60
124
  onError: (error: Error) => void;
61
125
  }): DelayedPluginTask => {
62
- let activeFiber: ReturnType<typeof Effect.runFork> | undefined;
63
- let version = 0;
64
-
65
- const cancelActiveEffect = (): Effect.Effect<void, Error, never> =>
66
- Effect.sync(() => {
67
- const fiber = activeFiber;
68
- activeFiber = undefined;
69
- return fiber;
70
- }).pipe(
71
- Effect.flatMap(fiber =>
72
- fiber ? Fiber.interrupt(fiber).pipe(Effect.asVoid) : Effect.void
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 fiber: ReturnType<typeof Effect.runFork>;
87
- fiber = Effect.runFork(
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 (activeFiber === fiber) {
98
- activeFiber = undefined;
153
+ if (fiber === activeFiber) {
154
+ fiber = undefined;
99
155
  }
100
156
  })
101
157
  )
102
158
  )
103
159
  );
104
- activeFiber = fiber;
160
+ fiber = activeFiber;
105
161
  };
106
162
 
107
- return {
108
- schedule(): void {
109
- if (activeFiber) {
110
- return;
111
- }
112
- version += 1;
113
- start(version);
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
+ };
@@ -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
- const program = parseProgram(source, resourcePath);
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>;