@reckona/mreact-router 0.0.82 → 0.0.84

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 (80) hide show
  1. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  2. package/dist/adapters/aws-lambda.js +17 -22
  3. package/dist/adapters/aws-lambda.js.map +1 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/build.js +3 -1
  6. package/dist/build.js.map +1 -1
  7. package/dist/client-route-inference.d.ts +2 -0
  8. package/dist/client-route-inference.d.ts.map +1 -0
  9. package/dist/client-route-inference.js +2 -0
  10. package/dist/client-route-inference.js.map +1 -0
  11. package/dist/client.d.ts +2 -1
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +25 -19
  14. package/dist/client.js.map +1 -1
  15. package/dist/layout-composer.d.ts +24 -0
  16. package/dist/layout-composer.d.ts.map +1 -0
  17. package/dist/layout-composer.js +83 -0
  18. package/dist/layout-composer.js.map +1 -0
  19. package/dist/metadata.d.ts +9 -0
  20. package/dist/metadata.d.ts.map +1 -0
  21. package/dist/metadata.js +419 -0
  22. package/dist/metadata.js.map +1 -0
  23. package/dist/middleware.d.ts +22 -0
  24. package/dist/middleware.d.ts.map +1 -0
  25. package/dist/middleware.js +94 -0
  26. package/dist/middleware.js.map +1 -0
  27. package/dist/module-runner.d.ts +2 -2
  28. package/dist/module-runner.d.ts.map +1 -1
  29. package/dist/module-runner.js +1 -1
  30. package/dist/module-runner.js.map +1 -1
  31. package/dist/navigation-runtime.d.ts +2 -0
  32. package/dist/navigation-runtime.d.ts.map +1 -0
  33. package/dist/navigation-runtime.js +2 -0
  34. package/dist/navigation-runtime.js.map +1 -0
  35. package/dist/preload-policy.d.ts +24 -0
  36. package/dist/preload-policy.d.ts.map +1 -0
  37. package/dist/preload-policy.js +45 -0
  38. package/dist/preload-policy.js.map +1 -0
  39. package/dist/render.d.ts +1 -3
  40. package/dist/render.d.ts.map +1 -1
  41. package/dist/render.js +32 -676
  42. package/dist/render.js.map +1 -1
  43. package/dist/route-hydration-contract.d.ts +11 -0
  44. package/dist/route-hydration-contract.d.ts.map +1 -0
  45. package/dist/route-hydration-contract.js +21 -0
  46. package/dist/route-hydration-contract.js.map +1 -0
  47. package/dist/route-module-loader.d.ts +10 -0
  48. package/dist/route-module-loader.d.ts.map +1 -0
  49. package/dist/route-module-loader.js +61 -0
  50. package/dist/route-module-loader.js.map +1 -0
  51. package/dist/route-source.d.ts.map +1 -1
  52. package/dist/route-source.js +7 -5
  53. package/dist/route-source.js.map +1 -1
  54. package/dist/routes.js +6 -0
  55. package/dist/routes.js.map +1 -1
  56. package/dist/serve.d.ts +13 -0
  57. package/dist/serve.d.ts.map +1 -1
  58. package/dist/serve.js +63 -28
  59. package/dist/serve.js.map +1 -1
  60. package/dist/vite.d.ts.map +1 -1
  61. package/dist/vite.js +2 -1
  62. package/dist/vite.js.map +1 -1
  63. package/package.json +11 -11
  64. package/src/adapters/aws-lambda.ts +21 -28
  65. package/src/build.ts +9 -6
  66. package/src/client-route-inference.ts +18 -0
  67. package/src/client.ts +32 -20
  68. package/src/layout-composer.ts +142 -0
  69. package/src/metadata.ts +578 -0
  70. package/src/middleware.ts +153 -0
  71. package/src/module-runner.ts +3 -2
  72. package/src/navigation-runtime.ts +16 -0
  73. package/src/preload-policy.ts +89 -0
  74. package/src/render.ts +75 -986
  75. package/src/route-hydration-contract.ts +22 -0
  76. package/src/route-module-loader.ts +95 -0
  77. package/src/route-source.ts +9 -4
  78. package/src/routes.ts +8 -0
  79. package/src/serve.ts +110 -29
  80. package/src/vite.ts +6 -4
