@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.1

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 (275) hide show
  1. package/README.md +23 -0
  2. package/dist/cjs/backend-federation-build/codegen.js +29 -18
  3. package/dist/cjs/backend-federation-build/config.js +84 -45
  4. package/dist/cjs/backend-federation-build/emit.js +63 -22
  5. package/dist/cjs/build-config/build-environment.js +25 -162
  6. package/dist/cjs/build-config/internal-effect-discovery.js +143 -0
  7. package/dist/cjs/build-config/public.js +2 -5
  8. package/dist/cjs/build-config/remote-address.js +58 -0
  9. package/dist/cjs/cloudflare/artifacts.js +8 -19
  10. package/dist/cjs/cloudflare/delivery-unit.js +125 -43
  11. package/dist/cjs/cloudflare/index.js +14 -4
  12. package/dist/cjs/cloudflare/native-renderer.js +100 -0
  13. package/dist/cjs/cloudflare/renderer-identity.js +106 -0
  14. package/dist/cjs/cloudflare/worker-manifest.js +54 -13
  15. package/dist/cjs/cloudflare/worker-options.js +48 -0
  16. package/dist/cjs/cloudflare/wrangler-config.js +210 -13
  17. package/dist/cjs/cloudflare-builder.js +307 -89
  18. package/dist/cjs/cloudflare-output-contract.js +36 -4
  19. package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
  20. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  21. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  22. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  23. package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
  24. package/dist/cjs/config.js +10 -5
  25. package/dist/cjs/deploy-output/npmAliases.js +92 -6
  26. package/dist/cjs/deploy-output/plugin.js +27 -6
  27. package/dist/cjs/deploy-output/public-assets.js +146 -0
  28. package/dist/cjs/deploy-output/target.js +57 -1
  29. package/dist/cjs/deploy-output/trace-files.js +99 -0
  30. package/dist/cjs/module-federation-config/exposes.js +70 -0
  31. package/dist/cjs/module-federation-config/index.js +73 -0
  32. package/dist/cjs/module-federation-config/inspect.js +103 -0
  33. package/dist/cjs/module-federation-config/object-literal.js +87 -0
  34. package/dist/cjs/module-federation-config/syntax.js +151 -0
  35. package/dist/cjs/module-federation-config/types.js +18 -0
  36. package/dist/cjs/native-type-checker.js +130 -13
  37. package/dist/cjs/policy-defaults.js +38 -29
  38. package/dist/cjs/release-envelope/canonical.js +2 -9
  39. package/dist/cjs/release-envelope/contract.js +68 -0
  40. package/dist/cjs/release-envelope/framework-output.js +296 -105
  41. package/dist/cjs/release-envelope/index.js +150 -36
  42. package/dist/cjs/release-envelope/plugin.js +24 -15
  43. package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
  44. package/dist/cjs/release-envelope/types.js +10 -8
  45. package/dist/cjs/release-identity.js +8 -5
  46. package/dist/cjs/renderer-build-identity.js +134 -0
  47. package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  48. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  49. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  50. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  51. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  52. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  53. package/dist/cjs/workspace-topology.js +84 -0
  54. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  55. package/dist/esm/backend-federation-build/config.mjs +74 -38
  56. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  57. package/dist/esm/build-config/build-environment.mjs +26 -160
  58. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  59. package/dist/esm/build-config/public.mjs +1 -1
  60. package/dist/esm/build-config/remote-address.mjs +20 -0
  61. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  62. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  63. package/dist/esm/cloudflare/index.mjs +15 -5
  64. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  65. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  66. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  67. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  68. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  69. package/dist/esm/cloudflare-builder.mjs +301 -89
  70. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  71. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  72. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  73. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  74. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  75. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  76. package/dist/esm/config.mjs +3 -1
  77. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  78. package/dist/esm/deploy-output/plugin.mjs +24 -6
  79. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  80. package/dist/esm/deploy-output/target.mjs +50 -3
  81. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  82. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  83. package/dist/esm/module-federation-config/index.mjs +4 -0
  84. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  85. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  86. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  87. package/dist/esm/module-federation-config/types.mjs +0 -0
  88. package/dist/esm/native-type-checker.mjs +124 -13
  89. package/dist/esm/policy-defaults.mjs +36 -30
  90. package/dist/esm/release-envelope/canonical.mjs +2 -9
  91. package/dist/esm/release-envelope/contract.mjs +21 -0
  92. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  93. package/dist/esm/release-envelope/index.mjs +149 -38
  94. package/dist/esm/release-envelope/plugin.mjs +24 -15
  95. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  96. package/dist/esm/release-envelope/types.mjs +2 -3
  97. package/dist/esm/release-identity.mjs +8 -5
  98. package/dist/esm/renderer-build-identity.mjs +82 -0
  99. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  100. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  101. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  102. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  103. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  104. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  105. package/dist/esm/workspace-topology.mjs +21 -0
  106. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  107. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  108. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  109. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  110. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  111. package/dist/esm-node/build-config/public.mjs +1 -1
  112. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  113. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  114. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  115. package/dist/esm-node/cloudflare/index.mjs +15 -5
  116. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  117. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  118. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  119. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  120. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  121. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  122. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  123. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  124. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  125. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  126. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  127. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  128. package/dist/esm-node/config.mjs +3 -1
  129. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  130. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  131. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  132. package/dist/esm-node/deploy-output/target.mjs +50 -3
  133. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  134. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  135. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  136. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  137. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  138. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  139. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  140. package/dist/esm-node/native-type-checker.mjs +124 -13
  141. package/dist/esm-node/policy-defaults.mjs +36 -30
  142. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  143. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  144. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  145. package/dist/esm-node/release-envelope/index.mjs +149 -38
  146. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  147. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  148. package/dist/esm-node/release-envelope/types.mjs +2 -3
  149. package/dist/esm-node/release-identity.mjs +8 -5
  150. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  151. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  152. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  153. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  154. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  155. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  156. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  157. package/dist/esm-node/workspace-topology.mjs +22 -0
  158. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  159. package/dist/types/backend-federation-build/config.d.ts +10 -57
  160. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  161. package/dist/types/backend-federation-build/index.d.ts +1 -1
  162. package/dist/types/build-config/build-environment.d.ts +1 -9
  163. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  164. package/dist/types/build-config/public.d.ts +1 -1
  165. package/dist/types/build-config/remote-address.d.ts +3 -0
  166. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  167. package/dist/types/cloudflare/constants.d.ts +1 -1
  168. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  169. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  170. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  171. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  172. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  173. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  174. package/dist/types/cloudflare-builder.d.ts +51 -8
  175. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  176. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  177. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  178. package/dist/types/config.d.ts +98 -13
  179. package/dist/types/deploy-output/plugin.d.ts +29 -9
  180. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  181. package/dist/types/deploy-output/target.d.ts +28 -5
  182. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  183. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  184. package/dist/types/module-federation-config/index.d.ts +5 -0
  185. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  186. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  187. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  188. package/dist/types/module-federation-config/types.d.ts +20 -0
  189. package/dist/types/native-type-checker.d.ts +26 -3
  190. package/dist/types/policy-defaults.d.ts +12 -2
  191. package/dist/types/release-envelope/canonical.d.ts +5 -5
  192. package/dist/types/release-envelope/contract.d.mts +8 -0
  193. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  194. package/dist/types/release-envelope/index.d.ts +17 -5
  195. package/dist/types/release-envelope/plugin.d.ts +10 -6
  196. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  197. package/dist/types/release-envelope/types.d.ts +42 -4
  198. package/dist/types/release-identity.d.ts +3 -2
  199. package/dist/types/renderer-build-identity.d.ts +30 -0
  200. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  201. package/dist/types/workspace-topology.d.ts +91 -0
  202. package/package.json +87 -27
  203. package/src/backend-federation-build/codegen.ts +28 -19
  204. package/src/backend-federation-build/config.ts +189 -123
  205. package/src/backend-federation-build/emit.ts +119 -38
  206. package/src/backend-federation-build/index.ts +4 -1
  207. package/src/build-config/build-environment.ts +59 -285
  208. package/src/build-config/internal-effect-discovery.ts +239 -0
  209. package/src/build-config/public.ts +0 -1
  210. package/src/build-config/remote-address.ts +33 -0
  211. package/src/cloudflare/artifacts.ts +22 -43
  212. package/src/cloudflare/delivery-unit.ts +283 -60
  213. package/src/cloudflare/index.ts +35 -19
  214. package/src/cloudflare/native-renderer.ts +117 -0
  215. package/src/cloudflare/renderer-identity.ts +171 -0
  216. package/src/cloudflare/worker-manifest.ts +108 -10
  217. package/src/cloudflare/worker-options.ts +60 -0
  218. package/src/cloudflare/wrangler-config.ts +390 -14
  219. package/src/cloudflare-builder.ts +543 -170
  220. package/src/cloudflare-output-contract.ts +45 -2
  221. package/src/cloudflare-output-verifier/identity.ts +138 -7
  222. package/src/cloudflare-output-verifier/index.ts +20 -0
  223. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  224. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  225. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  226. package/src/config.ts +101 -19
  227. package/src/deploy-output/npmAliases.ts +141 -4
  228. package/src/deploy-output/plugin.ts +55 -18
  229. package/src/deploy-output/public-assets.ts +224 -0
  230. package/src/deploy-output/target.ts +97 -14
  231. package/src/deploy-output/trace-files.ts +80 -0
  232. package/src/module-federation-config/exposes.ts +46 -0
  233. package/src/module-federation-config/index.ts +22 -0
  234. package/src/module-federation-config/inspect.ts +157 -0
  235. package/src/module-federation-config/object-literal.ts +76 -0
  236. package/src/module-federation-config/syntax.ts +212 -0
  237. package/src/module-federation-config/types.ts +22 -0
  238. package/src/native-type-checker.ts +233 -20
  239. package/src/policy-defaults.ts +47 -17
  240. package/src/release-envelope/canonical.ts +6 -18
  241. package/src/release-envelope/contract.mjs +28 -0
  242. package/src/release-envelope/framework-output.ts +681 -156
  243. package/src/release-envelope/index.ts +315 -60
  244. package/src/release-envelope/plugin.ts +54 -38
  245. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  246. package/src/release-envelope/types.ts +75 -6
  247. package/src/release-identity.ts +19 -2
  248. package/src/renderer-build-identity.ts +168 -0
  249. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  250. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  251. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  252. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  253. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  254. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  255. package/src/workspace-topology.ts +125 -0
  256. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  257. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  258. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  259. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  260. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  261. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  262. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  263. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  264. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  265. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  266. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  267. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  268. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  269. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  270. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  271. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  272. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  273. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  274. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  275. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -2,9 +2,13 @@ function createNoopMonitors() {
2
2
  const noop = () => {};
3
3
 
4
4
  return {
5
+ counter: noop,
5
6
  debug: noop,
6
7
  error: noop,
7
8
  info: noop,
9
+ push: noop,
10
+ timing: noop,
11
+ trace: noop,
8
12
  warn: noop,
9
13
  };
10
14
  }
