@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.3 → 3.9.0-ultramodern.5

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 (200) hide show
  1. package/dist/cjs/buildIdentity.js +73 -0
  2. package/dist/cjs/contextExtensions.js +74 -0
  3. package/dist/cjs/headRuntime.js +91 -0
  4. package/dist/cjs/helmetContext.js +54 -0
  5. package/dist/cjs/index.js +18 -4
  6. package/dist/cjs/localisedLoaderIdentity.js +51 -0
  7. package/dist/cjs/prefetchPolicy.js +52 -0
  8. package/dist/cjs/reactContext.js +45 -0
  9. package/dist/cjs/request-policy/browser.js +139 -0
  10. package/dist/cjs/request-policy/client.js +234 -0
  11. package/dist/cjs/request-policy/node.js +149 -0
  12. package/dist/cjs/request-policy/policyCore.js +266 -0
  13. package/dist/cjs/request-policy/requestContext.js +137 -0
  14. package/dist/cjs/request-policy/traceparent.js +56 -0
  15. package/dist/cjs/request-policy/transport.js +234 -0
  16. package/dist/cjs/request-policy/types.js +57 -0
  17. package/dist/cjs/routeHydration.js +127 -0
  18. package/dist/cjs/routerPrefetchPolicy.js +153 -0
  19. package/dist/cjs/routerProvider.js +170 -0
  20. package/dist/cjs/routerState.js +167 -0
  21. package/dist/cjs/routerStatePlugin.js +86 -0
  22. package/dist/cjs/routerStateTypes.js +18 -0
  23. package/dist/cjs/rsc-disabled/rscDisabledClientBrowserRuntime.js +69 -0
  24. package/dist/cjs/rsc-disabled/rscDisabledClientEdgeRuntime.js +61 -0
  25. package/dist/cjs/rsc-disabled/rscDisabledClientNodeRuntime.js +65 -0
  26. package/dist/cjs/rsc-disabled/rscDisabledRuntimeError.js +42 -0
  27. package/dist/cjs/rsc-disabled/rscDisabledServerEdgeRuntime.js +93 -0
  28. package/dist/cjs/rsc-disabled/rscDisabledServerNodeRuntime.js +101 -0
  29. package/dist/cjs/runtimeContextProjection.js +112 -0
  30. package/dist/cjs/server-config/bffRuntime.js +18 -0
  31. package/dist/cjs/server-config/index.js +18 -0
  32. package/dist/cjs/server-config/serverTelemetry.js +18 -0
  33. package/dist/cjs/shellStream.js +34 -2
  34. package/dist/cjs/workspaceEvents.js +159 -0
  35. package/dist/esm/buildIdentity.mjs +35 -0
  36. package/dist/esm/contextExtensions.mjs +33 -0
  37. package/dist/esm/headRuntime.mjs +43 -0
  38. package/dist/esm/helmetContext.mjs +13 -0
  39. package/dist/esm/index.mjs +2 -0
  40. package/dist/esm/localisedLoaderIdentity.mjs +13 -0
  41. package/dist/esm/prefetchPolicy.mjs +11 -0
  42. package/dist/esm/reactContext.mjs +7 -0
  43. package/dist/esm/request-policy/browser.mjs +11 -0
  44. package/dist/esm/request-policy/client.mjs +195 -0
  45. package/dist/esm/request-policy/policyCore.mjs +171 -0
  46. package/dist/esm/request-policy/requestContext.mjs +75 -0
  47. package/dist/esm/request-policy/traceparent.mjs +18 -0
  48. package/dist/esm/request-policy/transport.mjs +196 -0
  49. package/dist/esm/request-policy/types.mjs +10 -0
  50. package/dist/esm/routeHydration.mjs +77 -0
  51. package/dist/esm/routerPrefetchPolicy.mjs +115 -0
  52. package/dist/esm/routerProvider.mjs +117 -0
  53. package/dist/esm/routerState.mjs +105 -0
  54. package/dist/esm/routerStatePlugin.mjs +48 -0
  55. package/dist/esm/routerStateTypes.mjs +0 -0
  56. package/dist/esm/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +10 -0
  57. package/dist/esm/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +8 -0
  58. package/dist/esm/rsc-disabled/rscDisabledClientNodeRuntime.mjs +9 -0
  59. package/dist/esm/rsc-disabled/rscDisabledRuntimeError.mjs +4 -0
  60. package/dist/esm/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +16 -0
  61. package/dist/esm/rsc-disabled/rscDisabledServerNodeRuntime.mjs +18 -0
  62. package/dist/esm/runtimeContextProjection.mjs +74 -0
  63. package/dist/esm/server-config/bffRuntime.mjs +0 -0
  64. package/dist/esm/server-config/index.mjs +0 -0
  65. package/dist/esm/server-config/serverTelemetry.mjs +0 -0
  66. package/dist/esm/shellStream.mjs +34 -2
  67. package/dist/esm/workspaceEvents.mjs +66 -0
  68. package/dist/esm-node/buildIdentity.mjs +36 -0
  69. package/dist/esm-node/contextExtensions.mjs +34 -0
  70. package/dist/esm-node/headRuntime.mjs +44 -0
  71. package/dist/esm-node/helmetContext.mjs +14 -0
  72. package/dist/esm-node/index.mjs +2 -0
  73. package/dist/esm-node/localisedLoaderIdentity.mjs +14 -0
  74. package/dist/esm-node/prefetchPolicy.mjs +12 -0
  75. package/dist/esm-node/reactContext.mjs +8 -0
  76. package/dist/esm-node/request-policy/browser.mjs +12 -0
  77. package/dist/esm-node/request-policy/client.mjs +196 -0
  78. package/dist/esm-node/request-policy/node.mjs +22 -0
  79. package/dist/esm-node/request-policy/policyCore.mjs +172 -0
  80. package/dist/esm-node/request-policy/requestContext.mjs +76 -0
  81. package/dist/esm-node/request-policy/traceparent.mjs +19 -0
  82. package/dist/esm-node/request-policy/transport.mjs +197 -0
  83. package/dist/esm-node/request-policy/types.mjs +11 -0
  84. package/dist/esm-node/routeHydration.mjs +78 -0
  85. package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
  86. package/dist/esm-node/routerProvider.mjs +118 -0
  87. package/dist/esm-node/routerState.mjs +106 -0
  88. package/dist/esm-node/routerStatePlugin.mjs +49 -0
  89. package/dist/esm-node/routerStateTypes.mjs +1 -0
  90. package/dist/esm-node/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +11 -0
  91. package/dist/esm-node/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +9 -0
  92. package/dist/esm-node/rsc-disabled/rscDisabledClientNodeRuntime.mjs +10 -0
  93. package/dist/esm-node/rsc-disabled/rscDisabledRuntimeError.mjs +5 -0
  94. package/dist/esm-node/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +17 -0
  95. package/dist/esm-node/rsc-disabled/rscDisabledServerNodeRuntime.mjs +19 -0
  96. package/dist/esm-node/runtimeContextProjection.mjs +75 -0
  97. package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
  98. package/dist/esm-node/server-config/index.mjs +1 -0
  99. package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
  100. package/dist/esm-node/shellStream.mjs +34 -2
  101. package/dist/esm-node/workspaceEvents.mjs +67 -0
  102. package/dist/types/buildIdentity.d.ts +37 -0
  103. package/dist/types/contextExtensions.d.ts +34 -0
  104. package/dist/types/headRuntime.d.ts +12 -0
  105. package/dist/types/helmetContext.d.ts +10 -0
  106. package/dist/types/index.d.ts +2 -0
  107. package/dist/types/localisedLoaderIdentity.d.ts +10 -0
  108. package/dist/types/prefetchPolicy.d.ts +4 -0
  109. package/dist/types/reactContext.d.ts +6 -0
  110. package/dist/types/request-policy/browser.d.ts +10 -0
  111. package/dist/types/request-policy/client.d.ts +14 -0
  112. package/dist/types/request-policy/node.d.ts +10 -0
  113. package/dist/types/request-policy/policyCore.d.ts +107 -0
  114. package/dist/types/request-policy/requestContext.d.ts +22 -0
  115. package/dist/types/request-policy/traceparent.d.ts +10 -0
  116. package/dist/types/request-policy/transport.d.ts +12 -0
  117. package/dist/types/request-policy/types.d.ts +144 -0
  118. package/dist/types/routeHydration.d.ts +24 -0
  119. package/dist/types/routerPrefetchPolicy.d.ts +28 -0
  120. package/dist/types/routerProvider.d.ts +77 -0
  121. package/dist/types/routerState.d.ts +12 -0
  122. package/dist/types/routerStatePlugin.d.ts +19 -0
  123. package/dist/types/routerStateTypes.d.ts +40 -0
  124. package/dist/types/rsc-disabled/rscDisabledClientBrowserRuntime.d.ts +9 -0
  125. package/dist/types/rsc-disabled/rscDisabledClientEdgeRuntime.d.ts +7 -0
  126. package/dist/types/rsc-disabled/rscDisabledClientNodeRuntime.d.ts +8 -0
  127. package/dist/types/rsc-disabled/rscDisabledRuntimeError.d.ts +1 -0
  128. package/dist/types/rsc-disabled/rscDisabledServerEdgeRuntime.d.ts +15 -0
  129. package/dist/types/rsc-disabled/rscDisabledServerNodeRuntime.d.ts +17 -0
  130. package/dist/types/runtimeContextProjection.d.ts +30 -0
  131. package/dist/types/server-config/bffRuntime.d.ts +122 -0
  132. package/dist/types/server-config/index.d.ts +2 -0
  133. package/dist/types/server-config/serverTelemetry.d.ts +322 -0
  134. package/dist/types/workspaceEvents.d.ts +62 -0
  135. package/package.json +238 -13
  136. package/rslib.config.mts +17 -2
  137. package/rstest.config.mts +33 -2
  138. package/src/buildIdentity.ts +61 -0
  139. package/src/contextExtensions.ts +81 -0
  140. package/src/headRuntime.tsx +77 -0
  141. package/src/helmetContext.ts +31 -0
  142. package/src/index.ts +2 -0
  143. package/src/localisedLoaderIdentity.ts +30 -0
  144. package/src/prefetchPolicy.ts +33 -0
  145. package/src/reactContext.ts +16 -0
  146. package/src/request-policy/browser.ts +22 -0
  147. package/src/request-policy/client.ts +404 -0
  148. package/src/request-policy/node.ts +41 -0
  149. package/src/request-policy/policyCore.ts +424 -0
  150. package/src/request-policy/requestContext.ts +149 -0
  151. package/src/request-policy/traceparent.ts +43 -0
  152. package/src/request-policy/transport.ts +349 -0
  153. package/src/request-policy/types.ts +229 -0
  154. package/src/routeHydration.ts +208 -0
  155. package/src/routerPrefetchPolicy.ts +195 -0
  156. package/src/routerProvider.ts +340 -0
  157. package/src/routerState.ts +208 -0
  158. package/src/routerStatePlugin.ts +86 -0
  159. package/src/routerStateTypes.ts +46 -0
  160. package/src/rsc-disabled/rscDisabledClientBrowserRuntime.ts +10 -0
  161. package/src/rsc-disabled/rscDisabledClientEdgeRuntime.ts +8 -0
  162. package/src/rsc-disabled/rscDisabledClientNodeRuntime.ts +9 -0
  163. package/src/rsc-disabled/rscDisabledRuntimeError.ts +3 -0
  164. package/src/rsc-disabled/rscDisabledServerEdgeRuntime.ts +16 -0
  165. package/src/rsc-disabled/rscDisabledServerNodeRuntime.ts +18 -0
  166. package/src/runtimeContextProjection.ts +105 -0
  167. package/src/server-config/bffRuntime.ts +136 -0
  168. package/src/server-config/index.ts +2 -0
  169. package/src/server-config/serverTelemetry.ts +337 -0
  170. package/src/shellStream.ts +55 -25
  171. package/src/workspaceEvents.ts +324 -0
  172. package/tests/buildIdentity.test.ts +70 -0
  173. package/tests/contextExtensions.test.ts +71 -0
  174. package/tests/headRuntime.test.tsx +369 -0
  175. package/tests/nativeSafeFailureCompatibility.test.ts +117 -0
  176. package/tests/prefetchPolicy.test.ts +51 -0
  177. package/tests/rendererHead.test.ts +1 -72
  178. package/tests/request-policy/browser-policy.test.ts +608 -0
  179. package/tests/request-policy/client-isolation.test.ts +149 -0
  180. package/tests/request-policy/identityBinding.test.ts +75 -0
  181. package/tests/request-policy/node-policy.test.ts +741 -0
  182. package/tests/request-policy/policyCore.test.ts +250 -0
  183. package/tests/request-policy/requestContext.test.ts +80 -0
  184. package/tests/request-policy/requestFactory.outbound-contract.test.ts +691 -0
  185. package/tests/request-policy/traceparent.test.ts +55 -0
  186. package/tests/request-policy/transport-retry.test.ts +370 -0
  187. package/tests/routeHydration.test.ts +180 -0
  188. package/tests/routerPrefetchPolicy.test.ts +102 -0
  189. package/tests/routerProvider.test.ts +38 -0
  190. package/tests/routerState.test.ts +157 -0
  191. package/tests/routerStatePlugin.test.ts +108 -0
  192. package/tests/runtimeContextProjection.test.ts +135 -0
  193. package/tests/shellStream.test.ts +37 -0
  194. package/tests/workspaceEvents.test.ts +391 -0
  195. package/dist/cjs/rscHtmlStream.js +0 -128
  196. package/dist/esm/rscHtmlStream.mjs +0 -86
  197. package/dist/esm-node/rscHtmlStream.mjs +0 -87
  198. package/dist/types/rscHtmlStream.d.ts +0 -4
  199. package/src/rscHtmlStream.ts +0 -142
  200. package/tests/rscHtmlStream.test.ts +0 -153
