@reckona/mreact-router 0.0.11 → 0.0.13

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 (51) hide show
  1. package/README.md +35 -3
  2. package/dist/adapters/aws-lambda.d.ts +1 -0
  3. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  4. package/dist/adapters/aws-lambda.js +77 -1
  5. package/dist/adapters/aws-lambda.js.map +1 -1
  6. package/dist/adapters/cloudflare.js +1 -1
  7. package/dist/adapters/cloudflare.js.map +1 -1
  8. package/dist/app-router-globals.d.ts +6 -0
  9. package/dist/app-router-globals.d.ts.map +1 -1
  10. package/dist/app-router-globals.js.map +1 -1
  11. package/dist/build.d.ts +2 -1
  12. package/dist/build.d.ts.map +1 -1
  13. package/dist/build.js +51 -28
  14. package/dist/build.js.map +1 -1
  15. package/dist/cli-options.d.ts +17 -0
  16. package/dist/cli-options.d.ts.map +1 -0
  17. package/dist/cli-options.js +97 -0
  18. package/dist/cli-options.js.map +1 -0
  19. package/dist/cli.js +53 -27
  20. package/dist/cli.js.map +1 -1
  21. package/dist/client.d.ts +11 -0
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js +36 -2
  24. package/dist/client.js.map +1 -1
  25. package/dist/config.d.ts +4 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +17 -0
  28. package/dist/config.js.map +1 -1
  29. package/dist/dev-server.d.ts +2 -0
  30. package/dist/dev-server.d.ts.map +1 -1
  31. package/dist/dev-server.js +24 -0
  32. package/dist/dev-server.js.map +1 -1
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/module-runner.d.ts.map +1 -1
  38. package/dist/module-runner.js +16 -3
  39. package/dist/module-runner.js.map +1 -1
  40. package/dist/render.d.ts +8 -0
  41. package/dist/render.d.ts.map +1 -1
  42. package/dist/render.js +263 -10
  43. package/dist/render.js.map +1 -1
  44. package/dist/serve.d.ts +6 -0
  45. package/dist/serve.d.ts.map +1 -1
  46. package/dist/serve.js +38 -9
  47. package/dist/serve.js.map +1 -1
  48. package/dist/vite.d.ts.map +1 -1
  49. package/dist/vite.js +20 -1
  50. package/dist/vite.js.map +1 -1
  51. package/package.json +8 -8
package/dist/render.js CHANGED
@@ -24,13 +24,60 @@ const nativeEscapeTransform = {
24
24
  };
25
25
  const authRuntimeStateKey = "__mreactAuthRuntimeState";
26
26
  const authSessionScriptId = "__mreact_auth_session";
27
+ export async function preloadBuiltRequestModules(options) {
28
+ const tasks = [];
29
+ const middlewareFiles = [
30
+ join(options.appDir, "middleware.ts"),
31
+ join(options.appDir, "middleware.mreact.ts"),
32
+ ];
33
+ for (const file of middlewareFiles) {
34
+ if (options.serverSourceFiles.has(file)) {
35
+ tasks.push(loadMiddlewareModule({
36
+ appDir: options.appDir,
37
+ file,
38
+ importPolicy: options.importPolicy,
39
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
40
+ serverSourceFiles: options.serverSourceFiles,
41
+ }));
42
+ }
43
+ }
44
+ for (const route of options.routes) {
45
+ const code = await readServerSourceFile(route.file, options.serverModuleCacheVersion, options.serverSourceFiles);
46
+ if (route.kind === "server") {
47
+ tasks.push(loadServerRouteModule({
48
+ file: route.file,
49
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
50
+ serverSourceFiles: options.serverSourceFiles,
51
+ }));
52
+ continue;
53
+ }
54
+ if (hasLoaderExport(code)) {
55
+ tasks.push(loadRouteLoaderModule({
56
+ appDir: options.appDir,
57
+ code,
58
+ filename: route.file,
59
+ importPolicy: options.importPolicy,
60
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
61
+ }));
62
+ }
63
+ }
64
+ await Promise.all(tasks);
65
+ }
27
66
  const serverTransformCache = new Map();
28
67
  const serverSourceFileCache = new Map();
29
68
  const routeSourceAnalysisCache = new Map();
69
+ const routeOutOfOrderBoundaryAnalysisCache = new Map();
70
+ const routeLoaderModuleCache = new Map();
71
+ const middlewareModuleCache = new Map();
72
+ const serverRouteModuleCache = new Map();
30
73
  const composedRouteMetadataCache = new Map();
