@reckona/mreact-compiler 0.0.81 → 0.0.82
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 +15 -0
- package/dist/boundary-graph.d.ts +66 -0
- package/dist/boundary-graph.d.ts.map +1 -0
- package/dist/boundary-graph.js +568 -0
- package/dist/boundary-graph.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +14 -1
- package/dist/internal.js.map +1 -1
- package/package.json +2 -2
- package/src/boundary-graph.ts +904 -0
- package/src/index.ts +16 -0
- package/src/internal.ts +25 -1
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectClientRouteModuleAnalysis,
|
|
3
|
+
collectFormActionExpressionReferences,
|
|
4
|
+
hasModuleDirective,
|
|
5
|
+
} from "./internal.js";
|
|
6
|
+
import type {
|
|
7
|
+
ClientRouteModuleAnalysis,
|
|
8
|
+
ClientRouteStaticImportReference,
|
|
9
|
+
StaticExportReference,
|
|
10
|
+
StaticImportSpecifierReference,
|
|
11
|
+
TopLevelExportRenderInfo,
|
|
12
|
+
} from "./internal.js";
|
|
13
|
+
import type { Diagnostic } from "./types.js";
|
|
14
|
+
|
|
15
|
+
export type BoundaryGraphEntryKind =
|
|
16
|
+
| "module"
|
|
17
|
+
| "route-layout"
|
|
18
|
+
| "route-page"
|
|
19
|
+
| "route-template";
|
|
20
|
+
|
|
21
|
+
export type BoundaryClassification =
|
|
22
|
+
| "client-boundary"
|
|
23
|
+
| "client-route"
|
|
24
|
+
| "server-action"
|
|
25
|
+
| "server-only"
|
|
26
|
+
| "server-render"
|
|
27
|
+
| "shared"
|
|
28
|
+
| "unknown";
|
|
29
|
+
|
|
30
|
+
export interface BoundaryGraphEntry {
|
|
31
|
+
file: string;
|
|
32
|
+
kind: BoundaryGraphEntryKind;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BoundaryGraphInput {
|
|
36
|
+
entries: readonly BoundaryGraphEntry[];
|
|
37
|
+
readModule(file: string): Promise<string | undefined> | string | undefined;
|
|
38
|
+
resolveModule(input: {
|
|
39
|
+
importer: string;
|
|
40
|
+
source: string;
|
|
41
|
+
}): Promise<string | undefined> | string | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface BoundaryGraphExport {
|
|
45
|
+
classification: BoundaryClassification;
|
|
46
|
+
name: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface BoundaryGraphModule {
|
|
50
|
+
classification: BoundaryClassification;
|
|
51
|
+
exports: BoundaryGraphExport[];
|
|
52
|
+
file: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BoundaryGraphClientBoundary {
|
|
56
|
+
exportNames?: readonly string[];
|
|
57
|
+
importerFile: string;
|
|
58
|
+
moduleFile: string;
|
|
59
|
+
source: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface BoundaryGraphServerActionSite {
|
|
63
|
+
end: number;
|
|
64
|
+
exportName: string;
|
|
65
|
+
expression: string;
|
|
66
|
+
expressionEnd: number;
|
|
67
|
+
expressionStart: number;
|
|
68
|
+
file: string;
|
|
69
|
+
inferred: boolean;
|
|
70
|
+
moduleFile: string;
|
|
71
|
+
start: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type BoundaryGraphTraceKind =
|
|
75
|
+
| "client-boundary"
|
|
76
|
+
| "export"
|
|
77
|
+
| "module"
|
|
78
|
+
| "server-action";
|
|
79
|
+
|
|
80
|
+
export type BoundaryGraphTraceReason =
|
|
81
|
+
| "client-runtime-export"
|
|
82
|
+
| "module-classification"
|
|
83
|
+
| "node-builtin-import"
|
|
84
|
+
| "rendered-import"
|
|
85
|
+
| "server-action-expression"
|
|
86
|
+
| "server-render-export"
|
|
87
|
+
| "static-export"
|
|
88
|
+
| "use-client-directive"
|
|
89
|
+
| "unknown-module"
|
|
90
|
+
| "use-server-directive";
|
|
91
|
+
|
|
92
|
+
export interface BoundaryGraphTraceEvent {
|
|
93
|
+
classification: BoundaryClassification;
|
|
94
|
+
exportName?: string;
|
|
95
|
+
exportNames?: readonly string[];
|
|
96
|
+
expression?: string;
|
|
97
|
+
file: string;
|
|
98
|
+
importerFile?: string;
|
|
99
|
+
inferred?: boolean;
|
|
100
|
+
kind: BoundaryGraphTraceKind;
|
|
101
|
+
moduleFile?: string;
|
|
102
|
+
reason: BoundaryGraphTraceReason;
|
|
103
|
+
source?: string;
|
|
104
|
+
viaExportName?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface BoundaryGraphResult {
|
|
108
|
+
clientBoundaries: BoundaryGraphClientBoundary[];
|
|
109
|
+
diagnostics: Diagnostic[];
|
|
110
|
+
modules: BoundaryGraphModule[];
|
|
111
|
+
serverActions: BoundaryGraphServerActionSite[];
|
|
112
|
+
trace: BoundaryGraphTraceEvent[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface ResolvedServerActionTarget {
|
|
116
|
+
exportName: string;
|
|
117
|
+
inferred: boolean;
|
|
118
|
+
moduleFile: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function analyzeBoundaryGraph(
|
|
122
|
+
input: BoundaryGraphInput,
|
|
123
|
+
): Promise<BoundaryGraphResult> {
|
|
124
|
+
const modules = new Map<string, BoundaryGraphModule>();
|
|
125
|
+
const clientBoundaries: BoundaryGraphClientBoundary[] = [];
|
|
126
|
+
const diagnostics: Diagnostic[] = [];
|
|
127
|
+
const serverActions: BoundaryGraphServerActionSite[] = [];
|
|
128
|
+
const trace: BoundaryGraphTraceEvent[] = [];
|
|
129
|
+
const visiting = new Set<string>();
|
|
130
|
+
|
|
131
|
+
for (const entry of input.entries) {
|
|
132
|
+
await analyzeModule({
|
|
133
|
+
diagnostics,
|
|
134
|
+
entryKind: entry.kind,
|
|
135
|
+
file: entry.file,
|
|
136
|
+
input,
|
|
137
|
+
clientBoundaries,
|
|
138
|
+
modules,
|
|
139
|
+
serverActions,
|
|
140
|
+
trace,
|
|
141
|
+
visiting,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
clientBoundaries,
|
|
147
|
+
diagnostics,
|
|
148
|
+
modules: Array.from(modules.values()),
|
|
149
|
+
serverActions,
|
|
150
|
+
trace,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function analyzeModule(options: {
|
|
155
|
+
diagnostics: Diagnostic[];
|
|
156
|
+
entryKind: BoundaryGraphEntryKind;
|
|
157
|
+
file: string;
|
|
158
|
+
input: BoundaryGraphInput;
|
|
159
|
+
clientBoundaries: BoundaryGraphClientBoundary[];
|
|
160
|
+
modules: Map<string, BoundaryGraphModule>;
|
|
161
|
+
serverActions: BoundaryGraphServerActionSite[];
|
|
162
|
+
trace: BoundaryGraphTraceEvent[];
|
|
163
|
+
visiting: Set<string>;
|
|
164
|
+
}): Promise<void> {
|
|
165
|
+
if (options.modules.has(options.file) || options.visiting.has(options.file)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
options.visiting.add(options.file);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const code = await options.input.readModule(options.file);
|
|
173
|
+
|
|
174
|
+
if (code === undefined) {
|
|
175
|
+
options.diagnostics.push({
|
|
176
|
+
code: "MR_BOUNDARY_GRAPH_MODULE_NOT_FOUND",
|
|
177
|
+
level: "warn",
|
|
178
|
+
message: `Boundary graph could not read module ${JSON.stringify(options.file)}.`,
|
|
179
|
+
});
|
|
180
|
+
options.modules.set(options.file, {
|
|
181
|
+
classification: "unknown",
|
|
182
|
+
exports: [],
|
|
183
|
+
file: options.file,
|
|
184
|
+
});
|
|
185
|
+
options.trace.push({
|
|
186
|
+
classification: "unknown",
|
|
187
|
+
file: options.file,
|
|
188
|
+
kind: "module",
|
|
189
|
+
reason: "unknown-module",
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const analysis = collectClientRouteModuleAnalysis({
|
|
195
|
+
code,
|
|
196
|
+
filename: options.file,
|
|
197
|
+
});
|
|
198
|
+
const serverOnly = isServerOnlyModule(analysis);
|
|
199
|
+
const explicitClient = analysis.hasUseClientDirective;
|
|
200
|
+
const exports = analysis.topLevelExportRenderInfo.map((info) => ({
|
|
201
|
+
classification: serverOnly
|
|
202
|
+
? "server-only"
|
|
203
|
+
: exportClassification({
|
|
204
|
+
explicitClient,
|
|
205
|
+
info,
|
|
206
|
+
entryKind: options.entryKind,
|
|
207
|
+
}),
|
|
208
|
+
name: info.name,
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
options.modules.set(options.file, {
|
|
212
|
+
classification: moduleClassification(exports.map((item) => item.classification)),
|
|
213
|
+
exports,
|
|
214
|
+
file: options.file,
|
|
215
|
+
});
|
|
216
|
+
const module = options.modules.get(options.file);
|
|
217
|
+
|
|
218
|
+
if (module !== undefined) {
|
|
219
|
+
options.trace.push({
|
|
220
|
+
classification: module.classification,
|
|
221
|
+
file: options.file,
|
|
222
|
+
kind: "module",
|
|
223
|
+
reason: "module-classification",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
options.trace.push(
|
|
227
|
+
...analysis.topLevelExportRenderInfo.map((info) => {
|
|
228
|
+
const classification = serverOnly
|
|
229
|
+
? "server-only"
|
|
230
|
+
: exportClassification({
|
|
231
|
+
explicitClient,
|
|
232
|
+
info,
|
|
233
|
+
entryKind: options.entryKind,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
classification,
|
|
238
|
+
exportName: info.name,
|
|
239
|
+
file: options.file,
|
|
240
|
+
kind: "export" as const,
|
|
241
|
+
reason: exportTraceReason({ analysis, classification, explicitClient, serverOnly }),
|
|
242
|
+
};
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const inferredServerActions = await inferServerActionSites({
|
|
247
|
+
analysis,
|
|
248
|
+
code,
|
|
249
|
+
file: options.file,
|
|
250
|
+
input: options.input,
|
|
251
|
+
});
|
|
252
|
+
options.serverActions.push(...inferredServerActions);
|
|
253
|
+
options.trace.push(
|
|
254
|
+
...inferredServerActions.map((action) => ({
|
|
255
|
+
classification: "server-action" as const,
|
|
256
|
+
exportName: action.exportName,
|
|
257
|
+
expression: action.expression,
|
|
258
|
+
file: action.file,
|
|
259
|
+
inferred: action.inferred,
|
|
260
|
+
kind: "server-action" as const,
|
|
261
|
+
moduleFile: action.moduleFile,
|
|
262
|
+
reason: "server-action-expression" as const,
|
|
263
|
+
})),
|
|
264
|
+
);
|
|
265
|
+
await analyzeStaticExports({
|
|
266
|
+
analysis,
|
|
267
|
+
diagnostics: options.diagnostics,
|
|
268
|
+
file: options.file,
|
|
269
|
+
input: options.input,
|
|
270
|
+
clientBoundaries: options.clientBoundaries,
|
|
271
|
+
modules: options.modules,
|
|
272
|
+
serverActions: options.serverActions,
|
|
273
|
+
trace: options.trace,
|
|
274
|
+
visiting: options.visiting,
|
|
275
|
+
});
|
|
276
|
+
await analyzeRenderedImports({
|
|
277
|
+
analysis,
|
|
278
|
+
diagnostics: options.diagnostics,
|
|
279
|
+
file: options.file,
|
|
280
|
+
input: options.input,
|
|
281
|
+
clientBoundaries: options.clientBoundaries,
|
|
282
|
+
modules: options.modules,
|
|
283
|
+
serverActions: options.serverActions,
|
|
284
|
+
trace: options.trace,
|
|
285
|
+
visiting: options.visiting,
|
|
286
|
+
});
|
|
287
|
+
} finally {
|
|
288
|
+
options.visiting.delete(options.file);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function exportTraceReason(options: {
|
|
293
|
+
analysis: ClientRouteModuleAnalysis;
|
|
294
|
+
classification: BoundaryClassification;
|
|
295
|
+
explicitClient: boolean;
|
|
296
|
+
serverOnly: boolean;
|
|
297
|
+
}): BoundaryGraphTraceReason {
|
|
298
|
+
if (options.serverOnly) {
|
|
299
|
+
return options.analysis.hasUseServerDirective ? "use-server-directive" : "node-builtin-import";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (options.explicitClient) {
|
|
303
|
+
return "use-client-directive";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return options.classification === "server-render"
|
|
307
|
+
? "server-render-export"
|
|
308
|
+
: "client-runtime-export";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isServerOnlyModule(analysis: ClientRouteModuleAnalysis): boolean {
|
|
312
|
+
return (
|
|
313
|
+
analysis.hasUseServerDirective ||
|
|
314
|
+
analysis.staticImports.some((reference) => nodeBuiltinPackages.has(reference.source))
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const nodeBuiltinPackages = new Set([
|
|
319
|
+
"assert",
|
|
320
|
+
"async_hooks",
|
|
321
|
+
"buffer",
|
|
322
|
+
"child_process",
|
|
323
|
+
"cluster",
|
|
324
|
+
"console",
|
|
325
|
+
"crypto",
|
|
326
|
+
"dgram",
|
|
327
|
+
"diagnostics_channel",
|
|
328
|
+
"dns",
|
|
329
|
+
"domain",
|
|
330
|
+
"events",
|
|
331
|
+
"fs",
|
|
332
|
+
"fs/promises",
|
|
333
|
+
"http",
|
|
334
|
+
"http2",
|
|
335
|
+
"https",
|
|
336
|
+
"module",
|
|
337
|
+
"net",
|
|
338
|
+
"os",
|
|
339
|
+
"path",
|
|
340
|
+
"perf_hooks",
|
|
341
|
+
"process",
|
|
342
|
+
"punycode",
|
|
343
|
+
"querystring",
|
|
344
|
+
"readline",
|
|
345
|
+
"repl",
|
|
346
|
+
"stream",
|
|
347
|
+
"stream/consumers",
|
|
348
|
+
"stream/promises",
|
|
349
|
+
"stream/web",
|
|
350
|
+
"string_decoder",
|
|
351
|
+
"timers",
|
|
352
|
+
"timers/promises",
|
|
353
|
+
"tls",
|
|
354
|
+
"trace_events",
|
|
355
|
+
"tty",
|
|
356
|
+
"url",
|
|
357
|
+
"util",
|
|
358
|
+
"v8",
|
|
359
|
+
"vm",
|
|
360
|
+
"wasi",
|
|
361
|
+
"worker_threads",
|
|
362
|
+
"zlib",
|
|
363
|
+
].flatMap((name) => [name, `node:${name}`]));
|
|
364
|
+
|
|
365
|
+
async function inferServerActionSites(options: {
|
|
366
|
+
analysis: ClientRouteModuleAnalysis;
|
|
367
|
+
code: string;
|
|
368
|
+
file: string;
|
|
369
|
+
input: BoundaryGraphInput;
|
|
370
|
+
}): Promise<BoundaryGraphServerActionSite[]> {
|
|
371
|
+
const actions: BoundaryGraphServerActionSite[] = [];
|
|
372
|
+
const references = collectFormActionExpressionReferences({
|
|
373
|
+
code: options.code,
|
|
374
|
+
filename: options.file,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
for (const reference of references) {
|
|
378
|
+
const target = await resolveServerActionExpression({
|
|
379
|
+
analysis: options.analysis,
|
|
380
|
+
code: options.code,
|
|
381
|
+
expression: reference.expression,
|
|
382
|
+
file: options.file,
|
|
383
|
+
input: options.input,
|
|
384
|
+
seen: new Set(),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
if (target !== undefined) {
|
|
388
|
+
actions.push({
|
|
389
|
+
end: reference.end,
|
|
390
|
+
exportName: target.exportName,
|
|
391
|
+
expression: reference.expression,
|
|
392
|
+
expressionEnd: reference.expressionEnd,
|
|
393
|
+
expressionStart: reference.expressionStart,
|
|
394
|
+
file: options.file,
|
|
395
|
+
inferred: target.inferred,
|
|
396
|
+
moduleFile: target.moduleFile,
|
|
397
|
+
start: reference.start,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return actions;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function resolveServerActionExpression(options: {
|
|
406
|
+
analysis: ClientRouteModuleAnalysis;
|
|
407
|
+
code: string;
|
|
408
|
+
expression: string;
|
|
409
|
+
file: string;
|
|
410
|
+
input: BoundaryGraphInput;
|
|
411
|
+
seen: Set<string>;
|
|
412
|
+
}): Promise<ResolvedServerActionTarget | undefined> {
|
|
413
|
+
const expression = options.expression.trim();
|
|
414
|
+
|
|
415
|
+
if (options.seen.has(expression)) {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
options.seen.add(expression);
|
|
420
|
+
|
|
421
|
+
if (identifierPattern.test(expression)) {
|
|
422
|
+
const imported = await importedActionReference({
|
|
423
|
+
analysis: options.analysis,
|
|
424
|
+
expression,
|
|
425
|
+
file: options.file,
|
|
426
|
+
input: options.input,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (imported !== undefined) {
|
|
430
|
+
return imported;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const alias = localAliasExpression(options.code, expression);
|
|
434
|
+
|
|
435
|
+
return alias === undefined
|
|
436
|
+
? undefined
|
|
437
|
+
: await resolveServerActionExpression({
|
|
438
|
+
...options,
|
|
439
|
+
expression: alias,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const member = memberExpressionPattern.exec(expression);
|
|
444
|
+
|
|
445
|
+
if (member !== null) {
|
|
446
|
+
const objectName = member.groups?.object;
|
|
447
|
+
const propertyName = member.groups?.property;
|
|
448
|
+
const namespace =
|
|
449
|
+
objectName === undefined || propertyName === undefined
|
|
450
|
+
? undefined
|
|
451
|
+
: await namespaceActionReference({
|
|
452
|
+
analysis: options.analysis,
|
|
453
|
+
exportName: propertyName,
|
|
454
|
+
file: options.file,
|
|
455
|
+
input: options.input,
|
|
456
|
+
localName: objectName,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (namespace !== undefined) {
|
|
460
|
+
return namespace;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const propertyExpression =
|
|
464
|
+
objectName === undefined || propertyName === undefined
|
|
465
|
+
? undefined
|
|
466
|
+
: objectLiteralPropertyExpression(options.code, objectName, propertyName);
|
|
467
|
+
|
|
468
|
+
return propertyExpression === undefined
|
|
469
|
+
? undefined
|
|
470
|
+
: await resolveServerActionExpression({
|
|
471
|
+
...options,
|
|
472
|
+
expression: propertyExpression,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function namespaceActionReference(options: {
|
|
480
|
+
analysis: ClientRouteModuleAnalysis;
|
|
481
|
+
exportName: string;
|
|
482
|
+
file: string;
|
|
483
|
+
input: BoundaryGraphInput;
|
|
484
|
+
localName: string;
|
|
485
|
+
}): Promise<ResolvedServerActionTarget | undefined> {
|
|
486
|
+
for (const staticImport of options.analysis.staticImports) {
|
|
487
|
+
const specifier = staticImport.specifiers.find(
|
|
488
|
+
(candidate) => candidate.kind === "namespace" && candidate.localName === options.localName,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (specifier === undefined) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const moduleFile = await options.input.resolveModule({
|
|
496
|
+
importer: options.file,
|
|
497
|
+
source: staticImport.source,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (moduleFile === undefined) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
exportName: options.exportName,
|
|
506
|
+
inferred: await isInferredServerAction(options.input, moduleFile),
|
|
507
|
+
moduleFile,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function importedActionReference(options: {
|
|
515
|
+
analysis: ClientRouteModuleAnalysis;
|
|
516
|
+
expression: string;
|
|
517
|
+
file: string;
|
|
518
|
+
input: BoundaryGraphInput;
|
|
519
|
+
}): Promise<ResolvedServerActionTarget | undefined> {
|
|
520
|
+
for (const staticImport of options.analysis.staticImports) {
|
|
521
|
+
const specifier = staticImport.specifiers.find(
|
|
522
|
+
(candidate) => candidate.localName === options.expression,
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
if (specifier === undefined || specifier.kind === "namespace") {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const moduleFile = await options.input.resolveModule({
|
|
530
|
+
importer: options.file,
|
|
531
|
+
source: staticImport.source,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (moduleFile === undefined) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
exportName: importedActionExportName(specifier),
|
|
540
|
+
inferred: await isInferredServerAction(options.input, moduleFile),
|
|
541
|
+
moduleFile,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function localAliasExpression(code: string, name: string): string | undefined {
|
|
549
|
+
const match = new RegExp(
|
|
550
|
+
String.raw`\b(?:const|let|var)\s+${escapeRegExp(name)}\s*=\s*(?<expression>[^;]+);`,
|
|
551
|
+
).exec(code);
|
|
552
|
+
const expression = match?.groups?.expression;
|
|
553
|
+
|
|
554
|
+
return expression === undefined ? undefined : expression.trim();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function objectLiteralPropertyExpression(
|
|
558
|
+
code: string,
|
|
559
|
+
objectName: string,
|
|
560
|
+
propertyName: string,
|
|
561
|
+
): string | undefined {
|
|
562
|
+
const object = new RegExp(
|
|
563
|
+
String.raw`\b(?:const|let|var)\s+${escapeRegExp(objectName)}\s*=\s*\{(?<body>[\s\S]*?)\}\s*;`,
|
|
564
|
+
).exec(code);
|
|
565
|
+
const body = object?.groups?.body;
|
|
566
|
+
|
|
567
|
+
if (body === undefined) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const property = escapeRegExp(propertyName);
|
|
572
|
+
const assignment = new RegExp(
|
|
573
|
+
String.raw`(?:^|,)\s*${property}\s*:\s*(?<expression>[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)\s*(?:,|$)`,
|
|
574
|
+
).exec(body);
|
|
575
|
+
|
|
576
|
+
if (assignment?.groups?.expression !== undefined) {
|
|
577
|
+
return assignment.groups.expression;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const shorthand = new RegExp(String.raw`(?:^|,)\s*(?<name>${property})\s*(?:,|$)`).exec(body);
|
|
581
|
+
|
|
582
|
+
return shorthand?.groups?.name;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function escapeRegExp(value: string): string {
|
|
586
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function importedActionExportName(specifier: StaticImportSpecifierReference): string {
|
|
590
|
+
return specifier.kind === "default" ? "default" : specifier.importedName;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function isInferredServerAction(
|
|
594
|
+
input: BoundaryGraphInput,
|
|
595
|
+
moduleFile: string,
|
|
596
|
+
): Promise<boolean> {
|
|
597
|
+
const code = await input.readModule(moduleFile);
|
|
598
|
+
|
|
599
|
+
if (code === undefined) {
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return !hasModuleDirective({ code, directive: "use server", filename: moduleFile });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
607
|
+
const memberExpressionPattern =
|
|
608
|
+
/^(?<object>[A-Za-z_$][\w$]*)\.(?<property>[A-Za-z_$][\w$]*)$/;
|
|
609
|
+
|
|
610
|
+
async function analyzeStaticExports(options: {
|
|
611
|
+
analysis: ClientRouteModuleAnalysis;
|
|
612
|
+
diagnostics: Diagnostic[];
|
|
613
|
+
file: string;
|
|
614
|
+
input: BoundaryGraphInput;
|
|
615
|
+
clientBoundaries: BoundaryGraphClientBoundary[];
|
|
616
|
+
modules: Map<string, BoundaryGraphModule>;
|
|
617
|
+
serverActions: BoundaryGraphServerActionSite[];
|
|
618
|
+
trace: BoundaryGraphTraceEvent[];
|
|
619
|
+
visiting: Set<string>;
|
|
620
|
+
}): Promise<void> {
|
|
621
|
+
for (const reference of options.analysis.staticExports) {
|
|
622
|
+
const resolved = await options.input.resolveModule({
|
|
623
|
+
importer: options.file,
|
|
624
|
+
source: reference.source,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
if (resolved === undefined) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
await analyzeModule({
|
|
632
|
+
diagnostics: options.diagnostics,
|
|
633
|
+
entryKind: "module",
|
|
634
|
+
file: resolved,
|
|
635
|
+
input: options.input,
|
|
636
|
+
clientBoundaries: options.clientBoundaries,
|
|
637
|
+
modules: options.modules,
|
|
638
|
+
serverActions: options.serverActions,
|
|
639
|
+
trace: options.trace,
|
|
640
|
+
visiting: options.visiting,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
propagateStaticExport({
|
|
644
|
+
file: options.file,
|
|
645
|
+
modules: options.modules,
|
|
646
|
+
reference,
|
|
647
|
+
resolved,
|
|
648
|
+
trace: options.trace,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function propagateStaticExport(options: {
|
|
654
|
+
file: string;
|
|
655
|
+
modules: Map<string, BoundaryGraphModule>;
|
|
656
|
+
reference: StaticExportReference;
|
|
657
|
+
resolved: string;
|
|
658
|
+
trace: BoundaryGraphTraceEvent[];
|
|
659
|
+
}): void {
|
|
660
|
+
const module = options.modules.get(options.file);
|
|
661
|
+
const exported = options.modules.get(options.resolved);
|
|
662
|
+
|
|
663
|
+
if (module === undefined || exported === undefined) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const propagated = propagatedStaticExports(options.reference, exported);
|
|
668
|
+
|
|
669
|
+
if (propagated.length === 0) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const exportsByName = new Map(module.exports.map((item) => [item.name, item]));
|
|
674
|
+
|
|
675
|
+
for (const item of propagated) {
|
|
676
|
+
exportsByName.set(item.export.name, item.export);
|
|
677
|
+
options.trace.push({
|
|
678
|
+
classification: item.export.classification,
|
|
679
|
+
exportName: item.export.name,
|
|
680
|
+
file: options.file,
|
|
681
|
+
kind: "export",
|
|
682
|
+
moduleFile: options.resolved,
|
|
683
|
+
reason: "static-export",
|
|
684
|
+
source: options.reference.source,
|
|
685
|
+
viaExportName: item.viaExportName,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const exports = Array.from(exportsByName.values()).sort((left, right) =>
|
|
690
|
+
left.name.localeCompare(right.name),
|
|
691
|
+
);
|
|
692
|
+
const classification = moduleClassification(exports.map((item) => item.classification));
|
|
693
|
+
options.modules.set(options.file, {
|
|
694
|
+
...module,
|
|
695
|
+
classification,
|
|
696
|
+
exports,
|
|
697
|
+
});
|
|
698
|
+
options.trace.push({
|
|
699
|
+
classification,
|
|
700
|
+
file: options.file,
|
|
701
|
+
kind: "module",
|
|
702
|
+
moduleFile: options.resolved,
|
|
703
|
+
reason: "static-export",
|
|
704
|
+
source: options.reference.source,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function propagatedStaticExports(
|
|
709
|
+
reference: StaticExportReference,
|
|
710
|
+
exported: BoundaryGraphModule,
|
|
711
|
+
): { export: BoundaryGraphExport; viaExportName: string }[] {
|
|
712
|
+
if (reference.exportAll) {
|
|
713
|
+
return exported.exports.map((item) => ({
|
|
714
|
+
export: item,
|
|
715
|
+
viaExportName: item.name,
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (reference.specifiers.length === 0) {
|
|
720
|
+
return exported.exports
|
|
721
|
+
.filter((item) => reference.exportedNames.includes(item.name))
|
|
722
|
+
.map((item) => ({
|
|
723
|
+
export: item,
|
|
724
|
+
viaExportName: item.name,
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return reference.specifiers.flatMap((specifier) => {
|
|
729
|
+
const item = exported.exports.find((candidate) => candidate.name === specifier.localName);
|
|
730
|
+
|
|
731
|
+
return item === undefined
|
|
732
|
+
? []
|
|
733
|
+
: [
|
|
734
|
+
{
|
|
735
|
+
export: { ...item, name: specifier.exportedName },
|
|
736
|
+
viaExportName: specifier.localName,
|
|
737
|
+
},
|
|
738
|
+
];
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async function analyzeRenderedImports(options: {
|
|
743
|
+
analysis: ClientRouteModuleAnalysis;
|
|
744
|
+
diagnostics: Diagnostic[];
|
|
745
|
+
file: string;
|
|
746
|
+
input: BoundaryGraphInput;
|
|
747
|
+
clientBoundaries: BoundaryGraphClientBoundary[];
|
|
748
|
+
modules: Map<string, BoundaryGraphModule>;
|
|
749
|
+
serverActions: BoundaryGraphServerActionSite[];
|
|
750
|
+
trace: BoundaryGraphTraceEvent[];
|
|
751
|
+
visiting: Set<string>;
|
|
752
|
+
}): Promise<void> {
|
|
753
|
+
const renderedRoots = new Set(
|
|
754
|
+
options.analysis.topLevelExportRenderInfo.flatMap((info) => [
|
|
755
|
+
...info.renderedComponentRoots,
|
|
756
|
+
...info.calledComponentRoots,
|
|
757
|
+
]),
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
for (const reference of options.analysis.staticImports) {
|
|
761
|
+
if (reference.sideEffect && isStyleModuleSpecifier(reference.source)) {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (!isRenderedImport(reference, renderedRoots)) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const resolved = await options.input.resolveModule({
|
|
770
|
+
importer: options.file,
|
|
771
|
+
source: reference.source,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
if (resolved === undefined) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const exportNames = renderedImportedExportNames(reference, renderedRoots);
|
|
779
|
+
await analyzeModule({
|
|
780
|
+
diagnostics: options.diagnostics,
|
|
781
|
+
entryKind: "module",
|
|
782
|
+
file: resolved,
|
|
783
|
+
input: options.input,
|
|
784
|
+
clientBoundaries: options.clientBoundaries,
|
|
785
|
+
modules: options.modules,
|
|
786
|
+
serverActions: options.serverActions,
|
|
787
|
+
trace: options.trace,
|
|
788
|
+
visiting: options.visiting,
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const module = options.modules.get(resolved);
|
|
792
|
+
|
|
793
|
+
if (module !== undefined && hasClientBoundaryExport(module, exportNames)) {
|
|
794
|
+
options.clientBoundaries.push({
|
|
795
|
+
...(exportNames === undefined ? {} : { exportNames }),
|
|
796
|
+
importerFile: options.file,
|
|
797
|
+
moduleFile: resolved,
|
|
798
|
+
source: reference.source,
|
|
799
|
+
});
|
|
800
|
+
options.trace.push({
|
|
801
|
+
classification: "client-boundary",
|
|
802
|
+
...(exportNames === undefined ? {} : { exportNames }),
|
|
803
|
+
file: options.file,
|
|
804
|
+
importerFile: options.file,
|
|
805
|
+
kind: "client-boundary",
|
|
806
|
+
moduleFile: resolved,
|
|
807
|
+
reason: "rendered-import",
|
|
808
|
+
source: reference.source,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function renderedImportedExportNames(
|
|
815
|
+
reference: ClientRouteStaticImportReference,
|
|
816
|
+
renderedRoots: ReadonlySet<string>,
|
|
817
|
+
): string[] | undefined {
|
|
818
|
+
if (reference.sideEffect) {
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const names: string[] = [];
|
|
823
|
+
|
|
824
|
+
for (const specifier of reference.specifiers) {
|
|
825
|
+
if (!renderedRoots.has(specifier.localName)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (specifier.kind === "namespace") {
|
|
830
|
+
return undefined;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
names.push(specifier.kind === "default" ? "default" : specifier.importedName);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return names;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function hasClientBoundaryExport(
|
|
840
|
+
module: BoundaryGraphModule,
|
|
841
|
+
exportNames: readonly string[] | undefined,
|
|
842
|
+
): boolean {
|
|
843
|
+
return module.exports.some(
|
|
844
|
+
(item) =>
|
|
845
|
+
item.classification === "client-boundary" &&
|
|
846
|
+
(exportNames === undefined || exportNames.includes(item.name)),
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function isRenderedImport(
|
|
851
|
+
reference: ClientRouteStaticImportReference,
|
|
852
|
+
renderedRoots: ReadonlySet<string>,
|
|
853
|
+
): boolean {
|
|
854
|
+
return (
|
|
855
|
+
reference.sideEffect ||
|
|
856
|
+
reference.localNames.some((localName) => renderedRoots.has(localName))
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function isStyleModuleSpecifier(source: string): boolean {
|
|
861
|
+
return /\.(?:css|less|sass|scss|styl|stylus)(?:[?#].*)?$/u.test(source);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function exportClassification(options: {
|
|
865
|
+
explicitClient: boolean;
|
|
866
|
+
info: TopLevelExportRenderInfo;
|
|
867
|
+
entryKind: BoundaryGraphEntryKind;
|
|
868
|
+
}): BoundaryClassification {
|
|
869
|
+
if (!options.explicitClient && !options.info.clientRuntime) {
|
|
870
|
+
return "server-render";
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return isRouteEntryKind(options.entryKind) ? "client-route" : "client-boundary";
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function isRouteEntryKind(kind: BoundaryGraphEntryKind): boolean {
|
|
877
|
+
return kind === "route-layout" || kind === "route-page" || kind === "route-template";
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function moduleClassification(
|
|
881
|
+
classifications: readonly BoundaryClassification[],
|
|
882
|
+
): BoundaryClassification {
|
|
883
|
+
if (classifications.includes("client-route")) {
|
|
884
|
+
return "client-route";
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (classifications.includes("client-boundary")) {
|
|
888
|
+
return "client-boundary";
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (classifications.includes("server-action")) {
|
|
892
|
+
return "server-action";
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (classifications.includes("unknown")) {
|
|
896
|
+
return "unknown";
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (classifications.includes("server-only")) {
|
|
900
|
+
return "server-only";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return "server-render";
|
|
904
|
+
}
|