@reckona/mreact-router 0.0.25 → 0.0.27
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 +12 -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 +3 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +61 -7
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/app-router-globals.d.ts +1 -0
- package/dist/app-router-globals.d.ts.map +1 -1
- package/dist/app-router-globals.js.map +1 -1
- package/dist/build.d.ts +20 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +435 -44
- 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 +2 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +21 -3
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +11 -1
- 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 +23 -47
- 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,7 +457,14 @@ async function buildServerModuleArtifacts(options) {
|
|
|
300
457
|
}
|
|
301
458
|
continue;
|
|
302
459
|
}
|
|
303
|
-
const
|
|
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
|
|
304
468
|
? ["stream", "string"]
|
|
305
469
|
: ["string"];
|
|
306
470
|
const code = route === undefined ? source : stripRouteBuildExports(source);
|
|
@@ -323,6 +487,7 @@ async function buildServerModuleArtifacts(options) {
|
|
|
323
487
|
route,
|
|
324
488
|
routeCode: code,
|
|
325
489
|
source,
|
|
490
|
+
streamRoute,
|
|
326
491
|
});
|
|
327
492
|
}
|
|
328
493
|
for (const serverOutput of serverOutputs) {
|
|
@@ -360,10 +525,124 @@ function builtRouteSourceAnalysisSummary(options) {
|
|
|
360
525
|
routeCode: options.routeCode,
|
|
361
526
|
routePath: options.route.path,
|
|
362
527
|
sourceHash: hashText(options.source),
|
|
363
|
-
streamRoute:
|
|
528
|
+
streamRoute: options.streamRoute,
|
|
364
529
|
usesRuntimeCacheControl: usesRuntimeCacheControl(options.source),
|
|
365
530
|
};
|
|
366
531
|
}
|
|
532
|
+
function shouldBuildRouteAsStream(options) {
|
|
533
|
+
return (isStreamRouteSource(options.source) ||
|
|
534
|
+
routeClosureMayUseAwaitBoundary({
|
|
535
|
+
filename: options.filename,
|
|
536
|
+
files: options.files,
|
|
537
|
+
projectRoot: options.projectRoot,
|
|
538
|
+
source: options.source,
|
|
539
|
+
seen: new Set(),
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
function routeClosureMayUseAwaitBoundary(options) {
|
|
543
|
+
if (options.seen.has(options.filename) ||
|
|
544
|
+
hasModuleDirective({ code: options.source, directive: "use client" })) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
options.seen.add(options.filename);
|
|
548
|
+
try {
|
|
549
|
+
if (mayUseAwaitBoundarySource(options.source)) {
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
const jsxComponentRoots = new Set(collectJsxComponentRootNames({
|
|
553
|
+
code: options.source,
|
|
554
|
+
filename: join(options.projectRoot, options.filename),
|
|
555
|
+
}));
|
|
556
|
+
for (const reference of collectStaticImportReferences({
|
|
557
|
+
code: options.source,
|
|
558
|
+
filename: join(options.projectRoot, options.filename),
|
|
559
|
+
})) {
|
|
560
|
+
if (!isRenderedStaticImportReference(reference, jsxComponentRoots)) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const resolved = resolveBuildLocalSourceImport(options.files, options.filename, reference.source);
|
|
564
|
+
if (resolved === undefined) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const importedSource = options.files[resolved];
|
|
568
|
+
if (importedSource !== undefined &&
|
|
569
|
+
routeClosureMayUseAwaitBoundary({
|
|
570
|
+
filename: resolved,
|
|
571
|
+
files: options.files,
|
|
572
|
+
projectRoot: options.projectRoot,
|
|
573
|
+
seen: options.seen,
|
|
574
|
+
source: importedSource,
|
|
575
|
+
})) {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
options.seen.delete(options.filename);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function isRenderedStaticImportReference(reference, jsxComponentRoots) {
|
|
586
|
+
return reference.localNames.some((localName) => jsxComponentRoots.has(localName));
|
|
587
|
+
}
|
|
588
|
+
function resolveBuildLocalSourceImport(files, importer, specifier) {
|
|
589
|
+
if (!specifier.startsWith(".")) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
const base = join(dirname(importer), specifier);
|
|
593
|
+
for (const candidate of buildSourceModuleCandidates(base)) {
|
|
594
|
+
if (files[candidate] !== undefined) {
|
|
595
|
+
return candidate;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
function buildSourceModuleCandidates(base) {
|
|
601
|
+
if (hasSourceModuleExtension(base)) {
|
|
602
|
+
return [base, ...typescriptSourceModuleCandidates(base)];
|
|
603
|
+
}
|
|
604
|
+
if (extname(base) !== "") {
|
|
605
|
+
return [];
|
|
606
|
+
}
|
|
607
|
+
return [
|
|
608
|
+
`${base}.ts`,
|
|
609
|
+
`${base}.tsx`,
|
|
610
|
+
`${base}.mreact.tsx`,
|
|
611
|
+
`${base}.js`,
|
|
612
|
+
`${base}.jsx`,
|
|
613
|
+
`${base}.mjs`,
|
|
614
|
+
`${base}.mts`,
|
|
615
|
+
`${base}.cjs`,
|
|
616
|
+
`${base}.cts`,
|
|
617
|
+
join(base, "index.ts"),
|
|
618
|
+
join(base, "index.tsx"),
|
|
619
|
+
join(base, "index.mreact.tsx"),
|
|
620
|
+
join(base, "index.js"),
|
|
621
|
+
join(base, "index.jsx"),
|
|
622
|
+
join(base, "index.mjs"),
|
|
623
|
+
join(base, "index.mts"),
|
|
624
|
+
join(base, "index.cjs"),
|
|
625
|
+
join(base, "index.cts"),
|
|
626
|
+
];
|
|
627
|
+
}
|
|
628
|
+
function hasSourceModuleExtension(path) {
|
|
629
|
+
return /\.(?:mreact\.tsx|tsx?|jsx?|mjs|mts|cjs|cts)$/.test(path);
|
|
630
|
+
}
|
|
631
|
+
function typescriptSourceModuleCandidates(path) {
|
|
632
|
+
if (path.endsWith(".js")) {
|
|
633
|
+
return [`${path.slice(0, -3)}.ts`, `${path.slice(0, -3)}.tsx`];
|
|
634
|
+
}
|
|
635
|
+
if (path.endsWith(".jsx")) {
|
|
636
|
+
return [`${path.slice(0, -4)}.tsx`];
|
|
637
|
+
}
|
|
638
|
+
if (path.endsWith(".mjs")) {
|
|
639
|
+
return [`${path.slice(0, -4)}.mts`];
|
|
640
|
+
}
|
|
641
|
+
if (path.endsWith(".cjs")) {
|
|
642
|
+
return [`${path.slice(0, -4)}.cts`];
|
|
643
|
+
}
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
367
646
|
function usesRuntimeCacheControl(code) {
|
|
368
647
|
return /\bcacheControl\s*\(/.test(code);
|
|
369
648
|
}
|
|
@@ -407,10 +686,9 @@ async function bundleRouteMetadataModuleCode(options) {
|
|
|
407
686
|
});
|
|
408
687
|
}
|
|
409
688
|
async function bundleRouteRequestModuleCode(options) {
|
|
410
|
-
const output = await
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
logLevel: "silent",
|
|
689
|
+
const output = await bundleRouterModule({
|
|
690
|
+
code: options.code,
|
|
691
|
+
filename: options.filename,
|
|
414
692
|
platform: "node",
|
|
415
693
|
plugins: [
|
|
416
694
|
createAppRouterImportPolicyPlugin({
|
|
@@ -419,18 +697,8 @@ async function bundleRouteRequestModuleCode(options) {
|
|
|
419
697
|
label: options.label,
|
|
420
698
|
}),
|
|
421
699
|
],
|
|
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
700
|
});
|
|
433
|
-
const code = output.
|
|
701
|
+
const code = output.code;
|
|
434
702
|
if (code === undefined) {
|
|
435
703
|
throw new Error(`Failed to compile ${options.label.toLowerCase()} for ${options.filename}.`);
|
|
436
704
|
}
|
|
@@ -467,7 +735,15 @@ async function writeCloudflareRouteModules(options) {
|
|
|
467
735
|
const routeId = routeIdForPath(route.path);
|
|
468
736
|
const routeModuleFile = `routes/${routeId}.mjs`;
|
|
469
737
|
let routeModuleExports;
|
|
470
|
-
const serverOutput =
|
|
738
|
+
const serverOutput = options.serverModules[routeFile]?.analysis?.streamRoute === true ||
|
|
739
|
+
shouldBuildRouteAsStream({
|
|
740
|
+
filename: routeFile,
|
|
741
|
+
files: options.files,
|
|
742
|
+
projectRoot: options.projectRoot,
|
|
743
|
+
source,
|
|
744
|
+
})
|
|
745
|
+
? "stream"
|
|
746
|
+
: "string";
|
|
471
747
|
try {
|
|
472
748
|
const componentOutput = serverOutput === "stream"
|
|
473
749
|
? await buildCloudflareStreamRouteComponentModule({
|
|
@@ -518,6 +794,7 @@ async function writeCloudflareRouteModules(options) {
|
|
|
518
794
|
``,
|
|
519
795
|
].join("\n");
|
|
520
796
|
await writeFile(join(options.cloudflareDir, "route-modules.mjs"), registrySource);
|
|
797
|
+
return { registryFile: "route-modules.mjs" };
|
|
521
798
|
}
|
|
522
799
|
async function buildCloudflareServerComponentModule(options) {
|
|
523
800
|
const entry = `import * as routeModule from ${JSON.stringify(options.filename)};
|
|
@@ -820,23 +1097,16 @@ async function buildCloudflareRouteLoaderModule(options) {
|
|
|
820
1097
|
});
|
|
821
1098
|
}
|
|
822
1099
|
async function bundleCloudflareModule(options) {
|
|
823
|
-
const output = await
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
logLevel: "silent",
|
|
1100
|
+
const output = await bundleRouterModule({
|
|
1101
|
+
code: options.entry,
|
|
1102
|
+
filename: options.filename,
|
|
827
1103
|
minify: true,
|
|
828
1104
|
platform: "browser",
|
|
1105
|
+
preserveExports: true,
|
|
829
1106
|
plugins: options.plugins,
|
|
830
1107
|
target: "es2022",
|
|
831
|
-
write: false,
|
|
832
|
-
stdin: {
|
|
833
|
-
contents: options.entry,
|
|
834
|
-
loader: "js",
|
|
835
|
-
resolveDir: options.resolveDir,
|
|
836
|
-
sourcefile: options.filename,
|
|
837
|
-
},
|
|
838
1108
|
});
|
|
839
|
-
const code = output.
|
|
1109
|
+
const code = output.code;
|
|
840
1110
|
if (code === undefined) {
|
|
841
1111
|
throw new Error(`Failed to build Cloudflare route module for ${options.filename}.`);
|
|
842
1112
|
}
|
|
@@ -1153,18 +1423,139 @@ async function writeNavigationRuntimeBundle(clientDir) {
|
|
|
1153
1423
|
await writeFile(join(clientDir, script), output.code);
|
|
1154
1424
|
return script;
|
|
1155
1425
|
}
|
|
1156
|
-
async function
|
|
1157
|
-
|
|
1426
|
+
async function writeAwsLambdaHandlerArtifact(outDir) {
|
|
1427
|
+
const awsLambdaDir = join(outDir, "aws-lambda");
|
|
1428
|
+
await mkdir(awsLambdaDir, { recursive: true });
|
|
1429
|
+
await writeFile(join(awsLambdaDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".."));
|
|
1430
|
+
}
|
|
1431
|
+
async function writeCloudflareWorkerArtifact(options) {
|
|
1432
|
+
await writeFile(join(options.cloudflareDir, "worker.mjs"), [
|
|
1433
|
+
`import { createCloudflareBuiltRequestHandler, createCloudflareRouteModuleRenderer, createCloudflareStaticAssetLoader } from "@reckona/mreact-router/adapters/cloudflare";`,
|
|
1434
|
+
`import { routeModules } from ${JSON.stringify(`./${options.modulesFile}`)};`,
|
|
1435
|
+
``,
|
|
1436
|
+
`const serverManifest = ${JSON.stringify(options.serverManifest, null, 2)};`,
|
|
1437
|
+
`const clientManifest = ${JSON.stringify(options.clientManifest, null, 2)};`,
|
|
1438
|
+
``,
|
|
1439
|
+
`export default createCloudflareBuiltRequestHandler({`,
|
|
1440
|
+
` assets: createCloudflareStaticAssetLoader({`,
|
|
1441
|
+
` binding: (env) => env?.ASSETS,`,
|
|
1442
|
+
` clientManifest,`,
|
|
1443
|
+
` }),`,
|
|
1444
|
+
` clientManifest,`,
|
|
1445
|
+
` renderRoute: createCloudflareRouteModuleRenderer({ modules: routeModules }),`,
|
|
1446
|
+
` serverManifest,`,
|
|
1447
|
+
`});`,
|
|
1448
|
+
``,
|
|
1449
|
+
].join("\n"));
|
|
1450
|
+
}
|
|
1451
|
+
export async function packageAwsLambdaArtifact(options) {
|
|
1452
|
+
await assertRequiredBuildFile(join(options.fromDir, "server", "manifest.json"));
|
|
1453
|
+
await assertRequiredBuildFile(join(options.fromDir, "server", "import-policy.json"));
|
|
1454
|
+
await assertRequiredBuildFile(join(options.fromDir, "client", "manifest.json"));
|
|
1455
|
+
await rm(options.outDir, { force: true, recursive: true });
|
|
1456
|
+
await mkdir(options.outDir, { recursive: true });
|
|
1457
|
+
await cp(options.fromDir, join(options.outDir, ".mreact"), {
|
|
1458
|
+
force: true,
|
|
1459
|
+
recursive: true,
|
|
1460
|
+
});
|
|
1461
|
+
await copyAwsLambdaProjectMetadata({
|
|
1462
|
+
fromDir: dirname(options.fromDir),
|
|
1463
|
+
outDir: options.outDir,
|
|
1464
|
+
});
|
|
1465
|
+
await writeFile(join(options.outDir, "mreact-handler.mjs"), awsLambdaHandlerSource(".mreact"));
|
|
1466
|
+
const files = await collectAwsLambdaArtifactFiles(options.outDir, "");
|
|
1467
|
+
const manifest = {
|
|
1468
|
+
files,
|
|
1469
|
+
handler: "mreact-handler.handler",
|
|
1470
|
+
runtime: "aws-lambda",
|
|
1471
|
+
totalBytes: files.reduce((total, file) => total + file.bytes, 0),
|
|
1472
|
+
version: 1,
|
|
1473
|
+
};
|
|
1474
|
+
await writeFile(join(options.outDir, "mreact-lambda-artifact.json"), JSON.stringify(manifest, null, 2));
|
|
1475
|
+
return manifest;
|
|
1476
|
+
}
|
|
1477
|
+
async function assertRequiredBuildFile(path) {
|
|
1478
|
+
try {
|
|
1479
|
+
const info = await stat(path);
|
|
1480
|
+
if (info.isFile()) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
catch {
|
|
1485
|
+
// Throw the actionable error below.
|
|
1486
|
+
}
|
|
1487
|
+
throw new Error(`Missing required mreact build artifact: ${path}`);
|
|
1488
|
+
}
|
|
1489
|
+
async function copyAwsLambdaProjectMetadata(options) {
|
|
1490
|
+
for (const file of [
|
|
1491
|
+
"package.json",
|
|
1492
|
+
"package-lock.json",
|
|
1493
|
+
"npm-shrinkwrap.json",
|
|
1494
|
+
"pnpm-lock.yaml",
|
|
1495
|
+
"pnpm-workspace.yaml",
|
|
1496
|
+
"bun.lock",
|
|
1497
|
+
]) {
|
|
1498
|
+
try {
|
|
1499
|
+
await cp(join(options.fromDir, file), join(options.outDir, file), { force: true });
|
|
1500
|
+
}
|
|
1501
|
+
catch (error) {
|
|
1502
|
+
if (!isNodeError(error) || error.code !== "ENOENT") {
|
|
1503
|
+
throw error;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
async function collectAwsLambdaArtifactFiles(rootDir, relativeDir) {
|
|
1509
|
+
const entries = await readdir(join(rootDir, relativeDir), { withFileTypes: true });
|
|
1510
|
+
const files = [];
|
|
1511
|
+
for (const entry of entries) {
|
|
1512
|
+
const relativePath = relativeDir === "" ? entry.name : `${relativeDir}/${entry.name}`;
|
|
1513
|
+
const absolutePath = join(rootDir, relativePath);
|
|
1514
|
+
if (entry.isDirectory()) {
|
|
1515
|
+
files.push(...(await collectAwsLambdaArtifactFiles(rootDir, relativePath)));
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (entry.isFile()) {
|
|
1519
|
+
const info = await stat(absolutePath);
|
|
1520
|
+
files.push({ bytes: info.size, path: relativePath });
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return files.sort((left, right) => right.bytes - left.bytes || left.path.localeCompare(right.path));
|
|
1524
|
+
}
|
|
1525
|
+
function awsLambdaHandlerSource(outDirRelativeToHandler) {
|
|
1526
|
+
return `import { fileURLToPath } from "node:url";
|
|
1527
|
+
import { dirname, resolve } from "node:path";
|
|
1528
|
+
import { createPreloadedAwsLambdaRequestHandler } from "@reckona/mreact-router/adapters/aws-lambda";
|
|
1529
|
+
|
|
1530
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1531
|
+
|
|
1532
|
+
export const handler = await createPreloadedAwsLambdaRequestHandler({
|
|
1533
|
+
importPolicy: "generated",
|
|
1534
|
+
outDir: resolve(here, ${JSON.stringify(outDirRelativeToHandler)}),
|
|
1535
|
+
timings: process.env.MREACT_ROUTER_TIMINGS === "1",
|
|
1536
|
+
});
|
|
1537
|
+
`;
|
|
1538
|
+
}
|
|
1539
|
+
async function validateProductionRoutes(options) {
|
|
1540
|
+
for (const route of options.routes) {
|
|
1158
1541
|
if (route.kind !== "page") {
|
|
1159
1542
|
continue;
|
|
1160
1543
|
}
|
|
1161
1544
|
const source = await readFile(route.file, "utf8");
|
|
1545
|
+
const filename = relative(options.projectRoot, route.file);
|
|
1162
1546
|
const output = transform({
|
|
1163
1547
|
code: stripRouteBuildExports(source),
|
|
1164
1548
|
dev: false,
|
|
1165
1549
|
filename: route.file,
|
|
1166
1550
|
serverEscape: nativeEscapeTransform,
|
|
1167
|
-
serverOutput:
|
|
1551
|
+
serverOutput: shouldBuildRouteAsStream({
|
|
1552
|
+
filename,
|
|
1553
|
+
files: options.files,
|
|
1554
|
+
projectRoot: options.projectRoot,
|
|
1555
|
+
source,
|
|
1556
|
+
})
|
|
1557
|
+
? "stream"
|
|
1558
|
+
: "string",
|
|
1168
1559
|
target: "server",
|
|
1169
1560
|
});
|
|
1170
1561
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|