@@ -124,6 +128,24 @@ function extractRenderedFragmentHtml(html, boundaryId, expose) {
124
128
  return extractRenderedBoundaryElement(html, boundaryId, expose);
125
129
  }
126
130
 
131
+ function readOpeningTagAttributes(openingTag) {
132
+ const attributes = {};
133
+ const opening = /^<\/?[A-Za-z][\w:-]*/u.exec(openingTag);
134
+ if (!opening) return attributes;
135
+ let remaining = openingTag.slice(opening[0].length);
136
+ const attribute =
137
+ /^\s+([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/u;
138
+ while (remaining) {
139
+ const match = attribute.exec(remaining);
140
+ if (!match) break;
141
+ const name = match[1].toLowerCase();
142
+ if (!Object.hasOwn(attributes, name))
143
+ attributes[name] = match[2] ?? match[3] ?? match[4] ?? '';
144
+ remaining = remaining.slice(match[0].length);
145
+ }
146
+ return attributes;
147
+ }
148
+
127
149
  function readOpeningTagAttribute(openingTag, name) {
128
150
  const pattern = new RegExp(
129
151
  `\\s${escapeFragmentRegExp(name)}=(?:"([^"]*)"|'([^']*)')`,
@@ -142,7 +164,7 @@ function collectStylesheetHrefs(html) {
142
164
  const rel = readOpeningTagAttribute(link, 'rel');
143
165
  const href = readOpeningTagAttribute(link, 'href');
144
166
 
145
- if (href && rel?.split(/\s+/u).includes('stylesheet')) {
167
+ if (href && rel?.toLowerCase().split(/\s+/u).includes('stylesheet')) {
146
168
  hrefs.push(href);
147
169
  }
148
170
  }
@@ -163,7 +185,7 @@ function dedupeStylesheetLinks(html, seenHrefs = new Set(), requestUrl) {
163
185
  const rel = readOpeningTagAttribute(link, 'rel');
164
186
  const href = readOpeningTagAttribute(link, 'href');
165
187
 
166
- if (!href || !rel?.split(/\s+/u).includes('stylesheet')) {
188
+ if (!href || !rel?.toLowerCase().split(/\s+/u).includes('stylesheet')) {
167
189
  return link;
168
190
  }
169
191
 
@@ -180,7 +202,7 @@ function dedupeStylesheetLinks(html, seenHrefs = new Set(), requestUrl) {
180
202
  function stripStylesheetLinks(html) {
181
203
  return html.replace(/<link\b[^>]*>/giu, link => {
182
204
  const rel = readOpeningTagAttribute(link, 'rel');
183
- return rel?.split(/\s+/u).includes('stylesheet') ? '' : link;
205
+ return rel?.toLowerCase().split(/\s+/u).includes('stylesheet') ? '' : link;
184
206
  });
185
207
  }
186
208
 
@@ -194,61 +216,231 @@ function createStylesheetLinksHtml(stylesheetEntries) {
194
216
  .join('');
195
217
  }
196
218
 
197
- function createStylesheetLinkStream(body, stylesheetEntries, requestUrl) {
219
+ function createStylesheetLinkStream(
220
+ body,
221
+ stylesheetEntries,
222
+ requestUrl,
223
+ resolveRemoteCss = async () => [],
224
+ ) {
198
225
  const decoder = new TextDecoder();
199
226
  const encoder = new TextEncoder();
200
- let injected = false;
227
+ const seenHrefs = new Set();
228
+ const pendingEntries = [...stylesheetEntries];
229
+ const maxTagLength = 64 * 1024;
230
+ const maxHeadLength = 256 * 1024;
231
+ let reader;
232
+ let cancelled = false;
233
+ let finished = false;
234
+ let headParts;
235
+ let headLength = 0;
236
+ let headSlot;
237
+ let rawText;
201
238
  let pending = '';
202
239
 
203
- const flushReadyHtml = (controller, final) => {
204
- const sentinelIndex = pending.indexOf(CLOUDFLARE_STYLESHEET_LINKS_SENTINEL);
205
- const closingHeadIndex =
206
- sentinelIndex >= 0 ? pending.indexOf('</head>', sentinelIndex) : -1;
207
- if (!injected && sentinelIndex >= 0 && (closingHeadIndex >= 0 || final)) {
208
- const headEnd = closingHeadIndex >= 0 ? closingHeadIndex : pending.length;
209
- const existingHrefs = new Set(
210
- collectStylesheetHrefs(pending.slice(0, headEnd)).map(href =>
211
- normalizeStylesheetHref(href, requestUrl),
212
- ),
213
- );
214
- const links = createStylesheetLinksHtml(
215
- stylesheetEntries.filter(
216
- ({ preloadHref }) => !existingHrefs.has(preloadHref),
217
- ),
218
- );
219
- const output = pending.replace(
220
- CLOUDFLARE_STYLESHEET_LINKS_SENTINEL,
221
- links,
222
- );
223
- injected = true;
224
- pending = '';
225
- controller.enqueue(encoder.encode(output));
226
- return;
240
+ const linksFor = entries => {
241
+ const missing = entries.filter(({ preloadHref }) => {
242
+ if (seenHrefs.has(preloadHref)) return false;
243
+ seenHrefs.add(preloadHref);
244
+ return true;
245
+ });
246
+ return createStylesheetLinksHtml(missing);
247
+ };
248
+ const tagEnd = value => {
249
+ let quote;
250
+ for (let index = 1; index < value.length; index += 1) {
251
+ const character = value[index];
252
+ if (quote) {
253
+ if (character === quote) quote = undefined;
254
+ } else if (character === '"' || character === "'") {
255
+ quote = character;
256
+ } else if (character === '>') {
257
+ return index;
258
+ }
227
259
  }
260
+ return -1;
261
+ };
228
262
 
229
- if (!final && !injected) {
230
- return;
263
+ const consume = async final => {
264
+ let output = '';
265
+ const append = value => {
266
+ if (headParts === undefined) {
267
+ output += value;
268
+ } else {
269
+ headLength += encoder.encode(value).byteLength;
270
+ if (headLength > maxHeadLength)
271
+ throw new RangeError('Cloudflare HTML stream head exceeds 256 KiB');
272
+ headParts.push(value);
273
+ }
274
+ };
275
+ while (pending && !cancelled) {
276
+ if (rawText) {
277
+ const match = rawText.comment
278
+ ? /-->/u.exec(pending)
279
+ : new RegExp(`</${rawText.tag}(?=[\\s/>])`, 'iu').exec(pending);
280
+ if (!match) {
281
+ const keep = final ? 0 : rawText.comment ? 2 : rawText.tag.length + 2;
282
+ const available = Math.max(0, pending.length - keep);
283
+ append(pending.slice(0, available));
284
+ pending = pending.slice(available);
285
+ break;
286
+ }
287
+ const end = match.index + (rawText.comment ? match[0].length : 0);
288
+ append(pending.slice(0, end));
289
+ pending = pending.slice(end);
290
+ rawText = undefined;
291
+ continue;
292
+ }
293
+ const opening = pending.indexOf('<');
294
+ if (opening < 0) {
295
+ append(pending);
296
+ pending = '';
297
+ break;
298
+ }
299
+ if (opening > 0) {
300
+ append(pending.slice(0, opening));
301
+ pending = pending.slice(opening);
302
+ }
303
+ if (!final && pending.length < 4 && '<!--'.startsWith(pending)) break;
304
+ if (pending.startsWith('<!--')) {
305
+ append('<!--');
306
+ pending = pending.slice(4);
307
+ rawText = { comment: true };
308
+ continue;
309
+ }
310
+ const closing = tagEnd(pending);
311
+ if (closing < 0) {
312
+ if (encoder.encode(pending).byteLength > maxTagLength)
313
+ throw new RangeError(
314
+ 'Cloudflare HTML stream has an unterminated tag larger than 64 KiB',
315
+ );
316
+ if (final) {
317
+ append(pending);
318
+ pending = '';
319
+ }
320
+ break;
321
+ }
322
+ if (
323
+ encoder.encode(pending.slice(0, closing + 1)).byteLength > maxTagLength
324
+ )
325
+ throw new RangeError('Cloudflare HTML stream tag exceeds 64 KiB');
326
+ const tag = pending.slice(0, closing + 1);
327
+ pending = pending.slice(closing + 1);
328
+ if (/^<head(?:\s|>)/iu.test(tag)) {
329
+ headParts = [];
330
+ headLength = 0;
331
+ headSlot = undefined;
332
+ }
333
+ if (tag === CLOUDFLARE_STYLESHEET_LINKS_SENTINEL && headParts) {
334
+ if (headSlot === undefined) {
335
+ headSlot = headParts.length;
336
+ append('');
337
+ }
338
+ continue;
339
+ }
340
+ const attributes = readOpeningTagAttributes(tag);
341
+ if (
342
+ output &&
343
+ headParts === undefined &&
344
+ attributes['data-modern-boundary-id'] &&
345
+ attributes['data-modern-mf-expose']
346
+ ) {
347
+ pending = tag + pending;
348
+ return output;
349
+ }
350
+ if (/^<link(?=[\s/>])/iu.test(tag)) {
351
+ const rel = attributes.rel;
352
+ const href = attributes.href;
353
+ if (href && rel?.toLowerCase().split(/\s+/u).includes('stylesheet')) {
354
+ const normalized = normalizeStylesheetHref(href, requestUrl);
355
+ if (seenHrefs.has(normalized)) continue;
356
+ seenHrefs.add(normalized);
357
+ }
358
+ }
359
+ const remoteHrefs = await resolveRemoteCss(attributes);
360
+ const remoteEntries = remoteHrefs.map(href => ({
361
+ href,
362
+ preloadHref: normalizeStylesheetHref(href, requestUrl),
363
+ reactResource: true,
364
+ }));
365
+ if (headParts) pendingEntries.push(...remoteEntries);
366
+ else append(linksFor(remoteEntries));
367
+ if (/^<\/head\s*>$/iu.test(tag) && headParts) {
368
+ const links = linksFor(pendingEntries);
369
+ if (headSlot === undefined) append(links);
370
+ else {
371
+ headLength += encoder.encode(links).byteLength;
372
+ if (headLength > maxHeadLength)
373
+ throw new RangeError('Cloudflare HTML stream head exceeds 256 KiB');
374
+ headParts[headSlot] = links;
375
+ }
376
+ append(tag);
377
+ output += headParts.join('');
378
+ headParts = undefined;
379
+ } else {
380
+ append(tag);
381
+ }
382
+ const rawOpening = /^<(script|style|title|textarea)(?=[\s/>])/iu.exec(
383
+ tag,
384
+ );
385
+ if (rawOpening) rawText = { tag: rawOpening[1].toLowerCase() };
231
386
  }
232
- if (pending.length > 0) {
233
- controller.enqueue(encoder.encode(pending));
234
- pending = '';
387
+ if (final && headParts) {
388
+ if (headSlot !== undefined) {
389
+ const links = linksFor(pendingEntries);
390
+ headLength += encoder.encode(links).byteLength;
391
+ if (headLength > maxHeadLength)
392
+ throw new RangeError('Cloudflare HTML stream head exceeds 256 KiB');
393
+ headParts[headSlot] = links;
394
+ }
395
+ output += headParts.join('');
396
+ headParts = undefined;
235
397
  }
398
+ return output;
236
399
  };
237
400
 
238
- return body.pipeThrough(
239
- new TransformStream({
240
- flush(controller) {
241
- pending += decoder.decode();
242
- flushReadyHtml(controller, true);
401
+ return new ReadableStream(
402
+ {
403
+ async pull(controller) {
404
+ try {
405
+ reader ??= body.getReader();
406
+ while (!cancelled) {
407
+ const output = await consume(finished);
408
+ if (cancelled) return;
409
+ if (output) controller.enqueue(encoder.encode(output));
410
+ if (finished && pending.length === 0) {
411
+ reader.releaseLock();
412
+ controller.close();
413
+ }
414
+ if (output || finished) return;
415
+ const result = await reader.read();
416
+ finished = result.done;
417
+ pending += result.done
418
+ ? decoder.decode()
419
+ : typeof result.value === 'string'
420
+ ? result.value
421
+ : decoder.decode(result.value, { stream: true });
422
+ }
423
+ } catch (error) {
424
+ if (cancelled) return;
425
+ cancelled = true;
426
+ // The error reaches the client without waiting on the source's
427
+ // cancel hook, which may never settle.
428
+ reader?.cancel(error).catch(() => {});
429
+ reader?.releaseLock();
430
+ controller.error(error);
431
+ }
243
432
  },
244
- transform(chunk, controller) {
245
- pending +=
246
- typeof chunk === 'string'
247
- ? chunk
248
- : decoder.decode(chunk, { stream: true });
249
- flushReadyHtml(controller, false);
433
+ cancel(reason) {
434
+ cancelled = true;
435
+ if (reader) {
436
+ reader.cancel(reason).catch(() => {});
437
+ reader.releaseLock();
438
+ } else {
439
+ body.cancel(reason).catch(() => {});
440
+ }
250
441
  },
251
- }),
442
+ },
443
+ { highWaterMark: 0 },
252
444
  );
253
445
  }
254
446
 
@@ -511,21 +703,31 @@ function createDistributedSsrFragmentContext(request, env) {
511
703
  const pendingResolutions = new Set();
512
704
  const stylesheetHrefsByFragment = configuredFragments.map(() => new Set());
513
705
 
706
+ const getCurrentStylesheetHrefs = (boundaryId, expose) => {
707
+ const hrefs = new Set();
708
+ let configured = false;
709
+ for (const [index, { fragment }] of configuredFragments.entries()) {
710
+ if (
711
+ (boundaryId !== undefined && fragment.boundaryId !== boundaryId) ||
712
+ (expose !== undefined && fragment.expose !== expose)
713
+ ) {
714
+ continue;
715
+ }
716
+ configured = true;
717
+ for (const href of stylesheetHrefsByFragment[index]) hrefs.add(href);
718
+ }
719
+ if (boundaryId !== undefined && !configured) return undefined;
720
+ return [...hrefs];
721
+ };
722
+
514
723
  return {
515
724
  required: true,
725
+ getCurrentStylesheetHrefs,
516
726
  async getStylesheetHrefs() {
517
727
  while (pendingResolutions.size > 0) {
518
728
  await Promise.all([...pendingResolutions]);
519
729
  }
520
-
521
- const hrefs = new Set();
522
- for (const fragmentHrefs of stylesheetHrefsByFragment) {
523
- for (const href of fragmentHrefs) {
524
- hrefs.add(href);
525
- }
526
- }
527
-
528
- return [...hrefs];
730
+ return getCurrentStylesheetHrefs();
529
731
  },
530
732
  resolve(remote, expose, props) {
531
733
  const configuredIndex = configuredFragments.findIndex(
@@ -643,6 +845,8 @@ function createRequestHandlerOptions({
643
845
  htmlTemplate,
644
846
  routeManifest,
645
847
  loadableStats,
848
+ env,
849
+ executionContext,
646
850
  distributedSsrFragments,
647
851
  distributedSsrFragmentRequest,
648
852
  }) {
@@ -657,8 +861,10 @@ function createRequestHandlerOptions({
657
861
  entryName: route.entryName,
658
862
  },
659
863
  params: {},
660
- loaderContext: {},
864
+ loaderContext: new Map(),
661
865
  config: {},
866
+ platform: { kind: 'worker', bindings: env },
867
+ executionContext,
662
868
  locals: {
663
869
  ...(distributedSsrFragments === undefined
664
870
  ? {}
@@ -771,33 +977,54 @@ function getRemoteAssetBase(remoteManifest, manifestUrl) {
771
977
  }
772
978
  }
773
979
 
774
- async function fetchRemoteJson(jsonUrl) {
775
- if (!remoteJsonPromises.has(jsonUrl)) {
776
- remoteJsonPromises.set(
777
- jsonUrl,
778
- fetch(jsonUrl)
779
- .then(response => {
780
- if (!response.ok) {
781
- remoteJsonPromises.delete(jsonUrl);
980
+ // Per-request scope for in-flight remote JSON fetches. A fetch Promise is
981
+ // request-bound I/O in workerd: it must never be stored on a module-level
982
+ // map and awaited by a later, unrelated request (that throws "Cannot
983
+ // perform I/O on behalf of a different request"). This scope only dedupes
984
+ // concurrent lookups for the same URL made while handling one request; it is
985
+ // created fresh per request and discarded once that request completes.
986
+ function createRemoteJsonFetchScope() {
987
+ return new Map();
988
+ }
782
989
 
783
- return {};
784
- }
990
+ async function fetchRemoteJson(jsonUrl, pendingFetches) {
991
+ if (remoteJsonCache.has(jsonUrl)) {
992
+ return remoteJsonCache.get(jsonUrl);
993
+ }
785
994
 
786
- return response.json().catch(() => {
787
- remoteJsonPromises.delete(jsonUrl);
995
+ if (pendingFetches.has(jsonUrl)) {
996
+ return pendingFetches.get(jsonUrl);
997
+ }
788
998
 
789
- return {};
790
- });
791
- })
792
- .catch(() => {
793
- remoteJsonPromises.delete(jsonUrl);
999
+ const pending = (async () => {
1000
+ let response;
1001
+ try {
1002
+ response = await fetch(jsonUrl);
1003
+ } catch {
1004
+ return {};
1005
+ }
794
1006
 
795
- return {};
796
- }),
797
- );
798
- }
1007
+ if (!response.ok) {
1008
+ return {};
1009
+ }
1010
+
1011
+ const value = await response.json().catch(() => undefined);
1012
+ if (value === undefined) {
1013
+ return {};
1014
+ }
1015
+
1016
+ // Only a settled, plain value is safe to share across requests.
1017
+ remoteJsonCache.set(jsonUrl, value);
1018
+ return value;
1019
+ })();
1020
+
1021
+ pendingFetches.set(jsonUrl, pending);
799
1022
 
800
- return remoteJsonPromises.get(jsonUrl);
1023
+ try {
1024
+ return await pending;
1025
+ } finally {
1026
+ pendingFetches.delete(jsonUrl);
1027
+ }
801
1028
  }
802
1029
 
803
1030
  function findRemoteExpose(remoteManifest, exposePath) {
@@ -855,6 +1082,24 @@ function collectLocalFragmentCssAssets(html, request) {
855
1082
  }
856
1083
  }
857
1084
 
1085
+ // This app's own route rendered the fragment, so the fragment depends on
1086
+ // that route's stylesheets too (for example a global Tailwind sheet the
1087
+ // exposed component never imports). Report the deploy-time route CSS so
1088
+ // the composing shell links it without fetching this app's manifests, as
1089
+ // the Node composition does.
1090
+ const fragmentRequest = readDistributedSsrFragmentRequest(request);
1091
+ if (
1092
+ fragmentRequest &&
1093
+ (!containerName || fragmentRequest.boundaryId === containerName) &&
1094
+ findRemoteExpose(localManifest, fragmentRequest.expose)
1095
+ ) {
1096
+ for (const asset of Array.isArray(localManifest.routeCss)
1097
+ ? localManifest.routeCss
1098
+ : []) {
1099
+ assets.add(asset);
1100
+ }
1101
+ }
1102
+
858
1103
  return [...assets];
859
1104
  }
860
1105
 
@@ -917,7 +1162,13 @@ function collectRouteManifestCssAssets(routeManifest) {
917
1162
  return [...assets];
918
1163
  }
919
1164
 
920
- async function collectRenderedRemoteCssHrefs(html, request, env) {
1165
+ async function collectRenderedRemoteCssHrefs(
1166
+ html,
1167
+ request,
1168
+ env,
1169
+ exposes,
1170
+ pendingRemoteJsonFetches,
1171
+ ) {
921
1172
  const bodyStart = html.indexOf('</head>');
922
1173
  const composedFragmentHrefs = collectStylesheetHrefs(
923
1174
  bodyStart >= 0 ? html.slice(bodyStart + '</head>'.length) : html,
@@ -927,7 +1178,7 @@ async function collectRenderedRemoteCssHrefs(html, request, env) {
927
1178
  return [...new Set(composedFragmentHrefs)];
928
1179
  }
929
1180
 
930
- const renderedExposes = collectRenderedFederatedExposes(html);
1181
+ const renderedExposes = exposes ?? collectRenderedFederatedExposes(html);
931
1182
 
932
1183
  if (renderedExposes.length === 0) {
933
1184
  return [];
@@ -961,11 +1212,15 @@ async function collectRenderedRemoteCssHrefs(html, request, env) {
961
1212
  return;
962
1213
  }
963
1214
 
964
- const remoteManifest = await fetchRemoteJson(manifestUrl);
1215
+ const remoteManifest = await fetchRemoteJson(
1216
+ manifestUrl,
1217
+ pendingRemoteJsonFetches,
1218
+ );
965
1219
  const remoteExpose = findRemoteExpose(remoteManifest, expose);
966
1220
  const publicPath = getRemoteAssetBase(remoteManifest, manifestUrl);
967
1221
  const remoteRouteManifest = await fetchRemoteJson(
968
1222
  new URL('routes-manifest.json', publicPath).toString(),
1223
+ pendingRemoteJsonFetches,
969
1224
  );
970
1225
 
971
1226
  for (const asset of collectCssAssetEntries(remoteExpose?.assets)) {
@@ -1008,21 +1263,21 @@ async function withRouteCssLinks(
1008
1263
  request,
1009
1264
  env,
1010
1265
  distributedSsrFragmentCssHrefs,
1266
+ distributedSsrFragments,
1011
1267
  ) {
1012
1268
  const contentType = response.headers.get('content-type') || '';
1013
1269
 
1014
- if (!contentType.includes('text/html')) {
1270
+ if (!contentType.includes('text/html') || response.body === null) {
1015
1271
  return response;
1016
1272
  }
1017
1273
 
1018
- const resolvedDistributedSsrFragmentCssHrefs = await Promise.resolve(
1019
- distributedSsrFragmentCssHrefs ?? [],
1020
- );
1021
- if (
1022
- response.body !== null &&
1023
- resolvedDistributedSsrFragmentCssHrefs.length > 0 &&
1024
- readDistributedSsrFragmentRequest(request) === undefined
1025
- ) {
1274
+ // Scoped to this one request/response cycle; never stored on a module map.
1275
+ const pendingRemoteJsonFetches = createRemoteJsonFetchScope();
1276
+
1277
+ if (readDistributedSsrFragmentRequest(request) === undefined) {
1278
+ const resolvedDistributedSsrFragmentCssHrefs =
1279
+ distributedSsrFragments?.getCurrentStylesheetHrefs() ??
1280
+ (await Promise.resolve(distributedSsrFragmentCssHrefs ?? []));
1026
1281
  const headers = new Headers(response.headers);
1027
1282
  const cssEntries = [
1028
1283
  ...collectRouteCssAssets(route, routeManifest).map(asset => {
@@ -1061,8 +1316,34 @@ async function withRouteCssLinks(
1061
1316
  }
1062
1317
  headers.delete('content-length');
1063
1318
 
1319
+ const seenExposes = new Set();
1320
+ const resolveRemoteCss = async attributes => {
1321
+ const boundaryId = attributes['data-modern-boundary-id'];
1322
+ const expose = attributes['data-modern-mf-expose'];
1323
+ if (!boundaryId || !expose) return [];
1324
+ const verifiedHrefs = distributedSsrFragments?.getCurrentStylesheetHrefs(
1325
+ boundaryId,
1326
+ expose,
1327
+ );
1328
+ if (verifiedHrefs !== undefined) return verifiedHrefs;
1329
+ const key = JSON.stringify([boundaryId, expose]);
1330
+ if (seenExposes.has(key)) return [];
1331
+ seenExposes.add(key);
1332
+ return collectRenderedRemoteCssHrefs(
1333
+ '',
1334
+ request,
1335
+ env,
1336
+ [{ boundaryId, expose }],
1337
+ pendingRemoteJsonFetches,
1338
+ );
1339
+ };
1064
1340
  return new Response(
1065
- createStylesheetLinkStream(response.body, uniqueCssEntries, request.url),
1341
+ createStylesheetLinkStream(
1342
+ response.body,
1343
+ uniqueCssEntries,
1344
+ request.url,
1345
+ resolveRemoteCss,
1346
+ ),
1066
1347
  {
1067
1348
  headers,
1068
1349
  status: response.status,
@@ -1071,12 +1352,18 @@ async function withRouteCssLinks(
1071
1352
  );
1072
1353
  }
1073
1354
 
1355
+ const resolvedDistributedSsrFragmentCssHrefs = await Promise.resolve(
1356
+ distributedSsrFragments?.getStylesheetHrefs() ??
1357
+ distributedSsrFragmentCssHrefs ??
1358
+ [],
1359
+ );
1074
1360
  const html = dedupeStylesheetLinks(
1075
1361
  await response.text(),
1076
1362
  undefined,
1077
1363
  request.url,
1078
1364
  );
1079
1365
  const headers = new Headers(response.headers);
1366
+ headers.delete('content-length');
1080
1367
  const localFragmentCssAssets = collectLocalFragmentCssAssets(html, request);
1081
1368
  const localFragmentProvenance = await createLocalFragmentProvenance(
1082
1369
  html,
@@ -1108,7 +1395,13 @@ async function withRouteCssLinks(
1108
1395
  }),
1109
1396
  ...(resolvedDistributedSsrFragmentCssHrefs.length > 0
1110
1397
  ? resolvedDistributedSsrFragmentCssHrefs
1111
- : await collectRenderedRemoteCssHrefs(html, request, env)
1398
+ : await collectRenderedRemoteCssHrefs(
1399
+ html,
1400
+ request,
1401
+ env,
1402
+ undefined,
1403
+ pendingRemoteJsonFetches,
1404
+ )
1112
1405
  ).map(href => ({
1113
1406
  href,
1114
1407
  preloadHref: new URL(href, request.url).toString(),
@@ -1134,7 +1427,7 @@ async function withRouteCssLinks(
1134
1427
  seenCssHrefs.add(entry.preloadHref);
1135
1428
  return true;
1136
1429
  });
1137
- for (const { href, preloadHref } of uniqueCssEntries) {
1430
+ for (const { preloadHref } of uniqueCssEntries) {
1138
1431
  const preloadUrl = new URL(preloadHref);
1139
1432
  const preloadReference =
1140
1433
  preloadUrl.origin === requestOrigin
@@ -1170,9 +1463,17 @@ async function withRouteCssLinks(
1170
1463
  });
1171
1464
  }
1172
1465
 
1173
- async function getRequestHandlerOptions(route, request, env) {
1466
+ async function getRequestHandlerOptions(
1467
+ route,
1468
+ request,
1469
+ env,
1470
+ executionContext,
1471
+ includeHtmlTemplate = true,
1472
+ ) {
1174
1473
  const [htmlTemplate, routeManifest, loadableStats] = await Promise.all([
1175
- readAssetText(route.entryPath, request, env),
1474
+ includeHtmlTemplate
1475
+ ? readAssetText(route.entryPath, request, env)
1476
+ : undefined,
1176
1477
  readAssetJson(MODERN_WORKER_MANIFEST.resources.routeManifest, request, env),
1177
1478
  readAssetJson(MODERN_WORKER_MANIFEST.resources.loadableStats, request, env),
1178
1479
  ]);
@@ -1183,12 +1484,18 @@ async function getRequestHandlerOptions(route, request, env) {
1183
1484
  const distributedSsrFragmentRequest =
1184
1485
  readDistributedSsrFragmentRequest(request);
1185
1486
 
1186
- return createRequestHandlerOptions({
1487
+ const options = createRequestHandlerOptions({
1187
1488
  route,
1188
1489
  htmlTemplate: htmlTemplate || '',
1189
1490
  routeManifest,
1190
1491
  loadableStats,
1492
+ env,
1493
+ executionContext,
1191
1494
  distributedSsrFragments,
1192
1495
  distributedSsrFragmentRequest,
1193
1496
  });
1497
+ if (!includeHtmlTemplate) {
1498
+ delete options.resource.htmlTemplate;
1499
+ }
1500
+ return options;
1194
1501
  }