@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.
Files changed (59) hide show
  1. package/README.md +12 -15
  2. package/dist/actions.js +7 -10
  3. package/dist/actions.js.map +1 -1
  4. package/dist/adapters/aws-lambda.d.ts +5 -1
  5. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  6. package/dist/adapters/aws-lambda.js +50 -4
  7. package/dist/adapters/aws-lambda.js.map +1 -1
  8. package/dist/adapters/cloudflare.d.ts +3 -0
  9. package/dist/adapters/cloudflare.d.ts.map +1 -1
  10. package/dist/adapters/cloudflare.js +61 -7
  11. package/dist/adapters/cloudflare.js.map +1 -1
  12. package/dist/app-router-globals.d.ts +1 -0
  13. package/dist/app-router-globals.d.ts.map +1 -1
  14. package/dist/app-router-globals.js.map +1 -1
  15. package/dist/build.d.ts +20 -0
  16. package/dist/build.d.ts.map +1 -1
  17. package/dist/build.js +435 -44
  18. package/dist/build.js.map +1 -1
  19. package/dist/bundle-pipeline.d.ts +50 -0
  20. package/dist/bundle-pipeline.d.ts.map +1 -0
  21. package/dist/bundle-pipeline.js +211 -0
  22. package/dist/bundle-pipeline.js.map +1 -0
  23. package/dist/cli-options.d.ts +2 -0
  24. package/dist/cli-options.d.ts.map +1 -1
  25. package/dist/cli-options.js +21 -3
  26. package/dist/cli-options.js.map +1 -1
  27. package/dist/cli.js +11 -1
  28. package/dist/cli.js.map +1 -1
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +9 -29
  31. package/dist/client.js.map +1 -1
  32. package/dist/config.d.ts +1 -1
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/config.js +2 -2
  35. package/dist/config.js.map +1 -1
  36. package/dist/index.d.ts +5 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -2
  39. package/dist/index.js.map +1 -1
  40. package/dist/module-runner.d.ts.map +1 -1
  41. package/dist/module-runner.js +9 -31
  42. package/dist/module-runner.js.map +1 -1
  43. package/dist/native-route-matcher.d.ts.map +1 -1
  44. package/dist/native-route-matcher.js +13 -1
  45. package/dist/native-route-matcher.js.map +1 -1
  46. package/dist/navigation.d.ts +7 -0
  47. package/dist/navigation.d.ts.map +1 -1
  48. package/dist/navigation.js +27 -0
  49. package/dist/navigation.js.map +1 -1
  50. package/dist/render.d.ts.map +1 -1
  51. package/dist/render.js +23 -47
  52. package/dist/render.js.map +1 -1
  53. package/dist/route-source.d.ts +1 -0
  54. package/dist/route-source.d.ts.map +1 -1
  55. package/dist/route-source.js +4 -1
  56. package/dist/route-source.js.map +1 -1
  57. package/dist/types.d.ts +23 -0
  58. package/dist/types.d.ts.map +1 -1
  59. 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 { dirname, join, relative, sep } from "node:path";
4
- import { build as bundle } from "esbuild";
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
- const files = await collectBuildFiles(project.projectRoot, project.allowedSourceDirs);
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
- await writeFile(join(serverDir, "manifest.json"), JSON.stringify({
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
- }, null, 2));
101
- await writeFile(join(clientDir, "manifest.json"), JSON.stringify({ routes: clientManifestRoutes }, null, 2));
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 serverOutputs = route !== undefined && isStreamRouteSource(source)
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: isStreamRouteSource(options.source),
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 bundle({
411
- bundle: true,
412
- format: "esm",
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.outputFiles[0]?.text;
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 = isStreamRouteSource(source) ? "stream" : "string";
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 bundle({
824
- bundle: true,
825
- format: "esm",
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.outputFiles[0]?.text;
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 validateProductionRoutes(routes) {
1157
- for (const route of routes) {
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: isStreamRouteSource(source) ? "stream" : "string",
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");