@reckona/mreact-compiler 0.0.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/diagnostics.d.ts +12 -0
  4. package/dist/diagnostics.d.ts.map +1 -0
  5. package/dist/diagnostics.js +82 -0
  6. package/dist/diagnostics.js.map +1 -0
  7. package/dist/emit-client.d.ts +8 -0
  8. package/dist/emit-client.d.ts.map +1 -0
  9. package/dist/emit-client.js +401 -0
  10. package/dist/emit-client.js.map +1 -0
  11. package/dist/emit-compat.d.ts +11 -0
  12. package/dist/emit-compat.d.ts.map +1 -0
  13. package/dist/emit-compat.js +340 -0
  14. package/dist/emit-compat.js.map +1 -0
  15. package/dist/emit-escape-helper.d.ts +2 -0
  16. package/dist/emit-escape-helper.d.ts.map +1 -0
  17. package/dist/emit-escape-helper.js +45 -0
  18. package/dist/emit-escape-helper.js.map +1 -0
  19. package/dist/emit-server-stream.d.ts +18 -0
  20. package/dist/emit-server-stream.d.ts.map +1 -0
  21. package/dist/emit-server-stream.js +1415 -0
  22. package/dist/emit-server-stream.js.map +1 -0
  23. package/dist/emit-server.d.ts +13 -0
  24. package/dist/emit-server.d.ts.map +1 -0
  25. package/dist/emit-server.js +1103 -0
  26. package/dist/emit-server.js.map +1 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +3 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/internal.d.ts +30 -0
  32. package/dist/internal.d.ts.map +1 -0
  33. package/dist/internal.js +229 -0
  34. package/dist/internal.js.map +1 -0
  35. package/dist/ir.d.ts +115 -0
  36. package/dist/ir.d.ts.map +1 -0
  37. package/dist/ir.js +2 -0
  38. package/dist/ir.js.map +1 -0
  39. package/dist/oxc-analysis-types.d.ts +2 -0
  40. package/dist/oxc-analysis-types.d.ts.map +1 -0
  41. package/dist/oxc-analysis-types.js +2 -0
  42. package/dist/oxc-analysis-types.js.map +1 -0
  43. package/dist/oxc-await-analysis.d.ts +5 -0
  44. package/dist/oxc-await-analysis.d.ts.map +1 -0
  45. package/dist/oxc-await-analysis.js +109 -0
  46. package/dist/oxc-await-analysis.js.map +1 -0
  47. package/dist/oxc-await-ids.d.ts +3 -0
  48. package/dist/oxc-await-ids.d.ts.map +1 -0
  49. package/dist/oxc-await-ids.js +50 -0
  50. package/dist/oxc-await-ids.js.map +1 -0
  51. package/dist/oxc-await-validation.d.ts +4 -0
  52. package/dist/oxc-await-validation.d.ts.map +1 -0
  53. package/dist/oxc-await-validation.js +47 -0
  54. package/dist/oxc-await-validation.js.map +1 -0
  55. package/dist/oxc-bindings.d.ts +5 -0
  56. package/dist/oxc-bindings.d.ts.map +1 -0
  57. package/dist/oxc-bindings.js +55 -0
  58. package/dist/oxc-bindings.js.map +1 -0
  59. package/dist/oxc-body-lowering.d.ts +12 -0
  60. package/dist/oxc-body-lowering.d.ts.map +1 -0
  61. package/dist/oxc-body-lowering.js +131 -0
  62. package/dist/oxc-body-lowering.js.map +1 -0
  63. package/dist/oxc-child-analysis.d.ts +17 -0
  64. package/dist/oxc-child-analysis.d.ts.map +1 -0
  65. package/dist/oxc-child-analysis.js +292 -0
  66. package/dist/oxc-child-analysis.js.map +1 -0
  67. package/dist/oxc-code-utils.d.ts +3 -0
  68. package/dist/oxc-code-utils.d.ts.map +1 -0
  69. package/dist/oxc-code-utils.js +16 -0
  70. package/dist/oxc-code-utils.js.map +1 -0
  71. package/dist/oxc-component-detection.d.ts +22 -0
  72. package/dist/oxc-component-detection.d.ts.map +1 -0
  73. package/dist/oxc-component-detection.js +188 -0
  74. package/dist/oxc-component-detection.js.map +1 -0
  75. package/dist/oxc-component-props.d.ts +14 -0
  76. package/dist/oxc-component-props.d.ts.map +1 -0
  77. package/dist/oxc-component-props.js +92 -0
  78. package/dist/oxc-component-props.js.map +1 -0
  79. package/dist/oxc-component-references.d.ts +5 -0
  80. package/dist/oxc-component-references.d.ts.map +1 -0
  81. package/dist/oxc-component-references.js +118 -0
  82. package/dist/oxc-component-references.js.map +1 -0
  83. package/dist/oxc-dom-lowering.d.ts +2 -0
  84. package/dist/oxc-dom-lowering.d.ts.map +1 -0
  85. package/dist/oxc-dom-lowering.js +99 -0
  86. package/dist/oxc-dom-lowering.js.map +1 -0
  87. package/dist/oxc-expression-utils.d.ts +5 -0
  88. package/dist/oxc-expression-utils.d.ts.map +1 -0
  89. package/dist/oxc-expression-utils.js +31 -0
  90. package/dist/oxc-expression-utils.js.map +1 -0
  91. package/dist/oxc-jsx-attributes.d.ts +6 -0
  92. package/dist/oxc-jsx-attributes.d.ts.map +1 -0
  93. package/dist/oxc-jsx-attributes.js +88 -0
  94. package/dist/oxc-jsx-attributes.js.map +1 -0
  95. package/dist/oxc-jsx-text.d.ts +2 -0
  96. package/dist/oxc-jsx-text.d.ts.map +1 -0
  97. package/dist/oxc-jsx-text.js +50 -0
  98. package/dist/oxc-jsx-text.js.map +1 -0
  99. package/dist/oxc-nested-lowering.d.ts +8 -0
  100. package/dist/oxc-nested-lowering.d.ts.map +1 -0
  101. package/dist/oxc-nested-lowering.js +182 -0
  102. package/dist/oxc-nested-lowering.js.map +1 -0
  103. package/dist/oxc-node-utils.d.ts +8 -0
  104. package/dist/oxc-node-utils.d.ts.map +1 -0
  105. package/dist/oxc-node-utils.js +41 -0
  106. package/dist/oxc-node-utils.js.map +1 -0
  107. package/dist/oxc-raw-jsx.d.ts +3 -0
  108. package/dist/oxc-raw-jsx.d.ts.map +1 -0
  109. package/dist/oxc-raw-jsx.js +28 -0
  110. package/dist/oxc-raw-jsx.js.map +1 -0
  111. package/dist/oxc-render-values.d.ts +6 -0
  112. package/dist/oxc-render-values.d.ts.map +1 -0
  113. package/dist/oxc-render-values.js +149 -0
  114. package/dist/oxc-render-values.js.map +1 -0
  115. package/dist/oxc-runtime-emit.d.ts +4 -0
  116. package/dist/oxc-runtime-emit.d.ts.map +1 -0
  117. package/dist/oxc-runtime-emit.js +143 -0
  118. package/dist/oxc-runtime-emit.js.map +1 -0
  119. package/dist/oxc-transform.d.ts +4 -0
  120. package/dist/oxc-transform.d.ts.map +1 -0
  121. package/dist/oxc-transform.js +65 -0
  122. package/dist/oxc-transform.js.map +1 -0
  123. package/dist/oxc.d.ts +15 -0
  124. package/dist/oxc.d.ts.map +1 -0
  125. package/dist/oxc.js +250 -0
  126. package/dist/oxc.js.map +1 -0
  127. package/dist/transform.d.ts +3 -0
  128. package/dist/transform.d.ts.map +1 -0
  129. package/dist/transform.js +429 -0
  130. package/dist/transform.js.map +1 -0
  131. package/dist/types.d.ts +96 -0
  132. package/dist/types.d.ts.map +1 -0
  133. package/dist/types.js +2 -0
  134. package/dist/types.js.map +1 -0
  135. package/package.json +54 -0
