@reckona/mreact-router 0.0.196 → 0.0.197
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 +3 -1
- package/dist/boundaries.d.ts +66 -0
- package/dist/boundaries.d.ts.map +1 -0
- package/dist/boundaries.js +168 -0
- package/dist/boundaries.js.map +1 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +18 -1
- package/dist/build.js.map +1 -1
- package/dist/cli-options.d.ts +1 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +30 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -1
- package/dist/client-route-inference.d.ts +1 -1
- package/dist/client-route-inference.d.ts.map +1 -1
- package/dist/client-route-inference.js.map +1 -1
- package/dist/client.d.ts +11 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +411 -22
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/boundaries.ts +285 -0
- package/src/build.ts +25 -1
- package/src/cli-options.ts +36 -1
- package/src/cli.ts +23 -0
- package/src/client-route-inference.ts +3 -0
- package/src/client.ts +647 -40
- package/src/index.ts +24 -5
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import { builtinModules } from "node:module";
|
|
4
|
-
import { basename, dirname, extname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { analyzeBoundaryGraph, collectClientRouteModuleAnalysis, formatDiagnostic, } from "@reckona/mreact-compiler";
|
|
6
6
|
import { collectClientRouteModuleAnalysisFromContext, createCompilerModuleContext, hasUnguardedBrowserGlobalReference, readTopLevelBooleanExport, readTopLevelBooleanExportFromContext, stripTypeScriptWithOxc, transformCompilerModuleContext, } from "@reckona/mreact-compiler/internal";
|
|
7
7
|
import { assetPath } from "./assets.js";
|
|
@@ -54,6 +54,16 @@ export async function isClientRouteModule(options) {
|
|
|
54
54
|
export async function inferClientRouteModule(options) {
|
|
55
55
|
const cache = options.cache ?? createClientRouteInferenceCache();
|
|
56
56
|
const sourceTransform = clientRouteSourceTransformForVitePlugins(options.vitePlugins);
|
|
57
|
+
const componentCollector = options.collectComponents
|
|
58
|
+
? {
|
|
59
|
+
clientReachable: new Set(),
|
|
60
|
+
components: new Map(),
|
|
61
|
+
localExportNamesByFile: new Map(),
|
|
62
|
+
pendingExportValidations: [],
|
|
63
|
+
reachable: new Set(),
|
|
64
|
+
staticExportEdges: [],
|
|
65
|
+
}
|
|
66
|
+
: undefined;
|
|
57
67
|
const code = await transformClientRouteSource({
|
|
58
68
|
code: options.code,
|
|
59
69
|
filename: options.filename,
|
|
@@ -63,9 +73,11 @@ export async function inferClientRouteModule(options) {
|
|
|
63
73
|
const routeInference = await inferClientRouteModuleSource({
|
|
64
74
|
cache,
|
|
65
75
|
code,
|
|
76
|
+
componentCollector,
|
|
66
77
|
filename: options.filename,
|
|
67
78
|
...(sourceTransform === undefined ? { moduleContext: options.moduleContext } : {}),
|
|
68
79
|
root: true,
|
|
80
|
+
routeEntry: true,
|
|
69
81
|
seen: new Set(),
|
|
70
82
|
sourceTransform,
|
|
71
83
|
});
|
|
@@ -78,21 +90,38 @@ export async function inferClientRouteModule(options) {
|
|
|
78
90
|
}))
|
|
79
91
|
: routeInference;
|
|
80
92
|
if (options.appDir === undefined) {
|
|
81
|
-
|
|
93
|
+
const exportDiagnostics = finalizeClientRouteComponentExportValidations(componentCollector);
|
|
94
|
+
return withClientRouteDiagnosticPath({
|
|
95
|
+
...mergedRouteInference,
|
|
96
|
+
...(componentCollector === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: {
|
|
99
|
+
components: normalizedClientRouteComponents(componentCollector, options.filename),
|
|
100
|
+
}),
|
|
101
|
+
diagnostics: [...mergedRouteInference.diagnostics, ...exportDiagnostics],
|
|
102
|
+
}, options.routePath);
|
|
82
103
|
}
|
|
83
104
|
const shellInferences = await inferClientRouteShellModules({
|
|
84
105
|
appDir: options.appDir,
|
|
85
106
|
cache,
|
|
107
|
+
componentCollector,
|
|
86
108
|
filename: options.filename,
|
|
87
109
|
sourceTransform,
|
|
88
110
|
});
|
|
111
|
+
const exportDiagnostics = finalizeClientRouteComponentExportValidations(componentCollector);
|
|
89
112
|
return withClientRouteDiagnosticPath({
|
|
90
113
|
client: mergedRouteInference.client || shellInferences.some((inference) => inference.client),
|
|
91
114
|
clientBoundaryImports: mergedRouteInference.clientBoundaryImports,
|
|
92
115
|
clientBoundaryFallbackImports: mergedRouteInference.clientBoundaryFallbackImports,
|
|
116
|
+
...(componentCollector === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: {
|
|
119
|
+
components: normalizedClientRouteComponents(componentCollector, options.filename),
|
|
120
|
+
}),
|
|
93
121
|
diagnostics: [
|
|
94
122
|
...mergedRouteInference.diagnostics,
|
|
95
123
|
...shellInferences.flatMap((inference) => inference.diagnostics),
|
|
124
|
+
...exportDiagnostics,
|
|
96
125
|
],
|
|
97
126
|
}, options.routePath);
|
|
98
127
|
}
|
|
@@ -161,6 +190,7 @@ export async function collectClientRouteReferences(options) {
|
|
|
161
190
|
filename: options.filename,
|
|
162
191
|
moduleContext: routeModuleContext,
|
|
163
192
|
root: true,
|
|
193
|
+
routeEntry: true,
|
|
164
194
|
seen: new Set(),
|
|
165
195
|
sourceTransform,
|
|
166
196
|
});
|
|
@@ -184,6 +214,7 @@ export async function collectClientRouteReferences(options) {
|
|
|
184
214
|
filename: sourceOptions.filename,
|
|
185
215
|
moduleContext,
|
|
186
216
|
root: true,
|
|
217
|
+
routeEntry: false,
|
|
187
218
|
seen: new Set(),
|
|
188
219
|
sourceTransform,
|
|
189
220
|
}));
|
|
@@ -227,6 +258,7 @@ export async function collectClientRouteReferences(options) {
|
|
|
227
258
|
filename: shell,
|
|
228
259
|
moduleContext,
|
|
229
260
|
root: true,
|
|
261
|
+
routeEntry: false,
|
|
230
262
|
seen: new Set(),
|
|
231
263
|
sourceTransform,
|
|
232
264
|
}),
|
|
@@ -343,8 +375,10 @@ async function inferClientRouteShellModules(options) {
|
|
|
343
375
|
return await inferClientRouteModuleSource({
|
|
344
376
|
cache: options.cache,
|
|
345
377
|
code,
|
|
378
|
+
componentCollector: options.componentCollector,
|
|
346
379
|
filename: shell,
|
|
347
380
|
root: true,
|
|
381
|
+
routeEntry: false,
|
|
348
382
|
seen: new Set(),
|
|
349
383
|
sourceTransform: options.sourceTransform,
|
|
350
384
|
});
|
|
@@ -366,6 +400,256 @@ export function isClientRouteSource(code) {
|
|
|
366
400
|
const analysis = collectClientRouteModuleAnalysis({ code });
|
|
367
401
|
return (analysis.hasUseClientDirective || (!analysis.hasUseServerDirective && analysis.clientRuntime));
|
|
368
402
|
}
|
|
403
|
+
function collectClientRouteComponentsForModule(collector, options) {
|
|
404
|
+
if (collector === undefined) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (options.root) {
|
|
408
|
+
markClientRouteComponentReachable(collector, options.filename, ["default"]);
|
|
409
|
+
}
|
|
410
|
+
const localExportNames = collector.localExportNamesByFile.get(options.filename) ?? new Set();
|
|
411
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
412
|
+
localExportNames.add(info.name);
|
|
413
|
+
}
|
|
414
|
+
collector.localExportNamesByFile.set(options.filename, localExportNames);
|
|
415
|
+
const serverOnly = options.analysis.hasUseServerDirective || hasServerOnlyImports(options.analysis);
|
|
416
|
+
const explicitClient = isExplicitClientRouteSource(options.analysis, options.filename);
|
|
417
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
418
|
+
const component = {
|
|
419
|
+
classification: serverOnly
|
|
420
|
+
? "server-only"
|
|
421
|
+
: explicitClient || info.clientRuntime
|
|
422
|
+
? options.routeEntry && info.name === "default"
|
|
423
|
+
? "client-route"
|
|
424
|
+
: "client-boundary"
|
|
425
|
+
: "server-render",
|
|
426
|
+
exportName: info.name,
|
|
427
|
+
file: options.filename,
|
|
428
|
+
origin: clientRouteComponentOrigin({
|
|
429
|
+
analysis: options.analysis,
|
|
430
|
+
filename: options.filename,
|
|
431
|
+
info,
|
|
432
|
+
serverOnly,
|
|
433
|
+
}),
|
|
434
|
+
};
|
|
435
|
+
collector.components.set(clientRouteComponentKey(component.file, component.exportName), component);
|
|
436
|
+
}
|
|
437
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
438
|
+
if (!isClientRouteComponentReachable(collector, options.filename, info.name)) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const renderedNames = new Set([
|
|
442
|
+
...(options.analysis.reachableExportRenderedComponentNames[info.name] ?? []),
|
|
443
|
+
...(options.analysis.reachableExportRenderedComponentRoots[info.name] ?? []),
|
|
444
|
+
]);
|
|
445
|
+
for (const rendered of options.analysis.topLevelExportRenderInfo) {
|
|
446
|
+
if (rendered.name !== "default" &&
|
|
447
|
+
(renderedNames.has(rendered.name) || renderedNames.has(rendered.localName ?? rendered.name))) {
|
|
448
|
+
markClientRouteComponentReachable(collector, options.filename, [rendered.name], {
|
|
449
|
+
clientExecution: clientRouteComponentRunsOnClient(collector, options.filename, info.name),
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function clientRouteComponentOrigin(options) {
|
|
456
|
+
if (options.analysis.hasUseServerDirective) {
|
|
457
|
+
return "use-server-directive";
|
|
458
|
+
}
|
|
459
|
+
if (options.serverOnly) {
|
|
460
|
+
return "server-only-import";
|
|
461
|
+
}
|
|
462
|
+
if (options.analysis.hasUseClientDirective) {
|
|
463
|
+
return "use-client-directive";
|
|
464
|
+
}
|
|
465
|
+
if (/\.compat(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
466
|
+
return "compat-filename";
|
|
467
|
+
}
|
|
468
|
+
if (/\.client(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
469
|
+
return "client-filename";
|
|
470
|
+
}
|
|
471
|
+
return options.info.clientRuntime ? "inferred-client-runtime" : "server-render";
|
|
472
|
+
}
|
|
473
|
+
function normalizedClientRouteComponents(collector, routeFile) {
|
|
474
|
+
return Array.from(collector.components.values())
|
|
475
|
+
.filter((component) => collector.reachable.has(clientRouteComponentKey(component.file, component.exportName)) ||
|
|
476
|
+
collector.reachable.has(clientRouteComponentKey(component.file, "*")))
|
|
477
|
+
.map((component) => component.classification === "server-render" &&
|
|
478
|
+
(collector.clientReachable.has(clientRouteComponentKey(component.file, component.exportName)) ||
|
|
479
|
+
collector.clientReachable.has(clientRouteComponentKey(component.file, "*")))
|
|
480
|
+
? { ...component, classification: "shared" }
|
|
481
|
+
: component)
|
|
482
|
+
.sort((left, right) => {
|
|
483
|
+
const leftRoute = left.file === routeFile && left.exportName === "default";
|
|
484
|
+
const rightRoute = right.file === routeFile && right.exportName === "default";
|
|
485
|
+
if (leftRoute !== rightRoute) {
|
|
486
|
+
return leftRoute ? -1 : 1;
|
|
487
|
+
}
|
|
488
|
+
return left.file === right.file
|
|
489
|
+
? left.exportName === right.exportName
|
|
490
|
+
? left.classification.localeCompare(right.classification)
|
|
491
|
+
: left.exportName.localeCompare(right.exportName)
|
|
492
|
+
: left.file.localeCompare(right.file);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function clientRouteComponentKey(file, exportName) {
|
|
496
|
+
return `${file}\0${exportName}`;
|
|
497
|
+
}
|
|
498
|
+
function markClientRouteComponentReachable(collector, file, exportNames, options = {}) {
|
|
499
|
+
if (collector === undefined) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (exportNames === undefined) {
|
|
503
|
+
collector.reachable.add(clientRouteComponentKey(file, "*"));
|
|
504
|
+
if (options.clientExecution === true) {
|
|
505
|
+
collector.clientReachable.add(clientRouteComponentKey(file, "*"));
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
for (const exportName of exportNames) {
|
|
510
|
+
const key = clientRouteComponentKey(file, exportName);
|
|
511
|
+
collector.reachable.add(key);
|
|
512
|
+
if (options.clientExecution === true) {
|
|
513
|
+
collector.clientReachable.add(key);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function clientRouteComponentRunsOnClient(collector, file, exportName) {
|
|
518
|
+
if (collector === undefined) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
const key = clientRouteComponentKey(file, exportName);
|
|
522
|
+
const component = collector.components.get(key);
|
|
523
|
+
return (component?.classification === "client-boundary" ||
|
|
524
|
+
component?.classification === "client-route" ||
|
|
525
|
+
component?.classification === "shared" ||
|
|
526
|
+
collector.clientReachable.has(key) ||
|
|
527
|
+
collector.clientReachable.has(clientRouteComponentKey(file, "*")));
|
|
528
|
+
}
|
|
529
|
+
function isClientRouteComponentReachable(collector, file, exportName) {
|
|
530
|
+
return (collector === undefined ||
|
|
531
|
+
collector.reachable.has(clientRouteComponentKey(file, exportName)) ||
|
|
532
|
+
collector.reachable.has(clientRouteComponentKey(file, "*")));
|
|
533
|
+
}
|
|
534
|
+
function propagateClientRouteComponentStaticExport(collector, file, resolved, reference) {
|
|
535
|
+
if (collector === undefined) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const wildcardReachable = collector.reachable.has(clientRouteComponentKey(file, "*"));
|
|
539
|
+
const wildcardClientExecution = clientRouteComponentRunsOnClient(collector, file, "*");
|
|
540
|
+
if (reference.exportAll) {
|
|
541
|
+
if (wildcardReachable) {
|
|
542
|
+
markClientRouteComponentReachable(collector, resolved, undefined, {
|
|
543
|
+
clientExecution: wildcardClientExecution,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
for (const key of collector.reachable) {
|
|
547
|
+
const [reachableFile, exportName] = key.split("\0");
|
|
548
|
+
if (reachableFile === file && exportName !== undefined && exportName !== "*") {
|
|
549
|
+
markClientRouteComponentReachable(collector, resolved, [exportName], {
|
|
550
|
+
clientExecution: clientRouteComponentRunsOnClient(collector, file, exportName),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
for (const specifier of reference.specifiers) {
|
|
557
|
+
if (wildcardReachable ||
|
|
558
|
+
collector.reachable.has(clientRouteComponentKey(file, specifier.exportedName))) {
|
|
559
|
+
markClientRouteComponentReachable(collector, resolved, [specifier.localName], {
|
|
560
|
+
clientExecution: wildcardClientExecution ||
|
|
561
|
+
clientRouteComponentRunsOnClient(collector, file, specifier.exportedName),
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (reference.specifiers.length === 0) {
|
|
566
|
+
for (const exportName of reference.exportedNames) {
|
|
567
|
+
if (wildcardReachable || collector.reachable.has(clientRouteComponentKey(file, exportName))) {
|
|
568
|
+
markClientRouteComponentReachable(collector, resolved, [exportName], {
|
|
569
|
+
clientExecution: wildcardClientExecution ||
|
|
570
|
+
clientRouteComponentRunsOnClient(collector, file, exportName),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function collectUnknownClientRouteComponents(collector, options) {
|
|
577
|
+
if (collector === undefined) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
for (const exportName of options.exportNames ?? ["*"]) {
|
|
581
|
+
const component = {
|
|
582
|
+
classification: "unknown",
|
|
583
|
+
exportName,
|
|
584
|
+
file: options.file,
|
|
585
|
+
origin: "unresolved-reference",
|
|
586
|
+
};
|
|
587
|
+
collector.components.set(clientRouteComponentKey(component.file, component.exportName), component);
|
|
588
|
+
markClientRouteComponentReachable(collector, component.file, [component.exportName]);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function collectClientRoutePendingExportValidation(collector, options) {
|
|
592
|
+
if (collector === undefined || options.exportNames.length === 0) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
collector.pendingExportValidations.push(options);
|
|
596
|
+
}
|
|
597
|
+
function collectClientRouteStaticExportEdge(collector, options) {
|
|
598
|
+
collector?.staticExportEdges.push(options);
|
|
599
|
+
}
|
|
600
|
+
function finalizeClientRouteComponentExportValidations(collector) {
|
|
601
|
+
if (collector === undefined) {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
const exportNamesByFile = new Map();
|
|
605
|
+
for (const [file, names] of collector.localExportNamesByFile) {
|
|
606
|
+
exportNamesByFile.set(file, new Set(names));
|
|
607
|
+
}
|
|
608
|
+
let changed = true;
|
|
609
|
+
while (changed) {
|
|
610
|
+
changed = false;
|
|
611
|
+
for (const edge of collector.staticExportEdges) {
|
|
612
|
+
const exportedNames = exportNamesByFile.get(edge.file) ?? new Set();
|
|
613
|
+
const sourceNames = exportNamesByFile.get(edge.resolved) ?? new Set();
|
|
614
|
+
const namesToAdd = edge.reference.exportAll
|
|
615
|
+
? new Set([...sourceNames].filter((exportName) => exportName !== "default"))
|
|
616
|
+
: new Set(edge.reference.specifiers
|
|
617
|
+
.filter((specifier) => sourceNames.has(specifier.localName))
|
|
618
|
+
.map((specifier) => specifier.exportedName));
|
|
619
|
+
for (const exportName of namesToAdd) {
|
|
620
|
+
if (!exportedNames.has(exportName)) {
|
|
621
|
+
exportedNames.add(exportName);
|
|
622
|
+
changed = true;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
exportNamesByFile.set(edge.file, exportedNames);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const diagnostics = [];
|
|
629
|
+
const seen = new Set();
|
|
630
|
+
for (const pending of collector.pendingExportValidations) {
|
|
631
|
+
const availableExportNames = exportNamesByFile.get(pending.file) ?? new Set();
|
|
632
|
+
const missingExportNames = pending.exportNames.filter((exportName) => !availableExportNames.has(exportName));
|
|
633
|
+
if (missingExportNames.length === 0) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const key = `${pending.importer}\0${pending.source}\0${missingExportNames.join("\0")}`;
|
|
637
|
+
if (seen.has(key)) {
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
seen.add(key);
|
|
641
|
+
collectUnknownClientRouteComponents(collector, {
|
|
642
|
+
exportNames: missingExportNames,
|
|
643
|
+
file: pending.file,
|
|
644
|
+
});
|
|
645
|
+
diagnostics.push(unresolvedClientRouteReferenceDiagnostic({
|
|
646
|
+
exportNames: missingExportNames,
|
|
647
|
+
filename: pending.importer,
|
|
648
|
+
source: pending.source,
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
return diagnostics;
|
|
652
|
+
}
|
|
369
653
|
function isExplicitClientRouteSource(analysis, filename) {
|
|
370
654
|
return analysis.hasUseClientDirective || isClientBoundaryFilename(filename);
|
|
371
655
|
}
|
|
@@ -384,22 +668,35 @@ function hasServerOnlyImports(analysis) {
|
|
|
384
668
|
async function inferClientRouteModuleSource(options) {
|
|
385
669
|
const analysis = await clientRouteModuleAnalysisForSource(options);
|
|
386
670
|
const usesNavigationLinkLocal = detectLinkComponentUsage(analysis);
|
|
387
|
-
|
|
388
|
-
|
|
671
|
+
collectClientRouteComponentsForModule(options.componentCollector, {
|
|
672
|
+
analysis,
|
|
673
|
+
filename: options.filename,
|
|
674
|
+
root: options.root,
|
|
675
|
+
routeEntry: options.routeEntry,
|
|
676
|
+
});
|
|
677
|
+
const forcedInference = isServerOnlyClientRouteSource(analysis)
|
|
678
|
+
? emptyClientRouteModuleInferenceResult({
|
|
679
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
389
680
|
navigationLinkExportNames: detectLinkComponentExportNames(analysis),
|
|
390
681
|
serverOnly: true,
|
|
391
682
|
serverOnlyClientRuntime: analysis.clientRuntime,
|
|
392
683
|
usesNavigationLink: usesNavigationLinkLocal,
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
684
|
+
})
|
|
685
|
+
: isExplicitClientRouteSource(analysis, options.filename)
|
|
686
|
+
? emptyClientRouteModuleInferenceResult({
|
|
687
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
688
|
+
client: true,
|
|
689
|
+
clientBoundaryModule: true,
|
|
690
|
+
})
|
|
691
|
+
: undefined;
|
|
692
|
+
if (forcedInference !== undefined && options.componentCollector === undefined) {
|
|
693
|
+
return forcedInference;
|
|
400
694
|
}
|
|
401
695
|
if (options.seen.has(options.filename)) {
|
|
402
|
-
return
|
|
696
|
+
return (forcedInference ??
|
|
697
|
+
emptyClientRouteModuleInferenceResult({
|
|
698
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
699
|
+
}));
|
|
403
700
|
}
|
|
404
701
|
options.seen.add(options.filename);
|
|
405
702
|
try {
|
|
@@ -409,6 +706,7 @@ async function inferClientRouteModuleSource(options) {
|
|
|
409
706
|
const nestedClientExportNames = new Set();
|
|
410
707
|
const clientReferenceSourceFiles = [];
|
|
411
708
|
const diagnostics = [];
|
|
709
|
+
const availableExportNames = new Set(analysis.topLevelExportRenderInfo.map((info) => info.name));
|
|
412
710
|
let boundaryGraphFallbackRequired = false;
|
|
413
711
|
let clientProxy = false;
|
|
414
712
|
let nestedClient = false;
|
|
@@ -422,8 +720,11 @@ async function inferClientRouteModuleSource(options) {
|
|
|
422
720
|
}
|
|
423
721
|
}
|
|
424
722
|
if (hasServerOnlyImports(analysis) &&
|
|
425
|
-
(implicitModuleClient || clientBoundaryExportNames.size > 0)
|
|
723
|
+
(implicitModuleClient || clientBoundaryExportNames.size > 0) &&
|
|
724
|
+
options.componentCollector === undefined) {
|
|
426
725
|
return emptyClientRouteModuleInferenceResult({
|
|
726
|
+
availableExportNames: Array.from(availableExportNames),
|
|
727
|
+
navigationLinkExportNames: detectLinkComponentExportNames(analysis),
|
|
427
728
|
serverOnly: true,
|
|
428
729
|
serverOnlyClientRuntime: true,
|
|
429
730
|
});
|
|
@@ -450,15 +751,46 @@ async function inferClientRouteModuleSource(options) {
|
|
|
450
751
|
if (reference.sideEffect && isStyleModuleSpecifier(reference.source)) {
|
|
451
752
|
continue;
|
|
452
753
|
}
|
|
754
|
+
const renderingExportNames = rendered ? renderedLocalExportNames(reference, exportInfo) : [];
|
|
755
|
+
const renderedImportedNames = rendered
|
|
756
|
+
? renderedImportedExportNames(reference, renderedComponentRoots)
|
|
757
|
+
: [];
|
|
758
|
+
const renderedFromReachableExport = rendered &&
|
|
759
|
+
renderingExportNames.some((exportName) => isClientRouteComponentReachable(options.componentCollector, options.filename, exportName));
|
|
453
760
|
const resolved = await resolveAppLocalModule({
|
|
454
761
|
allowExplicitNonSource: options.sourceTransform !== undefined,
|
|
455
762
|
cache: options.cache,
|
|
456
763
|
importer: options.filename,
|
|
457
764
|
specifier: reference.source,
|
|
765
|
+
tolerateUnresolved: options.componentCollector !== undefined,
|
|
458
766
|
});
|
|
459
767
|
if (resolved === undefined) {
|
|
768
|
+
if (options.componentCollector !== undefined &&
|
|
769
|
+
renderedFromReachableExport &&
|
|
770
|
+
reference.source.startsWith(".")) {
|
|
771
|
+
collectUnknownClientRouteComponents(options.componentCollector, {
|
|
772
|
+
exportNames: renderedImportedNames,
|
|
773
|
+
file: resolve(dirname(options.filename), reference.source),
|
|
774
|
+
});
|
|
775
|
+
diagnostics.push(unresolvedClientRouteReferenceDiagnostic({
|
|
776
|
+
filename: options.filename,
|
|
777
|
+
source: reference.source,
|
|
778
|
+
}));
|
|
779
|
+
}
|
|
460
780
|
continue;
|
|
461
781
|
}
|
|
782
|
+
if (renderedFromReachableExport) {
|
|
783
|
+
const clientExecution = renderingExportNames.some((exportName) => clientRouteComponentRunsOnClient(options.componentCollector, options.filename, exportName));
|
|
784
|
+
markClientRouteComponentReachable(options.componentCollector, resolved, renderedImportedNames, { clientExecution });
|
|
785
|
+
if (renderedImportedNames !== undefined) {
|
|
786
|
+
collectClientRoutePendingExportValidation(options.componentCollector, {
|
|
787
|
+
exportNames: renderedImportedNames,
|
|
788
|
+
file: resolved,
|
|
789
|
+
importer: options.filename,
|
|
790
|
+
source: reference.source,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
462
794
|
const source = await readClientRouteSource({
|
|
463
795
|
cache: options.cache,
|
|
464
796
|
filename: resolved,
|
|
@@ -467,6 +799,7 @@ async function inferClientRouteModuleSource(options) {
|
|
|
467
799
|
const imported = await inferClientRouteModuleSource({
|
|
468
800
|
cache: options.cache,
|
|
469
801
|
code: source,
|
|
802
|
+
componentCollector: options.componentCollector,
|
|
470
803
|
filename: resolved,
|
|
471
804
|
moduleContext: await compilerModuleContextForSource({
|
|
472
805
|
cache: options.cache,
|
|
@@ -474,6 +807,7 @@ async function inferClientRouteModuleSource(options) {
|
|
|
474
807
|
filename: resolved,
|
|
475
808
|
}),
|
|
476
809
|
root: false,
|
|
810
|
+
routeEntry: false,
|
|
477
811
|
seen: options.seen,
|
|
478
812
|
sourceTransform: options.sourceTransform,
|
|
479
813
|
});
|
|
@@ -505,8 +839,8 @@ async function inferClientRouteModuleSource(options) {
|
|
|
505
839
|
continue;
|
|
506
840
|
}
|
|
507
841
|
if (rendered) {
|
|
508
|
-
const importedExportNames =
|
|
509
|
-
const renderedExportNames =
|
|
842
|
+
const importedExportNames = renderedImportedNames;
|
|
843
|
+
const renderedExportNames = renderingExportNames;
|
|
510
844
|
const importedBoundary = imported.clientBoundaryModule ||
|
|
511
845
|
matchesInferredExportNames(importedExportNames, imported.clientBoundaryExportNames);
|
|
512
846
|
const importedNested = matchesInferredExportNames(importedExportNames, imported.nestedClientExportNames);
|
|
@@ -553,10 +887,23 @@ async function inferClientRouteModuleSource(options) {
|
|
|
553
887
|
cache: options.cache,
|
|
554
888
|
importer: options.filename,
|
|
555
889
|
specifier: reference.source,
|
|
890
|
+
tolerateUnresolved: options.componentCollector !== undefined,
|
|
556
891
|
});
|
|
557
892
|
if (resolved === undefined) {
|
|
893
|
+
if (options.componentCollector !== undefined) {
|
|
894
|
+
diagnostics.push(unresolvedClientRouteReferenceDiagnostic({
|
|
895
|
+
filename: options.filename,
|
|
896
|
+
source: reference.source,
|
|
897
|
+
}));
|
|
898
|
+
}
|
|
558
899
|
continue;
|
|
559
900
|
}
|
|
901
|
+
propagateClientRouteComponentStaticExport(options.componentCollector, options.filename, resolved, reference);
|
|
902
|
+
collectClientRouteStaticExportEdge(options.componentCollector, {
|
|
903
|
+
file: options.filename,
|
|
904
|
+
reference,
|
|
905
|
+
resolved,
|
|
906
|
+
});
|
|
560
907
|
const source = await readClientRouteSource({
|
|
561
908
|
cache: options.cache,
|
|
562
909
|
filename: resolved,
|
|
@@ -565,6 +912,7 @@ async function inferClientRouteModuleSource(options) {
|
|
|
565
912
|
const exported = await inferClientRouteModuleSource({
|
|
566
913
|
cache: options.cache,
|
|
567
914
|
code: source,
|
|
915
|
+
componentCollector: options.componentCollector,
|
|
568
916
|
filename: resolved,
|
|
569
917
|
moduleContext: await compilerModuleContextForSource({
|
|
570
918
|
cache: options.cache,
|
|
@@ -572,10 +920,24 @@ async function inferClientRouteModuleSource(options) {
|
|
|
572
920
|
filename: resolved,
|
|
573
921
|
}),
|
|
574
922
|
root: false,
|
|
923
|
+
routeEntry: false,
|
|
575
924
|
seen: options.seen,
|
|
576
925
|
sourceTransform: options.sourceTransform,
|
|
577
926
|
});
|
|
578
927
|
diagnostics.push(...exported.diagnostics);
|
|
928
|
+
if (reference.exportAll) {
|
|
929
|
+
for (const exportName of exported.availableExportNames) {
|
|
930
|
+
availableExportNames.add(exportName);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
for (const specifier of reference.specifiers) {
|
|
935
|
+
availableExportNames.add(specifier.exportedName);
|
|
936
|
+
}
|
|
937
|
+
for (const exportName of reference.exportedNames) {
|
|
938
|
+
availableExportNames.add(exportName);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
579
941
|
// A re-export renders nothing itself, so it does not set the module's
|
|
580
942
|
// own `usesNavigationLink`; it only forwards per-export `Link` usage so
|
|
581
943
|
// an importer that renders this name can decide precisely. Map the
|
|
@@ -620,7 +982,8 @@ async function inferClientRouteModuleSource(options) {
|
|
|
620
982
|
}
|
|
621
983
|
}
|
|
622
984
|
}
|
|
623
|
-
|
|
985
|
+
const inferred = {
|
|
986
|
+
availableExportNames: Array.from(availableExportNames),
|
|
624
987
|
boundaryGraphFallbackCandidate: analysis.staticExports.length > 0 || boundaryGraphFallbackRequired,
|
|
625
988
|
boundaryGraphFallbackRequired,
|
|
626
989
|
client: clientBoundaryImports.length > 0 ||
|
|
@@ -640,6 +1003,15 @@ async function inferClientRouteModuleSource(options) {
|
|
|
640
1003
|
serverOnlyClientRuntime: false,
|
|
641
1004
|
usesNavigationLink,
|
|
642
1005
|
};
|
|
1006
|
+
return forcedInference === undefined
|
|
1007
|
+
? inferred
|
|
1008
|
+
: {
|
|
1009
|
+
...forcedInference,
|
|
1010
|
+
availableExportNames: inferred.availableExportNames,
|
|
1011
|
+
diagnostics,
|
|
1012
|
+
navigationLinkExportNames: inferred.navigationLinkExportNames,
|
|
1013
|
+
usesNavigationLink: inferred.usesNavigationLink,
|
|
1014
|
+
};
|
|
643
1015
|
}
|
|
644
1016
|
finally {
|
|
645
1017
|
options.seen.delete(options.filename);
|
|
@@ -647,6 +1019,7 @@ async function inferClientRouteModuleSource(options) {
|
|
|
647
1019
|
}
|
|
648
1020
|
function emptyClientRouteModuleInferenceResult(overrides = {}) {
|
|
649
1021
|
return {
|
|
1022
|
+
availableExportNames: [],
|
|
650
1023
|
boundaryGraphFallbackCandidate: false,
|
|
651
1024
|
boundaryGraphFallbackRequired: false,
|
|
652
1025
|
client: false,
|
|
@@ -883,7 +1256,7 @@ function matchingBraceEnd(source, openBraceIndex) {
|
|
|
883
1256
|
}
|
|
884
1257
|
continue;
|
|
885
1258
|
}
|
|
886
|
-
if (char === "
|
|
1259
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
887
1260
|
quote = char;
|
|
888
1261
|
continue;
|
|
889
1262
|
}
|
|
@@ -940,8 +1313,7 @@ function addDestructuredCallbackNames(names, destructured) {
|
|
|
940
1313
|
const property = destructuredBindingName(rawProperty);
|
|
941
1314
|
const alias = destructuredBindingName(rawAlias);
|
|
942
1315
|
const name = alias ?? property;
|
|
943
|
-
if (name !== undefined &&
|
|
944
|
-
(isCallbackPropName(property) || isCallbackPropName(name))) {
|
|
1316
|
+
if (name !== undefined && (isCallbackPropName(property) || isCallbackPropName(name))) {
|
|
945
1317
|
names.add(name);
|
|
946
1318
|
}
|
|
947
1319
|
}
|
|
@@ -1001,6 +1373,20 @@ function serverOnlyClientImportReferenceDiagnostic(options) {
|
|
|
1001
1373
|
source: options.reference.source,
|
|
1002
1374
|
};
|
|
1003
1375
|
}
|
|
1376
|
+
function unresolvedClientRouteReferenceDiagnostic(options) {
|
|
1377
|
+
const exportSuffix = options.exportNames === undefined || options.exportNames.length === 0
|
|
1378
|
+
? ""
|
|
1379
|
+
: ` (${options.exportNames.map((name) => JSON.stringify(name)).join(", ")})`;
|
|
1380
|
+
return {
|
|
1381
|
+
code: "MR_CLIENT_BOUNDARY_INFERENCE_UNRESOLVED_REFERENCE",
|
|
1382
|
+
filename: options.filename,
|
|
1383
|
+
level: "warn",
|
|
1384
|
+
localNames: [...(options.exportNames ?? [])],
|
|
1385
|
+
message: `${options.filename}: rendered component reference ${JSON.stringify(options.source)}${exportSuffix} ` +
|
|
1386
|
+
"could not be resolved to an exported component.",
|
|
1387
|
+
source: options.source,
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1004
1390
|
function functionCallInteractiveImportDiagnostic(options) {
|
|
1005
1391
|
const localNames = options.reference.localNames.filter(startsUppercase);
|
|
1006
1392
|
const component = localNames[0] ?? options.reference.localNames[0] ?? options.reference.source;
|
|
@@ -1066,7 +1452,7 @@ async function resolveAppLocalModule(options) {
|
|
|
1066
1452
|
if (!options.specifier.startsWith(".")) {
|
|
1067
1453
|
return undefined;
|
|
1068
1454
|
}
|
|
1069
|
-
const cacheKey = `${options.importer}\0${options.specifier}\0${options.allowExplicitNonSource === true ? "explicit" : "source"}`;
|
|
1455
|
+
const cacheKey = `${options.importer}\0${options.specifier}\0${options.allowExplicitNonSource === true ? "explicit" : "source"}\0${options.tolerateUnresolved === true ? "tolerant" : "strict"}`;
|
|
1070
1456
|
const cached = options.cache.resolvedByImport.get(cacheKey);
|
|
1071
1457
|
if (cached !== undefined) {
|
|
1072
1458
|
return cached;
|
|
@@ -1075,6 +1461,7 @@ async function resolveAppLocalModule(options) {
|
|
|
1075
1461
|
allowExplicitNonSource: options.allowExplicitNonSource === true,
|
|
1076
1462
|
importer: options.importer,
|
|
1077
1463
|
specifier: options.specifier,
|
|
1464
|
+
tolerateUnresolved: options.tolerateUnresolved === true,
|
|
1078
1465
|
});
|
|
1079
1466
|
options.cache.resolvedByImport.set(cacheKey, resolved);
|
|
1080
1467
|
return resolved;
|
|
@@ -1094,6 +1481,9 @@ async function resolveAppLocalModuleUncached(options) {
|
|
|
1094
1481
|
return candidate;
|
|
1095
1482
|
}
|
|
1096
1483
|
}
|
|
1484
|
+
if (options.tolerateUnresolved) {
|
|
1485
|
+
return undefined;
|
|
1486
|
+
}
|
|
1097
1487
|
throw new Error(`${importer}: could not resolve app-local import ${JSON.stringify(specifier)}.`);
|
|
1098
1488
|
}
|
|
1099
1489
|
async function isFile(path) {
|
|
@@ -1440,8 +1830,7 @@ export async function buildClientRouteEntrySource(options) {
|
|
|
1440
1830
|
const routeId = routeIdForPath(options.routePath);
|
|
1441
1831
|
const routeUsesCells = detectRouteCellStateHint(compiled.code);
|
|
1442
1832
|
const routeUsesReactiveEffect = detectRouteReactiveEffectHint(compiled.code);
|
|
1443
|
-
const routeUsesDomRefs = compiled.metadata.imports.some((entry) => entry.source === "@reckona/mreact-reactive-dom" &&
|
|
1444
|
-
entry.specifiers.includes("bindDomRef"));
|
|
1833
|
+
const routeUsesDomRefs = compiled.metadata.imports.some((entry) => entry.source === "@reckona/mreact-reactive-dom" && entry.specifiers.includes("bindDomRef"));
|
|
1445
1834
|
const routeUsesCleanupScope = routeUsesCells || routeUsesReactiveEffect || routeUsesDomRefs;
|
|
1446
1835
|
const routeExplicitlyRequiresHydration = isExplicitClientRouteSource(routeSourceAnalysis, options.filename);
|
|
1447
1836
|
const routeHasEventBindings = (compiled.metadata.eventHydrationManifest?.events.length ?? 0) > 0;
|