@reckona/mreact-router 0.0.73 → 0.0.75
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/actions.d.ts +14 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +55 -44
- package/dist/actions.js.map +1 -1
- package/dist/build.d.ts +12 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +151 -52
- package/dist/build.js.map +1 -1
- package/dist/cli-options.d.ts +13 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +73 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +17 -8
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +2 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -0
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +8 -0
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +15 -1
- package/dist/route-source.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +6 -0
- package/dist/serve.js.map +1 -1
- package/dist/server-action-inference.d.ts +50 -0
- package/dist/server-action-inference.d.ts.map +1 -0
- package/dist/server-action-inference.js +488 -0
- package/dist/server-action-inference.js.map +1 -0
- package/package.json +12 -11
- package/src/actions.ts +92 -62
- package/src/build.ts +231 -71
- package/src/cli-options.ts +103 -0
- package/src/cli.ts +6 -0
- package/src/client.ts +14 -11
- package/src/index.ts +1 -0
- package/src/render.ts +9 -0
- package/src/route-source.ts +25 -0
- package/src/serve.ts +22 -0
- package/src/server-action-inference.ts +804 -0
package/dist/build.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { copyFile, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { builtinModules } from "node:module";
|
|
4
4
|
import { dirname, join, relative, sep } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { collectStaticImportReferences, collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
|
|
6
6
|
import { transformCompilerModuleContext } from "@reckona/mreact-compiler/internal";
|
|
7
7
|
import { buildClientRouteBatchOutput, buildNavigationRuntimeBundle, clientScriptForPath, compilerModuleContextForSource, collectClientRouteReferences, createClientRouteInferenceCache, detectClientNavigationHint, detectNavigationRuntimeHint, formatClientRouteInferenceDiagnostic, inferClientRouteModule, routeIdForPath, } from "./client.js";
|
|
8
8
|
import { bundleAppRouterSourceModule, importAppRouterSourceModule } from "./module-runner.js";
|
|
@@ -12,11 +12,12 @@ import { resolveAppRouterProjectOptions, resolveBuildTargets, } from "./config.j
|
|
|
12
12
|
import { routeCachePolicyFromSource } from "./cache.js";
|
|
13
13
|
import { bundleMiddlewareModuleCode, renderAppRequest } from "./render.js";
|
|
14
14
|
import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
|
|
15
|
-
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
15
|
+
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, routeClosureMayUseAwaitBoundary, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteClientSource, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
16
16
|
import { bundleRouterModule, } from "./bundle-pipeline.js";
|
|
17
17
|
import { collectRouteCssFiles } from "./route-styles.js";
|
|
18
18
|
import { existingRouteShellCandidates } from "./route-shells.js";
|
|
19
19
|
import { sourceModuleCandidates } from "./source-modules.js";
|
|
20
|
+
import { collectBuildInferredServerActions } from "./server-action-inference.js";
|
|
20
21
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
21
22
|
const nativeEscapeTransform = {
|
|
22
23
|
batchImportName: "escapeHtmlBatch",
|
|
@@ -48,7 +49,7 @@ export async function buildApp(options) {
|
|
|
48
49
|
const publicAssets = [
|
|
49
50
|
...new Set([...(await collectPublicAssetPaths(project.publicDir)), ...appConventionPublicAssets]),
|
|
50
51
|
].sort();
|
|
51
|
-
const serverActionManifest = collectBuildServerActionManifest({
|
|
52
|
+
const serverActionManifest = await collectBuildServerActionManifest({
|
|
52
53
|
files,
|
|
53
54
|
projectRoot: project.projectRoot,
|
|
54
55
|
routes,
|
|
@@ -64,7 +65,7 @@ export async function buildApp(options) {
|
|
|
64
65
|
routes,
|
|
65
66
|
vitePlugins,
|
|
66
67
|
});
|
|
67
|
-
const generatedImportPolicy = buildGeneratedImportPolicy({
|
|
68
|
+
const generatedImportPolicy = await buildGeneratedImportPolicy({
|
|
68
69
|
files,
|
|
69
70
|
projectRoot: project.projectRoot,
|
|
70
71
|
routes,
|
|
@@ -116,6 +117,7 @@ export async function buildApp(options) {
|
|
|
116
117
|
vitePlugins,
|
|
117
118
|
});
|
|
118
119
|
}
|
|
120
|
+
const routeServerActionReferences = Object.fromEntries([...serverActionManifest.routeReferences.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
119
121
|
const serverManifest = {
|
|
120
122
|
allowedSourceDirs: project.allowedSourceDirs.map((directory) => relative(project.projectRoot, directory)),
|
|
121
123
|
...(project.assetBaseUrl === undefined ? {} : { assetBaseUrl: project.assetBaseUrl }),
|
|
@@ -127,7 +129,12 @@ export async function buildApp(options) {
|
|
|
127
129
|
...(project.publicAssetBaseUrl === undefined
|
|
128
130
|
? {}
|
|
129
131
|
: { publicAssetBaseUrl: project.publicAssetBaseUrl }),
|
|
130
|
-
...(
|
|
132
|
+
...(Object.keys(routeServerActionReferences).length === 0
|
|
133
|
+
? {}
|
|
134
|
+
: { routeServerActionReferences }),
|
|
135
|
+
...(serverActionManifest.allowedActions.length === 0
|
|
136
|
+
? {}
|
|
137
|
+
: { serverActionManifest: serverActionManifest.allowedActions }),
|
|
131
138
|
...(Object.keys(serverModuleArtifacts.files).length === 0
|
|
132
139
|
? {}
|
|
133
140
|
: { serverModuleFiles: serverModuleArtifacts.files }),
|
|
@@ -250,15 +257,18 @@ const frameworkRuntimePackages = new Set([
|
|
|
250
257
|
"@reckona/mreact-router",
|
|
251
258
|
"@reckona/mreact-server",
|
|
252
259
|
]);
|
|
253
|
-
|
|
260
|
+
const maxRuntimePackageManifestReads = 1000;
|
|
261
|
+
async function buildGeneratedImportPolicy(options) {
|
|
254
262
|
const routePackages = new Map();
|
|
255
263
|
const allPackages = new Set();
|
|
256
264
|
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
265
|
+
const packageJsonLookupCache = new Map();
|
|
257
266
|
for (const route of options.routes) {
|
|
258
267
|
const file = relative(options.projectRoot, route.file);
|
|
259
|
-
const packages = collectRuntimePackagesForFile({
|
|
268
|
+
const packages = await collectRuntimePackagesForFile({
|
|
260
269
|
file,
|
|
261
270
|
files: options.files,
|
|
271
|
+
packageJsonLookupCache,
|
|
262
272
|
projectRoot: options.projectRoot,
|
|
263
273
|
seen: new Set(),
|
|
264
274
|
});
|
|
@@ -273,9 +283,10 @@ function buildGeneratedImportPolicy(options) {
|
|
|
273
283
|
.map((file) => (relativeRoutesDir === "" ? file : `${relativeRoutesDir}/${file}`))
|
|
274
284
|
.find((file) => options.files[file] !== undefined);
|
|
275
285
|
if (middlewareFile !== undefined) {
|
|
276
|
-
const packages = collectRuntimePackagesForFile({
|
|
286
|
+
const packages = await collectRuntimePackagesForFile({
|
|
277
287
|
file: middlewareFile,
|
|
278
288
|
files: options.files,
|
|
289
|
+
packageJsonLookupCache,
|
|
279
290
|
projectRoot: options.projectRoot,
|
|
280
291
|
seen: new Set(),
|
|
281
292
|
});
|
|
@@ -292,7 +303,7 @@ function buildGeneratedImportPolicy(options) {
|
|
|
292
303
|
version: 1,
|
|
293
304
|
};
|
|
294
305
|
}
|
|
295
|
-
function collectRuntimePackagesForFile(options) {
|
|
306
|
+
async function collectRuntimePackagesForFile(options) {
|
|
296
307
|
if (options.seen.has(options.file)) {
|
|
297
308
|
return [];
|
|
298
309
|
}
|
|
@@ -312,14 +323,22 @@ function collectRuntimePackagesForFile(options) {
|
|
|
312
323
|
filename: join(options.projectRoot, options.file),
|
|
313
324
|
})) {
|
|
314
325
|
if (isRuntimePackageSpecifier(reference.source)) {
|
|
315
|
-
|
|
326
|
+
const packageName = runtimePackageNameForSpecifier(reference.source);
|
|
327
|
+
packages.add(packageName);
|
|
328
|
+
for (const optionalPackageName of await collectRuntimeOptionalPackages({
|
|
329
|
+
packageJsonLookupCache: options.packageJsonLookupCache,
|
|
330
|
+
packageName,
|
|
331
|
+
projectRoot: options.projectRoot,
|
|
332
|
+
})) {
|
|
333
|
+
packages.add(optionalPackageName);
|
|
334
|
+
}
|
|
316
335
|
continue;
|
|
317
336
|
}
|
|
318
337
|
const localFile = resolveBuildLocalSourceImport(options.files, options.file, reference.source);
|
|
319
338
|
if (localFile === undefined) {
|
|
320
339
|
continue;
|
|
321
340
|
}
|
|
322
|
-
for (const packageName of collectRuntimePackagesForFile({
|
|
341
|
+
for (const packageName of await collectRuntimePackagesForFile({
|
|
323
342
|
...options,
|
|
324
343
|
file: localFile,
|
|
325
344
|
})) {
|
|
@@ -329,12 +348,99 @@ function collectRuntimePackagesForFile(options) {
|
|
|
329
348
|
options.seen.delete(options.file);
|
|
330
349
|
return [...packages].sort();
|
|
331
350
|
}
|
|
351
|
+
async function collectRuntimeOptionalPackages(options) {
|
|
352
|
+
const optionalPackages = new Set();
|
|
353
|
+
const seenPackageJson = new Set();
|
|
354
|
+
const queue = [
|
|
355
|
+
{ packageName: options.packageName, optional: false, startDir: options.projectRoot },
|
|
356
|
+
];
|
|
357
|
+
for (let index = 0; index < queue.length && seenPackageJson.size < maxRuntimePackageManifestReads; index += 1) {
|
|
358
|
+
const item = queue[index];
|
|
359
|
+
if (item === undefined || !isValidRuntimePackageName(item.packageName)) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const manifest = await readRuntimePackageManifest({
|
|
363
|
+
cache: options.packageJsonLookupCache,
|
|
364
|
+
packageName: item.packageName,
|
|
365
|
+
startDir: item.startDir,
|
|
366
|
+
});
|
|
367
|
+
if (manifest === undefined || seenPackageJson.has(manifest.packageJsonPath)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
seenPackageJson.add(manifest.packageJsonPath);
|
|
371
|
+
if (item.optional && isRuntimePackageName(item.packageName)) {
|
|
372
|
+
optionalPackages.add(item.packageName);
|
|
373
|
+
}
|
|
374
|
+
const packageDir = dirname(manifest.packageJsonPath);
|
|
375
|
+
for (const dependencyName of Object.keys(manifest.dependencies ?? {})) {
|
|
376
|
+
if (isValidRuntimePackageName(dependencyName)) {
|
|
377
|
+
queue.push({ packageName: dependencyName, optional: false, startDir: packageDir });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const optionalDependencyName of Object.keys(manifest.optionalDependencies ?? {})) {
|
|
381
|
+
if (isValidRuntimePackageName(optionalDependencyName)) {
|
|
382
|
+
queue.push({ packageName: optionalDependencyName, optional: true, startDir: packageDir });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return [...optionalPackages].sort();
|
|
387
|
+
}
|
|
388
|
+
async function readRuntimePackageManifest(options) {
|
|
389
|
+
const key = `${options.startDir}\0${options.packageName}`;
|
|
390
|
+
const cached = options.cache.get(key);
|
|
391
|
+
const manifest = cached ??
|
|
392
|
+
findRuntimePackageJson(options.packageName, options.startDir).then(async (packageJsonPath) => {
|
|
393
|
+
if (packageJsonPath === undefined) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const json = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
398
|
+
return { ...json, packageJsonPath };
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
if (cached === undefined) {
|
|
405
|
+
options.cache.set(key, manifest);
|
|
406
|
+
}
|
|
407
|
+
return (await manifest);
|
|
408
|
+
}
|
|
409
|
+
async function findRuntimePackageJson(packageName, startDir) {
|
|
410
|
+
if (!isValidRuntimePackageName(packageName)) {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
let current = startDir;
|
|
414
|
+
while (true) {
|
|
415
|
+
const candidate = join(current, "node_modules", ...packageName.split("/"), "package.json");
|
|
416
|
+
try {
|
|
417
|
+
if ((await stat(candidate)).isFile()) {
|
|
418
|
+
return candidate;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
// Keep walking toward the filesystem root.
|
|
423
|
+
}
|
|
424
|
+
const parent = dirname(current);
|
|
425
|
+
if (parent === current) {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
current = parent;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
332
431
|
function isRuntimePackageSpecifier(specifier) {
|
|
333
432
|
if (specifier.startsWith(".") || specifier.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)) {
|
|
334
433
|
return false;
|
|
335
434
|
}
|
|
336
435
|
const packageName = runtimePackageNameForSpecifier(specifier);
|
|
337
|
-
return !nodeBuiltinPackages.has(specifier) &&
|
|
436
|
+
return !nodeBuiltinPackages.has(specifier) && isRuntimePackageName(packageName);
|
|
437
|
+
}
|
|
438
|
+
function isRuntimePackageName(packageName) {
|
|
439
|
+
return !frameworkRuntimePackages.has(packageName);
|
|
440
|
+
}
|
|
441
|
+
function isValidRuntimePackageName(packageName) {
|
|
442
|
+
return (/^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/u.test(packageName) &&
|
|
443
|
+
!packageName.includes(".."));
|
|
338
444
|
}
|
|
339
445
|
function runtimePackageNameForSpecifier(specifier) {
|
|
340
446
|
if (!specifier.startsWith("@")) {
|
|
@@ -343,8 +449,9 @@ function runtimePackageNameForSpecifier(specifier) {
|
|
|
343
449
|
const [scope, name] = specifier.split("/");
|
|
344
450
|
return scope !== undefined && name !== undefined ? `${scope}/${name}` : specifier;
|
|
345
451
|
}
|
|
346
|
-
function collectBuildServerActionManifest(options) {
|
|
452
|
+
async function collectBuildServerActionManifest(options) {
|
|
347
453
|
const entries = new Map();
|
|
454
|
+
const routeReferences = new Map();
|
|
348
455
|
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
349
456
|
const routeSourceFiles = new Set(options.routes
|
|
350
457
|
.filter((route) => route.kind === "page")
|
|
@@ -360,56 +467,46 @@ function collectBuildServerActionManifest(options) {
|
|
|
360
467
|
}
|
|
361
468
|
}
|
|
362
469
|
if (routeSourceFiles.has(file)) {
|
|
363
|
-
|
|
470
|
+
const inference = await collectBuildInferredServerActionReferences({
|
|
364
471
|
file,
|
|
365
472
|
files: options.files,
|
|
366
473
|
relativeRoutesDir,
|
|
367
474
|
source: code,
|
|
368
|
-
})
|
|
475
|
+
});
|
|
476
|
+
for (const diagnostic of inference.diagnostics) {
|
|
477
|
+
console.warn(formatServerActionInferenceDiagnostic(diagnostic));
|
|
478
|
+
}
|
|
479
|
+
routeReferences.set(file, inference.references);
|
|
480
|
+
for (const reference of inference.references) {
|
|
369
481
|
const key = `${reference.moduleId}#${reference.exportName}`;
|
|
370
482
|
if (!entries.has(key)) {
|
|
371
|
-
entries.set(key,
|
|
483
|
+
entries.set(key, {
|
|
484
|
+
exportName: reference.exportName,
|
|
485
|
+
inferred: reference.inferred,
|
|
486
|
+
moduleId: reference.moduleId,
|
|
487
|
+
});
|
|
372
488
|
}
|
|
373
489
|
}
|
|
374
490
|
}
|
|
375
491
|
}
|
|
376
|
-
return
|
|
377
|
-
|
|
378
|
-
|
|
492
|
+
return {
|
|
493
|
+
allowedActions: [...entries.values()].sort((left, right) => left.moduleId === right.moduleId
|
|
494
|
+
? left.exportName.localeCompare(right.exportName)
|
|
495
|
+
: left.moduleId.localeCompare(right.moduleId)),
|
|
496
|
+
routeReferences,
|
|
497
|
+
};
|
|
379
498
|
}
|
|
380
499
|
function collectBuildInferredServerActionReferences(options) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const specifiers = match.groups?.specifiers;
|
|
389
|
-
if (source === undefined || specifiers === undefined || !source.startsWith(".")) {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const localFile = resolveBuildLocalSourceImport(options.files, options.file, source);
|
|
393
|
-
if (localFile === undefined) {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const moduleId = moduleIdForBuildFile(localFile, options.relativeRoutesDir);
|
|
397
|
-
for (const specifier of specifiers.split(",")) {
|
|
398
|
-
const [exportName, localName] = specifier.trim().split(/\s+as\s+/);
|
|
399
|
-
const imported = exportName?.trim();
|
|
400
|
-
const local = localName?.trim() ?? imported;
|
|
401
|
-
if (imported !== undefined &&
|
|
402
|
-
imported.length > 0 &&
|
|
403
|
-
local !== undefined &&
|
|
404
|
-
actionNames.has(local)) {
|
|
405
|
-
references.push({ moduleId, exportName: imported, inferred: true });
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return references;
|
|
500
|
+
return collectBuildInferredServerActions({
|
|
501
|
+
file: options.file,
|
|
502
|
+
files: options.files,
|
|
503
|
+
relativeRoutesDir: options.relativeRoutesDir,
|
|
504
|
+
resolveSourceImport: (importer, source) => resolveBuildLocalSourceImport(options.files, importer, source),
|
|
505
|
+
source: options.source,
|
|
506
|
+
});
|
|
410
507
|
}
|
|
411
|
-
function
|
|
412
|
-
return
|
|
508
|
+
function formatServerActionInferenceDiagnostic(diagnostic) {
|
|
509
|
+
return `${diagnostic.code}: ${diagnostic.message}`;
|
|
413
510
|
}
|
|
414
511
|
function isSourceModuleFile(file) {
|
|
415
512
|
return /\.(?:mreact\.tsx|tsx?|jsx?|mjs|mts|cjs|cts)$/.test(file);
|
|
@@ -667,7 +764,9 @@ async function buildServerModuleArtifacts(options) {
|
|
|
667
764
|
const clientInference = await inferClientRouteModule({
|
|
668
765
|
...(route === undefined ? {} : { appDir: options.project.routesDir }),
|
|
669
766
|
cache: options.clientRouteInferenceCache,
|
|
670
|
-
code:
|
|
767
|
+
code: route === undefined
|
|
768
|
+
? stripRouteClientOnlyExports(source)
|
|
769
|
+
: stripRouteClientSource({ code: source, filename: route.file }),
|
|
671
770
|
filename: join(options.projectRoot, file),
|
|
672
771
|
...(route === undefined ? {} : { routePath: route.path }),
|
|
673
772
|
vitePlugins: options.vitePlugins,
|
|
@@ -1781,7 +1880,7 @@ async function writeClientRouteBundles(options) {
|
|
|
1781
1880
|
vitePlugins: options.vitePlugins,
|
|
1782
1881
|
});
|
|
1783
1882
|
const source = await readFile(route.file, "utf8");
|
|
1784
|
-
const clientSource =
|
|
1883
|
+
const clientSource = stripRouteClientSource({ code: source, filename: route.file });
|
|
1785
1884
|
const navigation = detectNavigationRuntimeHint(source);
|
|
1786
1885
|
const references = await collectClientRouteReferences({
|
|
1787
1886
|
appDir: options.appDir,
|