rsbuild-plugin-react-router 0.1.1 → 0.3.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 +185 -204
- package/dist/451.js +1087 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +1 -1
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/config-imports.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +39 -0
- package/dist/dev-runtime-compilation.d.ts +32 -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 +11 -2
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +3085 -1272
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2039 -1299
- package/dist/lazy-compilation.d.ts +5 -0
- package/dist/manifest.d.ts +32 -9
- 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 +14 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +32 -0
- package/dist/prerender.d.ts +28 -2
- package/dist/react-router-config.d.ts +8 -1
- 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-utils.d.ts +3 -2
- package/dist/templates/entry.client.js +3 -3
- package/dist/templates/entry.server.cjs +11 -8
- package/dist/templates/entry.server.js +5 -5
- package/dist/typegen.d.ts +2 -0
- package/dist/types.d.ts +30 -12
- 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 +26 -22
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +168 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +34 -0
- package/src/config-imports.ts +45 -0
- package/src/constants.ts +91 -0
- package/src/dev-generation.ts +700 -0
- package/src/dev-runtime-artifacts.ts +207 -0
- package/src/dev-runtime-compilation.ts +92 -0
- package/src/dev-runtime-controller.ts +462 -0
- package/src/dev-runtime-session.ts +184 -0
- package/src/dev-server.ts +58 -0
- package/src/export-utils.ts +219 -0
- package/src/index.ts +1025 -0
- package/src/lazy-compilation.ts +94 -0
- package/src/manifest.ts +480 -0
- package/src/modify-browser-manifest.ts +245 -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 +332 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +623 -0
- package/src/prerender.ts +367 -0
- package/src/react-router-config.ts +220 -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 +322 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-utils.ts +210 -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 +49 -0
- package/src/types.ts +82 -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/906.js +0 -1
- package/dist/946.js +0 -1
- package/dist/babel.d.ts +0 -8
- package/dist/rslib-runtime.js +0 -23
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { readFileSync, statSync, type Stats } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, relative, resolve } from 'pathe';
|
|
4
|
+
import { JS_EXTENSIONS, PLUGIN_NAME } from './constants.js';
|
|
5
|
+
import {
|
|
6
|
+
getExportNamesAndExportAll,
|
|
7
|
+
getRouteModuleAnalysis,
|
|
8
|
+
} from './export-utils.js';
|
|
9
|
+
|
|
10
|
+
const tryStat = (path: string): Stats | null =>
|
|
11
|
+
statSync(path, { throwIfNoEntry: false }) ?? null;
|
|
12
|
+
|
|
13
|
+
type PackageExportTarget =
|
|
14
|
+
| string
|
|
15
|
+
| PackageExportTarget[]
|
|
16
|
+
| { [condition: string]: PackageExportTarget | undefined }
|
|
17
|
+
| null;
|
|
18
|
+
|
|
19
|
+
type PackageJson = {
|
|
20
|
+
exports?: PackageExportTarget;
|
|
21
|
+
main?: string;
|
|
22
|
+
module?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const PACKAGE_IMPORT_CONDITIONS = new Set(['import', 'node']);
|
|
26
|
+
const PACKAGE_RESOLUTION_NOT_APPLICABLE = Symbol(
|
|
27
|
+
'package resolution not applicable'
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const resolveIndexFile = (dirPath: string): string | null => {
|
|
31
|
+
for (const ext of JS_EXTENSIONS) {
|
|
32
|
+
const candidate = resolve(dirPath, `index${ext}`);
|
|
33
|
+
const stats = tryStat(candidate);
|
|
34
|
+
if (stats?.isFile()) {
|
|
35
|
+
return candidate;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const resolvePathWithExtensions = (basePath: string): string | null => {
|
|
42
|
+
const stats = tryStat(basePath);
|
|
43
|
+
if (stats?.isFile()) {
|
|
44
|
+
return basePath;
|
|
45
|
+
}
|
|
46
|
+
if (stats?.isDirectory()) {
|
|
47
|
+
return resolveIndexFile(basePath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const ext of JS_EXTENSIONS) {
|
|
51
|
+
const candidate = `${basePath}${ext}`;
|
|
52
|
+
const candidateStats = tryStat(candidate);
|
|
53
|
+
if (candidateStats?.isFile()) {
|
|
54
|
+
return candidate;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return resolveIndexFile(basePath);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const parsePackageSpecifier = (
|
|
62
|
+
specifier: string
|
|
63
|
+
): { packageName: string; packageSubpath: string } | null => {
|
|
64
|
+
if (
|
|
65
|
+
specifier.startsWith('.') ||
|
|
66
|
+
specifier.startsWith('/') ||
|
|
67
|
+
specifier.startsWith('#')
|
|
68
|
+
) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parts = specifier.split('/');
|
|
73
|
+
const packageName = specifier.startsWith('@')
|
|
74
|
+
? parts.slice(0, 2).join('/')
|
|
75
|
+
: parts[0];
|
|
76
|
+
const packagePathParts = specifier.startsWith('@')
|
|
77
|
+
? parts.slice(2)
|
|
78
|
+
: parts.slice(1);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
packageName,
|
|
82
|
+
packageSubpath:
|
|
83
|
+
packagePathParts.length > 0 ? `./${packagePathParts.join('/')}` : '.',
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const findPackageDirectory = (
|
|
88
|
+
importerPath: string,
|
|
89
|
+
packageName: string
|
|
90
|
+
): string | null => {
|
|
91
|
+
let currentDirectory = dirname(importerPath);
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
const packageDirectory = resolve(
|
|
95
|
+
currentDirectory,
|
|
96
|
+
'node_modules',
|
|
97
|
+
packageName
|
|
98
|
+
);
|
|
99
|
+
const packageJsonPath = resolve(packageDirectory, 'package.json');
|
|
100
|
+
if (tryStat(packageJsonPath)?.isFile()) {
|
|
101
|
+
return packageDirectory;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parentDirectory = dirname(currentDirectory);
|
|
105
|
+
if (parentDirectory === currentDirectory) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
currentDirectory = parentDirectory;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const readPackageJson = (packageDirectory: string): PackageJson | null => {
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(
|
|
115
|
+
readFileSync(resolve(packageDirectory, 'package.json'), 'utf8')
|
|
116
|
+
) as PackageJson;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const resolvePackageExportTarget = (
|
|
123
|
+
target: PackageExportTarget | undefined
|
|
124
|
+
): string | null => {
|
|
125
|
+
if (!target) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof target === 'string') {
|
|
130
|
+
return target;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(target)) {
|
|
134
|
+
for (const nestedTarget of target) {
|
|
135
|
+
const resolvedTarget = resolvePackageExportTarget(nestedTarget);
|
|
136
|
+
if (resolvedTarget) {
|
|
137
|
+
return resolvedTarget;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const [condition, nestedTarget] of Object.entries(target)) {
|
|
144
|
+
if (condition === 'default' || PACKAGE_IMPORT_CONDITIONS.has(condition)) {
|
|
145
|
+
const resolvedTarget = resolvePackageExportTarget(nestedTarget);
|
|
146
|
+
if (resolvedTarget) {
|
|
147
|
+
return resolvedTarget;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const resolvePackageExports = (
|
|
156
|
+
packageDirectory: string,
|
|
157
|
+
packageSubpath: string,
|
|
158
|
+
packageJson: PackageJson
|
|
159
|
+
): string | null => {
|
|
160
|
+
const exports = packageJson.exports;
|
|
161
|
+
if (!exports) {
|
|
162
|
+
const entry = packageJson.module ?? packageJson.main;
|
|
163
|
+
return entry
|
|
164
|
+
? resolvePathWithExtensions(resolve(packageDirectory, entry))
|
|
165
|
+
: null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const target =
|
|
169
|
+
typeof exports === 'object' &&
|
|
170
|
+
!Array.isArray(exports) &&
|
|
171
|
+
Object.keys(exports).some(key => key.startsWith('.'))
|
|
172
|
+
? exports[packageSubpath]
|
|
173
|
+
: packageSubpath === '.'
|
|
174
|
+
? exports
|
|
175
|
+
: undefined;
|
|
176
|
+
|
|
177
|
+
const resolvedTarget = resolvePackageExportTarget(target);
|
|
178
|
+
if (!resolvedTarget || !resolvedTarget.startsWith('./')) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return resolvePathWithExtensions(resolve(packageDirectory, resolvedTarget));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const resolvePackageImport = (
|
|
186
|
+
specifier: string,
|
|
187
|
+
importerPath: string
|
|
188
|
+
): string | null | typeof PACKAGE_RESOLUTION_NOT_APPLICABLE => {
|
|
189
|
+
const parsedSpecifier = parsePackageSpecifier(specifier);
|
|
190
|
+
if (!parsedSpecifier) {
|
|
191
|
+
return PACKAGE_RESOLUTION_NOT_APPLICABLE;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const packageDirectory = findPackageDirectory(
|
|
195
|
+
importerPath,
|
|
196
|
+
parsedSpecifier.packageName
|
|
197
|
+
);
|
|
198
|
+
if (!packageDirectory) {
|
|
199
|
+
return PACKAGE_RESOLUTION_NOT_APPLICABLE;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const packageJson = readPackageJson(packageDirectory);
|
|
203
|
+
if (!packageJson) {
|
|
204
|
+
return PACKAGE_RESOLUTION_NOT_APPLICABLE;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
packageJson.exports === undefined &&
|
|
209
|
+
!packageJson.module &&
|
|
210
|
+
!packageJson.main
|
|
211
|
+
) {
|
|
212
|
+
return PACKAGE_RESOLUTION_NOT_APPLICABLE;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return resolvePackageExports(
|
|
216
|
+
packageDirectory,
|
|
217
|
+
parsedSpecifier.packageSubpath,
|
|
218
|
+
packageJson
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const resolveExportAllModule = (
|
|
223
|
+
specifier: string,
|
|
224
|
+
importerPath: string
|
|
225
|
+
): string | null => {
|
|
226
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
227
|
+
const basePath = specifier.startsWith('/')
|
|
228
|
+
? specifier
|
|
229
|
+
: resolve(dirname(importerPath), specifier);
|
|
230
|
+
const resolvedPath = resolvePathWithExtensions(basePath);
|
|
231
|
+
if (resolvedPath) {
|
|
232
|
+
return resolvedPath;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const importResolvedPath = resolvePackageImport(specifier, importerPath);
|
|
237
|
+
if (importResolvedPath !== PACKAGE_RESOLUTION_NOT_APPLICABLE) {
|
|
238
|
+
return importResolvedPath;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
return createRequire(importerPath).resolve(specifier);
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export type RouteExportResolver = (
|
|
249
|
+
specifier: string,
|
|
250
|
+
importerPath: string
|
|
251
|
+
) => Promise<string | null> | string | null;
|
|
252
|
+
|
|
253
|
+
export type RouteModuleResolveCallback = (
|
|
254
|
+
error: Error | null,
|
|
255
|
+
resolved?: string | false
|
|
256
|
+
) => void;
|
|
257
|
+
|
|
258
|
+
export type RouteModuleResolver = (
|
|
259
|
+
context: string,
|
|
260
|
+
specifier: string,
|
|
261
|
+
callback: RouteModuleResolveCallback
|
|
262
|
+
) => void;
|
|
263
|
+
|
|
264
|
+
export const createBundlerRouteExportResolver =
|
|
265
|
+
(resolveModule: RouteModuleResolver): RouteExportResolver =>
|
|
266
|
+
(specifier, importerPath) =>
|
|
267
|
+
new Promise<string | null>(resolveResolvedPath => {
|
|
268
|
+
resolveModule(dirname(importerPath), specifier, (error, resolved) => {
|
|
269
|
+
resolveResolvedPath(error || !resolved ? null : resolved);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export const collectClientOnlyStubExportNames = async (
|
|
274
|
+
code: string,
|
|
275
|
+
resourcePath: string,
|
|
276
|
+
resolveModule: RouteExportResolver = resolveExportAllModule
|
|
277
|
+
): Promise<Set<string>> => {
|
|
278
|
+
const { exportNames: directExportNames, exportAllModules } =
|
|
279
|
+
await getExportNamesAndExportAll(code);
|
|
280
|
+
const exportNames = new Set(directExportNames);
|
|
281
|
+
const unresolvedExportAll = new Set<string>();
|
|
282
|
+
const visitedModules = new Set<string>();
|
|
283
|
+
|
|
284
|
+
const collectExportNamesFromModule = async (
|
|
285
|
+
modulePath: string
|
|
286
|
+
): Promise<void> => {
|
|
287
|
+
if (visitedModules.has(modulePath)) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
visitedModules.add(modulePath);
|
|
291
|
+
const { exports: moduleExportNames, exportAllModules: moduleExportAll } =
|
|
292
|
+
await getRouteModuleAnalysis(modulePath);
|
|
293
|
+
for (const name of moduleExportNames) {
|
|
294
|
+
if (name !== 'default') {
|
|
295
|
+
exportNames.add(name);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const nestedSpecifier of moduleExportAll) {
|
|
299
|
+
const nestedPath = await resolveModule(nestedSpecifier, modulePath);
|
|
300
|
+
if (!nestedPath) {
|
|
301
|
+
unresolvedExportAll.add(nestedSpecifier);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
await collectExportNamesFromModule(nestedPath);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
for (const specifier of exportAllModules) {
|
|
309
|
+
const resolvedPath = await resolveModule(specifier, resourcePath);
|
|
310
|
+
if (!resolvedPath) {
|
|
311
|
+
unresolvedExportAll.add(specifier);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
await collectExportNamesFromModule(resolvedPath);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (unresolvedExportAll.size > 0) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`[${PLUGIN_NAME}] Client-only module uses \`export * from\` with ` +
|
|
320
|
+
`unresolvable specifier(s): ${Array.from(unresolvedExportAll)
|
|
321
|
+
.map(spec => `\`${spec}\``)
|
|
322
|
+
.join(', ')}. ` +
|
|
323
|
+
`Please explicitly re-export named bindings in ` +
|
|
324
|
+
`\`${relative(process.cwd(), resourcePath)}\`.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return exportNames;
|
|
329
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { basename as pathBasename, relative } from 'pathe';
|
|
2
|
+
import { generate, parse } from './yuku.js';
|
|
3
|
+
import {
|
|
4
|
+
SERVER_ONLY_ROUTE_EXPORTS,
|
|
5
|
+
SERVER_ONLY_ROUTE_EXPORTS_SET,
|
|
6
|
+
} from './constants.js';
|
|
7
|
+
import { collectProgramExportNames } from './export-utils.js';
|
|
8
|
+
import {
|
|
9
|
+
removeExports,
|
|
10
|
+
removeUnusedImports,
|
|
11
|
+
transformRoute,
|
|
12
|
+
} from './plugin-utils.js';
|
|
13
|
+
import {
|
|
14
|
+
collectClientOnlyStubExportNames,
|
|
15
|
+
type RouteExportResolver,
|
|
16
|
+
} from './route-export-resolution.js';
|
|
17
|
+
import {
|
|
18
|
+
createRouteChunkArtifact,
|
|
19
|
+
createRouteClientEntryArtifact,
|
|
20
|
+
} from './route-artifacts.js';
|
|
21
|
+
import {
|
|
22
|
+
detectRouteChunksIfEnabled,
|
|
23
|
+
getRouteChunkModuleId,
|
|
24
|
+
type RouteChunkCache,
|
|
25
|
+
type RouteChunkConfig,
|
|
26
|
+
} from './route-chunks.js';
|
|
27
|
+
import { getProgram } from './route-ast.js';
|
|
28
|
+
|
|
29
|
+
export type RouteTransformResult = {
|
|
30
|
+
code: string;
|
|
31
|
+
map?: ReturnType<typeof generate>['map'];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type BaseRouteTransformTask = {
|
|
35
|
+
code: string;
|
|
36
|
+
resourcePath: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type RouteClientEntryTransformTask = BaseRouteTransformTask & {
|
|
40
|
+
kind: 'routeClientEntry';
|
|
41
|
+
environmentName?: string;
|
|
42
|
+
isBuild: boolean;
|
|
43
|
+
routeChunkConfig: RouteChunkConfig;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type RouteChunkTransformTask = BaseRouteTransformTask & {
|
|
47
|
+
kind: 'routeChunk';
|
|
48
|
+
resource: string;
|
|
49
|
+
isBuild: boolean;
|
|
50
|
+
routeChunkConfig: RouteChunkConfig;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type SplitRouteExportsTransformTask = BaseRouteTransformTask & {
|
|
54
|
+
kind: 'splitRouteExports';
|
|
55
|
+
routeChunkConfig: RouteChunkConfig;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ClientOnlyStubTransformTask = BaseRouteTransformTask & {
|
|
59
|
+
kind: 'clientOnlyStub';
|
|
60
|
+
resolveExportAllModule?: RouteExportResolver;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type RouteModuleTransformTask = BaseRouteTransformTask & {
|
|
64
|
+
kind: 'routeModule';
|
|
65
|
+
resource: string;
|
|
66
|
+
environmentName: string;
|
|
67
|
+
sourceMaps: boolean;
|
|
68
|
+
ssr: boolean;
|
|
69
|
+
isBuild: boolean;
|
|
70
|
+
isSpaMode: boolean;
|
|
71
|
+
rootRoutePath: string | null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type RouteTransformTask =
|
|
75
|
+
| RouteClientEntryTransformTask
|
|
76
|
+
| RouteChunkTransformTask
|
|
77
|
+
| SplitRouteExportsTransformTask
|
|
78
|
+
| ClientOnlyStubTransformTask
|
|
79
|
+
| RouteModuleTransformTask;
|
|
80
|
+
|
|
81
|
+
export type RouteTransformTaskOptions = {
|
|
82
|
+
routeChunkCache?: RouteChunkCache;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const defaultRouteChunkCache: RouteChunkCache = new Map();
|
|
86
|
+
|
|
87
|
+
const getRouteChunkCache = (options?: RouteTransformTaskOptions) =>
|
|
88
|
+
options?.routeChunkCache ?? defaultRouteChunkCache;
|
|
89
|
+
|
|
90
|
+
const splitRouteExports = async (
|
|
91
|
+
task: SplitRouteExportsTransformTask,
|
|
92
|
+
options?: RouteTransformTaskOptions
|
|
93
|
+
): Promise<RouteTransformResult> => {
|
|
94
|
+
const { exportNames, hasRouteChunks, chunkedExports } =
|
|
95
|
+
await detectRouteChunksIfEnabled(
|
|
96
|
+
getRouteChunkCache(options),
|
|
97
|
+
task.routeChunkConfig,
|
|
98
|
+
task.resourcePath,
|
|
99
|
+
task.code
|
|
100
|
+
);
|
|
101
|
+
if (!hasRouteChunks) {
|
|
102
|
+
return { code: task.code, map: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const chunkedExportSet = new Set<string>(chunkedExports);
|
|
106
|
+
const mainChunkReexports = exportNames
|
|
107
|
+
.filter(name => !chunkedExportSet.has(name))
|
|
108
|
+
.join(', ');
|
|
109
|
+
const chunkBasePath = `./${pathBasename(task.resourcePath)}`;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
code: [
|
|
113
|
+
mainChunkReexports
|
|
114
|
+
? `export { ${mainChunkReexports} } from "${getRouteChunkModuleId(
|
|
115
|
+
chunkBasePath,
|
|
116
|
+
'main'
|
|
117
|
+
)}";`
|
|
118
|
+
: null,
|
|
119
|
+
...chunkedExports.map(
|
|
120
|
+
exportName =>
|
|
121
|
+
`export { ${exportName} } from "${getRouteChunkModuleId(
|
|
122
|
+
chunkBasePath,
|
|
123
|
+
exportName
|
|
124
|
+
)}";`
|
|
125
|
+
),
|
|
126
|
+
]
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.join('\n'),
|
|
129
|
+
map: null,
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const createClientOnlyStub = async (
|
|
134
|
+
task: ClientOnlyStubTransformTask
|
|
135
|
+
): Promise<RouteTransformResult> => {
|
|
136
|
+
const exportNames = await collectClientOnlyStubExportNames(
|
|
137
|
+
task.code,
|
|
138
|
+
task.resourcePath,
|
|
139
|
+
task.resolveExportAllModule
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
code: Array.from(exportNames)
|
|
144
|
+
.map(name =>
|
|
145
|
+
name === 'default'
|
|
146
|
+
? 'export default undefined;'
|
|
147
|
+
: `export const ${name} = undefined;`
|
|
148
|
+
)
|
|
149
|
+
.join('\n'),
|
|
150
|
+
map: null,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const transformRouteModule = async (
|
|
155
|
+
task: RouteModuleTransformTask
|
|
156
|
+
): Promise<RouteTransformResult> => {
|
|
157
|
+
let code = task.code;
|
|
158
|
+
|
|
159
|
+
const defaultExportMatch = code.match(/\n\s{0,}([\w\d_]+)\sas default,?/);
|
|
160
|
+
if (defaultExportMatch && typeof defaultExportMatch.index === 'number') {
|
|
161
|
+
code =
|
|
162
|
+
code.slice(0, defaultExportMatch.index) +
|
|
163
|
+
code.slice(defaultExportMatch.index + defaultExportMatch[0].length);
|
|
164
|
+
code += `\nexport default ${defaultExportMatch[1]};`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const ast = parse(code, { sourceType: 'module' });
|
|
168
|
+
if (task.environmentName === 'web' && !task.ssr && task.isSpaMode) {
|
|
169
|
+
const resolvedExportNames = collectProgramExportNames(getProgram(ast));
|
|
170
|
+
const isRootRoute = task.resourcePath === task.rootRoutePath;
|
|
171
|
+
const relativePath = relative(process.cwd(), task.resourcePath);
|
|
172
|
+
|
|
173
|
+
const invalidServerOnly = resolvedExportNames.filter(exp => {
|
|
174
|
+
if (isRootRoute && exp === 'loader') return false;
|
|
175
|
+
return SERVER_ONLY_ROUTE_EXPORTS_SET.has(exp);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (invalidServerOnly.length > 0) {
|
|
179
|
+
const list = invalidServerOnly.map(e => `\`${e}\``).join(', ');
|
|
180
|
+
throw new Error(
|
|
181
|
+
`SPA Mode: ${invalidServerOnly.length} invalid route export(s) in ` +
|
|
182
|
+
`\`${relativePath}\`: ${list}. ` +
|
|
183
|
+
`See https://reactrouter.com/how-to/spa for more information.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!isRootRoute && resolvedExportNames.includes('HydrateFallback')) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`SPA Mode: Invalid \`HydrateFallback\` export found in ` +
|
|
190
|
+
`\`${relativePath}\`. ` +
|
|
191
|
+
`\`HydrateFallback\` is only permitted on the root route in SPA Mode. ` +
|
|
192
|
+
`See https://reactrouter.com/how-to/spa for more information.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const removedServerOnlyExports =
|
|
198
|
+
task.environmentName === 'web'
|
|
199
|
+
? removeExports(
|
|
200
|
+
ast,
|
|
201
|
+
SERVER_ONLY_ROUTE_EXPORTS,
|
|
202
|
+
SERVER_ONLY_ROUTE_EXPORTS_SET
|
|
203
|
+
)
|
|
204
|
+
: false;
|
|
205
|
+
transformRoute(ast);
|
|
206
|
+
if (removedServerOnlyExports) {
|
|
207
|
+
removeUnusedImports(ast);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return generate(ast, {
|
|
211
|
+
// Rsbuild merges this map with its downstream SWC transform. Only pay the
|
|
212
|
+
// code-generation cost when this environment actually emits JS maps.
|
|
213
|
+
sourceMaps: task.sourceMaps,
|
|
214
|
+
filename: task.resource,
|
|
215
|
+
sourceFileName: task.resourcePath,
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const executeRouteTransformTask = async (
|
|
220
|
+
task: RouteTransformTask,
|
|
221
|
+
options?: RouteTransformTaskOptions
|
|
222
|
+
): Promise<RouteTransformResult> => {
|
|
223
|
+
switch (task.kind) {
|
|
224
|
+
case 'routeClientEntry':
|
|
225
|
+
return createRouteClientEntryArtifact({
|
|
226
|
+
code: task.code,
|
|
227
|
+
resourcePath: task.resourcePath,
|
|
228
|
+
environmentName: task.environmentName,
|
|
229
|
+
isBuild: task.isBuild,
|
|
230
|
+
routeChunkCache: getRouteChunkCache(options),
|
|
231
|
+
routeChunkConfig: task.routeChunkConfig,
|
|
232
|
+
});
|
|
233
|
+
case 'routeChunk':
|
|
234
|
+
return createRouteChunkArtifact({
|
|
235
|
+
code: task.code,
|
|
236
|
+
resource: task.resource,
|
|
237
|
+
resourcePath: task.resourcePath,
|
|
238
|
+
isBuild: task.isBuild,
|
|
239
|
+
routeChunkCache: getRouteChunkCache(options),
|
|
240
|
+
routeChunkConfig: task.routeChunkConfig,
|
|
241
|
+
});
|
|
242
|
+
case 'splitRouteExports':
|
|
243
|
+
return splitRouteExports(task, options);
|
|
244
|
+
case 'clientOnlyStub':
|
|
245
|
+
return createClientOnlyStub(task);
|
|
246
|
+
case 'routeModule':
|
|
247
|
+
return transformRouteModule(task);
|
|
248
|
+
}
|
|
249
|
+
};
|