rsbuild-plugin-react-router 0.2.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.
- package/README.md +213 -201
- package/dist/451.js +1103 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +7 -4
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +3 -0
- package/dist/config-imports.d.ts +10 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +47 -0
- package/dist/dev-runtime-compilation.d.ts +47 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +16 -2
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +13775 -1412
- package/dist/index.d.ts +8 -1
- package/dist/index.js +9429 -1501
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +6 -0
- package/dist/manifest.d.ts +48 -10
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +29 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +36 -0
- package/dist/prerender.d.ts +27 -2
- package/dist/react-router-config.d.ts +15 -5
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/templates/entry.server.cjs +3 -3
- package/dist/templates/entry.server.js +3 -3
- package/dist/typegen.d.ts +15 -0
- package/dist/types.d.ts +41 -14
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +24 -19
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +205 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +15 -0
- package/src/config-imports.ts +83 -0
- package/src/constants.ts +91 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +690 -0
- package/src/dev-runtime-artifacts.ts +239 -0
- package/src/dev-runtime-compilation.ts +171 -0
- package/src/dev-runtime-controller.ts +542 -0
- package/src/dev-runtime-session.ts +191 -0
- package/src/dev-server.ts +88 -0
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +278 -0
- package/src/index.ts +990 -0
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +101 -0
- package/src/manifest.ts +591 -0
- package/src/modify-browser-manifest.ts +246 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +458 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +687 -0
- package/src/prerender.ts +349 -0
- package/src/react-router-config.ts +245 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +357 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +111 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +178 -0
- package/src/types.ts +92 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/0~rslib-runtime.js +0 -16
- package/dist/babel.d.ts +0 -8
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI, TransformHandler } from '@rsbuild/core';
|
|
2
|
+
import jsesc from 'jsesc';
|
|
3
|
+
import { relative } from 'pathe';
|
|
4
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
5
|
+
import {
|
|
6
|
+
getReactRouterManifestForDev,
|
|
7
|
+
type ReactRouterManifestStats,
|
|
8
|
+
} from './manifest.js';
|
|
9
|
+
import type { RouteTransformExecutor } from './parallel-route-transforms.js';
|
|
10
|
+
import type { ReactRouterPerformanceProfiler } from './performance.js';
|
|
11
|
+
import { createBundlerRouteExportResolver } from './route-export-resolution.js';
|
|
12
|
+
import type { RouteChunkConfig } from './route-chunks.js';
|
|
13
|
+
import type { PluginOptions, Route } from './types.js';
|
|
14
|
+
import { isSourceMapEnabled } from './warnings/warn-on-client-source-maps.js';
|
|
15
|
+
|
|
16
|
+
type ReactRouterManifest = Awaited<
|
|
17
|
+
ReturnType<typeof getReactRouterManifestForDev>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
type RegisterBuildOutputTransformsOptions = {
|
|
21
|
+
api: RsbuildPluginAPI;
|
|
22
|
+
resolvedServerOutput: 'module' | 'commonjs';
|
|
23
|
+
performanceProfiler: ReactRouterPerformanceProfiler;
|
|
24
|
+
getLatestServerManifest: () => ReactRouterManifest | null;
|
|
25
|
+
getLatestServerManifestByBundleId: (
|
|
26
|
+
bundleId: string
|
|
27
|
+
) => ReactRouterManifest | undefined;
|
|
28
|
+
routes: Record<string, Route>;
|
|
29
|
+
pluginOptions: PluginOptions;
|
|
30
|
+
getClientStats: () => ReactRouterManifestStats | undefined;
|
|
31
|
+
appDirectory: string;
|
|
32
|
+
getAssetPrefix: () => string;
|
|
33
|
+
routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5];
|
|
34
|
+
routeTransformExecutor: RouteTransformExecutor;
|
|
35
|
+
routeByFilePath: Map<string, Route>;
|
|
36
|
+
routeChunkConfig: RouteChunkConfig;
|
|
37
|
+
isBuild: boolean;
|
|
38
|
+
splitRouteModules: boolean;
|
|
39
|
+
ssr: boolean;
|
|
40
|
+
isSpaMode: boolean;
|
|
41
|
+
rootRoutePath: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const registerBuildOutputTransforms = ({
|
|
45
|
+
api,
|
|
46
|
+
resolvedServerOutput,
|
|
47
|
+
performanceProfiler,
|
|
48
|
+
getLatestServerManifest,
|
|
49
|
+
getLatestServerManifestByBundleId,
|
|
50
|
+
routes,
|
|
51
|
+
pluginOptions,
|
|
52
|
+
getClientStats,
|
|
53
|
+
appDirectory,
|
|
54
|
+
getAssetPrefix,
|
|
55
|
+
routeChunkOptions,
|
|
56
|
+
routeTransformExecutor,
|
|
57
|
+
routeByFilePath,
|
|
58
|
+
routeChunkConfig,
|
|
59
|
+
isBuild,
|
|
60
|
+
splitRouteModules,
|
|
61
|
+
ssr,
|
|
62
|
+
isSpaMode,
|
|
63
|
+
rootRoutePath,
|
|
64
|
+
}: RegisterBuildOutputTransformsOptions): void => {
|
|
65
|
+
const transformRouteModule = async (args: Parameters<TransformHandler>[0]) =>
|
|
66
|
+
performanceProfiler.record(
|
|
67
|
+
args.environment?.name,
|
|
68
|
+
'route:module',
|
|
69
|
+
args.resource,
|
|
70
|
+
async () =>
|
|
71
|
+
routeTransformExecutor.run({
|
|
72
|
+
kind: 'routeModule',
|
|
73
|
+
code: args.code,
|
|
74
|
+
resource: args.resource,
|
|
75
|
+
resourcePath: args.resourcePath,
|
|
76
|
+
environmentName: args.environment.name,
|
|
77
|
+
sourceMaps: isSourceMapEnabled(
|
|
78
|
+
args.environment.config.output.sourceMap
|
|
79
|
+
),
|
|
80
|
+
ssr,
|
|
81
|
+
isBuild,
|
|
82
|
+
isSpaMode,
|
|
83
|
+
rootRoutePath,
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
api.processAssets(
|
|
88
|
+
{ stage: 'additional', targets: ['node'] },
|
|
89
|
+
({ sources, compilation }) => {
|
|
90
|
+
const packageJsonPath = 'package.json';
|
|
91
|
+
const source = new sources.RawSource(
|
|
92
|
+
`{"type": "${resolvedServerOutput}"}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (compilation.getAsset(packageJsonPath)) {
|
|
96
|
+
compilation.updateAsset(packageJsonPath, source);
|
|
97
|
+
} else {
|
|
98
|
+
compilation.emitAsset(packageJsonPath, source);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
api.transform(
|
|
104
|
+
{
|
|
105
|
+
test: /virtual\/react-router\/(browser|server)-manifest/,
|
|
106
|
+
},
|
|
107
|
+
async args =>
|
|
108
|
+
performanceProfiler.record(
|
|
109
|
+
args.environment?.name,
|
|
110
|
+
'manifest:transform',
|
|
111
|
+
args.resource,
|
|
112
|
+
async () => {
|
|
113
|
+
if (args.environment.name === 'web') {
|
|
114
|
+
return {
|
|
115
|
+
code: `window.__reactRouterManifest = "PLACEHOLDER";`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const bundleMatch = args.resource.match(
|
|
120
|
+
/virtual\/react-router\/server-manifest(?:-([^?]+))?/
|
|
121
|
+
);
|
|
122
|
+
const bundleId = bundleMatch?.[1]?.replace(/\.js$/, '');
|
|
123
|
+
const latestServerManifest = getLatestServerManifest();
|
|
124
|
+
const manifest =
|
|
125
|
+
(latestServerManifest
|
|
126
|
+
? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ??
|
|
127
|
+
latestServerManifest)
|
|
128
|
+
: null) ??
|
|
129
|
+
(await getReactRouterManifestForDev(
|
|
130
|
+
routes,
|
|
131
|
+
pluginOptions,
|
|
132
|
+
getClientStats(),
|
|
133
|
+
appDirectory,
|
|
134
|
+
getAssetPrefix(),
|
|
135
|
+
routeChunkOptions
|
|
136
|
+
));
|
|
137
|
+
return {
|
|
138
|
+
code: `export default ${jsesc(manifest, { es6: true })};`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
api.transform(
|
|
145
|
+
{
|
|
146
|
+
resourceQuery: /__react-router-build-client-route/,
|
|
147
|
+
},
|
|
148
|
+
async args =>
|
|
149
|
+
performanceProfiler.record(
|
|
150
|
+
args.environment?.name,
|
|
151
|
+
'route:client-entry',
|
|
152
|
+
args.resource,
|
|
153
|
+
async () =>
|
|
154
|
+
routeTransformExecutor.run({
|
|
155
|
+
kind: 'routeClientEntry',
|
|
156
|
+
code: args.code,
|
|
157
|
+
resourcePath: args.resourcePath,
|
|
158
|
+
environmentName: args.environment?.name,
|
|
159
|
+
isBuild,
|
|
160
|
+
routeChunkConfig,
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
api.transform(
|
|
166
|
+
{
|
|
167
|
+
resourceQuery: /route-chunk=/,
|
|
168
|
+
environments: ['web'],
|
|
169
|
+
},
|
|
170
|
+
async args =>
|
|
171
|
+
performanceProfiler.record(
|
|
172
|
+
args.environment?.name,
|
|
173
|
+
'route:chunk',
|
|
174
|
+
args.resource,
|
|
175
|
+
async () =>
|
|
176
|
+
routeTransformExecutor.run({
|
|
177
|
+
kind: 'routeChunk',
|
|
178
|
+
code: args.code,
|
|
179
|
+
resource: args.resource,
|
|
180
|
+
resourcePath: args.resourcePath,
|
|
181
|
+
isBuild,
|
|
182
|
+
routeChunkConfig,
|
|
183
|
+
})
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (isBuild && splitRouteModules) {
|
|
188
|
+
api.transform(
|
|
189
|
+
{
|
|
190
|
+
test: path => routeByFilePath.has(path),
|
|
191
|
+
resourceQuery: {
|
|
192
|
+
not: /__react-router-build-client-route|react-router-route|route-chunk=/,
|
|
193
|
+
},
|
|
194
|
+
environments: ['web'],
|
|
195
|
+
},
|
|
196
|
+
async args =>
|
|
197
|
+
performanceProfiler.record(
|
|
198
|
+
args.environment?.name,
|
|
199
|
+
'route:split-exports',
|
|
200
|
+
args.resource,
|
|
201
|
+
async () =>
|
|
202
|
+
routeTransformExecutor.run({
|
|
203
|
+
kind: 'splitRouteExports',
|
|
204
|
+
code: args.code,
|
|
205
|
+
resourcePath: args.resourcePath,
|
|
206
|
+
routeChunkConfig,
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
api.transform(
|
|
213
|
+
{
|
|
214
|
+
test: /[\\/]\.server[\\/]|\.server(\.[cm]?[jt]sx?)?$/,
|
|
215
|
+
environments: ['web'],
|
|
216
|
+
},
|
|
217
|
+
async args =>
|
|
218
|
+
performanceProfiler.record(
|
|
219
|
+
args.environment?.name,
|
|
220
|
+
'module:server-only-guard',
|
|
221
|
+
args.resource,
|
|
222
|
+
async () => {
|
|
223
|
+
const relativePath = relative(process.cwd(), args.resourcePath);
|
|
224
|
+
throw new Error(
|
|
225
|
+
`[${PLUGIN_NAME}] Server-only module referenced by client: ${relativePath}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
api.transform(
|
|
232
|
+
{
|
|
233
|
+
test: /[\\/]\.client[\\/]|\.client(\.[cm]?[jt]sx?)?$/,
|
|
234
|
+
environments: ['node'],
|
|
235
|
+
},
|
|
236
|
+
async args =>
|
|
237
|
+
performanceProfiler.record(
|
|
238
|
+
args.environment?.name,
|
|
239
|
+
'module:client-only-stub',
|
|
240
|
+
args.resource,
|
|
241
|
+
async () => {
|
|
242
|
+
return routeTransformExecutor.run({
|
|
243
|
+
kind: 'clientOnlyStub',
|
|
244
|
+
code: args.code,
|
|
245
|
+
resourcePath: args.resourcePath,
|
|
246
|
+
resolveExportAllModule:
|
|
247
|
+
typeof args.resolve === 'function'
|
|
248
|
+
? createBundlerRouteExportResolver(args.resolve)
|
|
249
|
+
: undefined,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
api.transform(
|
|
256
|
+
{
|
|
257
|
+
resourceQuery: /\?react-router-route/,
|
|
258
|
+
order: 'post',
|
|
259
|
+
},
|
|
260
|
+
transformRouteModule
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
api.transform(
|
|
264
|
+
{
|
|
265
|
+
test: path => routeByFilePath.has(path),
|
|
266
|
+
resourceQuery: {
|
|
267
|
+
not: /__react-router-build-client-route|react-router-route|route-chunk=/,
|
|
268
|
+
},
|
|
269
|
+
order: 'post',
|
|
270
|
+
},
|
|
271
|
+
transformRouteModule
|
|
272
|
+
);
|
|
273
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { availableParallelism, cpus } from 'node:os';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_RESERVED_CORES = 2;
|
|
4
|
+
|
|
5
|
+
export const getAvailableCpuCount = (): number =>
|
|
6
|
+
typeof availableParallelism === 'function'
|
|
7
|
+
? availableParallelism()
|
|
8
|
+
: cpus().length;
|
|
9
|
+
|
|
10
|
+
export const getDefaultConcurrency = (
|
|
11
|
+
cpuCount: number = getAvailableCpuCount()
|
|
12
|
+
): number => Math.max(0, Math.floor(cpuCount) - DEFAULT_RESERVED_CORES);
|
|
13
|
+
|
|
14
|
+
export const getCappedPluginConcurrency = (cap = 16): number =>
|
|
15
|
+
Math.max(1, Math.min(cap, getDefaultConcurrency() || 1));
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ModuleCache } from 'jiti';
|
|
2
|
+
import { createJiti } from 'jiti';
|
|
3
|
+
import { resolve } from 'pathe';
|
|
4
|
+
|
|
5
|
+
type ConfigImporter = Pick<ReturnType<typeof createJiti>, 'import'>;
|
|
6
|
+
|
|
7
|
+
const normalizePath = (filePath: string): string => resolve(filePath);
|
|
8
|
+
|
|
9
|
+
const isNodeModulePath = (filePath: string): boolean =>
|
|
10
|
+
filePath.split(/[\\/]/).includes('node_modules');
|
|
11
|
+
|
|
12
|
+
export const collectConfigImportWatchPaths = (
|
|
13
|
+
configPath: string,
|
|
14
|
+
moduleCache: ModuleCache,
|
|
15
|
+
previousCacheKeys: ReadonlySet<string>
|
|
16
|
+
): string[] => {
|
|
17
|
+
const normalizedConfigPath = normalizePath(configPath);
|
|
18
|
+
const watchPaths = new Set<string>();
|
|
19
|
+
|
|
20
|
+
for (const [cacheKey, module] of Object.entries(moduleCache)) {
|
|
21
|
+
if (previousCacheKeys.has(cacheKey)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const importPath = normalizePath(module?.filename ?? cacheKey);
|
|
26
|
+
if (importPath === normalizedConfigPath || isNodeModulePath(importPath)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
watchPaths.add(importPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Array.from(watchPaths);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const clearConfigImportCache = (
|
|
37
|
+
moduleCache: ModuleCache,
|
|
38
|
+
filePaths: readonly string[]
|
|
39
|
+
): void => {
|
|
40
|
+
const normalizedFilePaths = new Set(filePaths.map(normalizePath));
|
|
41
|
+
|
|
42
|
+
for (const [cacheKey, module] of Object.entries(moduleCache)) {
|
|
43
|
+
const cachedPath = normalizePath(module?.filename ?? cacheKey);
|
|
44
|
+
if (normalizedFilePaths.has(cachedPath)) {
|
|
45
|
+
delete moduleCache[cacheKey];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const importConfigWithWatchPaths = async <T>(
|
|
51
|
+
configPath: string,
|
|
52
|
+
load: (importer: ConfigImporter) => PromiseLike<T> | T = async importer =>
|
|
53
|
+
importer.import<T>(configPath, { default: true })
|
|
54
|
+
): Promise<{ value: Awaited<T>; watchPaths: string | string[] }> => {
|
|
55
|
+
const jiti = createJiti(process.cwd(), {
|
|
56
|
+
moduleCache: true,
|
|
57
|
+
});
|
|
58
|
+
const previousCacheKeys = new Set(Object.keys(jiti.cache));
|
|
59
|
+
let importPaths: string[] = [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const value = await load(jiti);
|
|
63
|
+
importPaths = collectConfigImportWatchPaths(
|
|
64
|
+
configPath,
|
|
65
|
+
jiti.cache,
|
|
66
|
+
previousCacheKeys
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
value,
|
|
70
|
+
watchPaths:
|
|
71
|
+
importPaths.length > 0 ? [configPath, ...importPaths] : configPath,
|
|
72
|
+
};
|
|
73
|
+
} finally {
|
|
74
|
+
if (importPaths.length === 0) {
|
|
75
|
+
importPaths = collectConfigImportWatchPaths(
|
|
76
|
+
configPath,
|
|
77
|
+
jiti.cache,
|
|
78
|
+
previousCacheKeys
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
clearConfigImportCache(jiti.cache, [configPath, ...importPaths]);
|
|
82
|
+
}
|
|
83
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const PLUGIN_NAME = 'rsbuild:react-router';
|
|
2
|
+
|
|
3
|
+
export const JS_EXTENSIONS = [
|
|
4
|
+
'.tsx',
|
|
5
|
+
'.ts',
|
|
6
|
+
'.jsx',
|
|
7
|
+
'.js',
|
|
8
|
+
'.mjs',
|
|
9
|
+
'.mts',
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export const JS_LOADERS = {
|
|
13
|
+
'.ts': 'ts',
|
|
14
|
+
'.tsx': 'tsx',
|
|
15
|
+
'.js': 'js',
|
|
16
|
+
'.jsx': 'jsx',
|
|
17
|
+
'.mjs': 'js',
|
|
18
|
+
'.mts': 'ts',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const BUILD_CLIENT_ROUTE_QUERY_STRING =
|
|
22
|
+
'?__react-router-build-client-route';
|
|
23
|
+
|
|
24
|
+
export const SERVER_ONLY_ROUTE_EXPORTS = [
|
|
25
|
+
'loader',
|
|
26
|
+
'action',
|
|
27
|
+
'middleware',
|
|
28
|
+
'headers',
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
export const SERVER_ONLY_ROUTE_EXPORTS_SET: ReadonlySet<string> = new Set(
|
|
32
|
+
SERVER_ONLY_ROUTE_EXPORTS
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Client route exports are split into non-component exports and component exports.
|
|
36
|
+
// This mirrors upstream React Router Vite plugin intent and is used for export filtering.
|
|
37
|
+
export const CLIENT_NON_COMPONENT_EXPORTS = [
|
|
38
|
+
'clientAction',
|
|
39
|
+
'clientLoader',
|
|
40
|
+
'clientMiddleware',
|
|
41
|
+
'handle',
|
|
42
|
+
'meta',
|
|
43
|
+
'links',
|
|
44
|
+
'shouldRevalidate',
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export const CLIENT_COMPONENT_EXPORTS = [
|
|
48
|
+
'default',
|
|
49
|
+
'ErrorBoundary',
|
|
50
|
+
'HydrateFallback',
|
|
51
|
+
'Layout',
|
|
52
|
+
] as const;
|
|
53
|
+
|
|
54
|
+
export const CLIENT_ROUTE_EXPORTS: readonly (
|
|
55
|
+
| (typeof CLIENT_NON_COMPONENT_EXPORTS)[number]
|
|
56
|
+
| (typeof CLIENT_COMPONENT_EXPORTS)[number]
|
|
57
|
+
)[] = [...CLIENT_NON_COMPONENT_EXPORTS, ...CLIENT_COMPONENT_EXPORTS];
|
|
58
|
+
|
|
59
|
+
export const CLIENT_ROUTE_EXPORTS_SET: ReadonlySet<string> = new Set(
|
|
60
|
+
CLIENT_ROUTE_EXPORTS
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export const NAMED_COMPONENT_EXPORTS = [
|
|
64
|
+
'HydrateFallback',
|
|
65
|
+
'ErrorBoundary',
|
|
66
|
+
] as const;
|
|
67
|
+
|
|
68
|
+
export const NAMED_COMPONENT_EXPORTS_SET: ReadonlySet<string> = new Set(
|
|
69
|
+
NAMED_COMPONENT_EXPORTS
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const SERVER_EXPORTS = {
|
|
73
|
+
loader: 'loader',
|
|
74
|
+
action: 'action',
|
|
75
|
+
middleware: 'middleware',
|
|
76
|
+
headers: 'headers',
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export const CLIENT_EXPORTS = {
|
|
80
|
+
clientAction: 'clientAction',
|
|
81
|
+
clientLoader: 'clientLoader',
|
|
82
|
+
clientMiddleware: 'clientMiddleware',
|
|
83
|
+
default: 'default',
|
|
84
|
+
ErrorBoundary: 'ErrorBoundary',
|
|
85
|
+
handle: 'handle',
|
|
86
|
+
HydrateFallback: 'HydrateFallback',
|
|
87
|
+
Layout: 'Layout',
|
|
88
|
+
links: 'links',
|
|
89
|
+
meta: 'meta',
|
|
90
|
+
shouldRevalidate: 'shouldRevalidate',
|
|
91
|
+
} as const;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
2
|
+
import type { RouteConfigEntry } from '@react-router/dev/routes';
|
|
3
|
+
import * as Effect from 'effect/Effect';
|
|
4
|
+
import { relative } from 'pathe';
|
|
5
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
6
|
+
import {
|
|
7
|
+
createDelayedPluginTask,
|
|
8
|
+
DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
9
|
+
normalizeEffectError,
|
|
10
|
+
runPluginEffect,
|
|
11
|
+
tryPluginPromise,
|
|
12
|
+
} from './effect-runtime.js';
|
|
13
|
+
import {
|
|
14
|
+
createLazyCompilationPrewarmController,
|
|
15
|
+
normalizeLazyCompilationPrewarmOptions,
|
|
16
|
+
} from './lazy-compilation-prewarm.js';
|
|
17
|
+
import {
|
|
18
|
+
configRoutesToRouteManifestEntries,
|
|
19
|
+
type ReactRouterManifestForDev,
|
|
20
|
+
} from './manifest.js';
|
|
21
|
+
import type { RouteTransformExecutor } from './parallel-route-transforms.js';
|
|
22
|
+
import {
|
|
23
|
+
createRouteManifestSnapshot,
|
|
24
|
+
createRouteTopologyWatcher,
|
|
25
|
+
ensureDevRestartMarker,
|
|
26
|
+
type WatchFileConfig,
|
|
27
|
+
} from './route-watch.js';
|
|
28
|
+
import type { PluginOptions } from './types.js';
|
|
29
|
+
|
|
30
|
+
type RegisterReactRouterDevBackgroundResourcesOptions = {
|
|
31
|
+
api: RsbuildPluginAPI;
|
|
32
|
+
isBuild: boolean;
|
|
33
|
+
lazyCompilationPrewarm: PluginOptions['unstableLazyCompilationPrewarm'];
|
|
34
|
+
routeTransformExecutor: RouteTransformExecutor;
|
|
35
|
+
routeRestartMarkerPath: string;
|
|
36
|
+
watchDirectory: string;
|
|
37
|
+
getRouteTopology: () => Promise<Set<string>>;
|
|
38
|
+
initialRouteTopology: Set<string>;
|
|
39
|
+
onRouteTopologyChange: PluginOptions['onRouteTopologyChange'];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type ReactRouterDevBackgroundResources = {
|
|
43
|
+
setManifest(manifest: ReactRouterManifestForDev): void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const createReactRouterRouteTopology = ({
|
|
47
|
+
appDirectory,
|
|
48
|
+
rootRouteFile,
|
|
49
|
+
routeConfig,
|
|
50
|
+
loadRouteConfig,
|
|
51
|
+
getRootRoutePath,
|
|
52
|
+
}: {
|
|
53
|
+
appDirectory: string;
|
|
54
|
+
rootRouteFile: string;
|
|
55
|
+
routeConfig: RouteConfigEntry[];
|
|
56
|
+
loadRouteConfig: () => Promise<RouteConfigEntry[]>;
|
|
57
|
+
getRootRoutePath: () => string;
|
|
58
|
+
}): {
|
|
59
|
+
initialRouteTopology: Set<string>;
|
|
60
|
+
getRouteTopology: () => Promise<Set<string>>;
|
|
61
|
+
} => {
|
|
62
|
+
const createSnapshot = (
|
|
63
|
+
routeFile: string,
|
|
64
|
+
routeConfigEntries: RouteConfigEntry[]
|
|
65
|
+
): Set<string> =>
|
|
66
|
+
createRouteManifestSnapshot([
|
|
67
|
+
['root', { path: '', id: 'root', file: routeFile }],
|
|
68
|
+
...configRoutesToRouteManifestEntries(appDirectory, routeConfigEntries),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
initialRouteTopology: createSnapshot(rootRouteFile, routeConfig),
|
|
73
|
+
async getRouteTopology() {
|
|
74
|
+
const latestRouteConfig = await loadRouteConfig();
|
|
75
|
+
const latestRootRouteFile = relative(appDirectory, getRootRoutePath());
|
|
76
|
+
return createSnapshot(latestRootRouteFile, latestRouteConfig);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const createReactRouterRouteWatchFiles = ({
|
|
82
|
+
configWatchPaths,
|
|
83
|
+
routeConfigWatchPaths,
|
|
84
|
+
routeRestartMarkerPath,
|
|
85
|
+
onRouteTopologyChange,
|
|
86
|
+
}: {
|
|
87
|
+
configWatchPaths: string | string[];
|
|
88
|
+
routeConfigWatchPaths: string | string[];
|
|
89
|
+
routeRestartMarkerPath: string;
|
|
90
|
+
onRouteTopologyChange: PluginOptions['onRouteTopologyChange'];
|
|
91
|
+
}): WatchFileConfig[] => {
|
|
92
|
+
const watchFiles: WatchFileConfig[] = [
|
|
93
|
+
{
|
|
94
|
+
paths: configWatchPaths,
|
|
95
|
+
type: 'reload-server',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (!onRouteTopologyChange) {
|
|
100
|
+
watchFiles.push(
|
|
101
|
+
{
|
|
102
|
+
paths: routeConfigWatchPaths,
|
|
103
|
+
type: 'reload-server',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
paths: routeRestartMarkerPath,
|
|
107
|
+
type: 'reload-server',
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return watchFiles;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const closeAll = async (
|
|
116
|
+
message: string,
|
|
117
|
+
closers: Array<() => Promise<void>>
|
|
118
|
+
): Promise<void> => {
|
|
119
|
+
const results = await Promise.allSettled(closers.map(closer => closer()));
|
|
120
|
+
const errors = results
|
|
121
|
+
.filter(
|
|
122
|
+
(result): result is PromiseRejectedResult => result.status === 'rejected'
|
|
123
|
+
)
|
|
124
|
+
.map(result => normalizeEffectError(result.reason));
|
|
125
|
+
|
|
126
|
+
if (errors.length === 1) {
|
|
127
|
+
throw errors[0];
|
|
128
|
+
}
|
|
129
|
+
if (errors.length > 1) {
|
|
130
|
+
throw new AggregateError(errors, message);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const registerReactRouterDevBackgroundResources = ({
|
|
135
|
+
api,
|
|
136
|
+
isBuild,
|
|
137
|
+
lazyCompilationPrewarm,
|
|
138
|
+
routeTransformExecutor,
|
|
139
|
+
routeRestartMarkerPath,
|
|
140
|
+
watchDirectory,
|
|
141
|
+
getRouteTopology,
|
|
142
|
+
initialRouteTopology,
|
|
143
|
+
onRouteTopologyChange,
|
|
144
|
+
}: RegisterReactRouterDevBackgroundResourcesOptions): ReactRouterDevBackgroundResources => {
|
|
145
|
+
let closeActiveRouteTopologyWatcher: (() => Promise<void>) | undefined;
|
|
146
|
+
let routeTopologyWatcherClosed = false;
|
|
147
|
+
|
|
148
|
+
const reportRouteTopologyWatcherError = (error: unknown): void => {
|
|
149
|
+
api.logger.warn(
|
|
150
|
+
`[${PLUGIN_NAME}] Failed to watch route topology changes: ${String(normalizeEffectError(error))}`
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const routeTopologyWatcherTask = createDelayedPluginTask({
|
|
155
|
+
delayMs: DEV_BACKGROUND_STARTUP_DELAY_MS,
|
|
156
|
+
run: () =>
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
yield* tryPluginPromise(() =>
|
|
159
|
+
ensureDevRestartMarker(routeRestartMarkerPath)
|
|
160
|
+
);
|
|
161
|
+
const closeWatcher = yield* tryPluginPromise(() =>
|
|
162
|
+
createRouteTopologyWatcher({
|
|
163
|
+
watchDirectory,
|
|
164
|
+
getRouteTopology,
|
|
165
|
+
initialRouteTopology,
|
|
166
|
+
restartMarkerPath: routeRestartMarkerPath,
|
|
167
|
+
onRouteTopologyChange,
|
|
168
|
+
onError: reportRouteTopologyWatcherError,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
if (routeTopologyWatcherClosed) {
|
|
172
|
+
yield* tryPluginPromise(() => closeWatcher());
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
closeActiveRouteTopologyWatcher = closeWatcher;
|
|
176
|
+
}),
|
|
177
|
+
onError: reportRouteTopologyWatcherError,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const scheduleRouteTopologyWatcher = (): void => {
|
|
181
|
+
if (routeTopologyWatcherClosed || closeActiveRouteTopologyWatcher) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
routeTopologyWatcherTask.schedule();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const lazyCompilationPrewarmConfig = normalizeLazyCompilationPrewarmOptions(
|
|
188
|
+
lazyCompilationPrewarm
|
|
189
|
+
);
|
|
190
|
+
const lazyCompilationPrewarmController = lazyCompilationPrewarmConfig
|
|
191
|
+
? createLazyCompilationPrewarmController({
|
|
192
|
+
config: lazyCompilationPrewarmConfig,
|
|
193
|
+
onError: error =>
|
|
194
|
+
api.logger.warn(
|
|
195
|
+
`[${PLUGIN_NAME}] Lazy compilation prewarm skipped: ${error.message}`
|
|
196
|
+
),
|
|
197
|
+
})
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
if (!isBuild) {
|
|
201
|
+
api.onBeforeStartDevServer(() => {
|
|
202
|
+
routeTopologyWatcherClosed = false;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
api.onAfterStartDevServer(({ port }) => {
|
|
206
|
+
lazyCompilationPrewarmController?.setServerOrigin(
|
|
207
|
+
`http://localhost:${port}`
|
|
208
|
+
);
|
|
209
|
+
lazyCompilationPrewarmController?.schedule();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
api.onAfterDevCompile(() => {
|
|
213
|
+
scheduleRouteTopologyWatcher();
|
|
214
|
+
lazyCompilationPrewarmController?.schedule();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Spawn transform workers now so thread startup overlaps Rsbuild's own
|
|
218
|
+
// compiler creation instead of delaying the first route transform.
|
|
219
|
+
routeTransformExecutor.prewarm();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const closeRouteTopologyWatcher = async (): Promise<void> => {
|
|
223
|
+
routeTopologyWatcherClosed = true;
|
|
224
|
+
await runPluginEffect(routeTopologyWatcherTask.cancelEffect());
|
|
225
|
+
await closeActiveRouteTopologyWatcher?.();
|
|
226
|
+
closeActiveRouteTopologyWatcher = undefined;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const closeLazyCompilationPrewarm = async (): Promise<void> => {
|
|
230
|
+
await runPluginEffect(
|
|
231
|
+
lazyCompilationPrewarmController?.cancelEffect() ?? Effect.void
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const closeRouteTransformExecutor = (): Promise<void> =>
|
|
236
|
+
routeTransformExecutor.close();
|
|
237
|
+
|
|
238
|
+
api.onCloseDevServer(() =>
|
|
239
|
+
closeAll(
|
|
240
|
+
'[rsbuild-plugin-react-router] Failed to close dev server resources.',
|
|
241
|
+
[
|
|
242
|
+
closeRouteTopologyWatcher,
|
|
243
|
+
closeLazyCompilationPrewarm,
|
|
244
|
+
closeRouteTransformExecutor,
|
|
245
|
+
]
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
api.onCloseBuild(closeRouteTransformExecutor);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
setManifest(manifest) {
|
|
252
|
+
lazyCompilationPrewarmController?.setManifest(manifest);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
};
|