@@ -0,0 +1,208 @@
1
+ import { escapeHtmlAttribute } from './html';
2
+
3
+ type RouteAssetManifest = {
4
+ assets?: string[];
5
+ };
6
+
7
+ export type RouteManifestLike = {
8
+ routeAssets?: Record<string, RouteAssetManifest | undefined>;
9
+ };
10
+
11
+ type ScriptTagMatch = {
12
+ index: number;
13
+ tag: string;
14
+ src: string;
15
+ };
16
+
17
+ export type ScriptChunkLike = {
18
+ filename?: string;
19
+ url?: string;
20
+ };
21
+
22
+ function getScriptTags(template: string): ScriptTagMatch[] {
23
+ const scriptRegExp = /<script\b[^>]*\bsrc=(["'])(.*?)\1[^>]*><\/script>/g;
24
+ return Array.from(template.matchAll(scriptRegExp)).map(match => ({
25
+ index: match.index ?? 0,
26
+ tag: match[0],
27
+ src: match[2],
28
+ }));
29
+ }
30
+
31
+ function getAssetBasename(src: string) {
32
+ const withoutQuery = src.split(/[?#]/)[0];
33
+ return withoutQuery.split('/').pop() || withoutQuery;
34
+ }
35
+
36
+ function isEntryScript(src: string, entryName: string, asyncEntry: boolean) {
37
+ const basename = getAssetBasename(src);
38
+ const prefix = asyncEntry ? `async-${entryName}` : entryName;
39
+ return (
40
+ basename === `${prefix}.js` ||
41
+ basename.startsWith(`${prefix}.`) ||
42
+ basename.startsWith(`${prefix}-`)
43
+ );
44
+ }
45
+
46
+ const dedupeByUrl = <T extends ScriptChunkLike>(chunks: T[]) => {
47
+ const seen = new Set<string>();
48
+ return chunks.filter(chunk => {
49
+ if (chunk.url === undefined || chunk.url === '' || seen.has(chunk.url)) {
50
+ return false;
51
+ }
52
+ seen.add(chunk.url);
53
+ return true;
54
+ });
55
+ };
56
+
57
+ const isAsyncEntryScriptChunk = (chunk: ScriptChunkLike, entryName: string) => {
58
+ if (chunk.url === undefined || !chunk.url.endsWith('.js')) {
59
+ return false;
60
+ }
61
+
62
+ const asyncEntryName = `async-${entryName}`;
63
+ const filename = chunk.filename ?? chunk.url;
64
+ const basename = filename.split('/').pop() ?? filename;
65
+ return (
66
+ basename === `${asyncEntryName}.js` ||
67
+ basename.startsWith(`${asyncEntryName}.`) ||
68
+ basename.startsWith(`${asyncEntryName}-`)
69
+ );
70
+ };
71
+
72
+ export function getMatchedRouteChunks<T>(
73
+ routeManifest: RouteManifestLike | undefined,
74
+ matchedRouteIds: string[],
75
+ routeAssetToChunk: (asset: string) => T,
76
+ ): T[] {
77
+ const routeAssets = routeManifest?.routeAssets;
78
+ if (routeAssets === undefined) {
79
+ return [];
80
+ }
81
+
82
+ return matchedRouteIds
83
+ .flatMap(routeId => routeAssets[routeId]?.assets ?? [])
84
+ .map(routeAssetToChunk);
85
+ }
86
+
87
+ export const orderHydrationScriptChunks = <T extends ScriptChunkLike>({
88
+ asyncEntryChunks,
89
+ collectedChunks,
90
+ matchedRouteChunks,
91
+ entryName,
92
+ }: {
93
+ asyncEntryChunks: T[];
94
+ collectedChunks: T[];
95
+ matchedRouteChunks: T[];
96
+ entryName: string;
97
+ }) => {
98
+ const asyncEntryScriptChunks: T[] = [];
99
+ const asyncEntryDependencyChunks: T[] = [];
100
+
101
+ for (const chunk of asyncEntryChunks) {
102
+ if (isAsyncEntryScriptChunk(chunk, entryName)) {
103
+ asyncEntryScriptChunks.push(chunk);
104
+ } else {
105
+ asyncEntryDependencyChunks.push(chunk);
106
+ }
107
+ }
108
+
109
+ return dedupeByUrl([
110
+ ...asyncEntryDependencyChunks,
111
+ ...collectedChunks,
112
+ ...matchedRouteChunks,
113
+ ...asyncEntryScriptChunks,
114
+ ]);
115
+ };
116
+
117
+ export function injectBeforeHydrationEntryScript(
118
+ template: string,
119
+ scripts: string,
120
+ entryName = 'index',
121
+ ) {
122
+ if (scripts === '') {
123
+ return template;
124
+ }
125
+
126
+ const scriptTags = getScriptTags(template);
127
+ const target =
128
+ scriptTags.find(match => isEntryScript(match.src, entryName, false)) ??
129
+ scriptTags.find(match => isEntryScript(match.src, entryName, true));
130
+
131
+ if (target === undefined) {
132
+ return template;
133
+ }
134
+
135
+ return `${template.slice(0, target.index)}${scripts}${template.slice(
136
+ target.index,
137
+ )}`;
138
+ }
139
+
140
+ export function replaceChunkJsPlaceholder(
141
+ template: string,
142
+ scripts: string,
143
+ entryName: string | undefined,
144
+ placeholder: string,
145
+ ) {
146
+ if (!template.includes(placeholder)) {
147
+ return template;
148
+ }
149
+
150
+ if (scripts === '') {
151
+ return template.replace(placeholder, () => '');
152
+ }
153
+
154
+ const withoutPlaceholder = template.replace(placeholder, () => '');
155
+ const withEarlyScripts = injectBeforeHydrationEntryScript(
156
+ withoutPlaceholder,
157
+ scripts,
158
+ entryName,
159
+ );
160
+
161
+ if (withEarlyScripts !== withoutPlaceholder) {
162
+ return withEarlyScripts;
163
+ }
164
+
165
+ return template.replace(placeholder, () => scripts);
166
+ }
167
+
168
+ export function createRouteHydrationScriptTags(
169
+ routeManifest: RouteManifestLike | undefined,
170
+ matchedRouteIds: string[],
171
+ entryName: string,
172
+ options: {
173
+ nonce?: string;
174
+ template?: string;
175
+ } = {},
176
+ ) {
177
+ const { nonce, template } = options;
178
+ const routeAssets = routeManifest?.routeAssets;
179
+ if (routeAssets === undefined) {
180
+ return '';
181
+ }
182
+ const existingScriptSrcs =
183
+ template === undefined
184
+ ? new Set<string>()
185
+ : new Set(getScriptTags(template).map(({ src }) => src));
186
+
187
+ const jsAssets = Array.from(
188
+ new Set(
189
+ [
190
+ ...getMatchedRouteChunks(
191
+ routeManifest,
192
+ matchedRouteIds,
193
+ asset => asset,
194
+ ),
195
+ ...(routeAssets[`async-${entryName}`]?.assets ?? []),
196
+ ].filter((asset: string) => asset.endsWith('.js')),
197
+ ),
198
+ );
199
+ return jsAssets
200
+ .filter(asset => !existingScriptSrcs.has(asset))
201
+ .map(
202
+ asset =>
203
+ `<script src="${escapeHtmlAttribute(asset)}"${
204
+ nonce ? ` nonce="${escapeHtmlAttribute(nonce)}"` : ''
205
+ }></script>`,
206
+ )
207
+ .join(' ');
208
+ }
@@ -0,0 +1,195 @@
1
+ import { getNavigationWarmupCacheKey } from './navigationWarmup';
2
+ import { normalizePreloadBehavior } from './prefetchPolicy';
3
+
4
+ type PrefetchBehavior = 'intent' | 'render' | 'viewport' | 'none';
5
+ type PrefetchState = { code: boolean; data: boolean };
6
+
7
+ type NetworkInformationLike = {
8
+ saveData?: boolean;
9
+ effectiveType?: string;
10
+ };
11
+
12
+ type WarmupTask = {
13
+ key: string;
14
+ run: () => Promise<unknown>;
15
+ cancelled: boolean;
16
+ };
17
+
18
+ const INTENT_DELAY = 100;
19
+ const VIEWPORT_ROOT_MARGIN = '200px';
20
+ const MAX_CONCURRENT_WARMUPS = 4;
21
+ const WARMUP_TTL = 30_000;
22
+
23
+ /** Policy-only contract: native links supply elements, route inputs and loads. */
24
+ export function createRouterPrefetchPolicy() {
25
+ const cache = new Map<string, number>();
26
+ const queue: WarmupTask[] = [];
27
+ let activeWarmups = 0;
28
+
29
+ const canWarmup = () => {
30
+ const navigator = globalThis.navigator as
31
+ | (Navigator & {
32
+ connection?: NetworkInformationLike;
33
+ mozConnection?: NetworkInformationLike;
34
+ webkitConnection?: NetworkInformationLike;
35
+ })
36
+ | undefined;
37
+ const connection =
38
+ navigator?.connection ||
39
+ navigator?.mozConnection ||
40
+ navigator?.webkitConnection;
41
+ return (
42
+ !connection?.saveData &&
43
+ connection?.effectiveType !== 'slow-2g' &&
44
+ connection?.effectiveType !== '2g'
45
+ );
46
+ };
47
+
48
+ const runNext = () => {
49
+ while (activeWarmups < MAX_CONCURRENT_WARMUPS && queue.length > 0) {
50
+ const task = queue.shift()!;
51
+ if (task.cancelled) {
52
+ continue;
53
+ }
54
+ activeWarmups += 1;
55
+ task
56
+ .run()
57
+ .catch(() => {
58
+ cache.delete(task.key);
59
+ })
60
+ .finally(() => {
61
+ activeWarmups -= 1;
62
+ runNext();
63
+ });
64
+ }
65
+ };
66
+
67
+ return {
68
+ canWarmup,
69
+ allowData(route: unknown) {
70
+ return (
71
+ (route as { handle?: { navigationWarmup?: { data?: boolean } } })
72
+ ?.handle?.navigationWarmup?.data !== false
73
+ );
74
+ },
75
+ observe({
76
+ element,
77
+ prefetch: inputPrefetch,
78
+ preload: inputPreload,
79
+ notify,
80
+ }: {
81
+ element: HTMLAnchorElement | null;
82
+ prefetch?: PrefetchBehavior;
83
+ preload?: PrefetchBehavior | false;
84
+ notify: (state: PrefetchState) => void;
85
+ }) {
86
+ const prefetch = inputPrefetch ?? 'render';
87
+ const preload = normalizePreloadBehavior(inputPreload, prefetch);
88
+ let state: PrefetchState = {
89
+ code: prefetch === 'render' || preload === 'render',
90
+ data: prefetch === 'render',
91
+ };
92
+ let timer: ReturnType<typeof setTimeout> | undefined;
93
+ let observer: IntersectionObserver | undefined;
94
+ const update = (next: PrefetchState) => {
95
+ state = next;
96
+ notify(state);
97
+ };
98
+ const cancelTimer = () => {
99
+ if (timer !== undefined) {
100
+ clearTimeout(timer);
101
+ timer = undefined;
102
+ }
103
+ };
104
+ notify(state);
105
+
106
+ if (
107
+ element &&
108
+ (prefetch === 'viewport' || preload === 'viewport') &&
109
+ typeof IntersectionObserver !== 'undefined'
110
+ ) {
111
+ observer = new IntersectionObserver(
112
+ entries => {
113
+ if (!entries.some(entry => entry.isIntersecting)) {
114
+ return;
115
+ }
116
+ update({
117
+ code: true,
118
+ data: state.data || prefetch === 'viewport',
119
+ });
120
+ observer?.disconnect();
121
+ },
122
+ { rootMargin: VIEWPORT_ROOT_MARGIN },
123
+ );
124
+ observer.observe(element);
125
+ }
126
+
127
+ return {
128
+ onIntent() {
129
+ if (prefetch !== 'intent' && preload !== 'intent') {
130
+ return;
131
+ }
132
+ if (timer !== undefined) {
133
+ return;
134
+ }
135
+ timer = setTimeout(() => {
136
+ timer = undefined;
137
+ update({ code: true, data: state.data || prefetch === 'intent' });
138
+ }, INTENT_DELAY);
139
+ },
140
+ onCancel() {
141
+ if (prefetch === 'intent' || preload === 'intent') {
142
+ cancelTimer();
143
+ update({ code: false, data: false });
144
+ }
145
+ },
146
+ dispose() {
147
+ cancelTimer();
148
+ observer?.disconnect();
149
+ },
150
+ };
151
+ },
152
+ schedule({
153
+ runtimeContext,
154
+ chunkLoader,
155
+ publicPath,
156
+ key: operationKey,
157
+ run,
158
+ }: {
159
+ runtimeContext: object;
160
+ chunkLoader: object;
161
+ publicPath: string;
162
+ key: string;
163
+ run: () => Promise<unknown>;
164
+ }) {
165
+ if (!canWarmup()) {
166
+ return () => {};
167
+ }
168
+ const now = performance.now();
169
+ for (const [key, timestamp] of cache) {
170
+ if (now - timestamp > WARMUP_TTL) {
171
+ cache.delete(key);
172
+ }
173
+ }
174
+ const key = getNavigationWarmupCacheKey(
175
+ runtimeContext,
176
+ chunkLoader,
177
+ publicPath,
178
+ operationKey,
179
+ );
180
+ if (cache.has(key)) {
181
+ return () => {};
182
+ }
183
+ cache.set(key, now);
184
+ const task: WarmupTask = { key, run, cancelled: false };
185
+ queue.push(task);
186
+ runNext();
187
+ return () => {
188
+ task.cancelled = true;
189
+ if (queue.includes(task)) {
190
+ cache.delete(key);
191
+ }
192
+ };
193
+ },
194
+ };
195
+ }