@reckona/mreact-router 0.0.195 → 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/bundle-pipeline.d.ts +2 -0
- package/dist/bundle-pipeline.d.ts.map +1 -1
- package/dist/bundle-pipeline.js +25 -3
- package/dist/bundle-pipeline.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 +12 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +518 -54
- 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/dist/vite.js +1 -0
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/boundaries.ts +285 -0
- package/src/build.ts +25 -1
- package/src/bundle-pipeline.ts +50 -3
- 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 +769 -71
- package/src/index.ts +24 -5
- package/src/vite.ts +1 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { relative, sep } from "node:path";
|
|
3
|
+
import type { UserConfig } from "vite";
|
|
4
|
+
import type {
|
|
5
|
+
ClientRouteComponent,
|
|
6
|
+
ClientRouteComponentClassification,
|
|
7
|
+
ClientRouteComponentOrigin,
|
|
8
|
+
ClientRouteInferenceDiagnostic,
|
|
9
|
+
} from "./client-route-inference.js";
|
|
10
|
+
import {
|
|
11
|
+
createClientRouteInferenceCache,
|
|
12
|
+
inferClientRouteModule,
|
|
13
|
+
} from "./client-route-inference.js";
|
|
14
|
+
import { resolveAppRouterProjectOptions, type AppRouterProjectOptions } from "./config.js";
|
|
15
|
+
import { stripRouteClientSource } from "./route-source.js";
|
|
16
|
+
import { scanAppRoutes } from "./routes.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Describes one statically traced component in a route boundary report.
|
|
20
|
+
*/
|
|
21
|
+
export interface BoundaryReportComponent {
|
|
22
|
+
classification: ClientRouteComponentClassification;
|
|
23
|
+
exportName: string;
|
|
24
|
+
file: string;
|
|
25
|
+
origin: ClientRouteComponentOrigin;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Describes the rendered component boundary graph for one page route.
|
|
30
|
+
*/
|
|
31
|
+
export interface BoundaryReportRoute {
|
|
32
|
+
classification: "client-route" | "server-render";
|
|
33
|
+
components: readonly BoundaryReportComponent[];
|
|
34
|
+
entry: string;
|
|
35
|
+
path: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Counts route and component classifications across a boundary report.
|
|
40
|
+
*/
|
|
41
|
+
export interface BoundaryReportSummary {
|
|
42
|
+
clientBoundaries: number;
|
|
43
|
+
clientRoutes: number;
|
|
44
|
+
serverOnlyComponents: number;
|
|
45
|
+
serverRenderComponents: number;
|
|
46
|
+
serverRenderRoutes: number;
|
|
47
|
+
sharedComponents: number;
|
|
48
|
+
unknownComponents: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Contains a deterministic, versioned snapshot of app-router component boundaries.
|
|
53
|
+
*/
|
|
54
|
+
export interface BoundaryReport {
|
|
55
|
+
diagnostics: readonly ClientRouteInferenceDiagnostic[];
|
|
56
|
+
routes: readonly BoundaryReportRoute[];
|
|
57
|
+
summary: BoundaryReportSummary;
|
|
58
|
+
version: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CreateBoundaryReportRouteInput {
|
|
62
|
+
components: readonly ClientRouteComponent[];
|
|
63
|
+
diagnostics: readonly ClientRouteInferenceDiagnostic[];
|
|
64
|
+
entry: string;
|
|
65
|
+
path: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface CreateBoundaryReportInput {
|
|
69
|
+
projectRoot: string;
|
|
70
|
+
routes: readonly CreateBoundaryReportRouteInput[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Supplies project and Vite settings for standalone boundary analysis.
|
|
75
|
+
*/
|
|
76
|
+
export interface AnalyzeAppBoundariesOptions extends AppRouterProjectOptions {
|
|
77
|
+
viteConfig?: Pick<UserConfig, "define" | "plugins"> | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Inspects every page route without writing build artifacts or executing application code.
|
|
82
|
+
*/
|
|
83
|
+
export async function analyzeAppBoundaries(
|
|
84
|
+
options: AnalyzeAppBoundariesOptions,
|
|
85
|
+
): Promise<BoundaryReport> {
|
|
86
|
+
const project = resolveAppRouterProjectOptions(options);
|
|
87
|
+
const routes = (await scanAppRoutes({ appDir: project.routesDir })).filter(
|
|
88
|
+
(route) => route.kind === "page",
|
|
89
|
+
);
|
|
90
|
+
const cache = createClientRouteInferenceCache();
|
|
91
|
+
const analyzedRoutes = await Promise.all(
|
|
92
|
+
routes.map(async (route): Promise<CreateBoundaryReportRouteInput> => {
|
|
93
|
+
const source = await readFile(route.file, "utf8");
|
|
94
|
+
const inference = await inferClientRouteModule({
|
|
95
|
+
appDir: project.routesDir,
|
|
96
|
+
cache,
|
|
97
|
+
code: stripRouteClientSource({ code: source, filename: route.file }),
|
|
98
|
+
collectComponents: true,
|
|
99
|
+
filename: route.file,
|
|
100
|
+
routePath: route.path,
|
|
101
|
+
vitePlugins: options.viteConfig?.plugins,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
components: inference.components ?? [],
|
|
106
|
+
diagnostics: inference.diagnostics,
|
|
107
|
+
entry: route.file,
|
|
108
|
+
path: route.path,
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return createBoundaryReport({
|
|
114
|
+
projectRoot: project.projectRoot,
|
|
115
|
+
routes: analyzedRoutes,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createBoundaryReport(input: CreateBoundaryReportInput): BoundaryReport {
|
|
120
|
+
const routes = input.routes
|
|
121
|
+
.map((route): BoundaryReportRoute => {
|
|
122
|
+
const entry = projectRelativePath(input.projectRoot, route.entry);
|
|
123
|
+
const components = normalizeComponents(input.projectRoot, entry, route.components);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
classification: components.some(
|
|
127
|
+
(component) =>
|
|
128
|
+
component.file === entry &&
|
|
129
|
+
component.exportName === "default" &&
|
|
130
|
+
component.classification === "client-route",
|
|
131
|
+
)
|
|
132
|
+
? "client-route"
|
|
133
|
+
: "server-render",
|
|
134
|
+
components,
|
|
135
|
+
entry,
|
|
136
|
+
path: route.path,
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
.sort((left, right) =>
|
|
140
|
+
left.path === right.path
|
|
141
|
+
? left.entry.localeCompare(right.entry)
|
|
142
|
+
: left.path.localeCompare(right.path),
|
|
143
|
+
);
|
|
144
|
+
const diagnostics = input.routes
|
|
145
|
+
.flatMap((route) => route.diagnostics)
|
|
146
|
+
.map((diagnostic) => {
|
|
147
|
+
const filename = projectRelativePath(input.projectRoot, diagnostic.filename);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
...diagnostic,
|
|
151
|
+
filename,
|
|
152
|
+
message: diagnostic.message.split(diagnostic.filename).join(filename),
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
.sort((left, right) =>
|
|
156
|
+
left.filename === right.filename
|
|
157
|
+
? left.code.localeCompare(right.code)
|
|
158
|
+
: left.filename.localeCompare(right.filename),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
diagnostics,
|
|
163
|
+
routes,
|
|
164
|
+
summary: summarizeBoundaryRoutes(routes),
|
|
165
|
+
version: 1,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function formatBoundaryReport(report: BoundaryReport): string {
|
|
170
|
+
const lines = ["Boundaries:"];
|
|
171
|
+
|
|
172
|
+
for (const route of report.routes) {
|
|
173
|
+
lines.push(` ${route.path} [${route.classification}]`);
|
|
174
|
+
|
|
175
|
+
for (const component of route.components) {
|
|
176
|
+
lines.push(
|
|
177
|
+
` ${component.file}#${component.exportName} ${component.classification}${formatComponentOrigin(component.origin)}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
lines.push("");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (report.diagnostics.length > 0) {
|
|
185
|
+
lines.push("Warnings:");
|
|
186
|
+
for (const diagnostic of report.diagnostics) {
|
|
187
|
+
lines.push(` ${diagnostic.code}: ${diagnostic.message}`);
|
|
188
|
+
}
|
|
189
|
+
lines.push("");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const summary = report.summary;
|
|
193
|
+
lines.push(
|
|
194
|
+
[
|
|
195
|
+
`Summary: ${summary.serverRenderRoutes} ${plural(summary.serverRenderRoutes, "server-render route")}`,
|
|
196
|
+
`${summary.clientRoutes} ${plural(summary.clientRoutes, "client route")}`,
|
|
197
|
+
`${summary.clientBoundaries} ${plural(summary.clientBoundaries, "client boundary")}`,
|
|
198
|
+
`${summary.serverRenderComponents} ${plural(summary.serverRenderComponents, "server-render component")}`,
|
|
199
|
+
`${summary.serverOnlyComponents} ${plural(summary.serverOnlyComponents, "server-only component")}`,
|
|
200
|
+
`${summary.sharedComponents} ${plural(summary.sharedComponents, "shared component")}`,
|
|
201
|
+
`${summary.unknownComponents} ${plural(summary.unknownComponents, "unknown component")}`,
|
|
202
|
+
].join(", "),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return `${lines.join("\n")}\n`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function formatBoundaryReportJson(report: BoundaryReport): string {
|
|
209
|
+
return `${JSON.stringify(report, null, 2)}\n`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function normalizeComponents(
|
|
213
|
+
projectRoot: string,
|
|
214
|
+
entry: string,
|
|
215
|
+
components: readonly ClientRouteComponent[],
|
|
216
|
+
): BoundaryReportComponent[] {
|
|
217
|
+
const unique = new Map<string, BoundaryReportComponent>();
|
|
218
|
+
|
|
219
|
+
for (const component of components) {
|
|
220
|
+
const normalized = {
|
|
221
|
+
...component,
|
|
222
|
+
file: projectRelativePath(projectRoot, component.file),
|
|
223
|
+
};
|
|
224
|
+
unique.set(
|
|
225
|
+
`${normalized.file}\0${normalized.exportName}\0${normalized.classification}`,
|
|
226
|
+
normalized,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return Array.from(unique.values()).sort((left, right) => {
|
|
231
|
+
const leftEntry = left.file === entry;
|
|
232
|
+
const rightEntry = right.file === entry;
|
|
233
|
+
|
|
234
|
+
if (leftEntry !== rightEntry) {
|
|
235
|
+
return leftEntry ? -1 : 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return left.file === right.file
|
|
239
|
+
? left.exportName === right.exportName
|
|
240
|
+
? left.classification.localeCompare(right.classification)
|
|
241
|
+
: left.exportName.localeCompare(right.exportName)
|
|
242
|
+
: left.file.localeCompare(right.file);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function summarizeBoundaryRoutes(routes: readonly BoundaryReportRoute[]): BoundaryReportSummary {
|
|
247
|
+
const components = routes.flatMap((route) => route.components);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
clientBoundaries: countClassification(components, "client-boundary"),
|
|
251
|
+
clientRoutes: routes.filter((route) => route.classification === "client-route").length,
|
|
252
|
+
serverOnlyComponents: countClassification(components, "server-only"),
|
|
253
|
+
serverRenderComponents: countClassification(components, "server-render"),
|
|
254
|
+
serverRenderRoutes: routes.filter((route) => route.classification === "server-render").length,
|
|
255
|
+
sharedComponents: countClassification(components, "shared"),
|
|
256
|
+
unknownComponents: countClassification(components, "unknown"),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function countClassification(
|
|
261
|
+
components: readonly BoundaryReportComponent[],
|
|
262
|
+
classification: ClientRouteComponentClassification,
|
|
263
|
+
): number {
|
|
264
|
+
return components.filter((component) => component.classification === classification).length;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function projectRelativePath(projectRoot: string, file: string): string {
|
|
268
|
+
const value = relative(projectRoot, file).split(sep).join("/");
|
|
269
|
+
return value === "" ? "." : value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function formatComponentOrigin(origin: ClientRouteComponentOrigin): string {
|
|
273
|
+
if (origin === "use-client-directive") return ' ("use client")';
|
|
274
|
+
if (origin === "use-server-directive") return ' ("use server")';
|
|
275
|
+
if (origin === "client-filename") return " (.client.*)";
|
|
276
|
+
if (origin === "compat-filename") return " (.compat.*)";
|
|
277
|
+
if (origin === "inferred-client-runtime") return " (inferred)";
|
|
278
|
+
if (origin === "server-only-import") return " (server-only import)";
|
|
279
|
+
if (origin === "unresolved-reference") return " (unresolved)";
|
|
280
|
+
return "";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function plural(count: number, singular: string): string {
|
|
284
|
+
return count === 1 ? singular : `${singular}s`;
|
|
285
|
+
}
|
package/src/build.ts
CHANGED
|
@@ -36,8 +36,11 @@ import {
|
|
|
36
36
|
navigationRuntimeLinkDisabledDiagnostic,
|
|
37
37
|
resolveNavigationRuntime,
|
|
38
38
|
type ClientRouteManifestEntry,
|
|
39
|
+
type ClientRouteComponent,
|
|
40
|
+
type ClientRouteInferenceDiagnostic,
|
|
39
41
|
type ClientRouteInferenceCache,
|
|
40
42
|
} from "./client-route-inference.js";
|
|
43
|
+
import { createBoundaryReport, type BoundaryReport } from "./boundaries.js";
|
|
41
44
|
import {
|
|
42
45
|
buildClientRouteBatchOutput,
|
|
43
46
|
buildNavigationRuntimeBundle,
|
|
@@ -129,6 +132,8 @@ export type AwsLambdaGeneratedHandlerPreloadMode =
|
|
|
129
132
|
export interface BuildAppOptions extends AppRouterProjectOptions {
|
|
130
133
|
awsLambdaPreload?: AwsLambdaGeneratedHandlerPreloadMode | undefined;
|
|
131
134
|
awsLambdaPreloadRoutes?: readonly string[] | undefined;
|
|
135
|
+
/** Receives the full route and component boundary report after source analysis. */
|
|
136
|
+
onBoundaryReport?: ((report: BoundaryReport) => void) | undefined;
|
|
132
137
|
onBuildProgress?: ((event: BuildAppProgressEvent) => void) | undefined;
|
|
133
138
|
onBuildPhaseTiming?: ((timing: BuildAppPhaseTiming) => void) | undefined;
|
|
134
139
|
outDir: string;
|
|
@@ -382,6 +387,8 @@ interface BuildRouteSourceAnalysis extends BuildSourceAnalysis {
|
|
|
382
387
|
clientBoundaryImports: readonly string[];
|
|
383
388
|
clientBoundaryFallbackImports: readonly string[];
|
|
384
389
|
clientRoute: boolean;
|
|
390
|
+
components: readonly ClientRouteComponent[];
|
|
391
|
+
diagnostics: readonly ClientRouteInferenceDiagnostic[];
|
|
385
392
|
file: string;
|
|
386
393
|
route: AppRoute & { kind: "page" };
|
|
387
394
|
routeCode: string;
|
|
@@ -389,6 +396,7 @@ interface BuildRouteSourceAnalysis extends BuildSourceAnalysis {
|
|
|
389
396
|
}
|
|
390
397
|
|
|
391
398
|
interface BuildSourceAnalysisScope {
|
|
399
|
+
boundaryReport: BoundaryReport;
|
|
392
400
|
byFile: ReadonlyMap<string, BuildSourceAnalysis>;
|
|
393
401
|
byRouteFile: ReadonlyMap<string, BuildRouteSourceAnalysis>;
|
|
394
402
|
}
|
|
@@ -485,6 +493,7 @@ async function buildAppWithResolvedProject(
|
|
|
485
493
|
vitePlugins,
|
|
486
494
|
}),
|
|
487
495
|
);
|
|
496
|
+
options.onBoundaryReport?.(sourceAnalysis.boundaryReport);
|
|
488
497
|
|
|
489
498
|
if (shouldTrackBuildPhases === false) {
|
|
490
499
|
await validateProductionRoutes({
|
|
@@ -1347,6 +1356,7 @@ async function analyzeBuildRouteSources(options: {
|
|
|
1347
1356
|
appDir: options.project.routesDir,
|
|
1348
1357
|
cache: options.clientRouteInferenceCache,
|
|
1349
1358
|
code: stripRouteClientSource({ code: source, filename: route.file }),
|
|
1359
|
+
collectComponents: true,
|
|
1350
1360
|
filename: route.file,
|
|
1351
1361
|
routePath: route.path,
|
|
1352
1362
|
vitePlugins: options.vitePlugins,
|
|
@@ -1363,6 +1373,8 @@ async function analyzeBuildRouteSources(options: {
|
|
|
1363
1373
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1364
1374
|
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1365
1375
|
clientRoute: clientInference.client,
|
|
1376
|
+
components: clientInference.components ?? [],
|
|
1377
|
+
diagnostics: clientInference.diagnostics,
|
|
1366
1378
|
file,
|
|
1367
1379
|
route,
|
|
1368
1380
|
routeCode,
|
|
@@ -1384,7 +1396,19 @@ async function analyzeBuildRouteSources(options: {
|
|
|
1384
1396
|
}
|
|
1385
1397
|
}
|
|
1386
1398
|
|
|
1387
|
-
return {
|
|
1399
|
+
return {
|
|
1400
|
+
boundaryReport: createBoundaryReport({
|
|
1401
|
+
projectRoot: options.projectRoot,
|
|
1402
|
+
routes: Array.from(byRouteFile.values(), (analysis) => ({
|
|
1403
|
+
components: analysis.components,
|
|
1404
|
+
diagnostics: analysis.diagnostics,
|
|
1405
|
+
entry: analysis.route.file,
|
|
1406
|
+
path: analysis.route.path,
|
|
1407
|
+
})),
|
|
1408
|
+
}),
|
|
1409
|
+
byFile,
|
|
1410
|
+
byRouteFile,
|
|
1411
|
+
};
|
|
1388
1412
|
}
|
|
1389
1413
|
|
|
1390
1414
|
function analyzeBuildSource(source: string, filename: string): BuildSourceAnalysis {
|
package/src/bundle-pipeline.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { builtinModules } from "node:module";
|
|
2
2
|
import { access } from "node:fs/promises";
|
|
3
|
-
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import {
|
|
6
6
|
build as viteBuild,
|
|
@@ -28,6 +28,7 @@ export interface RouterBundleOptions {
|
|
|
28
28
|
platform: "browser" | "node";
|
|
29
29
|
preserveExports?: boolean | undefined;
|
|
30
30
|
root?: string | undefined;
|
|
31
|
+
sourceRegionModulePaths?: ReadonlySet<string> | undefined;
|
|
31
32
|
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
32
33
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
33
34
|
sourceMap?: boolean | undefined;
|
|
@@ -47,6 +48,7 @@ export interface RouterBundleModulesOptions {
|
|
|
47
48
|
platform: "browser" | "node";
|
|
48
49
|
plugins?: readonly RouterCompatPlugin[] | undefined;
|
|
49
50
|
root?: string | undefined;
|
|
51
|
+
sourceRegionModulePaths?: ReadonlySet<string> | undefined;
|
|
50
52
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
51
53
|
sourceMap?: boolean | undefined;
|
|
52
54
|
target?: string | undefined;
|
|
@@ -351,7 +353,11 @@ async function bundleRouterModuleUncached(
|
|
|
351
353
|
|
|
352
354
|
return {
|
|
353
355
|
...(assets.length === 0 ? {} : { assets }),
|
|
354
|
-
code:
|
|
356
|
+
code: sanitizeBundleCode(
|
|
357
|
+
chunk.code,
|
|
358
|
+
options.sourceRegionModulePaths,
|
|
359
|
+
options.root ?? dirname(options.filename),
|
|
360
|
+
),
|
|
355
361
|
...(map !== undefined && typeof map.source === "string" ? { map: map.source } : {}),
|
|
356
362
|
};
|
|
357
363
|
}
|
|
@@ -448,7 +454,11 @@ export async function bundleRouterModules(
|
|
|
448
454
|
const map = mapAssets.get(`${chunk.fileName}.map`);
|
|
449
455
|
|
|
450
456
|
return {
|
|
451
|
-
code:
|
|
457
|
+
code: sanitizeBundleCode(
|
|
458
|
+
chunk.code,
|
|
459
|
+
options.sourceRegionModulePaths,
|
|
460
|
+
options.root ?? dirname(options.entries[0]?.filename ?? process.cwd()),
|
|
461
|
+
),
|
|
452
462
|
fileName: chunk.fileName,
|
|
453
463
|
imports: chunk.imports ?? [],
|
|
454
464
|
isEntry: chunk.isEntry === true,
|
|
@@ -577,6 +587,43 @@ function stripSourceMappingUrl(code: string): string {
|
|
|
577
587
|
return code.replace(/\n?\/\/# sourceMappingURL=[^\n]+\.map\s*$/u, "");
|
|
578
588
|
}
|
|
579
589
|
|
|
590
|
+
function sanitizeBundleCode(
|
|
591
|
+
code: string,
|
|
592
|
+
sourceRegionModulePaths: ReadonlySet<string> | undefined,
|
|
593
|
+
root: string,
|
|
594
|
+
): string {
|
|
595
|
+
const withoutSourceMapUrl = stripSourceMappingUrl(code);
|
|
596
|
+
|
|
597
|
+
if (sourceRegionModulePaths === undefined) {
|
|
598
|
+
return withoutSourceMapUrl;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const sourceRegionCandidates = new Set<string>();
|
|
602
|
+
|
|
603
|
+
for (const modulePath of sourceRegionModulePaths) {
|
|
604
|
+
sourceRegionCandidates.add(normalizeSourceRegionPath(modulePath));
|
|
605
|
+
sourceRegionCandidates.add(normalizeSourceRegionPath(relative(root, modulePath)));
|
|
606
|
+
sourceRegionCandidates.add(normalizeSourceRegionPath(relative(process.cwd(), modulePath)));
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return withoutSourceMapUrl.replace(
|
|
610
|
+
/^\/\/#region ([^\r\n]+)$/gmu,
|
|
611
|
+
(match, sourcePathWithQuery: string) => {
|
|
612
|
+
const queryIndex = sourcePathWithQuery.indexOf("?");
|
|
613
|
+
const sourcePath =
|
|
614
|
+
queryIndex === -1 ? sourcePathWithQuery : sourcePathWithQuery.slice(0, queryIndex);
|
|
615
|
+
|
|
616
|
+
return sourceRegionCandidates.has(normalizeSourceRegionPath(sourcePath))
|
|
617
|
+
? `//#region ${basename(normalizeSourceRegionPath(sourcePath))}${queryIndex === -1 ? "" : sourcePathWithQuery.slice(queryIndex)}`
|
|
618
|
+
: match;
|
|
619
|
+
},
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function normalizeSourceRegionPath(path: string): string {
|
|
624
|
+
return path.replaceAll("\\", "/");
|
|
625
|
+
}
|
|
626
|
+
|
|
580
627
|
function virtualEntryPlugin(entryId: string, code: string): VitePlugin {
|
|
581
628
|
return {
|
|
582
629
|
name: "mreact-router-virtual-entry",
|
package/src/cli-options.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface ParsedCliArguments {
|
|
|
21
21
|
help?: boolean | undefined;
|
|
22
22
|
host?: string | undefined;
|
|
23
23
|
hostPolicy?: RequestHostPolicy | undefined;
|
|
24
|
+
json?: boolean | undefined;
|
|
24
25
|
log?: CliRequestLogMode | undefined;
|
|
25
26
|
out?: string | undefined;
|
|
26
27
|
port?: number | undefined;
|
|
@@ -49,6 +50,14 @@ export function parseCliArguments(argv: readonly string[]): ParsedCliArguments {
|
|
|
49
50
|
continue;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if (value === "--json") {
|
|
54
|
+
if (command !== "boundaries") {
|
|
55
|
+
throw new Error("--json is only supported by the boundaries command");
|
|
56
|
+
}
|
|
57
|
+
parsed.json = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
if (value === "--log") {
|
|
53
62
|
parsed.log = parseCliRequestLogMode(readOptionValue(argv, index, "log"));
|
|
54
63
|
index += 1;
|
|
@@ -255,6 +264,29 @@ export function formatCliHelp(command?: string | undefined): string {
|
|
|
255
264
|
].join("\n");
|
|
256
265
|
}
|
|
257
266
|
|
|
267
|
+
if (command === "boundaries") {
|
|
268
|
+
return [
|
|
269
|
+
"Usage: mreact-router boundaries [appDir] [options]",
|
|
270
|
+
"",
|
|
271
|
+
"Inspect server and client component boundaries for every route without building.",
|
|
272
|
+
"",
|
|
273
|
+
"Project resolution:",
|
|
274
|
+
" Without appDir, load the same Vite project configuration as build.",
|
|
275
|
+
" With appDir, inspect that directory directly.",
|
|
276
|
+
"",
|
|
277
|
+
"Classifications:",
|
|
278
|
+
" server-render, client-boundary, client-route, server-only, shared, unknown",
|
|
279
|
+
"",
|
|
280
|
+
"Options:",
|
|
281
|
+
" --json Print a machine-readable versioned report.",
|
|
282
|
+
" -h, --help Show this help message.",
|
|
283
|
+
"",
|
|
284
|
+
"Examples:",
|
|
285
|
+
" mreact-router boundaries",
|
|
286
|
+
" mreact-router boundaries src/app --json",
|
|
287
|
+
].join("\n");
|
|
288
|
+
}
|
|
289
|
+
|
|
258
290
|
if (command === "package") {
|
|
259
291
|
return [
|
|
260
292
|
"Usage: mreact-router package <target> [options]",
|
|
@@ -335,6 +367,7 @@ export function formatCliHelp(command?: string | undefined): string {
|
|
|
335
367
|
"Commands:",
|
|
336
368
|
" dev [appDir] Start the development server.",
|
|
337
369
|
` build [appDir] Build ${defaultTargetLabel} artifacts by default.`,
|
|
370
|
+
" boundaries [appDir] Inspect route and component boundaries.",
|
|
338
371
|
" build --target=aws-lambda Build Lambda artifacts including generated handler and import policy.",
|
|
339
372
|
" start [outDir] Serve built Node output.",
|
|
340
373
|
" package aws-lambda --from .mreact --out .lambda",
|
|
@@ -477,7 +510,9 @@ function parseCliPort(value: string): number {
|
|
|
477
510
|
return port;
|
|
478
511
|
}
|
|
479
512
|
|
|
480
|
-
throw new Error(
|
|
513
|
+
throw new Error(
|
|
514
|
+
`Unsupported port ${JSON.stringify(value)}. Expected an integer from 0 to 65535.`,
|
|
515
|
+
);
|
|
481
516
|
}
|
|
482
517
|
|
|
483
518
|
function parseCliHostPolicy(value: string): RequestHostPolicy {
|
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
type BuildAppPhase,
|
|
10
10
|
type BuildAppProgressEvent,
|
|
11
11
|
} from "./build.js";
|
|
12
|
+
import {
|
|
13
|
+
analyzeAppBoundaries,
|
|
14
|
+
formatBoundaryReport,
|
|
15
|
+
formatBoundaryReportJson,
|
|
16
|
+
} from "./boundaries.js";
|
|
12
17
|
import {
|
|
13
18
|
buildTargetsFromCliTarget,
|
|
14
19
|
createCliRequestLogger,
|
|
@@ -70,6 +75,9 @@ if (parsed !== undefined) {
|
|
|
70
75
|
...(parsed.clientSourceMaps === undefined
|
|
71
76
|
? {}
|
|
72
77
|
: { clientSourceMaps: parsed.clientSourceMaps }),
|
|
78
|
+
onBoundaryReport(report) {
|
|
79
|
+
console.log(formatBoundaryReport(report).trimEnd());
|
|
80
|
+
},
|
|
73
81
|
onBuildProgress(event) {
|
|
74
82
|
activeBuildPhase = updateBuildProgressLog(event, activeBuildPhase);
|
|
75
83
|
},
|
|
@@ -90,6 +98,21 @@ if (parsed !== undefined) {
|
|
|
90
98
|
}
|
|
91
99
|
throw error;
|
|
92
100
|
}
|
|
101
|
+
} else if (command === "boundaries") {
|
|
102
|
+
const loaded =
|
|
103
|
+
routeArg === undefined
|
|
104
|
+
? await loadMreactRouterViteConfigDetails({ command: "build", cwd: process.cwd() })
|
|
105
|
+
: { project: { appDir: resolve(routeArg) }, viteConfig: undefined };
|
|
106
|
+
const report = await analyzeAppBoundaries({
|
|
107
|
+
...loaded.project,
|
|
108
|
+
viteConfig: loaded.viteConfig,
|
|
109
|
+
});
|
|
110
|
+
console.log(
|
|
111
|
+
(parsed.json === true
|
|
112
|
+
? formatBoundaryReportJson(report)
|
|
113
|
+
: formatBoundaryReport(report)
|
|
114
|
+
).trimEnd(),
|
|
115
|
+
);
|
|
93
116
|
} else if (command === "package") {
|
|
94
117
|
if (routeArg === "aws-lambda") {
|
|
95
118
|
const manifest = await packageAwsLambdaArtifact({
|
|
@@ -14,6 +14,9 @@ export {
|
|
|
14
14
|
resolveNavigationRuntime,
|
|
15
15
|
routeToClientManifestEntry,
|
|
16
16
|
type ClientReferenceImport,
|
|
17
|
+
type ClientRouteComponent,
|
|
18
|
+
type ClientRouteComponentClassification,
|
|
19
|
+
type ClientRouteComponentOrigin,
|
|
17
20
|
type ClientRouteInferenceCache,
|
|
18
21
|
type ClientRouteInferenceDiagnostic,
|
|
19
22
|
type ClientRouteInferenceResult,
|