@@ -0,0 +1,22 @@
1
+ export const routeHydrationContract = {
2
+ clientReferencesScriptPrefix: "mreact-client-references-",
3
+ hydratedAttribute: "data-mreact-hydrated",
4
+ hotRouteHydrateExport: "__mreactHotHydrateRoute",
5
+ propsScriptPrefix: "mreact-props-",
6
+ routeHydrateExport: "__mreactHydrateRoute",
7
+ routeMarkerAttribute: "data-mreact-route-id",
8
+ } as const;
9
+
10
+ export function routeDataScriptIds(routeId: string): [string, string] {
11
+ return [
12
+ `${routeHydrationContract.propsScriptPrefix}${routeId}`,
13
+ `${routeHydrationContract.clientReferencesScriptPrefix}${routeId}`,
14
+ ];
15
+ }
16
+
17
+ export function routeDataScriptSelector(): string {
18
+ return [
19
+ `script[type="application/json"][id^="${routeHydrationContract.propsScriptPrefix}"]`,
20
+ `script[type="application/json"][id^="${routeHydrationContract.clientReferencesScriptPrefix}"]`,
21
+ ].join(", ");
22
+ }
@@ -0,0 +1,95 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { BuiltServerModuleArtifact } from "./build.js";
3
+ import type { AppRouterImportPolicy } from "./import-policy.js";
4
+
5
+ export type BuiltServerModuleOutputLike = NonNullable<BuiltServerModuleArtifact["request"]>;
6
+
7
+ // Per-request hashText (SHA-256) is one of the hot path's dominant
8
+ // costs. Cache hashes for `code` strings we have already seen this
9
+ // process (common case: the prepared code is identical across requests
10
+ // when the source file is unchanged).
11
+ const codeHashCache = new Map<string, string>();
12
+ const MAX_CODE_HASH_ENTRIES = 256;
13
+
14
+ export function memoizedHashText(code: string): string {
15
+ const cached = codeHashCache.get(code);
16
+ if (cached !== undefined) {
17
+ return cached;
18
+ }
19
+
20
+ const hash = hashText(code);
21
+ if (codeHashCache.size >= MAX_CODE_HASH_ENTRIES) {
22
+ // Simple LRU eviction: drop the oldest entry (Map keeps insertion order).
23
+ const oldestKey = codeHashCache.keys().next().value;
24
+ if (oldestKey !== undefined) {
25
+ codeHashCache.delete(oldestKey);
26
+ }
27
+ }
28
+ codeHashCache.set(code, hash);
29
+ return hash;
30
+ }
31
+
32
+ export function prebuiltRequestModuleArtifact(
33
+ serverModules: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined,
34
+ file: string,
35
+ source: string,
36
+ kind: "request" | "routeMetadata" = "request",
37
+ ): BuiltServerModuleOutputLike | undefined {
38
+ const artifact = serverModules?.get(file)?.[kind];
39
+
40
+ return artifact !== undefined && artifact.sourceHash === memoizedHashText(source)
41
+ ? artifact
42
+ : undefined;
43
+ }
44
+
45
+ export function prebuiltRouteLoaderModuleArtifact(
46
+ serverModules: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined,
47
+ file: string,
48
+ source: string,
49
+ ): BuiltServerModuleOutputLike | undefined {
50
+ const artifact = serverModules?.get(file)?.loader;
51
+
52
+ return artifact !== undefined && artifact.sourceHash === memoizedHashText(source)
53
+ ? artifact
54
+ : prebuiltRequestModuleArtifact(serverModules, file, source);
55
+ }
56
+
57
+ export function prebuiltServerComponentModuleCode(
58
+ artifact: BuiltServerModuleArtifact["string"] | BuiltServerModuleArtifact["stream"] | undefined,
59
+ code: string,
60
+ codeHash: string,
61
+ ): string | undefined {
62
+ if (artifact === undefined) {
63
+ return undefined;
64
+ }
65
+
66
+ if (!prebuiltServerModuleOutputMatches(artifact, code, codeHash)) {
67
+ return undefined;
68
+ }
69
+
70
+ return artifact.bundleCode;
71
+ }
72
+
73
+ export function prebuiltServerModuleOutputMatches(
74
+ artifact: BuiltServerModuleOutputLike,
75
+ code: string,
76
+ codeHash: string,
77
+ ): boolean {
78
+ return artifact.sourceHash === codeHash || artifact.code === code;
79
+ }
80
+
81
+ export function importPolicyCacheKey(policy: AppRouterImportPolicy | undefined): string {
82
+ if (policy === undefined) {
83
+ return "";
84
+ }
85
+
86
+ return JSON.stringify({
87
+ allowedPackages: [...(policy.allowedPackages ?? [])].sort(),
88
+ allowedSourceDirs: [...(policy.allowedSourceDirs ?? [])].sort(),
89
+ projectRoot: policy.projectRoot ?? "",
90
+ });
91
+ }
92
+
93
+ function hashText(text: string): string {
94
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
95
+ }
@@ -8,6 +8,7 @@ import {
8
8
  hasModuleDirective,
9
9
  hasTopLevelExportDeclaration,
10
10
  stripTopLevelExportDeclarations,
11
+ stripUnusedStaticValueImports,
11
12
  } from "@reckona/mreact-compiler";