31
74
  const maxServerTransformCacheEntries = 512;
32
75
  const maxServerSourceFileCacheEntries = 512;
33
76
  const maxRouteSourceAnalysisCacheEntries = 512;
77
+ const maxRouteOutOfOrderBoundaryAnalysisCacheEntries = 512;
78
+ const maxRouteLoaderModuleCacheEntries = 512;
79
+ const maxMiddlewareModuleCacheEntries = 64;
80
+ const maxServerRouteModuleCacheEntries = 512;
34
81
  const maxComposedRouteMetadataCacheEntries = 512;
35
82
  // Issue 086: per-shell prefix/suffix cache. Pure layouts (whose
36
83
  // exported component takes zero arguments and therefore cannot
@@ -62,6 +109,8 @@ async function renderAppRequestInternal(options) {
62
109
  appDir: options.appDir,
63
110
  importPolicy: options.importPolicy,
64
111
  request: options.request,
112
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
113
+ serverSourceFiles: options.serverSourceFiles,
65
114
  });
66
115
  if (middlewareResponse !== undefined) {
67
116
  const location = rewriteLocation(middlewareResponse);
@@ -113,7 +162,13 @@ async function renderAppRequestInternal(options) {
113
162
  let routeCacheContext;
114
163
  try {
115
164
  if (matched.route.kind === "server") {
116
- return await dispatchServerRoute(matched.route.file, options.request, matched.params);
165
+ return await dispatchServerRoute({
166
+ file: matched.route.file,
167
+ params: matched.params,
168
+ request: options.request,
169
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
170
+ serverSourceFiles: options.serverSourceFiles,
171
+ });
117
172
  }
118
173
  // Issue 080: page routes render HTML for GET / HEAD only. Other
119
174
  // methods (PUT, PATCH, DELETE, PROPFIND, ...) get 405 with an
@@ -142,6 +197,7 @@ async function renderAppRequestInternal(options) {
142
197
  serverModuleCacheVersion: options.serverModuleCacheVersion,
143
198
  });
144
199
  const cachePolicy = originalAnalysis.cachePolicy;
200
+ const navigationScript = options.navigationScripts?.get(matched.route.path);
145
201
  const cacheKey = routeCacheKey(options.appDir, matched.route.path, url);
146
202
  const mayUseRouteCache = cachePolicy === undefined
147
203
  ? originalAnalysis.usesRuntimeCacheControl
@@ -185,6 +241,7 @@ async function renderAppRequestInternal(options) {
185
241
  appDir: options.appDir,
186
242
  filename: matched.route.file,
187
243
  importPolicy: options.importPolicy,
244
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
188
245
  })
189
246
  : undefined;
190
247
  recoveryRoute = {
@@ -206,7 +263,13 @@ async function renderAppRequestInternal(options) {
206
263
  "content-type": "text/html; charset=utf-8",
207
264
  "x-mreact-stream": "1",
208
265
  };
209
- if (loadingFile === undefined && !mayRenderOutOfOrderBoundary(routeCode)) {
266
+ const mayRenderOutOfOrder = await mayRenderOutOfOrderBoundaryDeep({
267
+ code: routeCode,
268
+ filename: matched.route.file,
269
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
270
+ serverSourceFiles: options.serverSourceFiles,
271
+ });
272
+ if (loadingFile === undefined && !mayRenderOutOfOrder) {
210
273
  const stringOutput = transformServerModule({
211
274
  code: routeCode,
212
275
  clientBoundaryImports: clientInference.clientBoundaryImports,
@@ -282,6 +345,7 @@ async function renderAppRequestInternal(options) {
282
345
  return withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
283
346
  assetBaseUrl: options.assetBaseUrl,
284
347
  currentScript: clientRoute ? clientScript : undefined,
348
+ currentNavigationScript: clientRoute ? undefined : navigationScript,
285
349
  routeScripts: options.clientScripts,
286
350
  })}${html}`, { headers }), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
287
351
  }
@@ -429,6 +493,7 @@ async function renderAppRequestInternal(options) {
429
493
  const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
430
494
  assetBaseUrl: options.assetBaseUrl,
431
495
  currentScript: clientRoute ? clientScript : undefined,
496
+ currentNavigationScript: clientRoute ? undefined : navigationScript,
432
497
  routeScripts: options.clientScripts,
433
498
  })}${html}`, {
434
499
  headers: responseHeadersForMetadata(metadata),
@@ -519,9 +584,15 @@ function modulePreloadTags(script, assetBaseUrl) {
519
584
  function clientNavigationHeadTags(options) {
520
585
  return [
521
586
  modulePreloadTags(options.currentScript, options.assetBaseUrl),
587
+ navigationRuntimeScriptTag(options.currentNavigationScript, options.assetBaseUrl),
522
588
  routePrefetchManifestScript(options.routeScripts, options.assetBaseUrl),
523
589
  ].join("");
524
590
  }
591
+ function navigationRuntimeScriptTag(script, assetBaseUrl) {
592
+ return script === undefined
593
+ ? ""
594
+ : `<script type="module" src="${escapeHtmlAttribute(assetPath(script, assetBaseUrl ?? "/_mreact/client/"))}"></script>`;
595
+ }
525
596
  function routePrefetchManifestScript(routeScripts, assetBaseUrl) {
526
597
  if (routeScripts === undefined || routeScripts.size === 0) {
527
598
  return "";
@@ -665,15 +736,15 @@ function normalizeErrorForProps(error) {
665
736
  }
666
737
  return { message: String(error) };
667
738
  }
668
- async function dispatchServerRoute(file, request, params) {
669
- const module = await importAppRouterFileModule(file);
670
- const handler = module[request.method] ?? module.ALL ?? module.default;
739
+ async function dispatchServerRoute(options) {
740
+ const module = await loadServerRouteModule(options);
741
+ const handler = module[options.request.method] ?? module.ALL ?? module.default;
671
742
  if (typeof handler !== "function") {
672
743
  return new Response("Method Not Allowed", { status: 405 });
673
744
  }
674
745
  let response;
675
746
  try {
676
- response = await handler(request, { params });
747
+ response = await handler(options.request, { params: options.params });
677
748
  }
678
749
  catch (error) {
679
750
  if (error instanceof Response) {
@@ -685,6 +756,29 @@ async function dispatchServerRoute(file, request, params) {
685
756
  ? response
686
757
  : new Response("Invalid route response", { status: 500 });
687
758
  }
759
+ async function loadServerRouteModule(options) {
760
+ if (options.serverModuleCacheVersion === undefined) {
761
+ return await importAppRouterFileModule(options.file);
762
+ }
763
+ const code = await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles);
764
+ const cacheKey = `server-route\0${options.file}\0${options.serverModuleCacheVersion}\0${memoizedHashText(code)}`;
765
+ const cached = serverRouteModuleCache.get(cacheKey);
766
+ if (cached !== undefined) {
767
+ return cached;
768
+ }
769
+ const loaded = importAppRouterSourceModule({
770
+ cacheKey,
771
+ code,
772
+ label: `server-route:${options.file}`,
773
+ resolveDir: dirname(options.file),
774
+ sourcefile: options.file,
775
+ }).catch((error) => {
776
+ serverRouteModuleCache.delete(cacheKey);
777
+ throw error;
778
+ });
779
+ setBoundedCacheEntry(serverRouteModuleCache, cacheKey, loaded, maxServerRouteModuleCacheEntries);
780
+ return loaded;
781
+ }
688
782
  async function runMiddleware(options) {
689
783
  const candidates = [
690
784
  join(options.appDir, "middleware.ts"),
@@ -701,6 +795,8 @@ async function runMiddleware(options) {
701
795
  appDir: options.appDir,
702
796
  file,
703
797
  importPolicy: options.importPolicy,
798
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
799
+ serverSourceFiles: options.serverSourceFiles,
704
800
  });
705
801
  if (!middlewareMatches(module.config, new URL(options.request.url).pathname)) {
706
802
  return undefined;
@@ -729,7 +825,34 @@ async function runMiddleware(options) {
729
825
  return undefined;
730
826
  }
731
827
  async function loadMiddlewareModule(options) {
732
- const code = await readFile(options.file, "utf8");
828
+ const code = await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles);
829
+ const cacheKey = options.serverModuleCacheVersion === undefined
830
+ ? undefined
831
+ : `middleware\0${options.appDir}\0${options.file}\0${options.serverModuleCacheVersion}\0${memoizedHashText(code)}\0${importPolicyCacheKey(options.importPolicy)}`;
832
+ if (cacheKey !== undefined) {
833
+ const cached = middlewareModuleCache.get(cacheKey);
834
+ if (cached !== undefined) {
835
+ return cached;
836
+ }
837
+ }
838
+ const loaded = bundleMiddlewareModule({
839
+ appDir: options.appDir,
840
+ code,
841
+ file: options.file,
842
+ importPolicy: options.importPolicy,
843
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
844
+ }).catch((error) => {
845
+ if (cacheKey !== undefined) {
846
+ middlewareModuleCache.delete(cacheKey);
847
+ }
848
+ throw error;
849
+ });
850
+ if (cacheKey !== undefined) {
851
+ setBoundedCacheEntry(middlewareModuleCache, cacheKey, loaded, maxMiddlewareModuleCacheEntries);
852
+ }
853
+ return loaded;
854
+ }
855
+ async function bundleMiddlewareModule(options) {
733
856
  const output = await bundle({
734
857
  bundle: true,
735
858
  format: "esm",
@@ -747,7 +870,7 @@ async function loadMiddlewareModule(options) {
747
870
  jsxFactory: "__mreact_jsx",
748
871
  jsxFragment: "__mreact_fragment",
749
872
  stdin: {
750
- contents: code,
873
+ contents: options.code,
751
874
  loader: "ts",
752
875
  resolveDir: dirname(options.file),
753
876
  sourcefile: options.file,
@@ -758,6 +881,11 @@ async function loadMiddlewareModule(options) {
758
881
  throw new Error(`Failed to compile middleware for ${options.file}.`);
759
882
  }
760
883
  return importAppRouterSourceModule({
884
+ ...(options.serverModuleCacheVersion === undefined
885
+ ? {}
886
+ : {
887
+ cacheKey: `middleware:${options.file}:${options.serverModuleCacheVersion}:${memoizedHashText(compiled)}`,
888
+ }),
761
889
  code: compiled,
762
890
  label: `middleware:${options.file}`,
763
891
  });
@@ -987,6 +1115,92 @@ function hasOutOfOrderBoundary(code) {
987
1115
  function mayRenderOutOfOrderBoundary(code) {
988
1116
  return (code.includes("<Await") || code.includes("Await(") || code.includes("renderOutOfOrderBoundary"));
989
1117
  }
1118
+ async function mayRenderOutOfOrderBoundaryDeep(options) {
1119
+ const seen = new Set();
1120
+ return await mayRenderOutOfOrderBoundaryDeepInner(options, seen);
1121
+ }
1122
+ async function mayRenderOutOfOrderBoundaryDeepInner(options, seen) {
1123
+ if (mayRenderOutOfOrderBoundary(options.code)) {
1124
+ return true;
1125
+ }
1126
+ if (seen.has(options.filename)) {
1127
+ return false;
1128
+ }
1129
+ seen.add(options.filename);
1130
+ const sourceHash = memoizedHashText(options.code);
1131
+ const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}`;
1132
+ const cached = routeOutOfOrderBoundaryAnalysisCache.get(cacheKey);
1133
+ if (cached !== undefined) {
1134
+ return cached;
1135
+ }
1136
+ const pending = mayRenderImportedOutOfOrderBoundary(options, seen).catch((error) => {
1137
+ routeOutOfOrderBoundaryAnalysisCache.delete(cacheKey);
1138
+ throw error;
1139
+ });
1140
+ setBoundedCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, pending, maxRouteOutOfOrderBoundaryAnalysisCacheEntries);
1141
+ return pending;
1142
+ }
1143
+ async function mayRenderImportedOutOfOrderBoundary(options, seen) {
1144
+ for (const specifier of localModuleSpecifiers(options.code)) {
1145
+ const file = await resolveLocalServerSourceImport(options.filename, specifier);
1146
+ if (file === undefined) {
1147
+ continue;
1148
+ }
1149
+ const code = await readServerSourceFile(file, options.serverModuleCacheVersion, options.serverSourceFiles);
1150
+ if (await mayRenderOutOfOrderBoundaryDeepInner({
1151
+ code,
1152
+ filename: file,
1153
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
1154
+ serverSourceFiles: options.serverSourceFiles,
1155
+ }, seen)) {
1156
+ return true;
1157
+ }
1158
+ }
1159
+ return false;
1160
+ }
1161
+ function localModuleSpecifiers(code) {
1162
+ const specifiers = new Set();
1163
+ const importPattern = /\b(?:import|export)\s+(?:type\s+)?(?:[^"']*?\s+from\s*)?["'](?<source>\.{1,2}\/[^"']+)["']/g;
1164
+ for (const match of code.matchAll(importPattern)) {
1165
+ const source = match.groups?.source;
1166
+ if (source !== undefined) {
1167
+ specifiers.add(source);
1168
+ }
1169
+ }
1170
+ return Array.from(specifiers);
1171
+ }
1172
+ async function resolveLocalServerSourceImport(fromFile, specifier) {
1173
+ const base = join(dirname(fromFile), specifier);
1174
+ const candidates = localServerSourceImportCandidates(base);
1175
+ for (const candidate of candidates) {
1176
+ try {
1177
+ await access(candidate);
1178
+ return candidate;
1179
+ }
1180
+ catch {
1181
+ // Try the next TypeScript route/source extension.
1182
+ }
1183
+ }
1184
+ return undefined;
1185
+ }
1186
+ function localServerSourceImportCandidates(base) {
1187
+ const candidates = [base];
1188
+ if (base.endsWith(".js")) {
1189
+ const withoutJs = base.slice(0, -".js".length);
1190
+ candidates.push(`${withoutJs}.ts`, `${withoutJs}.tsx`, `${withoutJs}.mreact.tsx`);
1191
+ }
1192
+ else if (base.endsWith(".jsx")) {
1193
+ const withoutJsx = base.slice(0, -".jsx".length);
1194
+ candidates.push(`${withoutJsx}.tsx`, `${withoutJsx}.mreact.tsx`);
1195
+ }
1196
+ else if (base.endsWith(".mreact")) {
1197
+ candidates.push(`${base}.tsx`);
1198
+ }
1199
+ else {
1200
+ candidates.push(`${base}.ts`, `${base}.tsx`, `${base}.mreact.tsx`, join(base, "index.ts"), join(base, "index.tsx"), join(base, "index.mreact.tsx"));
1201
+ }
1202
+ return candidates;
1203
+ }
990
1204
  async function runServerStreamModuleWithLoading(code, options) {
991
1205
  const loadingProps = {
992
1206
  data: undefined,
@@ -1338,6 +1552,31 @@ async function loadRouteData(options) {
1338
1552
  if (!hasLoaderExport(options.code)) {
1339
1553
  return undefined;
1340
1554
  }
1555
+ const module = await loadRouteLoaderModule(options);
1556
+ return module.loader === undefined ? undefined : await module.loader(options.context);
1557
+ }
1558
+ async function loadRouteLoaderModule(options) {
1559
+ const cacheKey = options.serverModuleCacheVersion === undefined
1560
+ ? undefined
1561
+ : `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${importPolicyCacheKey(options.importPolicy)}`;
1562
+ if (cacheKey !== undefined) {
1563
+ const cached = routeLoaderModuleCache.get(cacheKey);
1564
+ if (cached !== undefined) {
1565
+ return cached;
1566
+ }
1567
+ }
1568
+ const loaded = bundleRouteLoaderModule(options).catch((error) => {
1569
+ if (cacheKey !== undefined) {
1570
+ routeLoaderModuleCache.delete(cacheKey);
1571
+ }
1572
+ throw error;
1573
+ });
1574
+ if (cacheKey !== undefined) {
1575
+ setBoundedCacheEntry(routeLoaderModuleCache, cacheKey, loaded, maxRouteLoaderModuleCacheEntries);
1576
+ }
1577
+ return loaded;
1578
+ }
1579
+ async function bundleRouteLoaderModule(options) {
1341
1580
  const output = await bundle({
1342
1581
  bundle: true,
1343
1582
  format: "esm",
@@ -1365,11 +1604,25 @@ async function loadRouteData(options) {
1365
1604
  if (code === undefined) {
1366
1605
  throw new Error(`Failed to compile loader for ${options.filename}.`);
1367
1606
  }
1368
- const module = await importAppRouterSourceModule({
1607
+ return await importAppRouterSourceModule({
1608
+ ...(options.serverModuleCacheVersion === undefined
1609
+ ? {}
1610
+ : {
1611
+ cacheKey: `loader:${options.filename}:${options.serverModuleCacheVersion}:${memoizedHashText(code)}`,
1612
+ }),
1369
1613
  code,
1370
1614
  label: `loader:${options.filename}`,
1371
1615
  });
1372
- return module.loader === undefined ? undefined : await module.loader(options.context);
1616
+ }
1617
+ function importPolicyCacheKey(policy) {
1618
+ if (policy === undefined) {
1619
+ return "";
1620
+ }
1621
+ return JSON.stringify({
1622
+ allowedPackages: [...(policy.allowedPackages ?? [])].sort(),
1623
+ allowedSourceDirs: [...(policy.allowedSourceDirs ?? [])].sort(),
1624
+ projectRoot: policy.projectRoot ?? "",
1625
+ });
1373
1626
  }
1374
1627
  async function loadRouteMetadata(options) {
1375
1628
  if (!hasMetadataExport(options.code)) {