rsbuild-plugin-react-router 0.3.0 → 0.3.1

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 (56) hide show
  1. package/README.md +55 -24
  2. package/dist/451.js +36 -20
  3. package/dist/build-manifest.d.ts +6 -3
  4. package/dist/concurrency.d.ts +2 -1
  5. package/dist/config-imports.d.ts +7 -0
  6. package/dist/dev-background-resources.d.ts +38 -0
  7. package/dist/dev-generation.d.ts +2 -2
  8. package/dist/dev-runtime-artifacts.d.ts +9 -1
  9. package/dist/dev-runtime-compilation.d.ts +17 -2
  10. package/dist/dev-server.d.ts +5 -0
  11. package/dist/effect-runtime.d.ts +18 -0
  12. package/dist/export-utils.d.ts +2 -2
  13. package/dist/index.cjs +11448 -891
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +7950 -758
  16. package/dist/lazy-compilation-prewarm.d.ts +25 -0
  17. package/dist/lazy-compilation.d.ts +2 -1
  18. package/dist/manifest.d.ts +19 -4
  19. package/dist/parallel-route-transforms.d.ts +17 -2
  20. package/dist/prerender-build.d.ts +4 -0
  21. package/dist/prerender.d.ts +0 -1
  22. package/dist/react-router-config.d.ts +8 -5
  23. package/dist/server-build-resolution.d.ts +3 -0
  24. package/dist/ssr-externals.d.ts +1 -0
  25. package/dist/typegen.d.ts +14 -1
  26. package/dist/types.d.ts +15 -6
  27. package/package.json +11 -9
  28. package/src/build-manifest.ts +110 -73
  29. package/src/concurrency.ts +3 -22
  30. package/src/config-imports.ts +38 -0
  31. package/src/dev-background-resources.ts +255 -0
  32. package/src/dev-generation.ts +43 -53
  33. package/src/dev-runtime-artifacts.ts +50 -18
  34. package/src/dev-runtime-compilation.ts +80 -1
  35. package/src/dev-runtime-controller.ts +111 -31
  36. package/src/dev-runtime-session.ts +18 -11
  37. package/src/dev-server.ts +31 -1
  38. package/src/effect-runtime.ts +130 -0
  39. package/src/export-utils.ts +82 -23
  40. package/src/index.ts +118 -153
  41. package/src/lazy-compilation-prewarm.ts +279 -0
  42. package/src/lazy-compilation.ts +12 -5
  43. package/src/manifest.ts +366 -255
  44. package/src/modify-browser-manifest.ts +2 -1
  45. package/src/parallel-route-transforms.ts +195 -69
  46. package/src/prerender-build.ts +125 -61
  47. package/src/prerender.ts +0 -18
  48. package/src/react-router-config.ts +98 -73
  49. package/src/route-artifacts.ts +2 -2
  50. package/src/route-export-resolution.ts +3 -3
  51. package/src/route-watch.ts +119 -84
  52. package/src/server-build-resolution.ts +131 -0
  53. package/src/server-utils.ts +7 -106
  54. package/src/ssr-externals.ts +1 -1
  55. package/src/typegen.ts +162 -33
  56. package/src/types.ts +16 -6
