@reckona/mreact-router 0.0.210 → 0.0.211

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.
@@ -0,0 +1,70 @@
1
+ export interface StaticModuleChunk {
2
+ imports?: readonly string[] | undefined;
3
+ }
4
+
5
+ export function collectStaticModulePreloadDependencies(
6
+ entryFile: string,
7
+ chunks: ReadonlyMap<string, StaticModuleChunk>,
8
+ ): string[] {
9
+ if (!chunks.has(entryFile)) {
10
+ throw new Error(`Missing static JavaScript chunk for module preload entry: ${entryFile}`);
11
+ }
12
+
13
+ const dependencies = new Set<string>();
14
+ const visited = new Set<string>();
15
+ const pending = [...(chunks.get(entryFile)?.imports ?? [])];
16
+
17
+ while (pending.length > 0) {
18
+ const path = pending.pop();
19
+
20
+ if (
21
+ path === undefined ||
22
+ path === entryFile ||
23
+ visited.has(path) ||
24
+ !isLocalJavaScriptChunk(path)
25
+ ) {
26
+ continue;
27
+ }
28
+
29
+ visited.add(path);
30
+ const chunk = chunks.get(path);
31
+
32
+ if (chunk === undefined) {
33
+ throw new Error(`Missing static JavaScript chunk for module preload: ${path}`);
34
+ }
35
+
36
+ dependencies.add(path);
37
+ pending.push(...(chunk.imports ?? []));
38
+ }
39
+
40
+ return [...dependencies].sort();
41
+ }
42
+
43
+ function isLocalJavaScriptChunk(path: string): boolean {
44
+ if (path.startsWith("/") || hasUrlScheme(path)) {
45
+ return false;
46
+ }
47
+
48
+ return path.endsWith(".js") || path.endsWith(".mjs");
49
+ }
50
+
51
+ function hasUrlScheme(value: string): boolean {
52
+ const colon = value.indexOf(":");
53
+
54
+ if (colon <= 0) {
55
+ return false;
56
+ }
57
+
58
+ for (let index = 0; index < colon; index += 1) {
59
+ const code = value.charCodeAt(index);
60
+ const letter = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
61
+ const digit = code >= 48 && code <= 57;
62
+ const allowedSymbol = code === 43 || code === 45 || code === 46;
63
+
64
+ if (index === 0 ? !letter : !letter && !digit && !allowedSymbol) {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ return true;
70
+ }
package/src/render.ts CHANGED
@@ -205,6 +205,7 @@ export interface RenderAppRequestOptions {
205
205
  assetBaseUrl?: string | undefined;
206
206
  clientRouteInferenceCache?: ClientRouteInferenceCache | undefined;
207
207
  clientScripts?: ReadonlyMap<string, string>;
208
+ clientScriptPreloads?: ReadonlyMap<string, readonly string[]>;
208
209
  clientStylesByFile?: ReadonlyMap<string, readonly string[]>;
209
210
  clientStyles?: ReadonlyMap<string, readonly string[]>;
210
211
  define?: UserConfig["define"] | undefined;
@@ -977,6 +978,7 @@ async function renderAppRequestInternal(
977
978
  routePath: url.pathname,
978
979
  routeFile: notFoundFile,
979
980
  routeScripts: options.clientScripts,
981
+ routeScriptPreloads: options.clientScriptPreloads,
980
982
  serverModules: options.serverModules,
981
983
  serverModuleCacheVersion: options.serverModuleCacheVersion,
982
984
  serverSourceFiles: options.serverSourceFiles,
@@ -1363,8 +1365,10 @@ async function renderAppRequestInternal(
1363
1365
  assetBaseUrl: options.assetBaseUrl,
1364
1366
  currentStyleSheets: clientStyleSheets,
1365
1367
  currentScript: clientRoute ? clientScript : undefined,
1368
+ currentScriptPreloads: options.clientScriptPreloads?.get(matched.route.path),
1366
1369
  currentNavigationScript: navigationScript,
1367
1370
  routeScripts: options.clientScripts,
1371
+ routeScriptPreloads: options.clientScriptPreloads,
1368
1372
  })}${html}`,
1369
1373
  preparedActions.htmlReplacements,
1370
1374
  ),
@@ -1448,6 +1452,7 @@ async function renderAppRequestInternal(
1448
1452
  trackedRequest,
1449
1453
  routePath: matched.route.path,
1450
1454
  routeScripts: options.clientScripts,
1455
+ routeScriptPreloads: options.clientScriptPreloads,
1451
1456
  serverModules: options.serverModules,
1452
1457
  serverModuleCacheVersion: options.serverModuleCacheVersion,
1453
1458
  serverSourceFiles: options.serverSourceFiles,
@@ -1498,6 +1503,7 @@ async function renderAppRequestInternal(
1498
1503
  requestUrl: url.href,
1499
1504
  routePath: matched.route.path,
1500
1505
  routeScripts: options.clientScripts,
1506
+ routeScriptPreloads: options.clientScriptPreloads,
1501
1507
  serverModules: options.serverModules,
1502
1508
  serverModuleCacheVersion: options.serverModuleCacheVersion,
1503
1509
  serverSourceFiles: options.serverSourceFiles,
@@ -1682,8 +1688,10 @@ async function renderAppRequestInternal(
1682
1688
  assetBaseUrl: options.assetBaseUrl,
1683
1689
  currentStyleSheets: clientStyleSheets,
1684
1690
  currentScript: clientRoute ? clientScript : undefined,
1691
+ currentScriptPreloads: options.clientScriptPreloads?.get(matched.route.path),
1685
1692
  currentNavigationScript: navigationScript,
1686
1693
  routeScripts: options.clientScripts,
1694
+ routeScriptPreloads: options.clientScriptPreloads,
1687
1695
  })}${html}`,
1688
1696
  preparedActions.htmlReplacements,
1689
1697
  ),
@@ -1749,6 +1757,7 @@ async function renderAppRequestInternal(
1749
1757
  routePath: matched.route.path,
1750
1758
  routeFile: notFoundFile,
1751
1759
  routeScripts: options.clientScripts,
1760
+ routeScriptPreloads: options.clientScriptPreloads,
1752
1761
  serverModules: options.serverModules,
1753
1762
  serverModuleCacheVersion: options.serverModuleCacheVersion,
1754
1763
  serverSourceFiles: options.serverSourceFiles,
@@ -1777,6 +1786,7 @@ async function renderAppRequestInternal(
1777
1786
  routePath: matched.route.path,
1778
1787
  routeFile: errorFile,
1779
1788
  routeScripts: options.clientScripts,
1789
+ routeScriptPreloads: options.clientScriptPreloads,
1780
1790
  serverModules: options.serverModules,
1781
1791
  serverModuleCacheVersion: options.serverModuleCacheVersion,
1782
1792
  serverSourceFiles: options.serverSourceFiles,
@@ -1928,12 +1938,32 @@ function applyActionHtmlReplacements(
1928
1938
  return next;
1929
1939
  }
1930
1940
 
1931
- function modulePreloadTags(script: string | undefined, assetBaseUrl: string | undefined): string {
1932
- return script === undefined
1933
- ? ""
1934
- : `<link rel="modulepreload" href="${escapeHtmlAttribute(
1935
- assetPath(script, assetBaseUrl ?? "/_mreact/client/"),
1936
- )}">`;
1941
+ function modulePreloadTags(
1942
+ script: string | undefined,
1943
+ dependencies: readonly string[] | undefined,
1944
+ assetBaseUrl: string | undefined,
1945
+ ): string {
1946
+ if (script === undefined) {
1947
+ return "";
1948
+ }
1949
+
1950
+ const seen = new Set<string>();
1951
+ return [script, ...(dependencies ?? [])]
1952
+ .filter((file) => {
1953
+ if (seen.has(file)) {
1954
+ return false;
1955
+ }
1956
+
1957
+ seen.add(file);
1958
+ return true;
1959
+ })
1960
+ .map(
1961
+ (file) =>
1962
+ `<link rel="modulepreload" href="${escapeHtmlAttribute(
1963
+ assetPath(file, assetBaseUrl ?? "/_mreact/client/"),
1964
+ )}">`,
1965
+ )
1966
+ .join("");
1937
1967
  }
1938
1968
 
1939
1969
  function clientNavigationHeadTags(options: {
@@ -1941,13 +1971,23 @@ function clientNavigationHeadTags(options: {
1941
1971
  currentStyleSheets?: readonly string[] | undefined;
1942
1972
  currentNavigationScript?: string | undefined;
1943
1973
  currentScript: string | undefined;
1974
+ currentScriptPreloads?: readonly string[] | undefined;
1944
1975
  routeScripts: ReadonlyMap<string, string> | undefined;
1976
+ routeScriptPreloads?: ReadonlyMap<string, readonly string[]> | undefined;
1945
1977
  }): string {
1946
1978
  return [
1947
1979
  styleSheetTags(options.currentStyleSheets, options.assetBaseUrl),
1948
- modulePreloadTags(options.currentScript, options.assetBaseUrl),
1949
- navigationRuntimeScriptTag(options.currentNavigationScript, options.assetBaseUrl),
1950
- routePrefetchManifestScript(options.routeScripts, options.assetBaseUrl),
1980
+ modulePreloadTags(options.currentScript, options.currentScriptPreloads, options.assetBaseUrl),
1981
+ navigationRuntimeScriptTag(
1982
+ options.currentNavigationScript,
1983
+ options.assetBaseUrl,
1984
+ options.currentScript === undefined,
1985
+ ),
1986
+ routePrefetchManifestScript(
1987
+ options.routeScripts,
1988
+ options.routeScriptPreloads,
1989
+ options.assetBaseUrl,
1990
+ ),
1951
1991
  ].join("");
1952
1992
  }
1953
1993
 
@@ -1997,6 +2037,7 @@ function hasUrlScheme(value: string): boolean {
1997
2037
  function navigationRuntimeScriptTag(
1998
2038
  script: string | undefined,
1999
2039
  assetBaseUrl: string | undefined,
2040
+ execute: boolean,
2000
2041
  ): string {
2001
2042
  if (script === undefined) {
2002
2043
  return "";
@@ -2006,11 +2047,16 @@ function navigationRuntimeScriptTag(
2006
2047
  script: assetPath(script, assetBaseUrl ?? "/_mreact/client/"),
2007
2048
  }).replaceAll("<", "\\u003c");
2008
2049
 
2009
- return `<script type="application/json" id="mreact-navigation-runtime">${json}</script>`;
2050
+ const descriptor = `<script type="application/json" id="mreact-navigation-runtime">${json}</script>`;
2051
+ // Server-only routes have no client entry to load the navigation runtime.
2052
+ return execute
2053
+ ? `${descriptor}<script type="module" src="${escapeHtmlAttribute(assetPath(script, assetBaseUrl ?? "/_mreact/client/"))}"></script>`
2054
+ : descriptor;
2010
2055
  }
2011
2056
 
2012
2057
  function routePrefetchManifestScript(
2013
2058
  routeScripts: ReadonlyMap<string, string> | undefined,
2059
+ routeScriptPreloads: ReadonlyMap<string, readonly string[]> | undefined,
2014
2060
  assetBaseUrl: string | undefined,
2015
2061
  ): string {
2016
2062
  if (routeScripts === undefined || routeScripts.size === 0) {
@@ -2020,6 +2066,13 @@ function routePrefetchManifestScript(
2020
2066
  const routes = Array.from(routeScripts.entries(), ([path, script]) => ({
2021
2067
  path,
2022
2068
  script: assetPath(script, assetBaseUrl ?? "/_mreact/client/"),
2069
+ ...(routeScriptPreloads?.get(path) === undefined || routeScriptPreloads.get(path)?.length === 0
2070
+ ? {}
2071
+ : {
2072
+ modulePreloads: routeScriptPreloads
2073
+ .get(path)
2074
+ ?.map((file) => assetPath(file, assetBaseUrl ?? "/_mreact/client/")),
2075
+ }),
2023
2076
  }));
2024
2077
  const json = JSON.stringify(routes).replaceAll("<", "\\u003c");
2025
2078
 
@@ -2233,6 +2286,7 @@ async function renderSpecialRoute(options: {
2233
2286
  routePath?: string | undefined;
2234
2287
  routeFile: string;
2235
2288
  routeScripts?: ReadonlyMap<string, string> | undefined;
2289
+ routeScriptPreloads?: ReadonlyMap<string, readonly string[]> | undefined;
2236
2290
  serverModules?: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined;
2237
2291
  serverModuleCacheVersion?: string | undefined;
2238
2292
  serverSourceFiles?: ReadonlyMap<string, string> | undefined;
@@ -2294,7 +2348,11 @@ async function renderSpecialRoute(options: {
2294
2348
  currentStyleSheets: options.currentStyleSheets,
2295
2349
  currentScript:
2296
2350
  options.navigation?.clientRoute === true ? options.navigation.script : undefined,
2351
+ currentScriptPreloads: options.routeScriptPreloads?.get(
2352
+ options.navigation?.routePath ?? options.routePath ?? "",
2353
+ ),
2297
2354
  routeScripts: options.routeScripts,
2355
+ routeScriptPreloads: options.routeScriptPreloads,
2298
2356
  })}${html}`,
2299
2357
  {
2300
2358
  headers: { "content-type": "text/html; charset=utf-8" },
@@ -3518,6 +3576,7 @@ function runServerStreamModule(
3518
3576
  requestUrl: string;
3519
3577
  routePath: string;
3520
3578
  routeScripts?: ReadonlyMap<string, string> | undefined;
3579
+ routeScriptPreloads?: ReadonlyMap<string, readonly string[]> | undefined;
3521
3580
  clientRoute: boolean;
3522
3581
  clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
3523
3582
  define?: UserConfig["define"] | undefined;
@@ -3577,7 +3636,9 @@ function runServerStreamModule(
3577
3636
  clientNavigationHeadTags({
3578
3637
  assetBaseUrl: options.assetBaseUrl,
3579
3638
  currentScript: options.clientRoute ? options.script : undefined,
3639
+ currentScriptPreloads: options.routeScriptPreloads?.get(options.routePath),
3580
3640
  routeScripts: options.routeScripts,
3641
+ routeScriptPreloads: options.routeScriptPreloads,
3581
3642
  }),
3582
3643
  );
3583
3644
 
@@ -3801,6 +3862,7 @@ async function runServerStreamModuleWithLoading(
3801
3862
  trackedRequest?: TrackedHeaderRequest | undefined;
3802
3863
  routePath: string;
3803
3864
  routeScripts?: ReadonlyMap<string, string> | undefined;
3865
+ routeScriptPreloads?: ReadonlyMap<string, readonly string[]> | undefined;
3804
3866
  serverModules?: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined;
3805
3867
  serverModuleCacheVersion?: string | undefined;
3806
3868
  serverSourceFiles?: ReadonlyMap<string, string> | undefined;
@@ -3874,7 +3936,9 @@ async function runServerStreamModuleWithLoading(
3874
3936
  clientNavigationHeadTags({
3875
3937
  assetBaseUrl: options.assetBaseUrl,
3876
3938
  currentScript: options.clientRoute ? options.script : undefined,
3939
+ currentScriptPreloads: options.routeScriptPreloads?.get(options.routePath),
3877
3940
  routeScripts: options.routeScripts,
3941
+ routeScriptPreloads: options.routeScriptPreloads,
3878
3942
  }),
3879
3943
  );
3880
3944
 
package/src/serve.ts CHANGED
@@ -926,6 +926,7 @@ function builtRenderAppRequestOptions(
926
926
  appDir: options.runtime.appDir,
927
927
  assetBaseUrl: options.runtime.assetBaseUrl,
928
928
  clientScripts: options.runtime.clientScripts,
929
+ clientScriptPreloads: options.runtime.clientScriptPreloads,
929
930
  clientStylesByFile: options.runtime.clientStylesByFile,
930
931
  clientStyles: options.runtime.clientStyles,
931
932
  dehydrateOptions: options.dehydrateOptions,