@@ -0,0 +1,1415 @@
1
+ import { emitEscapeHtmlHelper } from "./emit-escape-helper.js";
2
+ import { escapeHtmlAttribute as escapeHtml } from "@reckona/mreact-shared/html-escape";
3
+ let currentUrlSafeHelperName = "_urlAttrSafe";
4
+ let currentClientBoundaryHelperName;
5
+ const URL_ATTRIBUTE_NAMES = new Set([
6
+ "href",
7
+ "src",
8
+ "action",
9
+ "formaction",
10
+ "xlink:href",
11
+ "ping",
12
+ "poster",
13
+ "background",
14
+ "manifest",
15
+ ]);
16
+ function isUrlAttribute(name) {
17
+ return URL_ATTRIBUTE_NAMES.has(name);
18
+ }
19
+ const DANGEROUS_HTML_ATTRIBUTE_NAMES = new Set(["srcdoc"]);
20
+ function isDangerousHtmlAttribute(name) {
21
+ return DANGEROUS_HTML_ATTRIBUTE_NAMES.has(name);
22
+ }
23
+ function isStaticUrlValueUnsafe(name, value) {
24
+ let start = 0;
25
+ while (start < value.length && value.charCodeAt(start) <= 0x20) {
26
+ start += 1;
27
+ }
28
+ const canonical = value.slice(start).replace(/[\t\r\n]/g, "");
29
+ const match = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(canonical);
30
+ if (match === null || match[1] === undefined)
31
+ return false;
32
+ const scheme = match[1].toLowerCase();
33
+ if (scheme === "javascript" || scheme === "vbscript" || scheme === "livescript" || scheme === "mhtml" || scheme === "file")
34
+ return true;
35
+ if (scheme === "data") {
36
+ if ((name === "src" || name === "poster") && /^data:image\//i.test(canonical))
37
+ return false;
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ export function emitServerStream(ir, options = {}) {
43
+ const serverBootstrap = options.serverBootstrap ?? "none";
44
+ const escapeHelperName = allocateHelperName(ir, "_escapeHtml");
45
+ const escapeBatchHelperName = options.escape === undefined
46
+ ? undefined
47
+ : allocateHelperName(ir, "_escapeHtmlBatch");
48
+ const asyncBoundaryHelperName = allocateHelperName(ir, "_renderAsyncBoundary");
49
+ const outOfOrderBoundaryHelperName = allocateHelperName(ir, "_renderOutOfOrderBoundary");
50
+ const reorderScriptHelperName = allocateHelperName(ir, "_renderOutOfOrderReorderScript");
51
+ const reactSuspenseBoundaryHelperName = allocateHelperName(ir, "_renderReactSuspenseBoundary");
52
+ const reactSuspenseOutOfOrderBoundaryHelperName = allocateHelperName(ir, "_renderReactSuspenseOutOfOrderBoundary");
53
+ const compatRenderToStringHelperName = allocateHelperName(ir, "_renderCompatToString");
54
+ const clientBoundaryHelperName = usesClientBoundary(ir)
55
+ ? allocateHelperName(ir, "_renderClientBoundary")
56
+ : undefined;
57
+ const urlSafeHelperName = allocateHelperName(ir, "_urlAttrSafe");
58
+ currentUrlSafeHelperName = urlSafeHelperName;
59
+ currentClientBoundaryHelperName = clientBoundaryHelperName;
60
+ const helper = emitEscapeHtmlHelper(escapeHelperName);
61
+ const urlSafeHelper = [
62
+ `function ${urlSafeHelperName}(name, value) {`,
63
+ ` if (typeof value !== "string") return value;`,
64
+ ` const _canonical = value`,
65
+ ` .replace(/^[\\x00-\\x20]+/u, "")`,
66
+ ` .replace(/[\\t\\r\\n]/g, "");`,
67
+ ` const _match = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(_canonical);`,
68
+ ` if (_match === null) return value;`,
69
+ ` const _scheme = _match[1].toLowerCase();`,
70
+ ` if (_scheme !== "javascript" && _scheme !== "vbscript" && _scheme !== "livescript" && _scheme !== "mhtml" && _scheme !== "file" && _scheme !== "data") return value;`,
71
+ ` if (_scheme === "data" && (name === "src" || name === "poster") && /^data:image\\//i.test(_canonical)) return value;`,
72
+ ` return undefined;`,
73
+ `}`,
74
+ ].join("\n");
75
+ const components = ir.components
76
+ .map((component) => emitComponent(component, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reorderScriptHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName, {
77
+ serverBootstrap,
78
+ ...(options.serverBootstrapNonce === undefined
79
+ ? {}
80
+ : { serverBootstrapNonce: options.serverBootstrapNonce }),
81
+ ...(options.serverBootstrapSrc === undefined
82
+ ? {}
83
+ : { serverBootstrapSrc: options.serverBootstrapSrc }),
84
+ ...(options.serverHydration === undefined
85
+ ? {}
86
+ : { serverHydration: options.serverHydration }),
87
+ ...(options.serverAwaitHydration === undefined
88
+ ? {}
89
+ : { serverAwaitHydration: options.serverAwaitHydration }),
90
+ ...(options.reactSuspenseRevealScriptSrc === undefined
91
+ ? {}
92
+ : { reactSuspenseRevealScriptSrc: options.reactSuspenseRevealScriptSrc }),
93
+ dynamicAttributes: options.dynamicAttributes ?? "emit",
94
+ ...(escapeBatchHelperName === undefined ? {} : { escapeBatchHelperName }),
95
+ }))
96
+ .join("\n\n");
97
+ // Emit batch escape import only when the helper is actually referenced
98
+ // (issue 048: dead-import elimination).
99
+ const escapeImport = options.escape === undefined ||
100
+ escapeBatchHelperName === undefined ||
101
+ !components.includes(escapeBatchHelperName)
102
+ ? ""
103
+ : `import { ${options.escape.batchImportName} as ${escapeBatchHelperName} } from ${stringLiteral(options.escape.batchImportSource)};`;
104
+ const imports = collectImports(ir, serverBootstrap);
105
+ const importAliases = {
106
+ renderAsyncBoundary: asyncBoundaryHelperName,
107
+ renderOutOfOrderBoundary: outOfOrderBoundaryHelperName,
108
+ renderOutOfOrderReorderScript: reorderScriptHelperName,
109
+ renderReactSuspenseBoundary: reactSuspenseBoundaryHelperName,
110
+ renderReactSuspenseOutOfOrderBoundary: reactSuspenseOutOfOrderBoundaryHelperName,
111
+ renderToString: compatRenderToStringHelperName,
112
+ };
113
+ const importLine = imports
114
+ .map((runtimeImport) => `import { ${runtimeImport.specifiers
115
+ .map((specifier) => `${specifier} as ${importAliases[specifier]}`)
116
+ .join(", ")} } from "${runtimeImport.source}";`)
117
+ .join("\n");
118
+ const userImports = emitUserImports(ir);
119
+ const moduleStatements = emitModuleStatements(ir);
120
+ const importsBlock = [importLine, escapeImport, userImports, moduleStatements].filter(Boolean).join("\n");
121
+ const urlSafeBlock = components.includes(urlSafeHelperName) ? `\n\n${urlSafeHelper}` : "";
122
+ const clientBoundaryBlock = clientBoundaryHelperName === undefined || !components.includes(clientBoundaryHelperName)
123
+ ? ""
124
+ : `\n\n${emitClientBoundaryHelper(clientBoundaryHelperName)}`;
125
+ return {
126
+ code: `${importsBlock === "" ? "" : `${importsBlock}\n\n`}${helper}${urlSafeBlock}${clientBoundaryBlock}\n\n${components}\n`,
127
+ imports,
128
+ };
129
+ }
130
+ function emitUserImports(ir) {
131
+ return ir.components.length === 0 ? "" : ir.userImports.join("\n");
132
+ }
133
+ function emitModuleStatements(ir) {
134
+ return ir.components.length === 0 ? "" : ir.moduleStatements.join("\n");
135
+ }
136
+ function collectImports(ir, serverBootstrap) {
137
+ const serverSpecifiers = [
138
+ ...(hasInOrderAsyncBoundary(ir) ? ["renderAsyncBoundary"] : []),
139
+ ...(hasOutOfOrderAsyncBoundary(ir) ? ["renderOutOfOrderBoundary"] : []),
140
+ ...(serverBootstrap === "out-of-order-reorder" && hasOutOfOrderAsyncBoundary(ir)
141
+ ? ["renderOutOfOrderReorderScript"]
142
+ : []),
143
+ ...(hasReactSuspenseBoundary(ir) ? ["renderReactSuspenseBoundary"] : []),
144
+ ...(hasReactSuspenseOutOfOrderBoundary(ir) ? ["renderReactSuspenseOutOfOrderBoundary"] : []),
145
+ ];
146
+ const imports = [];
147
+ if (serverSpecifiers.length > 0) {
148
+ imports.push({
149
+ source: "@reckona/mreact-server",
150
+ specifiers: serverSpecifiers,
151
+ });
152
+ }
153
+ if (hasCompatComponentReference(ir) || hasReactNodeRender(ir)) {
154
+ imports.push({
155
+ source: "@reckona/mreact-compat",
156
+ specifiers: ["renderToString"],
157
+ });
158
+ }
159
+ return imports;
160
+ }
161
+ function hasInOrderAsyncBoundary(ir) {
162
+ return ir.components.some((component) => containsAsyncBoundary(component.root, false));
163
+ }
164
+ function hasOutOfOrderAsyncBoundary(ir) {
165
+ return ir.components.some((component) => containsAsyncBoundary(component.root, true));
166
+ }
167
+ function hasReactSuspenseBoundary(ir) {
168
+ return ir.components.some((component) => containsReactSuspense(component.root, false));
169
+ }
170
+ function hasReactSuspenseOutOfOrderBoundary(ir) {
171
+ return ir.components.some((component) => containsReactSuspense(component.root, true));
172
+ }
173
+ function hasCompatComponentReference(ir) {
174
+ return ir.components.some((component) => containsCompatComponent(component.root));
175
+ }
176
+ function hasReactNodeRender(ir) {
177
+ return ir.components.some((component) => containsReactNodeRender(component.root));
178
+ }
179
+ function usesClientBoundary(ir) {
180
+ return ir.components.some((component) => containsClientBoundary(component.root));
181
+ }
182
+ function emitClientBoundaryHelper(name) {
183
+ return [
184
+ `function ${name}(name, props) {`,
185
+ ` const _name = String(name);`,
186
+ ` const _escapedName = _name.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");`,
187
+ ` const _json = (JSON.stringify(props ?? {}) ?? "{}").replaceAll("<", "\\\\u003c");`,
188
+ ` return \`<template data-mreact-client-boundary="\${_escapedName}"></template><script type="application/json" data-mreact-client-boundary-props="\${_escapedName}">\${_json}</script>\`;`,
189
+ `}`,
190
+ ].join("\n");
191
+ }
192
+ function emitComponent(component, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reorderScriptHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName, options) {
193
+ const { serverBootstrap, serverBootstrapNonce, serverBootstrapSrc } = options;
194
+ const sinkName = allocateComponentSinkName(component);
195
+ const parameters = [sinkName, ...component.parameters].join(", ");
196
+ const body = component.bodyStatements.map((statement) => ` ${statement}`);
197
+ const markerId = encodeURIComponent(component.name);
198
+ const hydrationStartStatements = options.serverHydration === true
199
+ ? [` ${sinkName}.append(${stringLiteral(`<!--mreact-h:start:${markerId}-->`)});`]
200
+ : [];
201
+ const appendStatements = emitAppendStatements(component.root, sinkName, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName, options.serverBootstrapNonce, options.reactSuspenseRevealScriptSrc, options.serverHydration === true, options.serverAwaitHydration === true, options.dynamicAttributes, options.escapeBatchHelperName);
202
+ const bootstrapStatements = serverBootstrap === "out-of-order-reorder" && containsAsyncBoundary(component.root, true)
203
+ ? [
204
+ ` ${reorderScriptHelperName}(${sinkName}${emitBootstrapOptions(serverBootstrapNonce, serverBootstrapSrc)});`,
205
+ ]
206
+ : [];
207
+ const hydrationEndStatements = options.serverHydration === true
208
+ ? [` ${sinkName}.append(${stringLiteral(`<!--mreact-h:end:${markerId}-->`)});`]
209
+ : [];
210
+ const exportPrefix = component.exportDefault === true ? "export default " : component.exported === false ? "" : "export ";
211
+ const asyncPrefix = component.async === true || containsAnyAsyncBoundary(component.root) ? "async " : "";
212
+ const functionKeyword = `${exportPrefix}${asyncPrefix}function`;
213
+ return [
214
+ `${functionKeyword} ${component.name}(${parameters}) {`,
215
+ ...body,
216
+ ...hydrationStartStatements,
217
+ ...appendStatements,
218
+ ...hydrationEndStatements,
219
+ ...bootstrapStatements,
220
+ `}`,
221
+ ].join("\n");
222
+ }
223
+ function emitBootstrapOptions(nonce, src) {
224
+ const entries = [
225
+ ...(nonce === undefined ? [] : [`nonce: ${stringLiteral(nonce)}`]),
226
+ ...(src === undefined ? [] : [`src: ${stringLiteral(src)}`]),
227
+ ];
228
+ return entries.length === 0 ? "" : `, { ${entries.join(", ")} }`;
229
+ }
230
+ function emitAppendStatements(node, sinkName, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName, reactSuspenseRevealScriptNonce, reactSuspenseRevealScriptSrc, hydration, awaitHydration, dynamicAttributes, escapeBatchHelperName) {
231
+ const collected = collectHtmlParts(node, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, {
232
+ dynamicAttributes,
233
+ ...(escapeBatchHelperName === undefined ? {} : { escapeBatchHelperName }),
234
+ hydration,
235
+ awaitHydration,
236
+ nextFragmentId: 0,
237
+ ...(reactSuspenseRevealScriptNonce === undefined ? {} : { reactSuspenseRevealScriptNonce }),
238
+ ...(reactSuspenseRevealScriptSrc === undefined ? {} : { reactSuspenseRevealScriptSrc }),
239
+ });
240
+ return coalesceAdjacentStaticParts(collected).map((part) => {
241
+ if (part.kind === "async-boundary") {
242
+ return emitAsyncBoundary(part, sinkName, asyncBoundaryHelperName, compatRenderToStringHelperName);
243
+ }
244
+ if (part.kind === "out-of-order-boundary") {
245
+ return emitOutOfOrderBoundary(part, sinkName, outOfOrderBoundaryHelperName, compatRenderToStringHelperName);
246
+ }
247
+ if (part.kind === "react-suspense-boundary") {
248
+ return emitReactSuspenseBoundary(part, sinkName, reactSuspenseBoundaryHelperName, compatRenderToStringHelperName);
249
+ }
250
+ if (part.kind === "react-suspense-out-of-order-boundary") {
251
+ return emitReactSuspenseOutOfOrderBoundary(part, sinkName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName);
252
+ }
253
+ if (part.kind === "component") {
254
+ if (part.runtime === "compat") {
255
+ return emitCompatComponentAppendStatements(part, sinkName, compatRenderToStringHelperName, " ");
256
+ }
257
+ return ` await ${part.name}(${sinkName}, ${emitPropsObject(part.props, part.children, part.escapeHelperName)});`;
258
+ }
259
+ if (part.kind === "react-node") {
260
+ return ` ${sinkName}.append(${compatRenderToStringHelperName}(() => (${part.code})));`;
261
+ }
262
+ if (part.kind === "list") {
263
+ return emitListPart(part, sinkName, compatRenderToStringHelperName, " ");
264
+ }
265
+ const expression = part.kind === "static"
266
+ ? stringLiteral(part.value)
267
+ : part.kind === "dynamic"
268
+ ? `${escapeHelperName}(${part.code})`
269
+ : part.code;
270
+ return ` ${sinkName}.append(${expression});`;
271
+ });
272
+ }
273
+ function isHtmlSyncPart(part) {
274
+ return (part.kind !== "async-boundary" &&
275
+ part.kind !== "out-of-order-boundary" &&
276
+ part.kind !== "react-suspense-boundary" &&
277
+ part.kind !== "react-suspense-out-of-order-boundary");
278
+ }
279
+ // Issue 085: collapse runs of adjacent `static` parts into a single
280
+ // `static` part. Each part becomes one `sink.append(...)` call at emit
281
+ // time and `sink.append` goes through 2-3 function frames, so merging
282
+ // `["<span", ">"]` into `"<span>"` halves the per-iteration call count
283
+ // for tag-heavy lists.
284
+ //
285
+ // Only adjacent static-kind parts are merged; dynamic / boundary /
286
+ // component / list / react-node parts stay where they are.
287
+ function coalesceAdjacentStaticParts(parts) {
288
+ if (parts.length < 2)
289
+ return parts;
290
+ const result = [];
291
+ let pending;
292
+ for (const part of parts) {
293
+ if (part.kind === "static") {
294
+ pending =
295
+ pending === undefined
296
+ ? { kind: "static", value: part.value }
297
+ : { kind: "static", value: pending.value + part.value };
298
+ continue;
299
+ }
300
+ if (pending !== undefined) {
301
+ result.push(pending);
302
+ pending = undefined;
303
+ }
304
+ result.push(part);
305
+ }
306
+ if (pending !== undefined) {
307
+ result.push(pending);
308
+ }
309
+ return result;
310
+ }
311
+ function emitSyncPartAsAppendStatement(part, sinkName, compatRenderToStringHelperName, indent) {
312
+ if (part.kind === "component") {
313
+ if (part.runtime === "compat") {
314
+ return emitCompatComponentAppendStatements(part, sinkName, compatRenderToStringHelperName, indent);
315
+ }
316
+ return `${indent}await ${part.name}(${sinkName}, ${emitPropsObject(part.props, part.children, part.escapeHelperName)});`;
317
+ }
318
+ if (part.kind === "react-node") {
319
+ return `${indent}${sinkName}.append(${compatRenderToStringHelperName}(() => (${part.code})));`;
320
+ }
321
+ if (part.kind === "list") {
322
+ return emitListPart(part, sinkName, compatRenderToStringHelperName, indent);
323
+ }
324
+ const expression = part.kind === "static"
325
+ ? stringLiteral(part.value)
326
+ : part.kind === "dynamic"
327
+ ? `${part.escapeHelperName}(${part.code})`
328
+ : part.code;
329
+ return `${indent}${sinkName}.append(${expression});`;
330
+ }
331
+ function emitListPart(part, sinkName, compatRenderToStringHelperName, indent) {
332
+ const innerIndent = indent + " ";
333
+ const itemBinding = `${innerIndent}const ${part.itemName} = _arr[_i];`;
334
+ const indexBinding = part.indexName === undefined ? undefined : `${innerIndent}const ${part.indexName} = _i;`;
335
+ const bodyLines = part.bodyStatements.map((statement) => `${innerIndent}${statement}`);
336
+ const coalescedParts = coalesceAdjacentStaticParts(part.parts);
337
+ // Issue 085 follow-up: if every child part can be expressed as a
338
+ // pure string expression (no `sink.append`/`await` required), build
339
+ // up a local ConsString accumulator and emit a single
340
+ // `sink.append(_listOut)` at the end of the iteration. This matches
341
+ // the string backend's `_out +=` pattern, which V8 turns into a
342
+ // shallow cons-string tree (~3 ns per append). Otherwise (the list
343
+ // contains components / nested lists with components / etc) fall
344
+ // back to per-part `sink.append` inside the loop.
345
+ const stringExpressions = coalescedParts.map((child) => tryEmitPartAsStringExpression(child, compatRenderToStringHelperName));
346
+ const allStringSafe = stringExpressions.every((expr) => expr !== undefined);
347
+ if (allStringSafe) {
348
+ const accumulatorName = "_listOut";
349
+ const concatLines = stringExpressions.map((expr) => `${innerIndent}${accumulatorName} += ${expr};`);
350
+ return [
351
+ `${indent}{`,
352
+ `${indent} const _arr = (${part.itemsCode});`,
353
+ `${indent} let ${accumulatorName} = "";`,
354
+ `${indent} for (let _i = 0, _len = _arr.length; _i < _len; _i++) {`,
355
+ itemBinding,
356
+ ...(indexBinding === undefined ? [] : [indexBinding]),
357
+ ...bodyLines,
358
+ ...concatLines,
359
+ `${indent} }`,
360
+ `${indent} ${sinkName}.append(${accumulatorName});`,
361
+ `${indent}}`,
362
+ ].join("\n");
363
+ }
364
+ const childLines = coalescedParts.map((child) => emitSyncPartAsAppendStatement(child, sinkName, compatRenderToStringHelperName, innerIndent));
365
+ return [
366
+ `${indent}{`,
367
+ `${indent} const _arr = (${part.itemsCode});`,
368
+ `${indent} for (let _i = 0, _len = _arr.length; _i < _len; _i++) {`,
369
+ itemBinding,
370
+ ...(indexBinding === undefined ? [] : [indexBinding]),
371
+ ...bodyLines,
372
+ ...childLines,
373
+ `${indent} }`,
374
+ `${indent}}`,
375
+ ].join("\n");
376
+ }
377
+ // Returns a string-typed expression for `part` if it can be evaluated
378
+ // synchronously without writing to the sink, otherwise undefined.
379
+ // Used by `emitListPart` to choose between the cons-string accumulator
380
+ // path and the per-part `sink.append` path.
381
+ function tryEmitPartAsStringExpression(part, compatRenderToStringHelperName) {
382
+ if (part.kind === "static")
383
+ return stringLiteral(part.value);
384
+ if (part.kind === "dynamic")
385
+ return `${part.escapeHelperName}(${part.code})`;
386
+ if (part.kind === "raw-dynamic")
387
+ return `(${part.code})`;
388
+ if (part.kind === "react-node") {
389
+ return `${compatRenderToStringHelperName}(() => (${part.code}))`;
390
+ }
391
+ // `component` parts require `await sink-write`; `list` with sink-
392
+ // needing children also can't collapse. Signal fallback.
393
+ return undefined;
394
+ }
395
+ function emitAsyncBoundary(part, sinkName, asyncBoundaryHelperName, compatRenderToStringHelperName) {
396
+ const optionFields = [];
397
+ if (part.catchName !== undefined && part.catchParts !== undefined) {
398
+ optionFields.push(`catch: (${sinkName}, ${part.catchName}) => {\n${emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName)}\n }`);
399
+ }
400
+ if (part.awaitId !== undefined) {
401
+ optionFields.push(`hydrationAwaitId: ${JSON.stringify(part.awaitId)}`);
402
+ }
403
+ const optionsExpression = optionFields.length === 0
404
+ ? ""
405
+ : `, { ${optionFields.join(", ")} }`;
406
+ return [
407
+ ` await ${asyncBoundaryHelperName}(${sinkName}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
408
+ emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
409
+ ` }${optionsExpression});`,
410
+ ].join("\n");
411
+ }
412
+ function emitOutOfOrderBoundary(part, sinkName, outOfOrderBoundaryHelperName, compatRenderToStringHelperName) {
413
+ const catchOption = part.catchName === undefined || part.catchParts === undefined
414
+ ? ""
415
+ : `,\n catch: (${sinkName}, ${part.catchName}) => {\n${emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName)}\n }`;
416
+ const hydrationAwaitIdOption = part.awaitId === undefined
417
+ ? ""
418
+ : `,\n hydrationAwaitId: ${JSON.stringify(part.awaitId)}`;
419
+ return [
420
+ ` ${outOfOrderBoundaryHelperName}(${sinkName}, ${JSON.stringify(part.id)}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
421
+ emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
422
+ ` }, {`,
423
+ ...(part.hydration ? [` hydration: true,`] : []),
424
+ ` placeholder: (${sinkName}) => {`,
425
+ emitNestedAppendStatements(part.placeholderParts, sinkName, compatRenderToStringHelperName),
426
+ ` }${catchOption}${hydrationAwaitIdOption}`,
427
+ ` });`,
428
+ ].join("\n");
429
+ }
430
+ function emitReactSuspenseBoundary(part, sinkName, reactSuspenseBoundaryHelperName, compatRenderToStringHelperName) {
431
+ return [
432
+ ` await ${reactSuspenseBoundaryHelperName}(${sinkName}, async (${sinkName}) => {`,
433
+ emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
434
+ ` });`,
435
+ ].join("\n");
436
+ }
437
+ function emitReactSuspenseOutOfOrderBoundary(part, sinkName, reactSuspenseOutOfOrderBoundaryHelperName, compatRenderToStringHelperName) {
438
+ const options = [
439
+ ` fallback: (${sinkName}) => {`,
440
+ emitNestedAppendStatements(part.fallbackParts, sinkName, compatRenderToStringHelperName),
441
+ ` },`,
442
+ ...(part.catchName === undefined || part.catchParts === undefined
443
+ ? []
444
+ : [
445
+ ` catch: (${sinkName}, ${part.catchName}) => {`,
446
+ emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName),
447
+ ` },`,
448
+ ]),
449
+ ...(part.nonce === undefined ? [] : [` nonce: ${stringLiteral(part.nonce)},`]),
450
+ ...(part.scriptSrc === undefined ? [] : [` src: ${stringLiteral(part.scriptSrc)},`]),
451
+ ];
452
+ return [
453
+ ` ${reactSuspenseOutOfOrderBoundaryHelperName}(${sinkName}, ${JSON.stringify(part.boundaryId)}, ${JSON.stringify(part.segmentId)}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
454
+ emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
455
+ ` }, {`,
456
+ ...options,
457
+ ` });`,
458
+ ].join("\n");
459
+ }
460
+ function emitNestedAppendStatements(parts, sinkName, compatRenderToStringHelperName) {
461
+ return coalesceAdjacentStaticParts(parts)
462
+ .map((part) => emitSyncPartAsAppendStatement(part, sinkName, compatRenderToStringHelperName, " "))
463
+ .join("\n");
464
+ }
465
+ function emitCompatComponentAppendStatements(part, sinkName, compatRenderToStringHelperName, indent) {
466
+ const rendered = `${compatRenderToStringHelperName}(${part.name}, ${emitPropsObject(part.props, part.children, part.escapeHelperName)})`;
467
+ const statements = part.hydrationId === undefined
468
+ ? [`${sinkName}.append(${rendered});`]
469
+ : [
470
+ `${sinkName}.append(${stringLiteral(`<!--mreact-h:start:${encodeURIComponent(part.hydrationId)}-->`)});`,
471
+ `${sinkName}.append(${rendered});`,
472
+ `${sinkName}.append(${stringLiteral(`<!--mreact-h:end:${encodeURIComponent(part.hydrationId)}-->`)});`,
473
+ ];
474
+ return statements.map((statement) => `${indent}${statement}`).join("\n");
475
+ }
476
+ function collectHtmlParts(node, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state) {
477
+ void asyncBoundaryHelperName;
478
+ void outOfOrderBoundaryHelperName;
479
+ void reactSuspenseBoundaryHelperName;
480
+ void reactSuspenseOutOfOrderBoundaryHelperName;
481
+ if (node.kind === "text") {
482
+ return [{ kind: "static", value: escapeHtml(node.value) }];
483
+ }
484
+ if (node.kind === "expr") {
485
+ if (node.renderMode === "html") {
486
+ return [{ kind: "raw-dynamic", code: rawHtmlExpression(node.code) }];
487
+ }
488
+ if (node.renderMode === "react-node") {
489
+ return [{ kind: "react-node", code: node.code }];
490
+ }
491
+ return [{ kind: "dynamic", code: node.code, escapeHelperName }];
492
+ }
493
+ if (node.kind === "conditional") {
494
+ return [
495
+ {
496
+ kind: "raw-dynamic",
497
+ code: `((${node.conditionCode}) ? ${emitHtmlExpressionFromChildren(node.whenTrue, escapeHelperName)} : ${emitHtmlExpressionFromChildren(node.whenFalse, escapeHelperName)})`,
498
+ },
499
+ ];
500
+ }
501
+ if (node.kind === "list") {
502
+ // Issue 085: try the direct-sink for-loop path first. We can only
503
+ // take it when every collected child part is sync (no
504
+ // async-boundary / out-of-order / react-suspense inside the
505
+ // list renderer). That matches the previous behaviour: the
506
+ // `.map().join("")` fallback never supported those either —
507
+ // boundaries cannot be embedded inside a synchronous string
508
+ // expression — so this is purely a performance change.
509
+ const collectedChildParts = node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state));
510
+ if (collectedChildParts.every(isHtmlSyncPart)) {
511
+ return [
512
+ {
513
+ kind: "list",
514
+ itemsCode: node.itemsCode,
515
+ itemName: node.itemName,
516
+ ...(node.indexName === undefined ? {} : { indexName: node.indexName }),
517
+ bodyStatements: node.bodyStatements ?? [],
518
+ parts: collectedChildParts,
519
+ },
520
+ ];
521
+ }
522
+ const parameters = node.indexName === undefined ? node.itemName : `${node.itemName}, ${node.indexName}`;
523
+ return [
524
+ {
525
+ kind: "raw-dynamic",
526
+ code: `(${node.itemsCode}).map(${emitListRenderer(node, parameters, escapeHelperName)}).join("")`,
527
+ },
528
+ ];
529
+ }
530
+ if (node.kind === "async-boundary") {
531
+ if (node.placeholderChildren !== undefined) {
532
+ const id = `mreact-${state.nextFragmentId}`;
533
+ state.nextFragmentId += 1;
534
+ return [
535
+ {
536
+ kind: "out-of-order-boundary",
537
+ id,
538
+ hydration: state.hydration,
539
+ valueCode: node.valueCode,
540
+ valueName: node.valueName,
541
+ parts: node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
542
+ placeholderParts: node.placeholderChildren.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
543
+ ...(state.awaitHydration && node.awaitId !== undefined
544
+ ? { awaitId: node.awaitId }
545
+ : {}),
546
+ ...(node.catchName === undefined || node.catchChildren === undefined
547
+ ? {}
548
+ : {
549
+ catchName: node.catchName,
550
+ catchParts: node.catchChildren.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
551
+ }),
552
+ },
553
+ ];
554
+ }
555
+ return [
556
+ {
557
+ kind: "async-boundary",
558
+ valueCode: node.valueCode,
559
+ valueName: node.valueName,
560
+ parts: node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
561
+ ...(state.awaitHydration && node.awaitId !== undefined
562
+ ? { awaitId: node.awaitId }
563
+ : {}),
564
+ ...(node.catchName === undefined || node.catchChildren === undefined
565
+ ? {}
566
+ : {
567
+ catchName: node.catchName,
568
+ catchParts: node.catchChildren.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
569
+ }),
570
+ },
571
+ ];
572
+ }
573
+ if (node.kind === "fragment") {
574
+ return node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state));
575
+ }
576
+ if (node.kind === "component") {
577
+ if (node.name === "Suspense") {
578
+ const asyncBoundary = findSuspenseAsyncBoundary(node.children);
579
+ if (asyncBoundary !== undefined) {
580
+ const id = state.nextFragmentId;
581
+ state.nextFragmentId += 1;
582
+ return [
583
+ {
584
+ kind: "react-suspense-out-of-order-boundary",
585
+ boundaryId: `B:${id}`,
586
+ segmentId: `S:${id}`,
587
+ valueCode: asyncBoundary.valueCode,
588
+ valueName: asyncBoundary.valueName,
589
+ parts: replaceSuspenseAsyncBoundary(node.children, asyncBoundary, asyncBoundary.children).flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
590
+ fallbackParts: collectSuspenseFallbackParts(node.props, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state),
591
+ ...(state.reactSuspenseRevealScriptNonce === undefined
592
+ ? {}
593
+ : { nonce: state.reactSuspenseRevealScriptNonce }),
594
+ ...(state.reactSuspenseRevealScriptSrc === undefined
595
+ ? {}
596
+ : { scriptSrc: state.reactSuspenseRevealScriptSrc }),
597
+ ...(asyncBoundary.catchName === undefined || asyncBoundary.catchChildren === undefined
598
+ ? {}
599
+ : {
600
+ catchName: asyncBoundary.catchName,
601
+ catchParts: replaceSuspenseAsyncBoundary(node.children, asyncBoundary, asyncBoundary.catchChildren).flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
602
+ }),
603
+ },
604
+ ];
605
+ }
606
+ if (containsAsyncComponent(node.children)) {
607
+ const id = state.nextFragmentId;
608
+ state.nextFragmentId += 1;
609
+ return [
610
+ {
611
+ kind: "react-suspense-out-of-order-boundary",
612
+ boundaryId: `B:${id}`,
613
+ segmentId: `S:${id}`,
614
+ valueCode: "undefined",
615
+ valueName: "_",
616
+ parts: node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
617
+ fallbackParts: collectSuspenseFallbackParts(node.props, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state),
618
+ ...(state.reactSuspenseRevealScriptNonce === undefined
619
+ ? {}
620
+ : { nonce: state.reactSuspenseRevealScriptNonce }),
621
+ ...(state.reactSuspenseRevealScriptSrc === undefined
622
+ ? {}
623
+ : { scriptSrc: state.reactSuspenseRevealScriptSrc }),
624
+ },
625
+ ];
626
+ }
627
+ return [
628
+ {
629
+ kind: "react-suspense-boundary",
630
+ parts: node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state)),
631
+ },
632
+ ];
633
+ }
634
+ if (isClientBoundaryPlaceholder(node)) {
635
+ const helperName = currentClientBoundaryHelperName;
636
+ if (helperName !== undefined) {
637
+ return [
638
+ {
639
+ kind: "raw-dynamic",
640
+ code: `${helperName}(${stringLiteral(node.name)}, ${emitPropsObject(node.props, node.children, escapeHelperName)})`,
641
+ },
642
+ ];
643
+ }
644
+ return [{ kind: "static", value: clientBoundaryPlaceholder(node) }];
645
+ }
646
+ return [
647
+ {
648
+ kind: "component",
649
+ name: node.name,
650
+ ...(node.runtime === undefined ? {} : { runtime: node.runtime }),
651
+ ...(node.async === undefined ? {} : { async: node.async }),
652
+ ...(node.runtime === "compat" && state.hydration
653
+ ? { hydrationId: `mreact-${state.nextFragmentId++}` }
654
+ : {}),
655
+ props: node.props,
656
+ children: node.children,
657
+ escapeHelperName,
658
+ },
659
+ ];
660
+ }
661
+ const closeTag = `</${node.tagName}>`;
662
+ if (node.tagName === "textarea") {
663
+ return [
664
+ { kind: "static", value: "<textarea" },
665
+ ...collectElementAttributeParts(node.tagName, node.attributes, escapeHelperName, state),
666
+ { kind: "static", value: ">" },
667
+ ...collectTextareaValueParts(node, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state),
668
+ { kind: "static", value: closeTag },
669
+ ];
670
+ }
671
+ const childSelectedValueCode = node.tagName === "select"
672
+ ? findFormValueAttributeCode(node.attributes)
673
+ : undefined;
674
+ const childState = childSelectedValueCode === undefined
675
+ ? state
676
+ : { ...state, selectedValueCode: childSelectedValueCode };
677
+ const selectedAttributePart = collectOptionSelectedAttributePart(node, state.selectedValueCode);
678
+ return [
679
+ { kind: "static", value: `<${node.tagName}` },
680
+ ...collectElementAttributeParts(node.tagName, node.attributes, escapeHelperName, state),
681
+ ...(selectedAttributePart === undefined ? [] : [selectedAttributePart]),
682
+ { kind: "static", value: ">" },
683
+ ...((childState.selectedValueCode === undefined
684
+ ? collectBatchedSimpleChildrenParts(node.children, state.escapeBatchHelperName)
685
+ : undefined) ??
686
+ node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, childState))),
687
+ { kind: "static", value: closeTag },
688
+ ];
689
+ }
690
+ function collectHtmlAttributeParts(tagName, attr, escapeHelperName, escapeBatchHelperName, dynamicAttributes) {
691
+ if (attr.kind === "event" || attr.kind === "spread-attr" || attr.name === "key") {
692
+ return [];
693
+ }
694
+ if (attr.kind === "static-attr") {
695
+ const htmlName = htmlAttributeNameForElement(tagName, attr.name);
696
+ if (isUrlAttribute(htmlName) && isStaticUrlValueUnsafe(htmlName, attr.value)) {
697
+ return [];
698
+ }
699
+ if (isDangerousHtmlAttribute(htmlName)) {
700
+ // Issue 077: literal srcdoc strings cannot match the opt-in shape.
701
+ return [];
702
+ }
703
+ return [
704
+ {
705
+ kind: "static",
706
+ value: ` ${htmlName}="${escapeHtml(attr.value)}"`,
707
+ },
708
+ ];
709
+ }
710
+ if (dynamicAttributes === "drop") {
711
+ return [];
712
+ }
713
+ if (attr.name === "style") {
714
+ return [{ kind: "raw-dynamic", code: emitDynamicStyleAttributeExpression(attr.code, escapeHelperName, escapeBatchHelperName) }];
715
+ }
716
+ const dynamicHtmlName = htmlAttributeNameForElement(tagName, attr.name);
717
+ if (isDangerousHtmlAttribute(dynamicHtmlName)) {
718
+ return [
719
+ {
720
+ kind: "raw-dynamic",
721
+ code: `(() => { const _value = (${attr.code}); if (_value == null || _value === false) return ""; if (typeof _value === "object" && _value !== null && typeof _value.__html === "string") return ${stringLiteral(` ${dynamicHtmlName}="`)} + ${escapeHelperName}(_value.__html) + ${stringLiteral("\"")}; return ""; })()`,
722
+ },
723
+ ];
724
+ }
725
+ return [
726
+ {
727
+ kind: "raw-dynamic",
728
+ code: emitDynamicAttributeExpression(dynamicHtmlName, attr.code, escapeHelperName),
729
+ },
730
+ ];
731
+ }
732
+ function collectElementAttributeParts(tagName, attrs, escapeHelperName, state) {
733
+ const escapeBatchHelperName = state.escapeBatchHelperName;
734
+ const hasExplicitInputValue = tagName === "input" &&
735
+ attrs.some((attr) => attr.kind !== "spread-attr" && attr.name === "value");
736
+ const hasExplicitInputChecked = tagName === "input" &&
737
+ attrs.some((attr) => attr.kind !== "spread-attr" && attr.name === "checked");
738
+ return attrs.flatMap((attr) => attr.kind !== "spread-attr" &&
739
+ ((tagName === "input" &&
740
+ ((attr.name === "defaultValue" && hasExplicitInputValue) ||
741
+ (attr.name === "defaultChecked" && hasExplicitInputChecked))) ||
742
+ ((tagName === "textarea" || tagName === "select") &&
743
+ (attr.name === "value" || attr.name === "defaultValue")))
744
+ ? []
745
+ : collectHtmlAttributeParts(tagName, attr, escapeHelperName, escapeBatchHelperName, state.dynamicAttributes));
746
+ }
747
+ function emitDynamicAttributeExpression(name, code, escapeHelperName) {
748
+ if (isUrlAttribute(name)) {
749
+ return `(() => { const _value = (${code}); if (_value == null || _value === false) return ""; const _checked = ${currentUrlSafeHelperName}(${stringLiteral(name)}, _value === true ? "" : _value); return _checked === undefined ? "" : ${stringLiteral(` ${name}="`)} + ${escapeHelperName}(_checked) + ${stringLiteral("\"")}; })()`;
750
+ }
751
+ const inlineExpr = simpleSideEffectFreeExpression(code);
752
+ if (inlineExpr !== undefined) {
753
+ // Inline 3 evaluations to avoid per-attribute IIFE closure allocation.
754
+ return `(${inlineExpr} == null || ${inlineExpr} === false ? "" : ${stringLiteral(` ${name}="`)} + ${escapeHelperName}(${inlineExpr} === true ? "" : ${inlineExpr}) + ${stringLiteral("\"")})`;
755
+ }
756
+ return `(() => { const _value = (${code}); return _value == null || _value === false ? "" : ${stringLiteral(` ${name}="`)} + ${escapeHelperName}(_value === true ? "" : _value) + ${stringLiteral("\"")}; })()`;
757
+ }
758
+ function emitDynamicStyleAttributeExpression(code, escapeHelperName, escapeBatchHelperName) {
759
+ const staticStyleExpression = emitStaticStyleObjectAttributeExpression(code, escapeHelperName);
760
+ if (staticStyleExpression !== undefined) {
761
+ return staticStyleExpression;
762
+ }
763
+ const escapedPair = escapeBatchHelperName === undefined
764
+ ? `${escapeHelperName}(_cssName) + ":" + ${escapeHelperName}(_styleValue === true ? "" : _styleValue)`
765
+ : `(() => { const _escaped = ${escapeBatchHelperName}([_cssName, _styleValue === true ? "" : _styleValue]); return _escaped[0] + ":" + _escaped[1]; })()`;
766
+ return `(() => { const _value = (${code}); if (_value == null || _value === false) return ""; if (typeof _value === "string") { const _style = ${escapeHelperName}(_value); return _style === "" ? "" : ${stringLiteral(" style=\"")} + _style + ${stringLiteral("\"")}; } const _style = Object.entries(_value).filter(([, _styleValue]) => _styleValue != null && _styleValue !== false).map(([_styleName, _styleValue]) => { const _cssName = String(_styleName).startsWith("--") ? String(_styleName) : String(_styleName).replace(/[A-Z]/g, (_char) => "-" + _char.toLowerCase()); return ${escapedPair}; }).join(";"); return _style === "" ? "" : ${stringLiteral(" style=\"")} + _style + ${stringLiteral("\"")}; })()`;
767
+ }
768
+ function emitStaticStyleObjectAttributeExpression(code, escapeHelperName) {
769
+ const entries = parseStaticStyleObjectLiteral(code);
770
+ if (entries === undefined) {
771
+ return undefined;
772
+ }
773
+ if (entries.length === 0) {
774
+ return `""`;
775
+ }
776
+ const literalEntries = entries.map((entry) => ({
777
+ cssName: entry.cssName,
778
+ literal: parseStyleLiteralValue(entry.valueCode),
779
+ }));
780
+ if (literalEntries.every((entry) => entry.literal !== undefined)) {
781
+ const parts = literalEntries
782
+ .filter((entry) => entry.literal !== null)
783
+ .map((entry) => `${entry.cssName}:${escapeHtml(String(entry.literal))}`);
784
+ if (parts.length === 0) {
785
+ return `""`;
786
+ }
787
+ return stringLiteral(` style="${parts.join(";")}"`);
788
+ }
789
+ const statements = entries.map((entry) => `{ const _v = (${entry.valueCode}); if (_v != null && _v !== false) _style += (_style === "" ? "" : ";") + ${stringLiteral(`${entry.cssName}:`)} + ${escapeHelperName}(_v === true ? "" : _v); }`);
790
+ return `(() => { let _style = ""; ${statements.join(" ")} return _style === "" ? "" : ${stringLiteral(" style=\"")} + _style + ${stringLiteral("\"")}; })()`;
791
+ }
792
+ function parseStyleLiteralValue(code) {
793
+ const trimmed = unwrapParenthesized(code.trim());
794
+ if (trimmed === "null" || trimmed === "false" || trimmed === "undefined") {
795
+ return null;
796
+ }
797
+ if (trimmed === "true") {
798
+ return "";
799
+ }
800
+ if (NUMERIC_LITERAL_RE.test(trimmed)) {
801
+ return Number(trimmed);
802
+ }
803
+ if (SIMPLE_STRING_LITERAL_RE.test(trimmed)) {
804
+ return JSON.parse(trimmed);
805
+ }
806
+ if (SIMPLE_SINGLE_QUOTE_RE.test(trimmed)) {
807
+ return JSON.parse(`"${trimmed.slice(1, -1).replaceAll('"', '\\"')}"`);
808
+ }
809
+ return undefined;
810
+ }
811
+ function parseStaticStyleObjectLiteral(code) {
812
+ const objectCode = unwrapParenthesized(code.trim());
813
+ if (!objectCode.startsWith("{") || !objectCode.endsWith("}")) {
814
+ return undefined;
815
+ }
816
+ const body = objectCode.slice(1, -1).trim();
817
+ if (body === "") {
818
+ return [];
819
+ }
820
+ const entries = [];
821
+ for (const property of splitTopLevel(body, ",")) {
822
+ const trimmed = property.trim();
823
+ if (trimmed === "" || trimmed.startsWith("...") || trimmed.startsWith("[")) {
824
+ return undefined;
825
+ }
826
+ const colonIndex = findTopLevelColon(trimmed);
827
+ if (colonIndex < 0) {
828
+ return undefined;
829
+ }
830
+ const rawKey = trimmed.slice(0, colonIndex).trim();
831
+ const valueCode = trimmed.slice(colonIndex + 1).trim();
832
+ const key = parseStaticObjectKey(rawKey);
833
+ if (key === undefined || valueCode === "") {
834
+ return undefined;
835
+ }
836
+ entries.push({ cssName: cssPropertyName(key), valueCode });
837
+ }
838
+ return entries;
839
+ }
840
+ // Side-effect-free expression detection — see emit-server.ts for rationale.
841
+ const SIMPLE_IDENT_CHAIN_RE = /^(this|[A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*$/;
842
+ const NUMERIC_LITERAL_RE = /^-?(?:\d+(?:\.\d+)?|\.\d+)$/;
843
+ const SIMPLE_STRING_LITERAL_RE = /^"(?:[^"\\]|\\.)*"$/;
844
+ const SIMPLE_SINGLE_QUOTE_RE = /^'(?:[^'\\]|\\.)*'$/;
845
+ function simpleSideEffectFreeExpression(code) {
846
+ const trimmed = unwrapParenthesized(code.trim());
847
+ if (trimmed === "") {
848
+ return undefined;
849
+ }
850
+ if (trimmed === "true" ||
851
+ trimmed === "false" ||
852
+ trimmed === "null" ||
853
+ trimmed === "undefined") {
854
+ return trimmed;
855
+ }
856
+ if (NUMERIC_LITERAL_RE.test(trimmed) ||
857
+ SIMPLE_STRING_LITERAL_RE.test(trimmed) ||
858
+ SIMPLE_SINGLE_QUOTE_RE.test(trimmed) ||
859
+ SIMPLE_IDENT_CHAIN_RE.test(trimmed)) {
860
+ return trimmed;
861
+ }
862
+ return undefined;
863
+ }
864
+ function unwrapParenthesized(code) {
865
+ let current = code;
866
+ while (current.startsWith("(") && current.endsWith(")") && findMatchingClose(current, 0) === current.length - 1) {
867
+ current = current.slice(1, -1).trim();
868
+ }
869
+ return current;
870
+ }
871
+ function splitTopLevel(code, separator) {
872
+ const parts = [];
873
+ let start = 0;
874
+ let depth = 0;
875
+ let quote;
876
+ for (let index = 0; index < code.length; index += 1) {
877
+ const char = code[index];
878
+ if (quote !== undefined) {
879
+ if (char === "\\") {
880
+ index += 1;
881
+ }
882
+ else if (char === quote) {
883
+ quote = undefined;
884
+ }
885
+ continue;
886
+ }
887
+ if (char === "\"" || char === "'" || char === "`") {
888
+ quote = char;
889
+ continue;
890
+ }
891
+ if (char === "{" || char === "[" || char === "(") {
892
+ depth += 1;
893
+ continue;
894
+ }
895
+ if (char === "}" || char === "]" || char === ")") {
896
+ depth -= 1;
897
+ continue;
898
+ }
899
+ if (depth === 0 && char === separator) {
900
+ parts.push(code.slice(start, index));
901
+ start = index + 1;
902
+ }
903
+ }
904
+ parts.push(code.slice(start));
905
+ return parts;
906
+ }
907
+ function findTopLevelColon(code) {
908
+ return splitTopLevel(code, ":")[0]?.length ?? -1;
909
+ }
910
+ function findMatchingClose(code, openIndex) {
911
+ let depth = 0;
912
+ let quote;
913
+ for (let index = openIndex; index < code.length; index += 1) {
914
+ const char = code[index];
915
+ if (quote !== undefined) {
916
+ if (char === "\\") {
917
+ index += 1;
918
+ }
919
+ else if (char === quote) {
920
+ quote = undefined;
921
+ }
922
+ continue;
923
+ }
924
+ if (char === "\"" || char === "'" || char === "`") {
925
+ quote = char;
926
+ continue;
927
+ }
928
+ if (char === "(") {
929
+ depth += 1;
930
+ }
931
+ else if (char === ")") {
932
+ depth -= 1;
933
+ if (depth === 0) {
934
+ return index;
935
+ }
936
+ }
937
+ }
938
+ return -1;
939
+ }
940
+ function parseStaticObjectKey(rawKey) {
941
+ if (/^[A-Za-z_$][\w$-]*$/.test(rawKey)) {
942
+ return rawKey;
943
+ }
944
+ if ((rawKey.startsWith("\"") && rawKey.endsWith("\"")) ||
945
+ (rawKey.startsWith("'") && rawKey.endsWith("'"))) {
946
+ return rawKey.slice(1, -1);
947
+ }
948
+ return undefined;
949
+ }
950
+ function cssPropertyName(name) {
951
+ return name.startsWith("--")
952
+ ? name
953
+ : name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
954
+ }
955
+ function htmlAttributeName(name) {
956
+ return HTML_ATTRIBUTE_ALIASES[name] ?? name;
957
+ }
958
+ const HTML_ATTRIBUTE_ALIASES = {
959
+ acceptCharset: "accept-charset",
960
+ autoFocus: "autofocus",
961
+ autoPlay: "autoplay",
962
+ charSet: "charset",
963
+ className: "class",
964
+ colSpan: "colspan",
965
+ contentEditable: "contenteditable",
966
+ crossOrigin: "crossorigin",
967
+ encType: "enctype",
968
+ formAction: "formaction",
969
+ frameBorder: "frameborder",
970
+ htmlFor: "for",
971
+ httpEquiv: "http-equiv",
972
+ maxLength: "maxlength",
973
+ minLength: "minlength",
974
+ noValidate: "novalidate",
975
+ playsInline: "playsinline",
976
+ readOnly: "readonly",
977
+ rowSpan: "rowspan",
978
+ spellCheck: "spellcheck",
979
+ srcDoc: "srcdoc",
980
+ srcSet: "srcset",
981
+ tabIndex: "tabindex",
982
+ useMap: "usemap",
983
+ };
984
+ function findFormValueAttributeCode(attrs) {
985
+ const valueAttr = attrs.find((attr) => attr.kind !== "spread-attr" && attr.name === "value");
986
+ const defaultValueAttr = attrs.find((attr) => attr.kind !== "spread-attr" && attr.name === "defaultValue");
987
+ const attr = valueAttr ?? defaultValueAttr;
988
+ if (attr === undefined || attr.kind === "event" || attr.kind === "spread-attr") {
989
+ return undefined;
990
+ }
991
+ return attr.kind === "static-attr" ? stringLiteral(attr.value) : `(${attr.code})`;
992
+ }
993
+ function collectTextareaValueParts(node, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state) {
994
+ const valueCode = findFormValueAttributeCode(node.attributes);
995
+ if (valueCode !== undefined) {
996
+ return [{ kind: "dynamic", code: valueCode, escapeHelperName }];
997
+ }
998
+ return node.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state));
999
+ }
1000
+ function collectOptionSelectedAttributePart(node, selectedValueCode) {
1001
+ if (selectedValueCode === undefined || node.tagName !== "option") {
1002
+ return undefined;
1003
+ }
1004
+ const optionValueCode = findOptionValueCode(node);
1005
+ if (optionValueCode === undefined) {
1006
+ return undefined;
1007
+ }
1008
+ return {
1009
+ kind: "raw-dynamic",
1010
+ code: `(() => { const _selected = (${selectedValueCode}); return _selected == null ? "" : String(_selected) === String(${optionValueCode}) ? ${stringLiteral(' selected=""')} : ""; })()`,
1011
+ };
1012
+ }
1013
+ function findOptionValueCode(node) {
1014
+ const valueAttr = node.attributes.find((attr) => attr.kind !== "spread-attr" && attr.name === "value");
1015
+ if (valueAttr !== undefined && valueAttr.kind !== "event" && valueAttr.kind !== "spread-attr") {
1016
+ return valueAttr.kind === "static-attr" ? stringLiteral(valueAttr.value) : `(${valueAttr.code})`;
1017
+ }
1018
+ return node.children.every((child) => child.kind === "text")
1019
+ ? stringLiteral(node.children.map((child) => child.value).join(""))
1020
+ : undefined;
1021
+ }
1022
+ function htmlAttributeNameForElement(tagName, name) {
1023
+ if (tagName === "input") {
1024
+ if (name === "defaultValue") {
1025
+ return "value";
1026
+ }
1027
+ if (name === "defaultChecked") {
1028
+ return "checked";
1029
+ }
1030
+ }
1031
+ return htmlAttributeName(name);
1032
+ }
1033
+ function findSuspenseAsyncBoundary(children) {
1034
+ for (const child of children) {
1035
+ if (child.kind === "async-boundary" && child.placeholderChildren === undefined) {
1036
+ return child;
1037
+ }
1038
+ const nested = child.kind === "element" || child.kind === "fragment" || child.kind === "component"
1039
+ ? findSuspenseAsyncBoundary(child.children)
1040
+ : child.kind === "conditional"
1041
+ ? findSuspenseAsyncBoundary([...child.whenTrue, ...child.whenFalse])
1042
+ : child.kind === "list"
1043
+ ? findSuspenseAsyncBoundary(child.children)
1044
+ : undefined;
1045
+ if (nested !== undefined) {
1046
+ return nested;
1047
+ }
1048
+ }
1049
+ return undefined;
1050
+ }
1051
+ function replaceSuspenseAsyncBoundary(children, target, replacement) {
1052
+ return children.flatMap((child) => {
1053
+ if (child === target) {
1054
+ return [...replacement];
1055
+ }
1056
+ if (child.kind === "element") {
1057
+ return [
1058
+ {
1059
+ ...child,
1060
+ children: replaceSuspenseAsyncBoundary(child.children, target, replacement),
1061
+ },
1062
+ ];
1063
+ }
1064
+ if (child.kind === "fragment") {
1065
+ return [
1066
+ {
1067
+ ...child,
1068
+ children: replaceSuspenseAsyncBoundary(child.children, target, replacement),
1069
+ },
1070
+ ];
1071
+ }
1072
+ if (child.kind === "component") {
1073
+ return [
1074
+ {
1075
+ ...child,
1076
+ children: replaceSuspenseAsyncBoundary(child.children, target, replacement),
1077
+ },
1078
+ ];
1079
+ }
1080
+ if (child.kind === "conditional") {
1081
+ return [
1082
+ {
1083
+ ...child,
1084
+ whenTrue: replaceSuspenseAsyncBoundary(child.whenTrue, target, replacement),
1085
+ whenFalse: replaceSuspenseAsyncBoundary(child.whenFalse, target, replacement),
1086
+ },
1087
+ ];
1088
+ }
1089
+ if (child.kind === "list") {
1090
+ return [
1091
+ {
1092
+ ...child,
1093
+ children: replaceSuspenseAsyncBoundary(child.children, target, replacement),
1094
+ },
1095
+ ];
1096
+ }
1097
+ return [child];
1098
+ });
1099
+ }
1100
+ function collectSuspenseFallbackParts(props, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state) {
1101
+ for (const prop of props) {
1102
+ if (prop.kind === "render-prop" && prop.name === "fallback") {
1103
+ return prop.children.flatMap((child) => collectHtmlParts(child, escapeHelperName, asyncBoundaryHelperName, outOfOrderBoundaryHelperName, reactSuspenseBoundaryHelperName, reactSuspenseOutOfOrderBoundaryHelperName, state));
1104
+ }
1105
+ if (prop.kind === "prop" && prop.name === "fallback") {
1106
+ return [
1107
+ {
1108
+ kind: "dynamic",
1109
+ code: prop.code,
1110
+ escapeHelperName,
1111
+ },
1112
+ ];
1113
+ }
1114
+ }
1115
+ return [];
1116
+ }
1117
+ function rawHtmlExpression(code) {
1118
+ return `(() => { const _value = (${code}); return Array.isArray(_value) ? _value.join("") : String(_value ?? ""); })()`;
1119
+ }
1120
+ function collectBatchedSimpleChildrenParts(children, escapeBatchHelperName) {
1121
+ if (escapeBatchHelperName === undefined) {
1122
+ return undefined;
1123
+ }
1124
+ const dynamicChildren = children.filter((child) => child.kind === "expr" && child.renderMode !== "html" && child.renderMode !== "react-node");
1125
+ if (dynamicChildren.length < 2) {
1126
+ return undefined;
1127
+ }
1128
+ if (children.some((child) => child.kind !== "text" &&
1129
+ !(child.kind === "expr" && child.renderMode !== "html" && child.renderMode !== "react-node"))) {
1130
+ return undefined;
1131
+ }
1132
+ const values = dynamicChildren.map((child) => child.code);
1133
+ let dynamicIndex = 0;
1134
+ const pieces = children.map((child) => {
1135
+ if (child.kind === "text") {
1136
+ return stringLiteral(escapeHtml(child.value));
1137
+ }
1138
+ const index = dynamicIndex;
1139
+ dynamicIndex += 1;
1140
+ return `_escaped[${index}]`;
1141
+ });
1142
+ return [
1143
+ {
1144
+ kind: "raw-dynamic",
1145
+ code: `(() => { const _escaped = ${escapeBatchHelperName}([${values.join(", ")}]); return ${pieces.join(" + ")}; })()`,
1146
+ },
1147
+ ];
1148
+ }
1149
+ function emitHtmlExpressionFromChildren(children, escapeHelperName) {
1150
+ if (children.length === 0) {
1151
+ return '""';
1152
+ }
1153
+ const parts = children.flatMap((child) => collectHtmlParts(child, escapeHelperName, "_renderAsyncBoundary", "_renderOutOfOrderBoundary", "_renderReactSuspenseBoundary", "_renderReactSuspenseOutOfOrderBoundary", {
1154
+ dynamicAttributes: "emit",
1155
+ hydration: false,
1156
+ awaitHydration: false,
1157
+ nextFragmentId: 0,
1158
+ }));
1159
+ const expressions = parts.map((part) => {
1160
+ if (part.kind === "static") {
1161
+ return stringLiteral(part.value);
1162
+ }
1163
+ if (part.kind === "dynamic") {
1164
+ return `${part.escapeHelperName}(${part.code})`;
1165
+ }
1166
+ if (part.kind === "raw-dynamic") {
1167
+ return part.code;
1168
+ }
1169
+ if (part.kind === "component") {
1170
+ return '""';
1171
+ }
1172
+ return '""';
1173
+ });
1174
+ return expressions.length === 0 ? '""' : expressions.join(" + ");
1175
+ }
1176
+ function emitListRenderer(node, parameters, escapeHelperName) {
1177
+ const valueExpression = emitHtmlExpressionFromChildren(node.children, escapeHelperName);
1178
+ if (node.bodyStatements === undefined || node.bodyStatements.length === 0) {
1179
+ return `(${parameters}) => ${valueExpression}`;
1180
+ }
1181
+ return `(${parameters}) => {\n${node.bodyStatements.map((statement) => ` ${statement}`).join("\n")}\n return ${valueExpression};\n }`;
1182
+ }
1183
+ function containsAsyncBoundary(node, outOfOrder) {
1184
+ if (node.kind === "async-boundary") {
1185
+ return outOfOrder
1186
+ ? node.placeholderChildren !== undefined
1187
+ : node.placeholderChildren === undefined;
1188
+ }
1189
+ if (node.kind === "conditional") {
1190
+ return [...node.whenTrue, ...node.whenFalse].some((child) => containsAsyncBoundary(child, outOfOrder));
1191
+ }
1192
+ if (node.kind === "list") {
1193
+ return node.children.some((child) => containsAsyncBoundary(child, outOfOrder));
1194
+ }
1195
+ if (node.kind === "element" || node.kind === "fragment") {
1196
+ return node.children.some((child) => containsAsyncBoundary(child, outOfOrder));
1197
+ }
1198
+ if (node.kind === "component") {
1199
+ if (isClientBoundaryPlaceholder(node)) {
1200
+ return false;
1201
+ }
1202
+ return node.name === "Suspense" ? false : true;
1203
+ }
1204
+ return false;
1205
+ }
1206
+ function containsAnyAsyncBoundary(node) {
1207
+ if (node.kind === "async-boundary") {
1208
+ return true;
1209
+ }
1210
+ if (node.kind === "conditional") {
1211
+ return [...node.whenTrue, ...node.whenFalse].some(containsAnyAsyncBoundary);
1212
+ }
1213
+ if (node.kind === "list") {
1214
+ return node.children.some(containsAnyAsyncBoundary);
1215
+ }
1216
+ if (node.kind === "element" || node.kind === "fragment") {
1217
+ return node.children.some(containsAnyAsyncBoundary);
1218
+ }
1219
+ if (node.kind === "component") {
1220
+ if (isClientBoundaryPlaceholder(node)) {
1221
+ return false;
1222
+ }
1223
+ return node.name === "Suspense" ? true : true;
1224
+ }
1225
+ return false;
1226
+ }
1227
+ function containsReactSuspense(node, outOfOrder) {
1228
+ if (node.kind === "component" && node.name === "Suspense") {
1229
+ return outOfOrder
1230
+ ? findSuspenseAsyncBoundary(node.children) !== undefined ||
1231
+ containsAsyncComponent(node.children)
1232
+ : findSuspenseAsyncBoundary(node.children) === undefined &&
1233
+ !containsAsyncComponent(node.children);
1234
+ }
1235
+ if (node.kind === "conditional") {
1236
+ return [...node.whenTrue, ...node.whenFalse].some((child) => containsReactSuspense(child, outOfOrder));
1237
+ }
1238
+ if (node.kind === "list") {
1239
+ return node.children.some((child) => containsReactSuspense(child, outOfOrder));
1240
+ }
1241
+ if (node.kind === "element" || node.kind === "fragment") {
1242
+ return node.children.some((child) => containsReactSuspense(child, outOfOrder));
1243
+ }
1244
+ if (node.kind === "async-boundary") {
1245
+ return [
1246
+ ...node.children,
1247
+ ...(node.placeholderChildren ?? []),
1248
+ ...(node.catchChildren ?? []),
1249
+ ].some((child) => containsReactSuspense(child, outOfOrder));
1250
+ }
1251
+ return false;
1252
+ }
1253
+ function containsAsyncComponent(children) {
1254
+ return children.some((child) => {
1255
+ if (child.kind === "component") {
1256
+ if (isClientBoundaryPlaceholder(child)) {
1257
+ return false;
1258
+ }
1259
+ return (child.async === true ||
1260
+ containsAsyncComponent(child.children) ||
1261
+ child.props.some((prop) => prop.kind === "render-prop" && containsAsyncComponent(prop.children)));
1262
+ }
1263
+ if (child.kind === "conditional") {
1264
+ return containsAsyncComponent([...child.whenTrue, ...child.whenFalse]);
1265
+ }
1266
+ if (child.kind === "list") {
1267
+ return containsAsyncComponent(child.children);
1268
+ }
1269
+ if (child.kind === "element" || child.kind === "fragment") {
1270
+ return containsAsyncComponent(child.children);
1271
+ }
1272
+ if (child.kind === "async-boundary") {
1273
+ return containsAsyncComponent([
1274
+ ...child.children,
1275
+ ...(child.placeholderChildren ?? []),
1276
+ ...(child.catchChildren ?? []),
1277
+ ]);
1278
+ }
1279
+ return false;
1280
+ });
1281
+ }
1282
+ function containsCompatComponent(node) {
1283
+ if (node.kind === "component") {
1284
+ return (node.runtime === "compat" ||
1285
+ node.children.some(containsCompatComponent) ||
1286
+ node.props.some((prop) => prop.kind === "render-prop" && prop.children.some(containsCompatComponent)));
1287
+ }
1288
+ if (node.kind === "conditional") {
1289
+ return [...node.whenTrue, ...node.whenFalse].some(containsCompatComponent);
1290
+ }
1291
+ if (node.kind === "list") {
1292
+ return node.children.some(containsCompatComponent);
1293
+ }
1294
+ if (node.kind === "element" || node.kind === "fragment") {
1295
+ return node.children.some(containsCompatComponent);
1296
+ }
1297
+ if (node.kind === "async-boundary") {
1298
+ return [
1299
+ ...node.children,
1300
+ ...(node.placeholderChildren ?? []),
1301
+ ...(node.catchChildren ?? []),
1302
+ ].some(containsCompatComponent);
1303
+ }
1304
+ return false;
1305
+ }
1306
+ function containsClientBoundary(node) {
1307
+ if (node.kind === "component" && isClientBoundaryPlaceholder(node)) {
1308
+ return true;
1309
+ }
1310
+ if (node.kind === "component") {
1311
+ return (node.children.some(containsClientBoundary) ||
1312
+ node.props.some((prop) => prop.kind === "render-prop" && prop.children.some(containsClientBoundary)));
1313
+ }
1314
+ if (node.kind === "conditional") {
1315
+ return [...node.whenTrue, ...node.whenFalse].some(containsClientBoundary);
1316
+ }
1317
+ if (node.kind === "list") {
1318
+ return node.children.some(containsClientBoundary);
1319
+ }
1320
+ if (node.kind === "element" || node.kind === "fragment") {
1321
+ return node.children.some(containsClientBoundary);
1322
+ }
1323
+ if (node.kind === "async-boundary") {
1324
+ return [
1325
+ ...node.children,
1326
+ ...(node.placeholderChildren ?? []),
1327
+ ...(node.catchChildren ?? []),
1328
+ ].some(containsClientBoundary);
1329
+ }
1330
+ return false;
1331
+ }
1332
+ function containsReactNodeRender(node) {
1333
+ if (node.kind === "expr") {
1334
+ return node.renderMode === "react-node";
1335
+ }
1336
+ if (node.kind === "component") {
1337
+ return (node.children.some(containsReactNodeRender) ||
1338
+ node.props.some((prop) => prop.kind === "render-prop" && prop.children.some(containsReactNodeRender)));
1339
+ }
1340
+ if (node.kind === "conditional") {
1341
+ return [...node.whenTrue, ...node.whenFalse].some(containsReactNodeRender);
1342
+ }
1343
+ if (node.kind === "list") {
1344
+ return node.children.some(containsReactNodeRender);
1345
+ }
1346
+ if (node.kind === "element" || node.kind === "fragment") {
1347
+ return node.children.some(containsReactNodeRender);
1348
+ }
1349
+ if (node.kind === "async-boundary") {
1350
+ return [
1351
+ ...node.children,
1352
+ ...(node.placeholderChildren ?? []),
1353
+ ...(node.catchChildren ?? []),
1354
+ ].some(containsReactNodeRender);
1355
+ }
1356
+ return false;
1357
+ }
1358
+ function emitPropsObject(props, children = [], escapeHelperName = "_escapeHtml") {
1359
+ const entries = props.map((prop) => {
1360
+ if (prop.kind === "spread-prop") {
1361
+ return `...(${prop.code})`;
1362
+ }
1363
+ if (prop.kind === "render-prop") {
1364
+ return `${emitPropName(prop.name)}: ${emitHtmlExpressionFromChildren(prop.children, escapeHelperName)}`;
1365
+ }
1366
+ return `${emitPropName(prop.name)}: (${prop.code})`;
1367
+ });
1368
+ if (children.length > 0) {
1369
+ entries.push(`children: ${emitHtmlExpressionFromChildren(children, escapeHelperName)}`);
1370
+ }
1371
+ return `{ ${entries.join(", ")} }`;
1372
+ }
1373
+ function emitPropName(name) {
1374
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
1375
+ }
1376
+ function isClientBoundaryPlaceholder(node) {
1377
+ return node.clientReference !== undefined && !isCompatClientReference(node);
1378
+ }
1379
+ function isCompatClientReference(node) {
1380
+ return node.clientReference !== undefined && /\.(?:compat)\.[cm]?[jt]sx?$/.test(node.clientReference.moduleId);
1381
+ }
1382
+ function clientBoundaryPlaceholder(node) {
1383
+ return `<!--mreact-client-boundary:${escapeHtml(node.name)}-->`;
1384
+ }
1385
+ function allocateComponentSinkName(component) {
1386
+ const reservedNames = new Set([component.name, component.exportName, ...component.bindingNames]);
1387
+ let name = "$sink";
1388
+ let index = 1;
1389
+ while (reservedNames.has(name)) {
1390
+ name = `$sink$${index}`;
1391
+ index += 1;
1392
+ }
1393
+ return name;
1394
+ }
1395
+ function allocateHelperName(ir, baseName) {
1396
+ const reservedNames = new Set(ir.moduleBindingNames);
1397
+ for (const component of ir.components) {
1398
+ reservedNames.add(component.name);
1399
+ reservedNames.add(component.exportName);
1400
+ for (const bindingName of component.bindingNames) {
1401
+ reservedNames.add(bindingName);
1402
+ }
1403
+ }
1404
+ let name = baseName;
1405
+ let index = 1;
1406
+ while (reservedNames.has(name)) {
1407
+ name = `${baseName}$${index}`;
1408
+ index += 1;
1409
+ }
1410
+ return name;
1411
+ }
1412
+ function stringLiteral(value) {
1413
+ return JSON.stringify(value);
1414
+ }
1415
+ //# sourceMappingURL=emit-server-stream.js.map