@@ -0,0 +1,279 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import type { ReactRouterManifestForDev } from './manifest.js';
3
+ import type { RouteManifestItem } from './types.js';
4
+ import { createDelayedPluginTask, tryPluginPromise } from './effect-runtime.js';
5
+
6
+ const DEFAULT_LAZY_COMPILATION_TRIGGER_PREFIX = '/_rspack/lazy/trigger';
7
+ const DEFAULT_PREWARM_DELAY_MS = 0;
8
+ const DEFAULT_ROUTE_PREWARM_LIMIT = 8;
9
+ const PREWARM_FETCH_CONCURRENCY = 8;
10
+ const PREWARM_TRIGGER_CANDIDATES = 4;
11
+
12
+ type LazyCompilationPrewarmConfig = {
13
+ entry: boolean;
14
+ routeLimit: number;
15
+ delayMs: number;
16
+ };
17
+
18
+ type LazyCompilationPrewarmController = {
19
+ setServerOrigin(origin: string): void;
20
+ setManifest(manifest: ReactRouterManifestForDev | null): void;
21
+ schedule(): void;
22
+ cancelEffect(): Effect.Effect<void, Error, never>;
23
+ };
24
+
25
+ export type RspackLazyCompilationTriggerClient = {
26
+ extractModuleKeys(source: string): string[];
27
+ trigger(
28
+ origin: string,
29
+ keys: readonly string[]
30
+ ): Effect.Effect<void, Error, never>;
31
+ };
32
+
33
+ export const normalizeLazyCompilationPrewarmOptions = (
34
+ options: boolean | undefined
35
+ ): LazyCompilationPrewarmConfig | null => {
36
+ if (!options) {
37
+ return null;
38
+ }
39
+
40
+ return {
41
+ entry: true,
42
+ routeLimit: DEFAULT_ROUTE_PREWARM_LIMIT,
43
+ delayMs: DEFAULT_PREWARM_DELAY_MS,
44
+ };
45
+ };
46
+
47
+ const collectRouteAssets = (
48
+ routes: Record<string, RouteManifestItem>,
49
+ config: LazyCompilationPrewarmConfig
50
+ ): string[] => {
51
+ if (config.routeLimit < 1) {
52
+ return [];
53
+ }
54
+
55
+ const assets: string[] = [];
56
+ for (const routeId in routes) {
57
+ const route = routes[routeId];
58
+ if (route.module) {
59
+ assets.push(route.module);
60
+ }
61
+ if (route.clientActionModule) {
62
+ assets.push(route.clientActionModule);
63
+ }
64
+ if (route.clientLoaderModule) {
65
+ assets.push(route.clientLoaderModule);
66
+ }
67
+ if (route.clientMiddlewareModule) {
68
+ assets.push(route.clientMiddlewareModule);
69
+ }
70
+ if (route.hydrateFallbackModule) {
71
+ assets.push(route.hydrateFallbackModule);
72
+ }
73
+ if (assets.length >= config.routeLimit) {
74
+ break;
75
+ }
76
+ }
77
+
78
+ return assets;
79
+ };
80
+
81
+ export const collectLazyCompilationPrewarmAssets = (
82
+ manifest: ReactRouterManifestForDev,
83
+ config: LazyCompilationPrewarmConfig
84
+ ): string[] => {
85
+ const assets = [
86
+ ...(config.entry ? [manifest.entry.module, ...manifest.entry.imports] : []),
87
+ ...collectRouteAssets(manifest.routes, config),
88
+ ].filter(Boolean);
89
+
90
+ return Array.from(new Set(assets));
91
+ };
92
+
93
+ const toAbsoluteUrl = (origin: string, asset: string): string =>
94
+ new URL(asset, origin).toString();
95
+
96
+ const parseJsonStringLiteral = (value: string): string | null => {
97
+ try {
98
+ const parsed = JSON.parse(value);
99
+ return typeof parsed === 'string' ? parsed : null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ };
104
+
105
+ const extractLazyCompilationModuleKeys = (source: string): string[] => {
106
+ const keys = new Set<string>();
107
+ const pattern = /activate\(\{\s*data:\s*("(?:\\.|[^"\\])*")/g;
108
+ let match: RegExpExecArray | null;
109
+
110
+ while ((match = pattern.exec(source))) {
111
+ const key = parseJsonStringLiteral(match[1]);
112
+ if (key) {
113
+ keys.add(key);
114
+ }
115
+ }
116
+
117
+ return Array.from(keys);
118
+ };
119
+
120
+ export const createRspackLazyCompilationTriggerClient = (
121
+ triggerPrefix: string = DEFAULT_LAZY_COMPILATION_TRIGGER_PREFIX
122
+ ): RspackLazyCompilationTriggerClient => ({
123
+ extractModuleKeys: extractLazyCompilationModuleKeys,
124
+ trigger(origin, keys) {
125
+ return postLazyCompilationKeys(origin, triggerPrefix, keys);
126
+ },
127
+ });
128
+
129
+ const fetchLazyCompilationKeys = (
130
+ origin: string,
131
+ assets: readonly string[],
132
+ triggerClient: RspackLazyCompilationTriggerClient
133
+ ): Effect.Effect<string[], Error, never> =>
134
+ Effect.forEach(
135
+ assets,
136
+ asset =>
137
+ tryPluginPromise(async () => {
138
+ const response = await fetch(toAbsoluteUrl(origin, asset));
139
+ if (!response.ok) {
140
+ return [];
141
+ }
142
+ return triggerClient.extractModuleKeys(await response.text());
143
+ }),
144
+ { concurrency: PREWARM_FETCH_CONCURRENCY }
145
+ ).pipe(Effect.map(results => Array.from(new Set(results.flat()))));
146
+
147
+ const getTriggerCandidates = (
148
+ origin: string,
149
+ triggerPrefix: string
150
+ ): string[] => {
151
+ const candidates = [triggerPrefix];
152
+ for (let index = 0; index < PREWARM_TRIGGER_CANDIDATES; index += 1) {
153
+ candidates.push(`${triggerPrefix}__${index}`);
154
+ }
155
+ return candidates.map(candidate => toAbsoluteUrl(origin, candidate));
156
+ };
157
+
158
+ const postLazyCompilationKeys = (
159
+ origin: string,
160
+ triggerPrefix: string,
161
+ keys: readonly string[]
162
+ ): Effect.Effect<void, Error, never> => {
163
+ if (keys.length === 0) {
164
+ return Effect.void;
165
+ }
166
+
167
+ return Effect.gen(function* () {
168
+ const body = keys.join('\n');
169
+ let lastError: Error | undefined;
170
+
171
+ for (const url of getTriggerCandidates(origin, triggerPrefix)) {
172
+ const accepted = yield* tryPluginPromise(async () => {
173
+ const response = await fetch(url, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'text/plain',
177
+ },
178
+ body,
179
+ });
180
+ return response.ok;
181
+ }).pipe(
182
+ Effect.catchAll(error => {
183
+ lastError = error;
184
+ return Effect.succeed(false);
185
+ })
186
+ );
187
+
188
+ if (accepted) {
189
+ return;
190
+ }
191
+ }
192
+
193
+ yield* Effect.fail(
194
+ lastError ??
195
+ new Error(
196
+ '[rsbuild-plugin-react-router] Lazy compilation prewarm trigger was not accepted.'
197
+ )
198
+ );
199
+ });
200
+ };
201
+
202
+ const prewarmLazyCompilation = ({
203
+ manifest,
204
+ serverOrigin,
205
+ config,
206
+ triggerClient = createRspackLazyCompilationTriggerClient(),
207
+ }: {
208
+ manifest: ReactRouterManifestForDev;
209
+ serverOrigin: string;
210
+ config: LazyCompilationPrewarmConfig;
211
+ triggerClient?: RspackLazyCompilationTriggerClient;
212
+ }): Effect.Effect<void, Error, never> =>
213
+ Effect.gen(function* () {
214
+ const assets = collectLazyCompilationPrewarmAssets(manifest, config);
215
+ const keys = yield* fetchLazyCompilationKeys(
216
+ serverOrigin,
217
+ assets,
218
+ triggerClient
219
+ );
220
+ yield* triggerClient.trigger(serverOrigin, keys);
221
+ });
222
+
223
+ export const createLazyCompilationPrewarmController = ({
224
+ config,
225
+ onError,
226
+ }: {
227
+ config: LazyCompilationPrewarmConfig;
228
+ onError: (error: Error) => void;
229
+ }): LazyCompilationPrewarmController => {
230
+ let serverOrigin: string | undefined;
231
+ let manifest: ReactRouterManifestForDev | null = null;
232
+ let lastPrewarmAssetsKey: string | undefined;
233
+ const task = createDelayedPluginTask({
234
+ delayMs: config.delayMs,
235
+ run: () =>
236
+ Effect.gen(function* () {
237
+ if (!serverOrigin || !manifest) {
238
+ return;
239
+ }
240
+ yield* prewarmLazyCompilation({
241
+ manifest,
242
+ serverOrigin,
243
+ config,
244
+ });
245
+ }),
246
+ onError,
247
+ });
248
+
249
+ return {
250
+ setServerOrigin(origin) {
251
+ serverOrigin = origin;
252
+ },
253
+ setManifest(nextManifest) {
254
+ manifest = nextManifest;
255
+ if (!nextManifest) {
256
+ lastPrewarmAssetsKey = undefined;
257
+ return;
258
+ }
259
+ const assetsKey = collectLazyCompilationPrewarmAssets(
260
+ nextManifest,
261
+ config
262
+ ).join('\n');
263
+ if (assetsKey === lastPrewarmAssetsKey) {
264
+ return;
265
+ }
266
+ lastPrewarmAssetsKey = assetsKey;
267
+ task.reschedule();
268
+ },
269
+ schedule() {
270
+ task.schedule();
271
+ },
272
+ cancelEffect() {
273
+ manifest = null;
274
+ serverOrigin = undefined;
275
+ lastPrewarmAssetsKey = undefined;
276
+ return task.cancelEffect();
277
+ },
278
+ };
279
+ };
@@ -49,10 +49,14 @@ const matchesLazyCompilationTest = (
49
49
 
50
50
  const createReactRouterHydrationModuleTest = (entryClientPath: string) => {
51
51
  const eagerPatterns = [
52
- normalizeSlashes(entryClientPath),
53
52
  'virtual/react-router/browser-manifest',
54
- BUILD_CLIENT_ROUTE_QUERY_STRING,
55
- '?react-router-route',
53
+ ...(entryClientPath
54
+ ? [
55
+ normalizeSlashes(entryClientPath),
56
+ BUILD_CLIENT_ROUTE_QUERY_STRING,
57
+ '?react-router-route',
58
+ ]
59
+ : []),
56
60
  ];
57
61
 
58
62
  return (module: LazyCompilationModule): boolean =>
@@ -65,9 +69,11 @@ const createReactRouterHydrationModuleTest = (entryClientPath: string) => {
65
69
  export const guardReactRouterLazyCompilation = ({
66
70
  lazyCompilation,
67
71
  entryClientPath,
72
+ prewarmReactRouterModules = false,
68
73
  }: {
69
74
  lazyCompilation: PluginOptions['lazyCompilation'] | undefined;
70
75
  entryClientPath: string;
76
+ prewarmReactRouterModules?: boolean;
71
77
  }): PluginOptions['lazyCompilation'] | undefined => {
72
78
  if (lazyCompilation === undefined || lazyCompilation === false) {
73
79
  return lazyCompilation;
@@ -78,8 +84,9 @@ export const guardReactRouterLazyCompilation = ({
78
84
  ? { entries: true, imports: true }
79
85
  : lazyCompilation;
80
86
  const userTest = options.test;
81
- const isReactRouterHydrationModule =
82
- createReactRouterHydrationModuleTest(entryClientPath);
87
+ const isReactRouterHydrationModule = prewarmReactRouterModules
88
+ ? createReactRouterHydrationModuleTest('')
89
+ : createReactRouterHydrationModuleTest(entryClientPath);
83
90
 
84
91
  return {
85
92
  ...options,