12
13
  import type { StaticImportReference } from "@reckona/mreact-compiler";
13
14
  import { sourceModuleCandidates } from "./source-modules.js";
@@ -44,10 +45,14 @@ export function stripRouteModuleExports(code: string): string {
44
45
  }
45
46
 
46
47
  export function stripRouteClientOnlyExports(code: string): string {
47
- return demoteRouteHelperExports(stripTopLevelExportDeclarations({
48
- code,
49
- names: routeClientOnlyExportNames,
50
- }));
48
+ return stripUnusedStaticValueImports({
49
+ code: demoteRouteHelperExports(
50
+ stripTopLevelExportDeclarations({
51
+ code,
52
+ names: routeClientOnlyExportNames,
53
+ }),
54
+ ),
55
+ });
51
56
  }
52
57
 
53
58
  export function stripRouteClientSource(input: {
package/src/routes.ts CHANGED
@@ -239,6 +239,10 @@ async function collectRouteFiles(directory: string, rootDirectory = directory):
239
239
  const path = join(directory, entry.name);
240
240
 
241
241
  if (entry.isDirectory()) {
242
+ if (shouldSkipRouteScanDirectory(entry.name)) {
243
+ continue;
244
+ }
245
+
242
246
  files.push(...(await collectRouteFiles(path, rootDirectory)));
243
247
  continue;
244
248
  }
@@ -257,6 +261,10 @@ async function collectRouteFiles(directory: string, rootDirectory = directory):
257
261
  return files;
258
262
  }
259
263
 
264
+ function shouldSkipRouteScanDirectory(name: string): boolean {
265
+ return name === ".vite" || name === "node_modules";
266
+ }
267
+
260
268
  function appFileConventionForRelativeFile(
261
269
  relativeFile: string,
262
270
  ): ReturnType<typeof appFileConventionForRootFilename> {
package/src/serve.ts CHANGED
@@ -8,7 +8,7 @@ import type {
8
8
  BuiltServerModuleArtifact,
9
9
  } from "./build.js";
10
10
  import type { AppRouterCache } from "./cache.js";
11
- import type { ClientRouteManifestEntry } from "./client.js";
11
+ import type { ClientRouteManifestEntry } from "./client-route-inference.js";
12
12
  import { createRouteMatcher, type AppRoute, type RouteMatcher } from "./routes.js";
13
13
  import type { AppRouterServerActionOptions } from "./actions.js";
14
14
  import type { AppRouterImportPolicy } from "./import-policy.js";
@@ -31,6 +31,7 @@ import {
31
31
  requestLogFields,
32
32
  type AppRouterLogger,
33
33
  } from "./logger.js";
34
+ import { builtAppRuntimePreloadPlan } from "./preload-policy.js";
34
35
  import { routeShellCandidates } from "./route-shells.js";
35
36
  import { normalizeRoutePath } from "./route-path.js";
36
37
  import type { HttpUpgradeHandler } from "./upgrade.js";
@@ -116,11 +117,32 @@ export interface RenderBuiltAppRequestOptions {
116
117
  request: Request;
117
118
  routeCache?: AppRouterCache | undefined;
118
119
  runtimeDir?: string | undefined;
120
+ immutableRuntime?: boolean | undefined;
119
121
  serverActions?: AppRouterServerActionOptions | undefined;
120
122
  sinkStrategy?: ResponseSinkStrategy;
121
123
  preload?: AppRouterRenderPreload | undefined;
122
124
  }
123
125
 
126
+ export interface BuiltRequestRuntimeOptions {
127
+ immutableRuntime?: boolean | undefined;
128
+ importPolicy?: AppRouterImportPolicy | undefined;
129
+ outDir: string;
130
+ runtimeDir?: string | undefined;
131
+ }
132
+
133
+ export type BuiltRequestRuntimeRenderOptions = Omit<
134
+ RenderBuiltAppRequestOptions,
135
+ "immutableRuntime" | "outDir" | "request" | "runtimeDir"
136
+ >;
137
+
138
+ export interface BuiltRequestRuntime {
139
+ preload(preload?: BuiltAppRuntimePreloadStrategy | undefined): Promise<void>;
140
+ render(
141
+ request: Request,
142
+ options?: BuiltRequestRuntimeRenderOptions | undefined,
143
+ ): Promise<Response>;
144
+ }
145
+
124
146
  export type BuiltAppRuntimePreloadMode =
125
147
  | "all"
126
148
  | "hot-route-requests"
@@ -201,6 +223,40 @@ export interface AppRouterPrerenderStore {
201
223
  withLock?<T>(path: string, task: () => Promise<T>): Promise<T>;
202
224
  }
203
225
 
226
+ export async function createBuiltRequestRuntime(
227
+ options: BuiltRequestRuntimeOptions,
228
+ ): Promise<BuiltRequestRuntime> {
229
+ const runtime = await readBuiltRuntime({
230
+ immutable: options.immutableRuntime,
231
+ outDir: options.outDir,
232
+ runtimeDir: options.runtimeDir,
233
+ });
234
+
235
+ return {
236
+ preload(preload) {
237
+ return preloadBuiltAppRuntimeWithRuntime({
238
+ importPolicy: options.importPolicy,
239
+ preload,
240
+ runtime,
241
+ });
242
+ },
243
+ async render(request, renderOptions = {}) {
244
+ const response = await renderBuiltAppRequestWithRuntime({
245
+ ...renderOptions,
246
+ importPolicy: renderOptions.importPolicy ?? options.importPolicy,
247
+ outDir: options.outDir,
248
+ request,
249
+ runtime,
250
+ });
251
+
252
+ return applyBuiltAppResponseHook(response, {
253
+ onResponse: renderOptions.onResponse,
254
+ request,
255
+ });
256
+ },
257
+ };
258
+ }
259
+
204
260
  export async function preloadBuiltAppRuntime(options: {
205
261
  importPolicy?: AppRouterImportPolicy | undefined;
206
262
  outDir: string;
@@ -211,38 +267,57 @@ export async function preloadBuiltAppRuntime(options: {
211
267
  outDir: options.outDir,
212
268
  runtimeDir: options.runtimeDir,
213
269
  });
214
- const strategy = options.preload ?? { mode: "all" };
270
+ await preloadBuiltAppRuntimeWithRuntime({
271
+ importPolicy: options.importPolicy,
272
+ preload: options.preload,
273
+ runtime,
274
+ });
275
+ }
215
276
 
216
- if (strategy.mode === "none") {
277
+ async function preloadBuiltAppRuntimeWithRuntime(options: {
278
+ importPolicy?: AppRouterImportPolicy | undefined;
279
+ preload?: BuiltAppRuntimePreloadStrategy | undefined;
280
+ runtime: BuiltRuntime;
281
+ }): Promise<void> {
282
+ const plan = builtAppRuntimePreloadPlan(options.preload);
283
+
284
+ if (!plan.shouldPreload) {
217
285
  return;
218
286
  }
219
287
 
220
- const routes = builtRuntimePreloadRoutes(runtime, strategy);
221
- if (strategy.mode === "all") {
222
- await loadBuiltServerModuleArtifacts(runtime, allBuiltServerModuleFiles(runtime), "all");
288
+ const routes = builtRuntimePreloadRoutes(options.runtime, plan);
289
+ if (plan.loadAllArtifacts) {
290
+ await loadBuiltServerModuleArtifacts(
291
+ options.runtime,
292
+ allBuiltServerModuleFiles(options.runtime),
293
+ "all",
294
+ );
223
295
  } else {
224
- await loadBuiltServerModuleArtifactsForRequest(runtime, undefined, {
225
- includeRender: strategy.mode !== "hot-route-requests",
226
- });
296
+ await loadBuiltServerModuleArtifactsForRequest(
297
+ options.runtime,
298
+ undefined,
299
+ plan.middlewareArtifacts,
300
+ );
227
301
  for (const route of routes) {
228
- await loadBuiltServerModuleArtifactsForRequest(runtime, route.file, {
229
- includeShells: strategy.mode !== "hot-route-requests",
230
- includeRender: strategy.mode !== "hot-route-requests",
231
- });
302
+ await loadBuiltServerModuleArtifactsForRequest(
303
+ options.runtime,
304
+ route.file,
305
+ plan.routeArtifacts,
306
+ );
232
307
  }
233
308
  }
234
309
  await preloadBuiltRequestModules({
235
- appDir: runtime.appDir,
310
+ appDir: options.runtime.appDir,
236
311
  importPolicy: {
237
312
  ...options.importPolicy,
238
- allowedSourceDirs: runtime.allowedSourceDirs,
239
- projectRoot: runtime.projectRoot,
313
+ allowedSourceDirs: options.runtime.allowedSourceDirs,
314
+ projectRoot: options.runtime.projectRoot,
240
315
  },
241
316
  routes,
242
- serverModules: runtime.serverModules,
243
- serverModuleCacheVersion: runtime.serverModuleCacheVersion,
244
- serverSourceFiles: runtime.serverSourceFiles,
245
- includeRenderModules: strategy.mode !== "hot-route-requests",
317
+ serverModules: options.runtime.serverModules,
318
+ serverModuleCacheVersion: options.runtime.serverModuleCacheVersion,
319
+ serverSourceFiles: options.runtime.serverSourceFiles,
320
+ includeRenderModules: plan.includeRenderModules,
246
321
  });
247
322
  }
248
323
 
@@ -275,6 +350,7 @@ export async function renderBuiltAppRequest(
275
350
  ...options,
276
351
  runtime: await readBuiltRuntime({
277
352
  outDir: options.outDir,
353
+ immutable: options.immutableRuntime,
278
354
  runtimeDir: options.runtimeDir,
279
355
  }),
280
356
  });
@@ -470,7 +546,10 @@ export async function startServer(
470
546
  options: StartServerOptions,
471
547
  ): Promise<{ close(): Promise<void>; server: Server; url: string }> {
472
548
  warnIfImplicitHostTrust(options);
473
- const runtime = await readBuiltRuntime({ outDir: options.outDir });
549
+ const runtime = await createBuiltRequestRuntime({
550
+ importPolicy: options.importPolicy,
551
+ outDir: options.outDir,
552
+ });
474
553
  const server = createServer(async (incoming, outgoing) => {
475
554
  const startedAt = logNow();
476
555
  const fallbackRequestFields = {
@@ -494,19 +573,15 @@ export async function startServer(
494
573
  ...logFields,
495
574
  type: "router:request:start",
496
575
  });
497
- const response = await applyBuiltAppResponseHook(await renderBuiltAppRequestWithRuntime({
498
- outDir: options.outDir,
499
- importPolicy: options.importPolicy,
576
+ const response = await runtime.render(request, {
500
577
  instrumentation: options.instrumentation,
501
578
  logger: options.logger,
502
579
  onResponse: options.onResponse,
503
580
  prerenderStore: options.prerenderStore,
504
- request,
505
581
  routeCache: options.routeCache,
506
- runtime,
507
582
  serverActions: options.serverActions,
508
583
  ...(options.sinkStrategy === undefined ? {} : { sinkStrategy: options.sinkStrategy }),
509
- }), { onResponse: options.onResponse, request });
584
+ });
510
585
  emitRouterLog(options.logger, "info", {
511
586
  ...logFields,
512
587
  durationMs: logDurationMs(startedAt),
@@ -632,17 +707,23 @@ async function readBuiltPublicAsset(
632
707
  }
633
708
 
634
709
  async function readBuiltRuntime(options: {
710
+ immutable?: boolean | undefined;
635
711
  outDir: string;
636
712
  runtimeDir?: string | undefined;
637
713
  }): Promise<BuiltRuntime> {
638
714
  const outDir = options.outDir;
639
715
  const runtimeDir = options.runtimeDir ?? join(outDir, "server", "runtime");
716
+ const cacheKey = `${outDir}\0${runtimeDir}`;
717
+ const cached = builtRuntimeCache.get(cacheKey);
718
+
719
+ if (options.immutable === true && cached !== undefined) {
720
+ return cached.runtime;
721
+ }
722
+
640
723
  const [serverManifestText, clientManifestText] = await Promise.all([
641
724
  readFile(join(outDir, "server", "manifest.json"), "utf8"),
642
725
  readFile(join(outDir, "client", "manifest.json"), "utf8"),
643
726
  ]);
644
- const cacheKey = `${outDir}\0${runtimeDir}`;
645
- const cached = builtRuntimeCache.get(cacheKey);
646
727
 
647
728
  if (
648
729
  cached !== undefined &&
package/src/vite.ts CHANGED
@@ -15,16 +15,18 @@ import {
15
15
  type ResolvedAppRouterProject,
16
16
  } from "./config.js";
17
17
  import type { AppRouterImportPolicy } from "./import-policy.js";
18
+ import {
19
+ collectClientRouteReferences,
20
+ detectNavigationRuntimeHint,
21
+ isClientRouteSource,
22
+ } from "./client-route-inference.js";
18
23
  import {
19
24
  buildNavigationRuntimeBundle,
20
25
  buildClientRouteBundle,
21
26
  buildClientRouteEntrySource,
22
27
  clientScriptForPath,
23
- collectClientRouteReferences,
24
- detectNavigationRuntimeHint,
25
- isClientRouteSource,
26
28
  navigationRuntimeScriptForDev,
27
- } from "./client.js";
29
+ } from "./navigation-runtime.js";
28
30
  import { nodeRequestToWebRequest, sendResponse } from "./http.js";
29
31
  import { renderAppRequest } from "./render.js";
30
32
  import { stripRouteClientOnlyExports } from "./route-source.js";