@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/src/client.ts
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 {
|
|
6
6
|
analyzeBoundaryGraph,
|
|
7
7
|
collectClientRouteModuleAnalysis,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type BoundaryGraphResult,
|
|
11
11
|
type ClientRouteModuleAnalysis,
|
|
12
12
|
type ClientRouteStaticImportReference,
|
|
13
|
+
type StaticExportReference,
|
|
13
14
|
type StaticImportReference,
|
|
14
15
|
type TopLevelExportRenderInfo,
|
|
15
16
|
} from "@reckona/mreact-compiler";
|
|
@@ -118,10 +119,37 @@ export interface ClientRouteInferenceResult {
|
|
|
118
119
|
client: boolean;
|
|
119
120
|
clientBoundaryImports: string[];
|
|
120
121
|
clientBoundaryFallbackImports: string[];
|
|
122
|
+
components?: ClientRouteComponent[] | undefined;
|
|
121
123
|
diagnostics: ClientRouteInferenceDiagnostic[];
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
export type ClientRouteComponentClassification =
|
|
127
|
+
| "client-boundary"
|
|
128
|
+
| "client-route"
|
|
129
|
+
| "server-only"
|
|
130
|
+
| "server-render"
|
|
131
|
+
| "shared"
|
|
132
|
+
| "unknown";
|
|
133
|
+
|
|
134
|
+
export type ClientRouteComponentOrigin =
|
|
135
|
+
| "client-filename"
|
|
136
|
+
| "compat-filename"
|
|
137
|
+
| "inferred-client-runtime"
|
|
138
|
+
| "server-only-import"
|
|
139
|
+
| "server-render"
|
|
140
|
+
| "unresolved-reference"
|
|
141
|
+
| "use-client-directive"
|
|
142
|
+
| "use-server-directive";
|
|
143
|
+
|
|
144
|
+
export interface ClientRouteComponent {
|
|
145
|
+
classification: ClientRouteComponentClassification;
|
|
146
|
+
exportName: string;
|
|
147
|
+
file: string;
|
|
148
|
+
origin: ClientRouteComponentOrigin;
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
interface ClientRouteModuleInferenceResult extends ClientRouteInferenceResult {
|
|
152
|
+
availableExportNames: string[];
|
|
125
153
|
boundaryGraphFallbackCandidate: boolean;
|
|
126
154
|
boundaryGraphFallbackRequired: boolean;
|
|
127
155
|
clientBoundaryExportNames: string[];
|
|
@@ -134,6 +162,28 @@ interface ClientRouteModuleInferenceResult extends ClientRouteInferenceResult {
|
|
|
134
162
|
usesNavigationLink: boolean;
|
|
135
163
|
}
|
|
136
164
|
|
|
165
|
+
interface ClientRouteComponentCollector {
|
|
166
|
+
clientReachable: Set<string>;
|
|
167
|
+
components: Map<string, ClientRouteComponent>;
|
|
168
|
+
localExportNamesByFile: Map<string, Set<string>>;
|
|
169
|
+
pendingExportValidations: ClientRoutePendingExportValidation[];
|
|
170
|
+
reachable: Set<string>;
|
|
171
|
+
staticExportEdges: ClientRouteStaticExportEdge[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface ClientRoutePendingExportValidation {
|
|
175
|
+
exportNames: string[];
|
|
176
|
+
file: string;
|
|
177
|
+
importer: string;
|
|
178
|
+
source: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface ClientRouteStaticExportEdge {
|
|
182
|
+
file: string;
|
|
183
|
+
reference: StaticExportReference;
|
|
184
|
+
resolved: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
137
187
|
export interface ClientReferenceImport {
|
|
138
188
|
exportName: string;
|
|
139
189
|
importSource: string;
|
|
@@ -148,10 +198,11 @@ export interface ClientRouteReferenceResult extends ClientRouteInferenceResult {
|
|
|
148
198
|
|
|
149
199
|
export interface ClientRouteInferenceDiagnostic {
|
|
150
200
|
code:
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
201
|
+
| "MR_CLIENT_BOUNDARY_INFERENCE_SERVER_ONLY_REFERENCE"
|
|
202
|
+
| "MR_CLIENT_BOUNDARY_INFERENCE_FUNCTION_CALL_INTERACTIVE"
|
|
203
|
+
| "MR_CLIENT_BOUNDARY_INFERENCE_UNRESOLVED_REFERENCE"
|
|
204
|
+
| "MR_CLIENT_BOUNDARY_INFERENCE_UNSUPPORTED_REFERENCE"
|
|
205
|
+
| "MR_NAVIGATION_RUNTIME_LINK_DISABLED";
|
|
155
206
|
filename: string;
|
|
156
207
|
level: "warn";
|
|
157
208
|
localNames: string[];
|
|
@@ -218,6 +269,7 @@ export async function inferClientRouteModule(options: {
|
|
|
218
269
|
appDir?: string | undefined;
|
|
219
270
|
cache?: ClientRouteInferenceCache | undefined;
|
|
220
271
|
code: string;
|
|
272
|
+
collectComponents?: boolean | undefined;
|
|
221
273
|
filename: string;
|
|
222
274
|
moduleContext?: CompilerModuleContext | undefined;
|
|
223
275
|
routePath?: string | undefined;
|
|
@@ -225,6 +277,16 @@ export async function inferClientRouteModule(options: {
|
|
|
225
277
|
}): Promise<ClientRouteInferenceResult> {
|
|
226
278
|
const cache = options.cache ?? createClientRouteInferenceCache();
|
|
227
279
|
const sourceTransform = clientRouteSourceTransformForVitePlugins(options.vitePlugins);
|
|
280
|
+
const componentCollector: ClientRouteComponentCollector | undefined = options.collectComponents
|
|
281
|
+
? {
|
|
282
|
+
clientReachable: new Set(),
|
|
283
|
+
components: new Map(),
|
|
284
|
+
localExportNamesByFile: new Map(),
|
|
285
|
+
pendingExportValidations: [],
|
|
286
|
+
reachable: new Set(),
|
|
287
|
+
staticExportEdges: [],
|
|
288
|
+
}
|
|
289
|
+
: undefined;
|
|
228
290
|
const code = await transformClientRouteSource({
|
|
229
291
|
code: options.code,
|
|
230
292
|
filename: options.filename,
|
|
@@ -235,9 +297,11 @@ export async function inferClientRouteModule(options: {
|
|
|
235
297
|
const routeInference = await inferClientRouteModuleSource({
|
|
236
298
|
cache,
|
|
237
299
|
code,
|
|
300
|
+
componentCollector,
|
|
238
301
|
filename: options.filename,
|
|
239
302
|
...(sourceTransform === undefined ? { moduleContext: options.moduleContext } : {}),
|
|
240
303
|
root: true,
|
|
304
|
+
routeEntry: true,
|
|
241
305
|
seen: new Set(),
|
|
242
306
|
sourceTransform,
|
|
243
307
|
});
|
|
@@ -254,15 +318,29 @@ export async function inferClientRouteModule(options: {
|
|
|
254
318
|
: routeInference;
|
|
255
319
|
|
|
256
320
|
if (options.appDir === undefined) {
|
|
257
|
-
|
|
321
|
+
const exportDiagnostics = finalizeClientRouteComponentExportValidations(componentCollector);
|
|
322
|
+
return withClientRouteDiagnosticPath(
|
|
323
|
+
{
|
|
324
|
+
...mergedRouteInference,
|
|
325
|
+
...(componentCollector === undefined
|
|
326
|
+
? {}
|
|
327
|
+
: {
|
|
328
|
+
components: normalizedClientRouteComponents(componentCollector, options.filename),
|
|
329
|
+
}),
|
|
330
|
+
diagnostics: [...mergedRouteInference.diagnostics, ...exportDiagnostics],
|
|
331
|
+
},
|
|
332
|
+
options.routePath,
|
|
333
|
+
);
|
|
258
334
|
}
|
|
259
335
|
|
|
260
336
|
const shellInferences = await inferClientRouteShellModules({
|
|
261
337
|
appDir: options.appDir,
|
|
262
338
|
cache,
|
|
339
|
+
componentCollector,
|
|
263
340
|
filename: options.filename,
|
|
264
341
|
sourceTransform,
|
|
265
342
|
});
|
|
343
|
+
const exportDiagnostics = finalizeClientRouteComponentExportValidations(componentCollector);
|
|
266
344
|
|
|
267
345
|
return withClientRouteDiagnosticPath(
|
|
268
346
|
{
|
|
@@ -270,9 +348,15 @@ export async function inferClientRouteModule(options: {
|
|
|
270
348
|
mergedRouteInference.client || shellInferences.some((inference) => inference.client),
|
|
271
349
|
clientBoundaryImports: mergedRouteInference.clientBoundaryImports,
|
|
272
350
|
clientBoundaryFallbackImports: mergedRouteInference.clientBoundaryFallbackImports,
|
|
351
|
+
...(componentCollector === undefined
|
|
352
|
+
? {}
|
|
353
|
+
: {
|
|
354
|
+
components: normalizedClientRouteComponents(componentCollector, options.filename),
|
|
355
|
+
}),
|
|
273
356
|
diagnostics: [
|
|
274
357
|
...mergedRouteInference.diagnostics,
|
|
275
358
|
...shellInferences.flatMap((inference) => inference.diagnostics),
|
|
359
|
+
...exportDiagnostics,
|
|
276
360
|
],
|
|
277
361
|
},
|
|
278
362
|
options.routePath,
|
|
@@ -377,6 +461,7 @@ export async function collectClientRouteReferences(options: {
|
|
|
377
461
|
filename: options.filename,
|
|
378
462
|
moduleContext: routeModuleContext,
|
|
379
463
|
root: true,
|
|
464
|
+
routeEntry: true,
|
|
380
465
|
seen: new Set(),
|
|
381
466
|
sourceTransform,
|
|
382
467
|
});
|
|
@@ -413,6 +498,7 @@ export async function collectClientRouteReferences(options: {
|
|
|
413
498
|
filename: sourceOptions.filename,
|
|
414
499
|
moduleContext,
|
|
415
500
|
root: true,
|
|
501
|
+
routeEntry: false,
|
|
416
502
|
seen: new Set(),
|
|
417
503
|
sourceTransform,
|
|
418
504
|
}));
|
|
@@ -459,6 +545,7 @@ export async function collectClientRouteReferences(options: {
|
|
|
459
545
|
filename: shell,
|
|
460
546
|
moduleContext,
|
|
461
547
|
root: true,
|
|
548
|
+
routeEntry: false,
|
|
462
549
|
seen: new Set(),
|
|
463
550
|
sourceTransform,
|
|
464
551
|
}),
|
|
@@ -604,6 +691,7 @@ export function navigationRuntimeLinkDisabledDiagnostic(options: {
|
|
|
604
691
|
async function inferClientRouteShellModules(options: {
|
|
605
692
|
appDir: string;
|
|
606
693
|
cache: ClientRouteInferenceCache;
|
|
694
|
+
componentCollector?: ClientRouteComponentCollector | undefined;
|
|
607
695
|
filename: string;
|
|
608
696
|
sourceTransform?: ClientRouteSourceTransform | undefined;
|
|
609
697
|
}): Promise<ClientRouteInferenceResult[]> {
|
|
@@ -620,8 +708,10 @@ async function inferClientRouteShellModules(options: {
|
|
|
620
708
|
return await inferClientRouteModuleSource({
|
|
621
709
|
cache: options.cache,
|
|
622
710
|
code,
|
|
711
|
+
componentCollector: options.componentCollector,
|
|
623
712
|
filename: shell,
|
|
624
713
|
root: true,
|
|
714
|
+
routeEntry: false,
|
|
625
715
|
seen: new Set(),
|
|
626
716
|
sourceTransform: options.sourceTransform,
|
|
627
717
|
});
|
|
@@ -662,6 +752,380 @@ export function isClientRouteSource(code: string): boolean {
|
|
|
662
752
|
);
|
|
663
753
|
}
|
|
664
754
|
|
|
755
|
+
function collectClientRouteComponentsForModule(
|
|
756
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
757
|
+
options: {
|
|
758
|
+
analysis: ClientRouteModuleAnalysis;
|
|
759
|
+
filename: string;
|
|
760
|
+
root: boolean;
|
|
761
|
+
routeEntry: boolean;
|
|
762
|
+
},
|
|
763
|
+
): void {
|
|
764
|
+
if (collector === undefined) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (options.root) {
|
|
769
|
+
markClientRouteComponentReachable(collector, options.filename, ["default"]);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const localExportNames =
|
|
773
|
+
collector.localExportNamesByFile.get(options.filename) ?? new Set<string>();
|
|
774
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
775
|
+
localExportNames.add(info.name);
|
|
776
|
+
}
|
|
777
|
+
collector.localExportNamesByFile.set(options.filename, localExportNames);
|
|
778
|
+
|
|
779
|
+
const serverOnly =
|
|
780
|
+
options.analysis.hasUseServerDirective || hasServerOnlyImports(options.analysis);
|
|
781
|
+
const explicitClient = isExplicitClientRouteSource(options.analysis, options.filename);
|
|
782
|
+
|
|
783
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
784
|
+
const component = {
|
|
785
|
+
classification: serverOnly
|
|
786
|
+
? "server-only"
|
|
787
|
+
: explicitClient || info.clientRuntime
|
|
788
|
+
? options.routeEntry && info.name === "default"
|
|
789
|
+
? "client-route"
|
|
790
|
+
: "client-boundary"
|
|
791
|
+
: "server-render",
|
|
792
|
+
exportName: info.name,
|
|
793
|
+
file: options.filename,
|
|
794
|
+
origin: clientRouteComponentOrigin({
|
|
795
|
+
analysis: options.analysis,
|
|
796
|
+
filename: options.filename,
|
|
797
|
+
info,
|
|
798
|
+
serverOnly,
|
|
799
|
+
}),
|
|
800
|
+
} satisfies ClientRouteComponent;
|
|
801
|
+
collector.components.set(
|
|
802
|
+
clientRouteComponentKey(component.file, component.exportName),
|
|
803
|
+
component,
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
for (const info of options.analysis.topLevelExportRenderInfo) {
|
|
808
|
+
if (!isClientRouteComponentReachable(collector, options.filename, info.name)) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const renderedNames = new Set([
|
|
813
|
+
...(options.analysis.reachableExportRenderedComponentNames[info.name] ?? []),
|
|
814
|
+
...(options.analysis.reachableExportRenderedComponentRoots[info.name] ?? []),
|
|
815
|
+
]);
|
|
816
|
+
|
|
817
|
+
for (const rendered of options.analysis.topLevelExportRenderInfo) {
|
|
818
|
+
if (
|
|
819
|
+
rendered.name !== "default" &&
|
|
820
|
+
(renderedNames.has(rendered.name) || renderedNames.has(rendered.localName ?? rendered.name))
|
|
821
|
+
) {
|
|
822
|
+
markClientRouteComponentReachable(collector, options.filename, [rendered.name], {
|
|
823
|
+
clientExecution: clientRouteComponentRunsOnClient(collector, options.filename, info.name),
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function clientRouteComponentOrigin(options: {
|
|
831
|
+
analysis: ClientRouteModuleAnalysis;
|
|
832
|
+
filename: string;
|
|
833
|
+
info: TopLevelExportRenderInfo;
|
|
834
|
+
serverOnly: boolean;
|
|
835
|
+
}): ClientRouteComponentOrigin {
|
|
836
|
+
if (options.analysis.hasUseServerDirective) {
|
|
837
|
+
return "use-server-directive";
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (options.serverOnly) {
|
|
841
|
+
return "server-only-import";
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (options.analysis.hasUseClientDirective) {
|
|
845
|
+
return "use-client-directive";
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (/\.compat(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
849
|
+
return "compat-filename";
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (/\.client(?:\.mreact)?\.[cm]?[jt]sx?$/.test(options.filename)) {
|
|
853
|
+
return "client-filename";
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return options.info.clientRuntime ? "inferred-client-runtime" : "server-render";
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function normalizedClientRouteComponents(
|
|
860
|
+
collector: ClientRouteComponentCollector,
|
|
861
|
+
routeFile: string,
|
|
862
|
+
): ClientRouteComponent[] {
|
|
863
|
+
return Array.from(collector.components.values())
|
|
864
|
+
.filter(
|
|
865
|
+
(component) =>
|
|
866
|
+
collector.reachable.has(clientRouteComponentKey(component.file, component.exportName)) ||
|
|
867
|
+
collector.reachable.has(clientRouteComponentKey(component.file, "*")),
|
|
868
|
+
)
|
|
869
|
+
.map((component) =>
|
|
870
|
+
component.classification === "server-render" &&
|
|
871
|
+
(collector.clientReachable.has(
|
|
872
|
+
clientRouteComponentKey(component.file, component.exportName),
|
|
873
|
+
) ||
|
|
874
|
+
collector.clientReachable.has(clientRouteComponentKey(component.file, "*")))
|
|
875
|
+
? { ...component, classification: "shared" as const }
|
|
876
|
+
: component,
|
|
877
|
+
)
|
|
878
|
+
.sort((left, right) => {
|
|
879
|
+
const leftRoute = left.file === routeFile && left.exportName === "default";
|
|
880
|
+
const rightRoute = right.file === routeFile && right.exportName === "default";
|
|
881
|
+
|
|
882
|
+
if (leftRoute !== rightRoute) {
|
|
883
|
+
return leftRoute ? -1 : 1;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return left.file === right.file
|
|
887
|
+
? left.exportName === right.exportName
|
|
888
|
+
? left.classification.localeCompare(right.classification)
|
|
889
|
+
: left.exportName.localeCompare(right.exportName)
|
|
890
|
+
: left.file.localeCompare(right.file);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function clientRouteComponentKey(file: string, exportName: string): string {
|
|
895
|
+
return `${file}\0${exportName}`;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function markClientRouteComponentReachable(
|
|
899
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
900
|
+
file: string,
|
|
901
|
+
exportNames: readonly string[] | undefined,
|
|
902
|
+
options: { clientExecution?: boolean | undefined } = {},
|
|
903
|
+
): void {
|
|
904
|
+
if (collector === undefined) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (exportNames === undefined) {
|
|
909
|
+
collector.reachable.add(clientRouteComponentKey(file, "*"));
|
|
910
|
+
if (options.clientExecution === true) {
|
|
911
|
+
collector.clientReachable.add(clientRouteComponentKey(file, "*"));
|
|
912
|
+
}
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
for (const exportName of exportNames) {
|
|
917
|
+
const key = clientRouteComponentKey(file, exportName);
|
|
918
|
+
collector.reachable.add(key);
|
|
919
|
+
if (options.clientExecution === true) {
|
|
920
|
+
collector.clientReachable.add(key);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function clientRouteComponentRunsOnClient(
|
|
926
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
927
|
+
file: string,
|
|
928
|
+
exportName: string,
|
|
929
|
+
): boolean {
|
|
930
|
+
if (collector === undefined) {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const key = clientRouteComponentKey(file, exportName);
|
|
935
|
+
const component = collector.components.get(key);
|
|
936
|
+
return (
|
|
937
|
+
component?.classification === "client-boundary" ||
|
|
938
|
+
component?.classification === "client-route" ||
|
|
939
|
+
component?.classification === "shared" ||
|
|
940
|
+
collector.clientReachable.has(key) ||
|
|
941
|
+
collector.clientReachable.has(clientRouteComponentKey(file, "*"))
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function isClientRouteComponentReachable(
|
|
946
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
947
|
+
file: string,
|
|
948
|
+
exportName: string,
|
|
949
|
+
): boolean {
|
|
950
|
+
return (
|
|
951
|
+
collector === undefined ||
|
|
952
|
+
collector.reachable.has(clientRouteComponentKey(file, exportName)) ||
|
|
953
|
+
collector.reachable.has(clientRouteComponentKey(file, "*"))
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function propagateClientRouteComponentStaticExport(
|
|
958
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
959
|
+
file: string,
|
|
960
|
+
resolved: string,
|
|
961
|
+
reference: StaticExportReference,
|
|
962
|
+
): void {
|
|
963
|
+
if (collector === undefined) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const wildcardReachable = collector.reachable.has(clientRouteComponentKey(file, "*"));
|
|
968
|
+
const wildcardClientExecution = clientRouteComponentRunsOnClient(collector, file, "*");
|
|
969
|
+
|
|
970
|
+
if (reference.exportAll) {
|
|
971
|
+
if (wildcardReachable) {
|
|
972
|
+
markClientRouteComponentReachable(collector, resolved, undefined, {
|
|
973
|
+
clientExecution: wildcardClientExecution,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
for (const key of collector.reachable) {
|
|
978
|
+
const [reachableFile, exportName] = key.split("\0");
|
|
979
|
+
|
|
980
|
+
if (reachableFile === file && exportName !== undefined && exportName !== "*") {
|
|
981
|
+
markClientRouteComponentReachable(collector, resolved, [exportName], {
|
|
982
|
+
clientExecution: clientRouteComponentRunsOnClient(collector, file, exportName),
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
for (const specifier of reference.specifiers) {
|
|
990
|
+
if (
|
|
991
|
+
wildcardReachable ||
|
|
992
|
+
collector.reachable.has(clientRouteComponentKey(file, specifier.exportedName))
|
|
993
|
+
) {
|
|
994
|
+
markClientRouteComponentReachable(collector, resolved, [specifier.localName], {
|
|
995
|
+
clientExecution:
|
|
996
|
+
wildcardClientExecution ||
|
|
997
|
+
clientRouteComponentRunsOnClient(collector, file, specifier.exportedName),
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (reference.specifiers.length === 0) {
|
|
1003
|
+
for (const exportName of reference.exportedNames) {
|
|
1004
|
+
if (wildcardReachable || collector.reachable.has(clientRouteComponentKey(file, exportName))) {
|
|
1005
|
+
markClientRouteComponentReachable(collector, resolved, [exportName], {
|
|
1006
|
+
clientExecution:
|
|
1007
|
+
wildcardClientExecution ||
|
|
1008
|
+
clientRouteComponentRunsOnClient(collector, file, exportName),
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function collectUnknownClientRouteComponents(
|
|
1016
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
1017
|
+
options: {
|
|
1018
|
+
exportNames: readonly string[] | undefined;
|
|
1019
|
+
file: string;
|
|
1020
|
+
},
|
|
1021
|
+
): void {
|
|
1022
|
+
if (collector === undefined) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
for (const exportName of options.exportNames ?? ["*"]) {
|
|
1027
|
+
const component = {
|
|
1028
|
+
classification: "unknown",
|
|
1029
|
+
exportName,
|
|
1030
|
+
file: options.file,
|
|
1031
|
+
origin: "unresolved-reference",
|
|
1032
|
+
} satisfies ClientRouteComponent;
|
|
1033
|
+
collector.components.set(
|
|
1034
|
+
clientRouteComponentKey(component.file, component.exportName),
|
|
1035
|
+
component,
|
|
1036
|
+
);
|
|
1037
|
+
markClientRouteComponentReachable(collector, component.file, [component.exportName]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function collectClientRoutePendingExportValidation(
|
|
1042
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
1043
|
+
options: ClientRoutePendingExportValidation,
|
|
1044
|
+
): void {
|
|
1045
|
+
if (collector === undefined || options.exportNames.length === 0) {
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
collector.pendingExportValidations.push(options);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function collectClientRouteStaticExportEdge(
|
|
1053
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
1054
|
+
options: ClientRouteStaticExportEdge,
|
|
1055
|
+
): void {
|
|
1056
|
+
collector?.staticExportEdges.push(options);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function finalizeClientRouteComponentExportValidations(
|
|
1060
|
+
collector: ClientRouteComponentCollector | undefined,
|
|
1061
|
+
): ClientRouteInferenceDiagnostic[] {
|
|
1062
|
+
if (collector === undefined) {
|
|
1063
|
+
return [];
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const exportNamesByFile = new Map<string, Set<string>>();
|
|
1067
|
+
for (const [file, names] of collector.localExportNamesByFile) {
|
|
1068
|
+
exportNamesByFile.set(file, new Set(names));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
let changed = true;
|
|
1072
|
+
while (changed) {
|
|
1073
|
+
changed = false;
|
|
1074
|
+
|
|
1075
|
+
for (const edge of collector.staticExportEdges) {
|
|
1076
|
+
const exportedNames = exportNamesByFile.get(edge.file) ?? new Set<string>();
|
|
1077
|
+
const sourceNames = exportNamesByFile.get(edge.resolved) ?? new Set<string>();
|
|
1078
|
+
const namesToAdd = edge.reference.exportAll
|
|
1079
|
+
? new Set([...sourceNames].filter((exportName) => exportName !== "default"))
|
|
1080
|
+
: new Set(
|
|
1081
|
+
edge.reference.specifiers
|
|
1082
|
+
.filter((specifier) => sourceNames.has(specifier.localName))
|
|
1083
|
+
.map((specifier) => specifier.exportedName),
|
|
1084
|
+
);
|
|
1085
|
+
|
|
1086
|
+
for (const exportName of namesToAdd) {
|
|
1087
|
+
if (!exportedNames.has(exportName)) {
|
|
1088
|
+
exportedNames.add(exportName);
|
|
1089
|
+
changed = true;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
exportNamesByFile.set(edge.file, exportedNames);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const diagnostics: ClientRouteInferenceDiagnostic[] = [];
|
|
1098
|
+
const seen = new Set<string>();
|
|
1099
|
+
for (const pending of collector.pendingExportValidations) {
|
|
1100
|
+
const availableExportNames = exportNamesByFile.get(pending.file) ?? new Set<string>();
|
|
1101
|
+
const missingExportNames = pending.exportNames.filter(
|
|
1102
|
+
(exportName) => !availableExportNames.has(exportName),
|
|
1103
|
+
);
|
|
1104
|
+
if (missingExportNames.length === 0) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const key = `${pending.importer}\0${pending.source}\0${missingExportNames.join("\0")}`;
|
|
1109
|
+
if (seen.has(key)) {
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
seen.add(key);
|
|
1113
|
+
collectUnknownClientRouteComponents(collector, {
|
|
1114
|
+
exportNames: missingExportNames,
|
|
1115
|
+
file: pending.file,
|
|
1116
|
+
});
|
|
1117
|
+
diagnostics.push(
|
|
1118
|
+
unresolvedClientRouteReferenceDiagnostic({
|
|
1119
|
+
exportNames: missingExportNames,
|
|
1120
|
+
filename: pending.importer,
|
|
1121
|
+
source: pending.source,
|
|
1122
|
+
}),
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return diagnostics;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
665
1129
|
function isExplicitClientRouteSource(
|
|
666
1130
|
analysis: ClientRouteModuleAnalysis,
|
|
667
1131
|
filename: string,
|
|
@@ -688,33 +1152,49 @@ function hasServerOnlyImports(analysis: ClientRouteModuleAnalysis): boolean {
|
|
|
688
1152
|
async function inferClientRouteModuleSource(options: {
|
|
689
1153
|
cache: ClientRouteInferenceCache;
|
|
690
1154
|
code: string;
|
|
1155
|
+
componentCollector?: ClientRouteComponentCollector | undefined;
|
|
691
1156
|
filename: string;
|
|
692
1157
|
moduleContext?: CompilerModuleContext | undefined;
|
|
693
1158
|
root: boolean;
|
|
1159
|
+
routeEntry: boolean;
|
|
694
1160
|
seen: Set<string>;
|
|
695
1161
|
sourceTransform?: ClientRouteSourceTransform | undefined;
|
|
696
1162
|
}): Promise<ClientRouteModuleInferenceResult> {
|
|
697
1163
|
const analysis = await clientRouteModuleAnalysisForSource(options);
|
|
698
1164
|
const usesNavigationLinkLocal = detectLinkComponentUsage(analysis);
|
|
1165
|
+
collectClientRouteComponentsForModule(options.componentCollector, {
|
|
1166
|
+
analysis,
|
|
1167
|
+
filename: options.filename,
|
|
1168
|
+
root: options.root,
|
|
1169
|
+
routeEntry: options.routeEntry,
|
|
1170
|
+
});
|
|
1171
|
+
const forcedInference = isServerOnlyClientRouteSource(analysis)
|
|
1172
|
+
? emptyClientRouteModuleInferenceResult({
|
|
1173
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
1174
|
+
navigationLinkExportNames: detectLinkComponentExportNames(analysis),
|
|
1175
|
+
serverOnly: true,
|
|
1176
|
+
serverOnlyClientRuntime: analysis.clientRuntime,
|
|
1177
|
+
usesNavigationLink: usesNavigationLinkLocal,
|
|
1178
|
+
})
|
|
1179
|
+
: isExplicitClientRouteSource(analysis, options.filename)
|
|
1180
|
+
? emptyClientRouteModuleInferenceResult({
|
|
1181
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
1182
|
+
client: true,
|
|
1183
|
+
clientBoundaryModule: true,
|
|
1184
|
+
})
|
|
1185
|
+
: undefined;
|
|
699
1186
|
|
|
700
|
-
if (
|
|
701
|
-
return
|
|
702
|
-
navigationLinkExportNames: detectLinkComponentExportNames(analysis),
|
|
703
|
-
serverOnly: true,
|
|
704
|
-
serverOnlyClientRuntime: analysis.clientRuntime,
|
|
705
|
-
usesNavigationLink: usesNavigationLinkLocal,
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
if (isExplicitClientRouteSource(analysis, options.filename)) {
|
|
710
|
-
return emptyClientRouteModuleInferenceResult({
|
|
711
|
-
client: true,
|
|
712
|
-
clientBoundaryModule: true,
|
|
713
|
-
});
|
|
1187
|
+
if (forcedInference !== undefined && options.componentCollector === undefined) {
|
|
1188
|
+
return forcedInference;
|
|
714
1189
|
}
|
|
715
1190
|
|
|
716
1191
|
if (options.seen.has(options.filename)) {
|
|
717
|
-
return
|
|
1192
|
+
return (
|
|
1193
|
+
forcedInference ??
|
|
1194
|
+
emptyClientRouteModuleInferenceResult({
|
|
1195
|
+
availableExportNames: analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
1196
|
+
})
|
|
1197
|
+
);
|
|
718
1198
|
}
|
|
719
1199
|
|
|
720
1200
|
options.seen.add(options.filename);
|
|
@@ -726,11 +1206,14 @@ async function inferClientRouteModuleSource(options: {
|
|
|
726
1206
|
const nestedClientExportNames = new Set<string>();
|
|
727
1207
|
const clientReferenceSourceFiles: string[] = [];
|
|
728
1208
|
const diagnostics: ClientRouteInferenceDiagnostic[] = [];
|
|
1209
|
+
const availableExportNames = new Set(
|
|
1210
|
+
analysis.topLevelExportRenderInfo.map((info) => info.name),
|
|
1211
|
+
);
|
|
729
1212
|
let boundaryGraphFallbackRequired = false;
|
|
730
1213
|
let clientProxy = false;
|
|
731
1214
|
let nestedClient = false;
|
|
732
1215
|
let usesNavigationLink = usesNavigationLinkLocal;
|
|
733
|
-
const navigationLinkExportNames = new Set
|
|
1216
|
+
const navigationLinkExportNames = new Set(detectLinkComponentExportNames(analysis));
|
|
734
1217
|
const exportInfo = analysis.topLevelExportRenderInfo;
|
|
735
1218
|
const implicitModuleClient = exportInfo.length === 0 && analysis.clientRuntime;
|
|
736
1219
|
for (const info of exportInfo) {
|
|
@@ -740,9 +1223,12 @@ async function inferClientRouteModuleSource(options: {
|
|
|
740
1223
|
}
|
|
741
1224
|
if (
|
|
742
1225
|
hasServerOnlyImports(analysis) &&
|
|
743
|
-
(implicitModuleClient || clientBoundaryExportNames.size > 0)
|
|
1226
|
+
(implicitModuleClient || clientBoundaryExportNames.size > 0) &&
|
|
1227
|
+
options.componentCollector === undefined
|
|
744
1228
|
) {
|
|
745
1229
|
return emptyClientRouteModuleInferenceResult({
|
|
1230
|
+
availableExportNames: Array.from(availableExportNames),
|
|
1231
|
+
navigationLinkExportNames: detectLinkComponentExportNames(analysis),
|
|
746
1232
|
serverOnly: true,
|
|
747
1233
|
serverOnlyClientRuntime: true,
|
|
748
1234
|
});
|
|
@@ -775,17 +1261,67 @@ async function inferClientRouteModuleSource(options: {
|
|
|
775
1261
|
continue;
|
|
776
1262
|
}
|
|
777
1263
|
|
|
1264
|
+
const renderingExportNames = rendered ? renderedLocalExportNames(reference, exportInfo) : [];
|
|
1265
|
+
const renderedImportedNames = rendered
|
|
1266
|
+
? renderedImportedExportNames(reference, renderedComponentRoots)
|
|
1267
|
+
: [];
|
|
1268
|
+
const renderedFromReachableExport =
|
|
1269
|
+
rendered &&
|
|
1270
|
+
renderingExportNames.some((exportName) =>
|
|
1271
|
+
isClientRouteComponentReachable(options.componentCollector, options.filename, exportName),
|
|
1272
|
+
);
|
|
778
1273
|
const resolved = await resolveAppLocalModule({
|
|
779
1274
|
allowExplicitNonSource: options.sourceTransform !== undefined,
|
|
780
1275
|
cache: options.cache,
|
|
781
1276
|
importer: options.filename,
|
|
782
1277
|
specifier: reference.source,
|
|
1278
|
+
tolerateUnresolved: options.componentCollector !== undefined,
|
|
783
1279
|
});
|
|
784
1280
|
|
|
785
1281
|
if (resolved === undefined) {
|
|
1282
|
+
if (
|
|
1283
|
+
options.componentCollector !== undefined &&
|
|
1284
|
+
renderedFromReachableExport &&
|
|
1285
|
+
reference.source.startsWith(".")
|
|
1286
|
+
) {
|
|
1287
|
+
collectUnknownClientRouteComponents(options.componentCollector, {
|
|
1288
|
+
exportNames: renderedImportedNames,
|
|
1289
|
+
file: resolve(dirname(options.filename), reference.source),
|
|
1290
|
+
});
|
|
1291
|
+
diagnostics.push(
|
|
1292
|
+
unresolvedClientRouteReferenceDiagnostic({
|
|
1293
|
+
filename: options.filename,
|
|
1294
|
+
source: reference.source,
|
|
1295
|
+
}),
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
786
1298
|
continue;
|
|
787
1299
|
}
|
|
788
1300
|
|
|
1301
|
+
if (renderedFromReachableExport) {
|
|
1302
|
+
const clientExecution = renderingExportNames.some((exportName) =>
|
|
1303
|
+
clientRouteComponentRunsOnClient(
|
|
1304
|
+
options.componentCollector,
|
|
1305
|
+
options.filename,
|
|
1306
|
+
exportName,
|
|
1307
|
+
),
|
|
1308
|
+
);
|
|
1309
|
+
markClientRouteComponentReachable(
|
|
1310
|
+
options.componentCollector,
|
|
1311
|
+
resolved,
|
|
1312
|
+
renderedImportedNames,
|
|
1313
|
+
{ clientExecution },
|
|
1314
|
+
);
|
|
1315
|
+
if (renderedImportedNames !== undefined) {
|
|
1316
|
+
collectClientRoutePendingExportValidation(options.componentCollector, {
|
|
1317
|
+
exportNames: renderedImportedNames,
|
|
1318
|
+
file: resolved,
|
|
1319
|
+
importer: options.filename,
|
|
1320
|
+
source: reference.source,
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
789
1325
|
const source = await readClientRouteSource({
|
|
790
1326
|
cache: options.cache,
|
|
791
1327
|
filename: resolved,
|
|
@@ -794,6 +1330,7 @@ async function inferClientRouteModuleSource(options: {
|
|
|
794
1330
|
const imported = await inferClientRouteModuleSource({
|
|
795
1331
|
cache: options.cache,
|
|
796
1332
|
code: source,
|
|
1333
|
+
componentCollector: options.componentCollector,
|
|
797
1334
|
filename: resolved,
|
|
798
1335
|
moduleContext: await compilerModuleContextForSource({
|
|
799
1336
|
cache: options.cache,
|
|
@@ -801,6 +1338,7 @@ async function inferClientRouteModuleSource(options: {
|
|
|
801
1338
|
filename: resolved,
|
|
802
1339
|
}),
|
|
803
1340
|
root: false,
|
|
1341
|
+
routeEntry: false,
|
|
804
1342
|
seen: options.seen,
|
|
805
1343
|
sourceTransform: options.sourceTransform,
|
|
806
1344
|
});
|
|
@@ -844,8 +1382,8 @@ async function inferClientRouteModuleSource(options: {
|
|
|
844
1382
|
}
|
|
845
1383
|
|
|
846
1384
|
if (rendered) {
|
|
847
|
-
const importedExportNames =
|
|
848
|
-
const renderedExportNames =
|
|
1385
|
+
const importedExportNames = renderedImportedNames;
|
|
1386
|
+
const renderedExportNames = renderingExportNames;
|
|
849
1387
|
const importedBoundary =
|
|
850
1388
|
imported.clientBoundaryModule ||
|
|
851
1389
|
matchesInferredExportNames(importedExportNames, imported.clientBoundaryExportNames);
|
|
@@ -908,12 +1446,33 @@ async function inferClientRouteModuleSource(options: {
|
|
|
908
1446
|
cache: options.cache,
|
|
909
1447
|
importer: options.filename,
|
|
910
1448
|
specifier: reference.source,
|
|
1449
|
+
tolerateUnresolved: options.componentCollector !== undefined,
|
|
911
1450
|
});
|
|
912
1451
|
|
|
913
1452
|
if (resolved === undefined) {
|
|
1453
|
+
if (options.componentCollector !== undefined) {
|
|
1454
|
+
diagnostics.push(
|
|
1455
|
+
unresolvedClientRouteReferenceDiagnostic({
|
|
1456
|
+
filename: options.filename,
|
|
1457
|
+
source: reference.source,
|
|
1458
|
+
}),
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
914
1461
|
continue;
|
|
915
1462
|
}
|
|
916
1463
|
|
|
1464
|
+
propagateClientRouteComponentStaticExport(
|
|
1465
|
+
options.componentCollector,
|
|
1466
|
+
options.filename,
|
|
1467
|
+
resolved,
|
|
1468
|
+
reference,
|
|
1469
|
+
);
|
|
1470
|
+
collectClientRouteStaticExportEdge(options.componentCollector, {
|
|
1471
|
+
file: options.filename,
|
|
1472
|
+
reference,
|
|
1473
|
+
resolved,
|
|
1474
|
+
});
|
|
1475
|
+
|
|
917
1476
|
const source = await readClientRouteSource({
|
|
918
1477
|
cache: options.cache,
|
|
919
1478
|
filename: resolved,
|
|
@@ -922,6 +1481,7 @@ async function inferClientRouteModuleSource(options: {
|
|
|
922
1481
|
const exported = await inferClientRouteModuleSource({
|
|
923
1482
|
cache: options.cache,
|
|
924
1483
|
code: source,
|
|
1484
|
+
componentCollector: options.componentCollector,
|
|
925
1485
|
filename: resolved,
|
|
926
1486
|
moduleContext: await compilerModuleContextForSource({
|
|
927
1487
|
cache: options.cache,
|
|
@@ -929,10 +1489,23 @@ async function inferClientRouteModuleSource(options: {
|
|
|
929
1489
|
filename: resolved,
|
|
930
1490
|
}),
|
|
931
1491
|
root: false,
|
|
1492
|
+
routeEntry: false,
|
|
932
1493
|
seen: options.seen,
|
|
933
1494
|
sourceTransform: options.sourceTransform,
|
|
934
1495
|
});
|
|
935
1496
|
diagnostics.push(...exported.diagnostics);
|
|
1497
|
+
if (reference.exportAll) {
|
|
1498
|
+
for (const exportName of exported.availableExportNames) {
|
|
1499
|
+
availableExportNames.add(exportName);
|
|
1500
|
+
}
|
|
1501
|
+
} else {
|
|
1502
|
+
for (const specifier of reference.specifiers) {
|
|
1503
|
+
availableExportNames.add(specifier.exportedName);
|
|
1504
|
+
}
|
|
1505
|
+
for (const exportName of reference.exportedNames) {
|
|
1506
|
+
availableExportNames.add(exportName);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
936
1509
|
// A re-export renders nothing itself, so it does not set the module's
|
|
937
1510
|
// own `usesNavigationLink`; it only forwards per-export `Link` usage so
|
|
938
1511
|
// an importer that renders this name can decide precisely. Map the
|
|
@@ -974,7 +1547,8 @@ async function inferClientRouteModuleSource(options: {
|
|
|
974
1547
|
}
|
|
975
1548
|
}
|
|
976
1549
|
|
|
977
|
-
|
|
1550
|
+
const inferred = {
|
|
1551
|
+
availableExportNames: Array.from(availableExportNames),
|
|
978
1552
|
boundaryGraphFallbackCandidate:
|
|
979
1553
|
analysis.staticExports.length > 0 || boundaryGraphFallbackRequired,
|
|
980
1554
|
boundaryGraphFallbackRequired,
|
|
@@ -996,6 +1570,15 @@ async function inferClientRouteModuleSource(options: {
|
|
|
996
1570
|
serverOnlyClientRuntime: false,
|
|
997
1571
|
usesNavigationLink,
|
|
998
1572
|
};
|
|
1573
|
+
return forcedInference === undefined
|
|
1574
|
+
? inferred
|
|
1575
|
+
: {
|
|
1576
|
+
...forcedInference,
|
|
1577
|
+
availableExportNames: inferred.availableExportNames,
|
|
1578
|
+
diagnostics,
|
|
1579
|
+
navigationLinkExportNames: inferred.navigationLinkExportNames,
|
|
1580
|
+
usesNavigationLink: inferred.usesNavigationLink,
|
|
1581
|
+
};
|
|
999
1582
|
} finally {
|
|
1000
1583
|
options.seen.delete(options.filename);
|
|
1001
1584
|
}
|
|
@@ -1005,6 +1588,7 @@ function emptyClientRouteModuleInferenceResult(
|
|
|
1005
1588
|
overrides: Partial<ClientRouteModuleInferenceResult> = {},
|
|
1006
1589
|
): ClientRouteModuleInferenceResult {
|
|
1007
1590
|
return {
|
|
1591
|
+
availableExportNames: [],
|
|
1008
1592
|
boundaryGraphFallbackCandidate: false,
|
|
1009
1593
|
boundaryGraphFallbackRequired: false,
|
|
1010
1594
|
client: false,
|
|
@@ -1426,7 +2010,7 @@ function removeSafeCallbackHandlerAttributes(
|
|
|
1426
2010
|
|
|
1427
2011
|
function matchingBraceEnd(source: string, openBraceIndex: number): number | undefined {
|
|
1428
2012
|
let depth = 0;
|
|
1429
|
-
let quote: "
|
|
2013
|
+
let quote: '"' | "'" | "`" | undefined;
|
|
1430
2014
|
let escaped = false;
|
|
1431
2015
|
|
|
1432
2016
|
for (let index = openBraceIndex; index < source.length; index += 1) {
|
|
@@ -1443,7 +2027,7 @@ function matchingBraceEnd(source: string, openBraceIndex: number): number | unde
|
|
|
1443
2027
|
continue;
|
|
1444
2028
|
}
|
|
1445
2029
|
|
|
1446
|
-
if (char === "
|
|
2030
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
1447
2031
|
quote = char;
|
|
1448
2032
|
continue;
|
|
1449
2033
|
}
|
|
@@ -1492,9 +2076,7 @@ function destructuredPropsCallbackNames(source: string): Set<string> {
|
|
|
1492
2076
|
addDestructuredCallbackNames(names, match[1] ?? "");
|
|
1493
2077
|
}
|
|
1494
2078
|
|
|
1495
|
-
for (const match of source.matchAll(
|
|
1496
|
-
/\{[^{}]*:\s*\{([^{}]+)\}\s*(?:=\s*\{\})?[^{}]*\}/gu,
|
|
1497
|
-
)) {
|
|
2079
|
+
for (const match of source.matchAll(/\{[^{}]*:\s*\{([^{}]+)\}\s*(?:=\s*\{\})?[^{}]*\}/gu)) {
|
|
1498
2080
|
addDestructuredCallbackNames(names, match[1] ?? "");
|
|
1499
2081
|
}
|
|
1500
2082
|
|
|
@@ -1532,10 +2114,7 @@ function addDestructuredCallbackNames(names: Set<string>, destructured: string):
|
|
|
1532
2114
|
const alias = destructuredBindingName(rawAlias);
|
|
1533
2115
|
const name = alias ?? property;
|
|
1534
2116
|
|
|
1535
|
-
if (
|
|
1536
|
-
name !== undefined &&
|
|
1537
|
-
(isCallbackPropName(property) || isCallbackPropName(name))
|
|
1538
|
-
) {
|
|
2117
|
+
if (name !== undefined && (isCallbackPropName(property) || isCallbackPropName(name))) {
|
|
1539
2118
|
names.add(name);
|
|
1540
2119
|
}
|
|
1541
2120
|
}
|
|
@@ -1624,6 +2203,28 @@ function serverOnlyClientImportReferenceDiagnostic(options: {
|
|
|
1624
2203
|
};
|
|
1625
2204
|
}
|
|
1626
2205
|
|
|
2206
|
+
function unresolvedClientRouteReferenceDiagnostic(options: {
|
|
2207
|
+
exportNames?: readonly string[] | undefined;
|
|
2208
|
+
filename: string;
|
|
2209
|
+
source: string;
|
|
2210
|
+
}): ClientRouteInferenceDiagnostic {
|
|
2211
|
+
const exportSuffix =
|
|
2212
|
+
options.exportNames === undefined || options.exportNames.length === 0
|
|
2213
|
+
? ""
|
|
2214
|
+
: ` (${options.exportNames.map((name) => JSON.stringify(name)).join(", ")})`;
|
|
2215
|
+
|
|
2216
|
+
return {
|
|
2217
|
+
code: "MR_CLIENT_BOUNDARY_INFERENCE_UNRESOLVED_REFERENCE",
|
|
2218
|
+
filename: options.filename,
|
|
2219
|
+
level: "warn",
|
|
2220
|
+
localNames: [...(options.exportNames ?? [])],
|
|
2221
|
+
message:
|
|
2222
|
+
`${options.filename}: rendered component reference ${JSON.stringify(options.source)}${exportSuffix} ` +
|
|
2223
|
+
"could not be resolved to an exported component.",
|
|
2224
|
+
source: options.source,
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
|
|
1627
2228
|
function functionCallInteractiveImportDiagnostic(options: {
|
|
1628
2229
|
filename: string;
|
|
1629
2230
|
reference: StaticImportReference;
|
|
@@ -1719,6 +2320,7 @@ async function resolveAppLocalModule(options: {
|
|
|
1719
2320
|
cache: ClientRouteInferenceCache;
|
|
1720
2321
|
importer: string;
|
|
1721
2322
|
specifier: string;
|
|
2323
|
+
tolerateUnresolved?: boolean | undefined;
|
|
1722
2324
|
}): Promise<string | undefined> {
|
|
1723
2325
|
if (!options.specifier.startsWith(".")) {
|
|
1724
2326
|
return undefined;
|
|
@@ -1726,7 +2328,7 @@ async function resolveAppLocalModule(options: {
|
|
|
1726
2328
|
|
|
1727
2329
|
const cacheKey = `${options.importer}\0${options.specifier}\0${
|
|
1728
2330
|
options.allowExplicitNonSource === true ? "explicit" : "source"
|
|
1729
|
-
}`;
|
|
2331
|
+
}\0${options.tolerateUnresolved === true ? "tolerant" : "strict"}`;
|
|
1730
2332
|
const cached = options.cache.resolvedByImport.get(cacheKey);
|
|
1731
2333
|
|
|
1732
2334
|
if (cached !== undefined) {
|
|
@@ -1737,6 +2339,7 @@ async function resolveAppLocalModule(options: {
|
|
|
1737
2339
|
allowExplicitNonSource: options.allowExplicitNonSource === true,
|
|
1738
2340
|
importer: options.importer,
|
|
1739
2341
|
specifier: options.specifier,
|
|
2342
|
+
tolerateUnresolved: options.tolerateUnresolved === true,
|
|
1740
2343
|
});
|
|
1741
2344
|
options.cache.resolvedByImport.set(cacheKey, resolved);
|
|
1742
2345
|
return resolved;
|
|
@@ -1746,6 +2349,7 @@ async function resolveAppLocalModuleUncached(options: {
|
|
|
1746
2349
|
allowExplicitNonSource: boolean;
|
|
1747
2350
|
importer: string;
|
|
1748
2351
|
specifier: string;
|
|
2352
|
+
tolerateUnresolved: boolean;
|
|
1749
2353
|
}): Promise<string | undefined> {
|
|
1750
2354
|
const { importer, specifier } = options;
|
|
1751
2355
|
const base = join(dirname(importer), specifier);
|
|
@@ -1765,6 +2369,10 @@ async function resolveAppLocalModuleUncached(options: {
|
|
|
1765
2369
|
}
|
|
1766
2370
|
}
|
|
1767
2371
|
|
|
2372
|
+
if (options.tolerateUnresolved) {
|
|
2373
|
+
return undefined;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
1768
2376
|
throw new Error(`${importer}: could not resolve app-local import ${JSON.stringify(specifier)}.`);
|
|
1769
2377
|
}
|
|
1770
2378
|
|
|
@@ -2268,8 +2876,7 @@ export async function buildClientRouteEntrySource(
|
|
|
2268
2876
|
const routeUsesReactiveEffect = detectRouteReactiveEffectHint(compiled.code);
|
|
2269
2877
|
const routeUsesDomRefs = compiled.metadata.imports.some(
|
|
2270
2878
|
(entry) =>
|
|
2271
|
-
entry.source === "@reckona/mreact-reactive-dom" &&
|
|
2272
|
-
entry.specifiers.includes("bindDomRef"),
|
|
2879
|
+
entry.source === "@reckona/mreact-reactive-dom" && entry.specifiers.includes("bindDomRef"),
|
|
2273
2880
|
);
|
|
2274
2881
|
const routeUsesCleanupScope = routeUsesCells || routeUsesReactiveEffect || routeUsesDomRefs;
|
|
2275
2882
|
const routeExplicitlyRequiresHydration = isExplicitClientRouteSource(
|
|
@@ -4813,8 +5420,8 @@ export function prepareReactiveEffectRunDevtoolsEvent() { return undefined; }`,
|
|
|
4813
5420
|
if (
|
|
4814
5421
|
isAbsolute(args.path) &&
|
|
4815
5422
|
isRouteClientDependencySourcePath(args.path, routeFiles) &&
|
|
4816
|
-
!runtimePackageDirs.some(
|
|
4817
|
-
|
|
5423
|
+
!runtimePackageDirs.some((runtimePackageDir) =>
|
|
5424
|
+
args.path.startsWith(`${runtimePackageDir}${sep}`),
|
|
4818
5425
|
)
|
|
4819
5426
|
) {
|
|
4820
5427
|
options.sourceRegionModulePaths?.add(args.path);
|