@reckona/mreact-router 0.0.26 → 0.0.28
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 +11 -15
- package/dist/actions.js +7 -10
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +5 -1
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +50 -4
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +20 -2
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +57 -13
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/build.d.ts +20 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +482 -49
- package/dist/build.js.map +1 -1
- package/dist/bundle-pipeline.d.ts +50 -0
- package/dist/bundle-pipeline.d.ts.map +1 -0
- package/dist/bundle-pipeline.js +211 -0
- package/dist/bundle-pipeline.js.map +1 -0
- package/dist/cli-options.d.ts +4 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +110 -6
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +54 -39
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +9 -29
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +9 -31
- package/dist/module-runner.js.map +1 -1
- package/dist/native-route-matcher.d.ts.map +1 -1
- package/dist/native-route-matcher.js +13 -1
- package/dist/native-route-matcher.js.map +1 -1
- package/dist/navigation.d.ts +7 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +27 -0
- package/dist/navigation.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +28 -55
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +1 -0
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +4 -1
- package/dist/route-source.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -9
package/dist/build.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
|
|
2
|
+
import { access, cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { builtinModules } from "node:module";
|
|
4
|
+
import { dirname, extname, join, relative, sep } from "node:path";
|
|
5
|
+
import { collectJsxComponentRootNames, collectStaticImportReferences, collectTopLevelValueExportNames, formatDiagnostic, hasModuleDirective, transform, } from "@reckona/mreact-compiler";
|
|
6
6
|
import { buildClientRouteOutput, buildNavigationRuntimeBundle, clientScriptForPath, createClientRouteInferenceCache, detectClientNavigationHint, detectNavigationRuntimeHint, formatClientRouteInferenceDiagnostic, inferClientRouteModule, isClientRouteModule, routeIdForPath, } from "./client.js";
|
|
7
7
|
import { bundleAppRouterSourceModule, importAppRouterSourceModule } from "./module-runner.js";
|
|
8
8
|
import { scanAppRoutes } from "./routes.js";
|
|
@@ -10,7 +10,8 @@ import { resolveAppRouterProjectOptions, resolveBuildTargets, } from "./config.j
|
|
|
10
10
|
import { routeCachePolicyFromSource } from "./cache.js";
|
|
11
11
|
import { bundleMiddlewareModuleCode, renderAppRequest } from "./render.js";
|
|
12
12
|
import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
|
|
13
|
-
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
13
|
+
import { hasGenerateStaticParamsExport, hasLoaderExport, hasPrerenderExport, isStreamRouteSource, mayUseAwaitBoundarySource, stripRouteBuildExports, stripRouteClientOnlyExports, stripRouteLoaderOnlyExports, stripRouteMetadataOnlyExports, stripRouteRequestOnlyExports, } from "./route-source.js";
|
|
14
|
+
import { bundleRouterModule, } from "./bundle-pipeline.js";
|
|
14
15
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
15
16
|
const nativeEscapeTransform = {
|
|
16
17
|
batchImportName: "escapeHtmlBatch",
|
|
@@ -20,11 +21,13 @@ export async function buildApp(options) {
|
|
|
20
21
|
const project = resolveAppRouterProjectOptions(options);
|
|
21
22
|
const buildTargets = resolveBuildTargets(options.targets ?? project.buildTargets);
|
|
22
23
|
const shouldBuildCloudflare = buildTargets.includes("cloudflare");
|
|
24
|
+
const shouldBuildAwsLambda = buildTargets.includes("aws-lambda");
|
|
23
25
|
const routes = await scanAppRoutes({ appDir: project.routesDir });
|
|
26
|
+
const files = await collectBuildFiles(project.projectRoot, project.allowedSourceDirs);
|
|
24
27
|
const serverDir = join(options.outDir, "server");
|
|
25
28
|
const clientDir = join(options.outDir, "client");
|
|
26
29
|
const cloudflareDir = join(options.outDir, "cloudflare");
|
|
27
|
-
await validateProductionRoutes(routes);
|
|
30
|
+
await validateProductionRoutes({ files, projectRoot: project.projectRoot, routes });
|
|
28
31
|
await rm(options.outDir, { force: true, recursive: true });
|
|
29
32
|
await mkdir(serverDir, { recursive: true });
|
|
30
33
|
await mkdir(clientDir, { recursive: true });
|
|
@@ -34,7 +37,8 @@ export async function buildApp(options) {
|
|
|
34
37
|
await mkdir(join(clientDir, ".vite"), { recursive: true });
|
|
35
38
|
await mkdir(join(clientDir, "assets", "routes"), { recursive: true });
|
|
36
39
|
await copyPublicAssets(project.publicDir, join(clientDir, "public"));
|
|
37
|
-
|
|
40
|
+
await copyPublicAssets(project.publicDir, clientDir);
|
|
41
|
+
const publicAssets = await collectPublicAssetPaths(project.publicDir);
|
|
38
42
|
const serverActionManifest = collectBuildServerActionManifest({
|
|
39
43
|
files,
|
|
40
44
|
projectRoot: project.projectRoot,
|
|
@@ -48,6 +52,12 @@ export async function buildApp(options) {
|
|
|
48
52
|
projectRoot: project.projectRoot,
|
|
49
53
|
routes,
|
|
50
54
|
});
|
|
55
|
+
const generatedImportPolicy = buildGeneratedImportPolicy({
|
|
56
|
+
files,
|
|
57
|
+
projectRoot: project.projectRoot,
|
|
58
|
+
routes,
|
|
59
|
+
routesDir: project.routesDir,
|
|
60
|
+
});
|
|
51
61
|
const serverModuleFiles = await writeServerModuleArtifactFiles(serverDir, serverModules);
|
|
52
62
|
const serverRoutes = routes.map((route) => ({
|
|
53
63
|
...route,
|
|
@@ -74,9 +84,11 @@ export async function buildApp(options) {
|
|
|
74
84
|
clientRoutes: clientManifestRoutes,
|
|
75
85
|
routes,
|
|
76
86
|
});
|
|
87
|
+
let cloudflareRouteModules;
|
|
77
88
|
if (shouldBuildCloudflare) {
|
|
78
|
-
await writeCloudflareRouteModules({
|
|
89
|
+
cloudflareRouteModules = await writeCloudflareRouteModules({
|
|
79
90
|
cloudflareDir,
|
|
91
|
+
files,
|
|
80
92
|
prerenderedRoutes,
|
|
81
93
|
projectRoot: project.projectRoot,
|
|
82
94
|
routesDir: project.routesDir,
|
|
@@ -84,7 +96,7 @@ export async function buildApp(options) {
|
|
|
84
96
|
serverModules,
|
|
85
97
|
});
|
|
86
98
|
}
|
|
87
|
-
|
|
99
|
+
const serverManifest = {
|
|
88
100
|
allowedSourceDirs: project.allowedSourceDirs.map((directory) => relative(project.projectRoot, directory)),
|
|
89
101
|
...(project.assetBaseUrl === undefined ? {} : { assetBaseUrl: project.assetBaseUrl }),
|
|
90
102
|
version: 1,
|
|
@@ -97,9 +109,26 @@ export async function buildApp(options) {
|
|
|
97
109
|
: { publicAssetBaseUrl: project.publicAssetBaseUrl }),
|
|
98
110
|
...(serverActionManifest.length === 0 ? {} : { serverActionManifest }),
|
|
99
111
|
...(Object.keys(serverModuleFiles).length === 0 ? {} : { serverModuleFiles }),
|
|
100
|
-
}
|
|
101
|
-
|
|
112
|
+
};
|
|
113
|
+
const clientManifest = {
|
|
114
|
+
...(publicAssets.length === 0 ? {} : { publicAssets }),
|
|
115
|
+
routes: clientManifestRoutes,
|
|
116
|
+
};
|
|
117
|
+
await writeFile(join(serverDir, "manifest.json"), JSON.stringify(serverManifest, null, 2));
|
|
118
|
+
await writeFile(join(serverDir, "import-policy.json"), JSON.stringify(generatedImportPolicy, null, 2));
|
|
119
|
+
await writeFile(join(clientDir, "manifest.json"), JSON.stringify(clientManifest, null, 2));
|
|
102
120
|
await writeFile(join(clientDir, ".vite", "manifest.json"), JSON.stringify(viteManifestFromClientRoutes(clientManifestRoutes), null, 2));
|
|
121
|
+
if (shouldBuildAwsLambda) {
|
|
122
|
+
await writeAwsLambdaHandlerArtifact(options.outDir);
|
|
123
|
+
}
|
|
124
|
+
if (shouldBuildCloudflare) {
|
|
125
|
+
await writeCloudflareWorkerArtifact({
|
|
126
|
+
cloudflareDir,
|
|
127
|
+
clientManifest,
|
|
128
|
+
modulesFile: cloudflareRouteModules?.registryFile ?? "route-modules.mjs",
|
|
129
|
+
serverManifest,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
103
132
|
return { routes };
|
|
104
133
|
}
|
|
105
134
|
async function writeServerModuleArtifactFiles(serverDir, serverModules) {
|
|
@@ -114,6 +143,104 @@ async function writeServerModuleArtifactFiles(serverDir, serverModules) {
|
|
|
114
143
|
}
|
|
115
144
|
return files;
|
|
116
145
|
}
|
|
146
|
+
const nodeBuiltinPackages = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));
|
|
147
|
+
const frameworkRuntimePackages = new Set([
|
|
148
|
+
"@reckona/mreact",
|
|
149
|
+
"@reckona/mreact-auth",
|
|
150
|
+
"@reckona/mreact-compiler",
|
|
151
|
+
"@reckona/mreact-query",
|
|
152
|
+
"@reckona/mreact-reactive-core",
|
|
153
|
+
"@reckona/mreact-router",
|
|
154
|
+
"@reckona/mreact-server",
|
|
155
|
+
]);
|
|
156
|
+
function buildGeneratedImportPolicy(options) {
|
|
157
|
+
const routePackages = new Map();
|
|
158
|
+
const allPackages = new Set();
|
|
159
|
+
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
160
|
+
for (const route of options.routes) {
|
|
161
|
+
const file = relative(options.projectRoot, route.file);
|
|
162
|
+
const packages = collectRuntimePackagesForFile({
|
|
163
|
+
file,
|
|
164
|
+
files: options.files,
|
|
165
|
+
projectRoot: options.projectRoot,
|
|
166
|
+
seen: new Set(),
|
|
167
|
+
});
|
|
168
|
+
if (packages.length > 0) {
|
|
169
|
+
routePackages.set(route.path, packages);
|
|
170
|
+
for (const packageName of packages) {
|
|
171
|
+
allPackages.add(packageName);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const middlewareFile = ["middleware.ts", "middleware.mreact.ts"]
|
|
176
|
+
.map((file) => (relativeRoutesDir === "" ? file : `${relativeRoutesDir}/${file}`))
|
|
177
|
+
.find((file) => options.files[file] !== undefined);
|
|
178
|
+
if (middlewareFile !== undefined) {
|
|
179
|
+
const packages = collectRuntimePackagesForFile({
|
|
180
|
+
file: middlewareFile,
|
|
181
|
+
files: options.files,
|
|
182
|
+
projectRoot: options.projectRoot,
|
|
183
|
+
seen: new Set(),
|
|
184
|
+
});
|
|
185
|
+
if (packages.length > 0) {
|
|
186
|
+
routePackages.set("middleware", packages);
|
|
187
|
+
for (const packageName of packages) {
|
|
188
|
+
allPackages.add(packageName);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
byRoute: Object.fromEntries([...routePackages.entries()].sort(([left], [right]) => left.localeCompare(right))),
|
|
194
|
+
runtimePackages: [...allPackages].sort(),
|
|
195
|
+
version: 1,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function collectRuntimePackagesForFile(options) {
|
|
199
|
+
if (options.seen.has(options.file)) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
const source = options.files[options.file];
|
|
203
|
+
if (source === undefined || hasModuleDirective({ code: source, directive: "use client" })) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
options.seen.add(options.file);
|
|
207
|
+
const packages = new Set();
|
|
208
|
+
for (const reference of collectStaticImportReferences({
|
|
209
|
+
code: source,
|
|
210
|
+
filename: join(options.projectRoot, options.file),
|
|
211
|
+
})) {
|
|
212
|
+
if (isRuntimePackageSpecifier(reference.source)) {
|
|
213
|
+
packages.add(runtimePackageNameForSpecifier(reference.source));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const localFile = resolveBuildLocalSourceImport(options.files, options.file, reference.source);
|
|
217
|
+
if (localFile === undefined) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
for (const packageName of collectRuntimePackagesForFile({
|
|
221
|
+
...options,
|
|
222
|
+
file: localFile,
|
|
223
|
+
})) {
|
|
224
|
+
packages.add(packageName);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
options.seen.delete(options.file);
|
|
228
|
+
return [...packages].sort();
|
|
229
|
+
}
|
|
230
|
+
function isRuntimePackageSpecifier(specifier) {
|
|
231
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(specifier)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const packageName = runtimePackageNameForSpecifier(specifier);
|
|
235
|
+
return !nodeBuiltinPackages.has(specifier) && !frameworkRuntimePackages.has(packageName);
|
|
236
|
+
}
|
|
237
|
+
function runtimePackageNameForSpecifier(specifier) {
|
|
238
|
+
if (!specifier.startsWith("@")) {
|
|
239
|
+
return specifier.split("/")[0] ?? specifier;
|
|
240
|
+
}
|
|
241
|
+
const [scope, name] = specifier.split("/");
|
|
242
|
+
return scope !== undefined && name !== undefined ? `${scope}/${name}` : specifier;
|
|
243
|
+
}
|
|
117
244
|
function collectBuildServerActionManifest(options) {
|
|
118
245
|
const entries = [];
|
|
119
246
|
const relativeRoutesDir = relative(options.projectRoot, options.routesDir);
|
|
@@ -153,6 +280,36 @@ async function copyPublicAssets(publicDir, outDir) {
|
|
|
153
280
|
throw error;
|
|
154
281
|
}
|
|
155
282
|
}
|
|
283
|
+
async function collectPublicAssetPaths(publicDir) {
|
|
284
|
+
try {
|
|
285
|
+
const info = await stat(publicDir);
|
|
286
|
+
if (!info.isDirectory()) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
const paths = [];
|
|
297
|
+
await collectPublicAssetPathsInner(publicDir, "", paths);
|
|
298
|
+
return paths.sort();
|
|
299
|
+
}
|
|
300
|
+
async function collectPublicAssetPathsInner(publicDir, relativeDir, paths) {
|
|
301
|
+
const entries = await readdir(join(publicDir, relativeDir), { withFileTypes: true });
|
|
302
|
+
for (const entry of entries) {
|
|
303
|
+
const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
|
|
304
|
+
if (entry.isDirectory()) {
|
|
305
|
+
await collectPublicAssetPathsInner(publicDir, relativePath, paths);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (entry.isFile()) {
|
|
309
|
+
paths.push(`/${relativePath}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
156
313
|
function isNodeError(error) {
|
|
157
314
|
return error instanceof Error && "code" in error;
|
|
158
315
|
}
|
|
@@ -300,9 +457,14 @@ async function buildServerModuleArtifacts(options) {
|
|
|
300
457
|
}
|
|
301
458
|
continue;
|
|
302
459
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
460
|
+
const streamRoute = route !== undefined &&
|
|
461
|
+
shouldBuildRouteAsStream({
|
|
462
|
+
filename: file,
|
|
463
|
+
files: options.files,
|
|
464
|
+
projectRoot: options.projectRoot,
|
|
465
|
+
source,
|
|
466
|
+
});
|
|
467
|
+
const serverOutputs = streamRoute ? ["stream", "string"] : ["string"];
|
|
306
468
|
const code = route === undefined ? source : stripRouteBuildExports(source);
|
|
307
469
|
const clientInference = route === undefined
|
|
308
470
|
? { client: false, clientBoundaryImports: [], diagnostics: [] }
|
|
@@ -323,6 +485,7 @@ async function buildServerModuleArtifacts(options) {
|
|
|
323
485
|
route,
|
|
324
486
|
routeCode: code,
|
|
325
487
|
source,
|
|
488
|
+
streamRoute,
|
|
326
489
|
});
|
|
327
490
|
}
|
|
328
491
|
for (const serverOutput of serverOutputs) {
|
|
@@ -337,6 +500,12 @@ async function buildServerModuleArtifacts(options) {
|
|
|
337
500
|
});
|
|
338
501
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
339
502
|
if (fatalDiagnostics.length > 0) {
|
|
503
|
+
if (serverOutput === "string" &&
|
|
504
|
+
streamRoute &&
|
|
505
|
+
route?.kind === "page" &&
|
|
506
|
+
fatalDiagnostics.every((diagnostic) => diagnostic.code === "MR_UNSUPPORTED_AWAIT_INNER_COMPONENT")) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
340
509
|
throw new Error(fatalDiagnostics.map((diagnostic) => formatDiagnostic(file, diagnostic)).join("\n"));
|
|
341
510
|
}
|
|
342
511
|
artifact[serverOutput] = {
|
|
@@ -360,10 +529,124 @@ function builtRouteSourceAnalysisSummary(options) {
|
|
|
360
529
|
routeCode: options.routeCode,
|
|
361
530
|
routePath: options.route.path,
|
|
362
531
|
sourceHash: hashText(options.source),
|
|
363
|
-
streamRoute:
|
|
532
|
+
streamRoute: options.streamRoute,
|
|
364
533
|
usesRuntimeCacheControl: usesRuntimeCacheControl(options.source),
|
|
365
534
|
};
|
|
366
535
|
}
|
|
536
|
+
function shouldBuildRouteAsStream(options) {
|
|
537
|
+
return (isStreamRouteSource(options.source) ||
|
|
538
|
+
routeClosureMayUseAwaitBoundary({
|
|
539
|
+
filename: options.filename,
|
|
540
|
+
files: options.files,
|
|
541
|
+
projectRoot: options.projectRoot,
|
|
542
|
+
source: options.source,
|
|
543
|
+
seen: new Set(),
|
|
544
|
+
}));
|
|
545
|
+
}
|
|
546
|
+
function routeClosureMayUseAwaitBoundary(options) {
|
|
547
|
+
if (options.seen.has(options.filename) ||
|
|
548
|
+
hasModuleDirective({ code: options.source, directive: "use client" })) {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
options.seen.add(options.filename);
|
|
552
|
+
try {
|
|
553
|
+
if (mayUseAwaitBoundarySource(options.source)) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
const jsxComponentRoots = new Set(collectJsxComponentRootNames({
|
|
557
|
+
code: options.source,
|
|
558
|
+
filename: join(options.projectRoot, options.filename),
|
|
559
|
+
}));
|
|
560
|
+
for (const reference of collectStaticImportReferences({
|
|
561
|
+
code: options.source,
|
|
562
|
+
filename: join(options.projectRoot, options.filename),
|
|
563
|
+
})) {
|
|
564
|
+
if (!isRenderedStaticImportReference(reference, jsxComponentRoots)) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const resolved = resolveBuildLocalSourceImport(options.files, options.filename, reference.source);
|
|
568
|
+
if (resolved === undefined) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const importedSource = options.files[resolved];
|
|
572
|
+
if (importedSource !== undefined &&
|
|
573
|
+
routeClosureMayUseAwaitBoundary({
|
|
574
|
+
filename: resolved,
|
|
575
|
+
files: options.files,
|
|
576
|
+
projectRoot: options.projectRoot,
|
|
577
|
+
seen: options.seen,
|
|
578
|
+
source: importedSource,
|
|
579
|
+
})) {
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
finally {
|
|
586
|
+
options.seen.delete(options.filename);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function isRenderedStaticImportReference(reference, jsxComponentRoots) {
|
|
590
|
+
return reference.localNames.some((localName) => jsxComponentRoots.has(localName));
|
|
591
|
+
}
|
|
592
|
+
function resolveBuildLocalSourceImport(files, importer, specifier) {
|
|
593
|
+
if (!specifier.startsWith(".")) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
const base = join(dirname(importer), specifier);
|
|
597
|
+
for (const candidate of buildSourceModuleCandidates(base)) {
|
|
598
|
+
if (files[candidate] !== undefined) {
|
|
599
|
+
return candidate;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
function buildSourceModuleCandidates(base) {
|
|
605
|
+
if (hasSourceModuleExtension(base)) {
|
|
606
|
+
return [base, ...typescriptSourceModuleCandidates(base)];
|
|
607
|
+
}
|
|
608
|
+
if (extname(base) !== "") {
|
|
609
|
+
return [];
|
|
610
|
+
}
|
|
611
|
+
return [
|
|
612
|
+
`${base}.ts`,
|
|
613
|
+
`${base}.tsx`,
|
|
614
|
+
`${base}.mreact.tsx`,
|
|
615
|
+
`${base}.js`,
|
|
616
|
+
`${base}.jsx`,
|
|
617
|
+
`${base}.mjs`,
|
|
618
|
+
`${base}.mts`,
|
|
619
|
+
`${base}.cjs`,
|
|
620
|
+
`${base}.cts`,
|
|
621
|
+
join(base, "index.ts"),
|
|
622
|
+
join(base, "index.tsx"),
|
|
623
|
+
join(base, "index.mreact.tsx"),
|
|
624
|
+
join(base, "index.js"),
|
|
625
|
+
join(base, "index.jsx"),
|
|
626
|
+
join(base, "index.mjs"),
|
|
627
|
+
join(base, "index.mts"),
|
|
628
|
+
join(base, "index.cjs"),
|
|
629
|
+
join(base, "index.cts"),
|
|
630
|
+
];
|
|
631
|
+
}
|
|
632
|
+
function hasSourceModuleExtension(path) {
|
|
633
|
+
return /\.(?:mreact\.tsx|tsx?|jsx?|mjs|mts|cjs|cts)$/.test(path);
|
|
634
|
+
}
|
|
635
|
+
function typescriptSourceModuleCandidates(path) {
|
|
636
|
+
if (path.endsWith(".js")) {
|
|
637
|
+
return [`${path.slice(0, -3)}.ts`, `${path.slice(0, -3)}.tsx`];
|
|
638
|
+
}
|
|
639
|
+
if (path.endsWith(".jsx")) {
|
|
640
|
+
return [`${path.slice(0, -4)}.tsx`];
|
|
641
|
+
}
|
|
642
|
+
if (path.endsWith(".mjs")) {
|
|
643
|
+
return [`${path.slice(0, -4)}.mts`];
|
|
644
|
+
}
|
|
645
|
+
if (path.endsWith(".cjs")) {
|
|
646
|
+
return [`${path.slice(0, -4)}.cts`];
|
|
647
|
+
}
|
|
648
|
+
return [];
|
|
649
|
+
}
|
|
367
650
|
function usesRuntimeCacheControl(code) {
|
|
368
651
|
return /\bcacheControl\s*\(/.test(code);
|
|
369
652
|
}
|
|
@@ -407,10 +690,9 @@ async function bundleRouteMetadataModuleCode(options) {
|
|
|
407
690
|
});
|
|
408
691
|
}
|
|
409
692
|
async function bundleRouteRequestModuleCode(options) {
|
|
410
|
-
const output = await
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
logLevel: "silent",
|
|
693
|
+
const output = await bundleRouterModule({
|
|
694
|
+
code: options.code,
|
|
695
|
+
filename: options.filename,
|
|
414
696
|
platform: "node",
|
|
415
697
|
plugins: [
|
|
416
698
|
createAppRouterImportPolicyPlugin({
|
|
@@ -419,18 +701,8 @@ async function bundleRouteRequestModuleCode(options) {
|
|
|
419
701
|
label: options.label,
|
|
420
702
|
}),
|
|
421
703
|
],
|
|
422
|
-
write: false,
|
|
423
|
-
jsx: "transform",
|
|
424
|
-
jsxFactory: "__mreact_jsx",
|
|
425
|
-
jsxFragment: "__mreact_fragment",
|
|
426
|
-
stdin: {
|
|
427
|
-
contents: options.code,
|
|
428
|
-
loader: "tsx",
|
|
429
|
-
resolveDir: dirname(options.filename),
|
|
430
|
-
sourcefile: options.filename,
|
|
431
|
-
},
|
|
432
704
|
});
|
|
433
|
-
const code = output.
|
|
705
|
+
const code = output.code;
|
|
434
706
|
if (code === undefined) {
|
|
435
707
|
throw new Error(`Failed to compile ${options.label.toLowerCase()} for ${options.filename}.`);
|
|
436
708
|
}
|
|
@@ -467,7 +739,32 @@ async function writeCloudflareRouteModules(options) {
|
|
|
467
739
|
const routeId = routeIdForPath(route.path);
|
|
468
740
|
const routeModuleFile = `routes/${routeId}.mjs`;
|
|
469
741
|
let routeModuleExports;
|
|
470
|
-
|
|
742
|
+
if (route.kind === "server") {
|
|
743
|
+
try {
|
|
744
|
+
const routeOutput = await buildCloudflareServerRouteModule({
|
|
745
|
+
filename: route.file,
|
|
746
|
+
});
|
|
747
|
+
const serverRouteFile = `routes/${routeId}.${hashText(routeOutput).slice(0, 8)}.server.mjs`;
|
|
748
|
+
await writeFile(join(options.cloudflareDir, serverRouteFile), routeOutput);
|
|
749
|
+
const serverRouteImport = `./${serverRouteFile.split("/").pop() ?? serverRouteFile}`;
|
|
750
|
+
routeModuleExports = [`export * from ${JSON.stringify(serverRouteImport)};`];
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
throw new Error(`Failed to build Cloudflare server route module for ${routeFile}: ${errorMessage(error)}`);
|
|
754
|
+
}
|
|
755
|
+
await writeFile(join(options.cloudflareDir, routeModuleFile), `${routeModuleExports.join("\n")}\n`);
|
|
756
|
+
registryEntries.push(`${JSON.stringify(routeFile)}: () => import(${JSON.stringify(`./${routeModuleFile}`)})`);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const serverOutput = options.serverModules[routeFile]?.analysis?.streamRoute === true ||
|
|
760
|
+
shouldBuildRouteAsStream({
|
|
761
|
+
filename: routeFile,
|
|
762
|
+
files: options.files,
|
|
763
|
+
projectRoot: options.projectRoot,
|
|
764
|
+
source,
|
|
765
|
+
})
|
|
766
|
+
? "stream"
|
|
767
|
+
: "string";
|
|
471
768
|
try {
|
|
472
769
|
const componentOutput = serverOutput === "stream"
|
|
473
770
|
? await buildCloudflareStreamRouteComponentModule({
|
|
@@ -518,6 +815,7 @@ async function writeCloudflareRouteModules(options) {
|
|
|
518
815
|
``,
|
|
519
816
|
].join("\n");
|
|
520
817
|
await writeFile(join(options.cloudflareDir, "route-modules.mjs"), registrySource);
|
|
818
|
+
return { registryFile: "route-modules.mjs" };
|
|
521
819
|
}
|
|
522
820
|
async function buildCloudflareServerComponentModule(options) {
|
|
523
821
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -819,24 +1117,37 @@ async function buildCloudflareRouteLoaderModule(options) {
|
|
|
819
1117
|
resolveDir: dirname(options.filename),
|
|
820
1118
|
});
|
|
821
1119
|
}
|
|
1120
|
+
async function buildCloudflareServerRouteModule(options) {
|
|
1121
|
+
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
1122
|
+
|
|
1123
|
+
export const GET = routeModule.GET;
|
|
1124
|
+
export const HEAD = routeModule.HEAD;
|
|
1125
|
+
export const POST = routeModule.POST;
|
|
1126
|
+
export const PUT = routeModule.PUT;
|
|
1127
|
+
export const PATCH = routeModule.PATCH;
|
|
1128
|
+
export const DELETE = routeModule.DELETE;
|
|
1129
|
+
export const OPTIONS = routeModule.OPTIONS;
|
|
1130
|
+
export const ALL = routeModule.ALL;
|
|
1131
|
+
const defaultHandler = routeModule.default;
|
|
1132
|
+
export default defaultHandler;`;
|
|
1133
|
+
return bundleCloudflareModule({
|
|
1134
|
+
entry,
|
|
1135
|
+
filename: `${options.filename}.mreact-cloudflare-server-route.js`,
|
|
1136
|
+
plugins: [cloudflareWorkspaceRuntimePlugin()],
|
|
1137
|
+
resolveDir: dirname(options.filename),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
822
1140
|
async function bundleCloudflareModule(options) {
|
|
823
|
-
const output = await
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
logLevel: "silent",
|
|
1141
|
+
const output = await bundleRouterModule({
|
|
1142
|
+
code: options.entry,
|
|
1143
|
+
filename: options.filename,
|
|
827
1144
|
minify: true,
|
|
828
1145
|
platform: "browser",
|
|
1146
|
+
preserveExports: true,
|
|
829
1147
|
plugins: options.plugins,
|
|
830
1148
|
target: "es2022",
|
|
831
|
-
write: false,
|
|
832
|
-
stdin: {
|
|
833
|
-
contents: options.entry,
|
|
834
|
-
loader: "js",
|
|
835
|
-
resolveDir: options.resolveDir,
|
|
836
|
-
sourcefile: options.filename,
|
|
837
|
-
},
|
|
838
1149
|
});
|
|
839
|
-
const code = output.
|
|
1150
|
+
const code = output.code;
|
|
840
1151
|
if (code === undefined) {
|
|
841
1152
|
throw new Error(`Failed to build Cloudflare route module for ${options.filename}.`);
|
|
842
1153
|
}
|
|
@@ -1039,9 +1350,10 @@ export { cookies, headers, html, json, next, notFound, redirect, redirectExterna
|
|
|
1039
1350
|
};
|
|
1040
1351
|
}
|
|
1041
1352
|
function cloudflareRouteRequiresGeneratedModule(route, prerenderedRoutes) {
|
|
1042
|
-
return (route.kind === "
|
|
1043
|
-
(route.
|
|
1044
|
-
|
|
1353
|
+
return (route.kind === "server" ||
|
|
1354
|
+
(route.kind === "page" &&
|
|
1355
|
+
(route.segments.some((segment) => segment.kind !== "static") ||
|
|
1356
|
+
prerenderedRoutes[route.path] === undefined)));
|
|
1045
1357
|
}
|
|
1046
1358
|
function isServerComponentFile(file) {
|
|
1047
1359
|
return /(?:^|\/)(?:page|layout|template|loading|error|not-found)(?:\.mreact)?\.tsx$/.test(file);
|
|
@@ -1153,18 +1465,139 @@ async function writeNavigationRuntimeBundle(clientDir) {
|
|
|
1153
1465
|
await writeFile(join(clientDir, script), output.code);
|
|
1154
1466
|
return script;
|
|
1155
1467
|
}
|
|
1156
|
-
async function
|
|
1157
|
-
|
|
1468
|
+
async function writeAwsLambdaHandlerArtifact(outDir) {
|
|
1469
|
+
const awsLambdaDir = join(outDir, "aws-lambda");
|
|
1470
|
+
await mkdir(awsLambdaDir, { recursive: true });
|
|
1471
|
+
await writeFile(join(awsLambdaDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".."));
|
|
1472
|
+
}
|
|
1473
|
+
async function writeCloudflareWorkerArtifact(options) {
|
|
1474
|
+
await writeFile(join(options.cloudflareDir, "worker.mjs"), [
|
|
1475
|
+
`import { createCloudflareBuiltRequestHandler, createCloudflareRouteModuleRenderer, createCloudflareStaticAssetLoader } from "@reckona/mreact-router/adapters/cloudflare";`,
|
|
1476
|
+
`import { routeModules } from ${JSON.stringify(`./${options.modulesFile}`)};`,
|
|
1477
|
+
``,
|
|
1478
|
+
`const serverManifest = ${JSON.stringify(options.serverManifest, null, 2)};`,
|
|
1479
|
+
`const clientManifest = ${JSON.stringify(options.clientManifest, null, 2)};`,
|
|
1480
|
+
``,
|
|
1481
|
+
`export default createCloudflareBuiltRequestHandler({`,
|
|
1482
|
+
` assets: createCloudflareStaticAssetLoader({`,
|
|
1483
|
+
` binding: (env) => env?.ASSETS,`,
|
|
1484
|
+
` clientManifest,`,
|
|
1485
|
+
` }),`,
|
|
1486
|
+
` clientManifest,`,
|
|
1487
|
+
` renderRoute: createCloudflareRouteModuleRenderer({ modules: routeModules }),`,
|
|
1488
|
+
` serverManifest,`,
|
|
1489
|
+
`});`,
|
|
1490
|
+
``,
|
|
1491
|
+
].join("\n"));
|
|
1492
|
+
}
|
|
1493
|
+
export async function packageAwsLambdaArtifact(options) {
|
|
1494
|
+
await assertRequiredBuildFile(join(options.fromDir, "server", "manifest.json"));
|
|
1495
|
+
await assertRequiredBuildFile(join(options.fromDir, "server", "import-policy.json"));
|
|
1496
|
+
await assertRequiredBuildFile(join(options.fromDir, "client", "manifest.json"));
|
|
1497
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
1498
|
+
await mkdir(options.outDir, { recursive: true });
|
|
1499
|
+
await cp(options.fromDir, join(options.outDir, ".mreact"), {
|
|
1500
|
+
force: true,
|
|
1501
|
+
recursive: true,
|
|
1502
|
+
});
|
|
1503
|
+
await copyAwsLambdaProjectMetadata({
|
|
1504
|
+
fromDir: dirname(options.fromDir),
|
|
1505
|
+
outDir: options.outDir,
|
|
1506
|
+
});
|
|
1507
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
|
|
1508
|
+
const files = await collectAwsLambdaArtifactFiles(options.outDir, "");
|
|
1509
|
+
const manifest = {
|
|
1510
|
+
files,
|
|
1511
|
+
handler: "mreact-handler.handler",
|
|
1512
|
+
runtime: "aws-lambda",
|
|
1513
|
+
totalBytes: files.reduce((total, file) => total + file.bytes, 0),
|
|
1514
|
+
version: 1,
|
|
1515
|
+
};
|
|
1516
|
+
await writeFile(join(options.outDir, "mreact-lambda-artifact.json"), JSON.stringify(manifest, null, 2));
|
|
1517
|
+
return manifest;
|
|
1518
|
+
}
|
|
1519
|
+
async function assertRequiredBuildFile(path) {
|
|
1520
|
+
try {
|
|
1521
|
+
const info = await stat(path);
|
|
1522
|
+
if (info.isFile()) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
catch {
|
|
1527
|
+
// Throw the actionable error below.
|
|
1528
|
+
}
|
|
1529
|
+
throw new Error(`Missing required mreact build artifact: ${path}`);
|
|
1530
|
+
}
|
|
1531
|
+
async function copyAwsLambdaProjectMetadata(options) {
|
|
1532
|
+
for (const file of [
|
|
1533
|
+
"package.json",
|
|
1534
|
+
"package-lock.json",
|
|
1535
|
+
"npm-shrinkwrap.json",
|
|
1536
|
+
"pnpm-lock.yaml",
|
|
1537
|
+
"pnpm-workspace.yaml",
|
|
1538
|
+
"bun.lock",
|
|
1539
|
+
]) {
|
|
1540
|
+
try {
|
|
1541
|
+
await cp(join(options.fromDir, file), join(options.outDir, file), { force: true });
|
|
1542
|
+
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
if (!isNodeError(error) || error.code !== "ENOENT") {
|
|
1545
|
+
throw error;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
async function collectAwsLambdaArtifactFiles(rootDir, relativeDir) {
|
|
1551
|
+
const entries = await readdir(join(rootDir, relativeDir), { withFileTypes: true });
|
|
1552
|
+
const files = [];
|
|
1553
|
+
for (const entry of entries) {
|
|
1554
|
+
const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
|
|
1555
|
+
const absolutePath = join(rootDir, relativePath);
|
|
1556
|
+
if (entry.isDirectory()) {
|
|
1557
|
+
files.push(...(await collectAwsLambdaArtifactFiles(rootDir, relativePath)));
|
|
1558
|
+
continue;
|
|
1559
|
+
}
|
|
1560
|
+
if (entry.isFile()) {
|
|
1561
|
+
const info = await stat(absolutePath);
|
|
1562
|
+
files.push({ bytes: info.size, path: relativePath });
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return files.sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path));
|
|
1566
|
+
}
|
|
1567
|
+
function awsLambdaHandlerSource(outDirRelativeToHandler) {
|
|
1568
|
+
return `import { fileURLToPath } from "node:url";
|
|
1569
|
+
import { dirname, resolve } from "node:path";
|
|
1570
|
+
import { createPreloadedAwsLambdaRequestHandler } from "@reckona/mreact-router/adapters/aws-lambda";
|
|
1571
|
+
|
|
1572
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1573
|
+
|
|
1574
|
+
export const handler = await createPreloadedAwsLambdaRequestHandler({
|
|
1575
|
+
importPolicy: "generated",
|
|
1576
|
+
outDir: resolve(here, ${JSON.stringify(outDirRelativeToHandler)}),
|
|
1577
|
+
timings: process.env.MREACT_ROUTER_TIMINGS === "1",
|
|
1578
|
+
});
|
|
1579
|
+
`;
|
|
1580
|
+
}
|
|
1581
|
+
async function validateProductionRoutes(options) {
|
|
1582
|
+
for (const route of options.routes) {
|
|
1158
1583
|
if (route.kind !== "page") {
|
|
1159
1584
|
continue;
|
|
1160
1585
|
}
|
|
1161
1586
|
const source = await readFile(route.file, "utf8");
|
|
1587
|
+
const filename = relative(options.projectRoot, route.file);
|
|
1162
1588
|
const output = transform({
|
|
1163
1589
|
code: stripRouteBuildExports(source),
|
|
1164
1590
|
dev: false,
|
|
1165
1591
|
filename: route.file,
|
|
1166
1592
|
serverEscape: nativeEscapeTransform,
|
|
1167
|
-
serverOutput:
|
|
1593
|
+
serverOutput: shouldBuildRouteAsStream({
|
|
1594
|
+
filename,
|
|
1595
|
+
files: options.files,
|
|
1596
|
+
projectRoot: options.projectRoot,
|
|
1597
|
+
source,
|
|
1598
|
+
})
|
|
1599
|
+
? "stream"
|
|
1600
|
+
: "string",
|
|
1168
1601
|
target: "server",
|
|
1169
1602
|
});
|
|
1170
1603
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|