@reckona/mreact-compiler 0.0.81 → 0.0.83

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 (39) hide show
  1. package/README.md +15 -0
  2. package/dist/boundary-graph.d.ts +66 -0
  3. package/dist/boundary-graph.d.ts.map +1 -0
  4. package/dist/boundary-graph.js +568 -0
  5. package/dist/boundary-graph.js.map +1 -0
  6. package/dist/emit-boundary-lowering.d.ts +43 -0
  7. package/dist/emit-boundary-lowering.d.ts.map +1 -0
  8. package/dist/emit-boundary-lowering.js +63 -0
  9. package/dist/emit-boundary-lowering.js.map +1 -0
  10. package/dist/emit-code-builder.d.ts +9 -0
  11. package/dist/emit-code-builder.d.ts.map +1 -0
  12. package/dist/emit-code-builder.js +17 -0
  13. package/dist/emit-code-builder.js.map +1 -0
  14. package/dist/emit-server-stream.d.ts.map +1 -1
  15. package/dist/emit-server-stream.js +64 -84
  16. package/dist/emit-server-stream.js.map +1 -1
  17. package/dist/emit-server.d.ts.map +1 -1
  18. package/dist/emit-server.js +12 -1
  19. package/dist/emit-server.js.map +1 -1
  20. package/dist/index.d.ts +3 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/internal.d.ts +5 -0
  25. package/dist/internal.d.ts.map +1 -1
  26. package/dist/internal.js +14 -1
  27. package/dist/internal.js.map +1 -1
  28. package/dist/types.d.ts +16 -69
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/types.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/boundary-graph.ts +904 -0
  33. package/src/emit-boundary-lowering.ts +165 -0
  34. package/src/emit-code-builder.ts +27 -0
  35. package/src/emit-server-stream.ts +61 -152
  36. package/src/emit-server.ts +12 -1
  37. package/src/index.ts +16 -0
  38. package/src/internal.ts +25 -1
  39. package/src/types.ts +32 -79
@@ -0,0 +1,165 @@
1
+ export type NestedAppendEmitter<Part> = (
2
+ parts: readonly Part[],
3
+ sinkName: string,
4
+ compatRenderToStringHelperName: string,
5
+ ) => string;
6
+
7
+ export interface BoundaryLoweringContext<Part> {
8
+ compatRenderToStringHelperName: string;
9
+ emitNestedAppendStatements: NestedAppendEmitter<Part>;
10
+ sinkName: string;
11
+ }
12
+
13
+ export interface AsyncBoundaryPart<Part> {
14
+ awaitId?: string;
15
+ catchName?: string;
16
+ catchParts?: readonly Part[];
17
+ parts: readonly Part[];
18
+ valueCode: string;
19
+ valueName: string;
20
+ }
21
+
22
+ export interface OutOfOrderBoundaryPart<Part> extends AsyncBoundaryPart<Part> {
23
+ id: string;
24
+ hydration: boolean;
25
+ placeholderParts: readonly Part[];
26
+ placeholderTagCode?: string;
27
+ }
28
+
29
+ export interface ReactSuspenseBoundaryPart<Part> {
30
+ parts: readonly Part[];
31
+ }
32
+
33
+ export interface ReactSuspenseOutOfOrderBoundaryPart<Part> extends AsyncBoundaryPart<Part> {
34
+ boundaryId: string;
35
+ fallbackParts: readonly Part[];
36
+ nonce?: string;
37
+ scriptSrc?: string;
38
+ segmentId: string;
39
+ }
40
+
41
+ export function emitAsyncBoundary<Part>(
42
+ part: AsyncBoundaryPart<Part>,
43
+ context: BoundaryLoweringContext<Part> & {
44
+ asyncBoundaryHelperName: string;
45
+ },
46
+ ): string {
47
+ const optionFields: string[] = [];
48
+
49
+ if (part.catchName !== undefined && part.catchParts !== undefined) {
50
+ optionFields.push(
51
+ `catch: (${context.sinkName}, ${part.catchName}) => {\n${context.emitNestedAppendStatements(part.catchParts, context.sinkName, context.compatRenderToStringHelperName)}\n }`,
52
+ );
53
+ }
54
+
55
+ if (part.awaitId !== undefined) {
56
+ optionFields.push(`hydrationAwaitId: ${JSON.stringify(part.awaitId)}`);
57
+ }
58
+
59
+ const optionsExpression = optionFields.length === 0 ? "" : `, { ${optionFields.join(", ")} }`;
60
+
61
+ return [
62
+ ` await ${context.asyncBoundaryHelperName}(${context.sinkName}, (${part.valueCode}), async (${context.sinkName}, ${part.valueName}) => {`,
63
+ context.emitNestedAppendStatements(
64
+ part.parts,
65
+ context.sinkName,
66
+ context.compatRenderToStringHelperName,
67
+ ),
68
+ ` }${optionsExpression});`,
69
+ ].join("\n");
70
+ }
71
+
72
+ export function emitOutOfOrderBoundary<Part>(
73
+ part: OutOfOrderBoundaryPart<Part>,
74
+ context: BoundaryLoweringContext<Part> & {
75
+ outOfOrderBoundaryHelperName: string;
76
+ },
77
+ ): string {
78
+ const catchOption =
79
+ part.catchName === undefined || part.catchParts === undefined
80
+ ? ""
81
+ : `,\n catch: (${context.sinkName}, ${part.catchName}) => {\n${context.emitNestedAppendStatements(part.catchParts, context.sinkName, context.compatRenderToStringHelperName)}\n }`;
82
+
83
+ const hydrationAwaitIdOption =
84
+ part.awaitId === undefined ? "" : `,\n hydrationAwaitId: ${JSON.stringify(part.awaitId)}`;
85
+ const placeholderTagOption =
86
+ part.placeholderTagCode === undefined ? "" : `,\n placeholderTag: (${part.placeholderTagCode})`;
87
+
88
+ return [
89
+ ` ${context.outOfOrderBoundaryHelperName}(${context.sinkName}, ${JSON.stringify(part.id)}, (${part.valueCode}), async (${context.sinkName}, ${part.valueName}) => {`,
90
+ context.emitNestedAppendStatements(
91
+ part.parts,
92
+ context.sinkName,
93
+ context.compatRenderToStringHelperName,
94
+ ),
95
+ ` }, {`,
96
+ ...(part.hydration ? [` hydration: true,`] : []),
97
+ ` placeholder: (${context.sinkName}) => {`,
98
+ context.emitNestedAppendStatements(
99
+ part.placeholderParts,
100
+ context.sinkName,
101
+ context.compatRenderToStringHelperName,
102
+ ),
103
+ ` }${catchOption}${hydrationAwaitIdOption}${placeholderTagOption}`,
104
+ ` });`,
105
+ ].join("\n");
106
+ }
107
+
108
+ export function emitReactSuspenseBoundary<Part>(
109
+ part: ReactSuspenseBoundaryPart<Part>,
110
+ context: BoundaryLoweringContext<Part> & {
111
+ reactSuspenseBoundaryHelperName: string;
112
+ },
113
+ ): string {
114
+ return [
115
+ ` await ${context.reactSuspenseBoundaryHelperName}(${context.sinkName}, async (${context.sinkName}) => {`,
116
+ context.emitNestedAppendStatements(
117
+ part.parts,
118
+ context.sinkName,
119
+ context.compatRenderToStringHelperName,
120
+ ),
121
+ ` });`,
122
+ ].join("\n");
123
+ }
124
+
125
+ export function emitReactSuspenseOutOfOrderBoundary<Part>(
126
+ part: ReactSuspenseOutOfOrderBoundaryPart<Part>,
127
+ context: BoundaryLoweringContext<Part> & {
128
+ reactSuspenseOutOfOrderBoundaryHelperName: string;
129
+ },
130
+ ): string {
131
+ const options = [
132
+ ` fallback: (${context.sinkName}) => {`,
133
+ context.emitNestedAppendStatements(
134
+ part.fallbackParts,
135
+ context.sinkName,
136
+ context.compatRenderToStringHelperName,
137
+ ),
138
+ ` },`,
139
+ ...(part.catchName === undefined || part.catchParts === undefined
140
+ ? []
141
+ : [
142
+ ` catch: (${context.sinkName}, ${part.catchName}) => {`,
143
+ context.emitNestedAppendStatements(
144
+ part.catchParts,
145
+ context.sinkName,
146
+ context.compatRenderToStringHelperName,
147
+ ),
148
+ ` },`,
149
+ ]),
150
+ ...(part.nonce === undefined ? [] : [` nonce: ${JSON.stringify(part.nonce)},`]),
151
+ ...(part.scriptSrc === undefined ? [] : [` src: ${JSON.stringify(part.scriptSrc)},`]),
152
+ ];
153
+
154
+ return [
155
+ ` ${context.reactSuspenseOutOfOrderBoundaryHelperName}(${context.sinkName}, ${JSON.stringify(part.boundaryId)}, ${JSON.stringify(part.segmentId)}, (${part.valueCode}), async (${context.sinkName}, ${part.valueName}) => {`,
156
+ context.emitNestedAppendStatements(
157
+ part.parts,
158
+ context.sinkName,
159
+ context.compatRenderToStringHelperName,
160
+ ),
161
+ ` }, {`,
162
+ ...options,
163
+ ` });`,
164
+ ].join("\n");
165
+ }
@@ -0,0 +1,27 @@
1
+ export interface CodeBuilder {
2
+ section(code: string | undefined, options?: CodeBuilderSectionOptions): void;
3
+ toString(): string;
4
+ }
5
+
6
+ export interface CodeBuilderSectionOptions {
7
+ leadingBlankLines?: number;
8
+ }
9
+
10
+ export function createCodeBuilder(): CodeBuilder {
11
+ const sections: string[] = [];
12
+
13
+ return {
14
+ section(code, options = {}) {
15
+ if (code === undefined || code === "") {
16
+ return;
17
+ }
18
+
19
+ const leadingBlankLines = options.leadingBlankLines ?? 1;
20
+ const prefix = sections.length === 0 ? "" : "\n".repeat(leadingBlankLines + 1);
21
+ sections.push(`${prefix}${code}`);
22
+ },
23
+ toString() {
24
+ return sections.length === 0 ? "\n" : `${sections.join("")}\n`;
25
+ },
26
+ };
27
+ }
@@ -8,6 +8,13 @@ import type {
8
8
  } from "./ir.js";
9
9
  import type { RuntimeImport, ServerBootstrapMode, ServerEscapeOptions } from "./types.js";
10
10
  import { emitEscapeHtmlHelper } from "./emit-escape-helper.js";
11
+ import { createCodeBuilder } from "./emit-code-builder.js";
12
+ import {
13
+ emitAsyncBoundary as emitLoweredAsyncBoundary,
14
+ emitOutOfOrderBoundary as emitLoweredOutOfOrderBoundary,
15
+ emitReactSuspenseBoundary as emitLoweredReactSuspenseBoundary,
16
+ emitReactSuspenseOutOfOrderBoundary as emitLoweredReactSuspenseOutOfOrderBoundary,
17
+ } from "./emit-boundary-lowering.js";
11
18
  import { escapeHtmlAttribute as escapeHtml } from "@reckona/mreact-shared/html-escape";
12
19
  import {
13
20
  htmlAttributeName,
@@ -160,22 +167,28 @@ export function emitServerStream(
160
167
  const importsBlock = [importLine, escapeImport, userImports, moduleStatements].filter(Boolean).join("\n");
161
168
  const needsSpreadAttributesHelper = components.includes(spreadAttributesHelperName);
162
169
  const urlSafeBlock =
163
- components.includes(urlSafeHelperName) || needsSpreadAttributesHelper
164
- ? `\n\n${urlSafeHelper}`
165
- : "";
170
+ components.includes(urlSafeHelperName) || needsSpreadAttributesHelper ? urlSafeHelper : "";
166
171
  const clientBoundaryBlock =
167
172
  clientBoundaryHelperName === undefined || !components.includes(clientBoundaryHelperName)
168
173
  ? ""
169
- : `\n\n${emitClientBoundaryHelper(clientBoundaryHelperName)}`;
174
+ : emitClientBoundaryHelper(clientBoundaryHelperName);
170
175
  const spreadAttributesBlock = needsSpreadAttributesHelper
171
- ? `\n\n${emitSpreadAttributesHelper(spreadAttributesHelperName, escapeHelperName, urlSafeHelperName)}`
176
+ ? emitSpreadAttributesHelper(spreadAttributesHelperName, escapeHelperName, urlSafeHelperName)
172
177
  : "";
173
178
  const streamNodeBlock = components.includes(streamNodeHelperName)
174
- ? `\n\n${emitStreamNodeHelper(streamNodeHelperName)}`
179
+ ? emitStreamNodeHelper(streamNodeHelperName)
175
180
  : "";
181
+ const code = createCodeBuilder();
182
+ code.section(importsBlock);
183
+ code.section(helper);
184
+ code.section(urlSafeBlock);
185
+ code.section(clientBoundaryBlock);
186
+ code.section(spreadAttributesBlock);
187
+ code.section(streamNodeBlock);
188
+ code.section(components);
176
189
 
177
190
  return {
178
- code: `${importsBlock === "" ? "" : `${importsBlock}\n\n`}${helper}${urlSafeBlock}${clientBoundaryBlock}${spreadAttributesBlock}${streamNodeBlock}\n\n${components}\n`,
191
+ code: code.toString(),
179
192
  imports,
180
193
  };
181
194
  }
@@ -532,39 +545,39 @@ function emitAppendStatements(
532
545
  try {
533
546
  return coalesceAdjacentStaticParts(collected).map((part) => {
534
547
  if (part.kind === "async-boundary") {
535
- return emitAsyncBoundary(
536
- part,
537
- sinkName,
548
+ return emitLoweredAsyncBoundary(part, {
538
549
  asyncBoundaryHelperName,
539
550
  compatRenderToStringHelperName,
540
- );
551
+ emitNestedAppendStatements,
552
+ sinkName,
553
+ });
541
554
  }
542
555
 
543
556
  if (part.kind === "out-of-order-boundary") {
544
- return emitOutOfOrderBoundary(
545
- part,
546
- sinkName,
547
- outOfOrderBoundaryHelperName,
557
+ return emitLoweredOutOfOrderBoundary(part, {
548
558
  compatRenderToStringHelperName,
549
- );
559
+ emitNestedAppendStatements,
560
+ outOfOrderBoundaryHelperName,
561
+ sinkName,
562
+ });
550
563
  }
551
564
 
552
565
  if (part.kind === "react-suspense-boundary") {
553
- return emitReactSuspenseBoundary(
554
- part,
555
- sinkName,
556
- reactSuspenseBoundaryHelperName,
566
+ return emitLoweredReactSuspenseBoundary(part, {
557
567
  compatRenderToStringHelperName,
558
- );
568
+ emitNestedAppendStatements,
569
+ reactSuspenseBoundaryHelperName,
570
+ sinkName,
571
+ });
559
572
  }
560
573
 
561
574
  if (part.kind === "react-suspense-out-of-order-boundary") {
562
- return emitReactSuspenseOutOfOrderBoundary(
563
- part,
564
- sinkName,
565
- reactSuspenseOutOfOrderBoundaryHelperName,
575
+ return emitLoweredReactSuspenseOutOfOrderBoundary(part, {
566
576
  compatRenderToStringHelperName,
567
- );
577
+ emitNestedAppendStatements,
578
+ reactSuspenseOutOfOrderBoundaryHelperName,
579
+ sinkName,
580
+ });
568
581
  }
569
582
 
570
583
  if (part.kind === "component") {
@@ -852,116 +865,12 @@ function emitListPartAsStringExpression(
852
865
  return `(() => { const _arr = (${part.itemsCode}); let _listOut = ""; for (let _i = 0, _len = _arr.length; _i < _len; _i++) { const ${part.itemName} = _arr[_i];${part.indexName === undefined ? "" : ` const ${part.indexName} = _i;`}${part.arrayName === undefined ? "" : ` const ${part.arrayName} = _arr;`}${part.bodyStatements.length === 0 ? "" : ` ${part.bodyStatements.join(" ")}`} ${concatLines.join(" ")} } return _listOut; })()`;
853
866
  }
854
867
 
855
- function emitAsyncBoundary(
856
- part: Extract<HtmlPart, { kind: "async-boundary" }>,
857
- sinkName: string,
858
- asyncBoundaryHelperName: string,
859
- compatRenderToStringHelperName: string,
860
- ): string {
861
- const optionFields: string[] = [];
862
-
863
- if (part.catchName !== undefined && part.catchParts !== undefined) {
864
- optionFields.push(
865
- `catch: (${sinkName}, ${part.catchName}) => {\n${emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName)}\n }`,
866
- );
867
- }
868
-
869
- if (part.awaitId !== undefined) {
870
- optionFields.push(`hydrationAwaitId: ${JSON.stringify(part.awaitId)}`);
871
- }
872
-
873
- const optionsExpression = optionFields.length === 0
874
- ? ""
875
- : `, { ${optionFields.join(", ")} }`;
876
-
877
- return [
878
- ` await ${asyncBoundaryHelperName}(${sinkName}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
879
- emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
880
- ` }${optionsExpression});`,
881
- ].join("\n");
882
- }
883
-
884
- function emitOutOfOrderBoundary(
885
- part: Extract<HtmlPart, { kind: "out-of-order-boundary" }>,
886
- sinkName: string,
887
- outOfOrderBoundaryHelperName: string,
888
- compatRenderToStringHelperName: string,
889
- ): string {
890
- const catchOption =
891
- part.catchName === undefined || part.catchParts === undefined
892
- ? ""
893
- : `,\n catch: (${sinkName}, ${part.catchName}) => {\n${emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName)}\n }`;
894
-
895
- const hydrationAwaitIdOption =
896
- part.awaitId === undefined
897
- ? ""
898
- : `,\n hydrationAwaitId: ${JSON.stringify(part.awaitId)}`;
899
- const placeholderTagOption =
900
- part.placeholderTagCode === undefined
901
- ? ""
902
- : `,\n placeholderTag: (${part.placeholderTagCode})`;
903
-
904
- return [
905
- ` ${outOfOrderBoundaryHelperName}(${sinkName}, ${JSON.stringify(part.id)}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
906
- emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
907
- ` }, {`,
908
- ...(part.hydration ? [` hydration: true,`] : []),
909
- ` placeholder: (${sinkName}) => {`,
910
- emitNestedAppendStatements(part.placeholderParts, sinkName, compatRenderToStringHelperName),
911
- ` }${catchOption}${hydrationAwaitIdOption}${placeholderTagOption}`,
912
- ` });`,
913
- ].join("\n");
914
- }
915
-
916
- function emitReactSuspenseBoundary(
917
- part: Extract<HtmlPart, { kind: "react-suspense-boundary" }>,
918
- sinkName: string,
919
- reactSuspenseBoundaryHelperName: string,
920
- compatRenderToStringHelperName: string,
921
- ): string {
922
- return [
923
- ` await ${reactSuspenseBoundaryHelperName}(${sinkName}, async (${sinkName}) => {`,
924
- emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
925
- ` });`,
926
- ].join("\n");
927
- }
928
-
929
- function emitReactSuspenseOutOfOrderBoundary(
930
- part: Extract<HtmlPart, { kind: "react-suspense-out-of-order-boundary" }>,
931
- sinkName: string,
932
- reactSuspenseOutOfOrderBoundaryHelperName: string,
933
- compatRenderToStringHelperName: string,
934
- ): string {
935
- const options = [
936
- ` fallback: (${sinkName}) => {`,
937
- emitNestedAppendStatements(part.fallbackParts, sinkName, compatRenderToStringHelperName),
938
- ` },`,
939
- ...(part.catchName === undefined || part.catchParts === undefined
940
- ? []
941
- : [
942
- ` catch: (${sinkName}, ${part.catchName}) => {`,
943
- emitNestedAppendStatements(part.catchParts, sinkName, compatRenderToStringHelperName),
944
- ` },`,
945
- ]),
946
- ...(part.nonce === undefined ? [] : [` nonce: ${stringLiteral(part.nonce)},`]),
947
- ...(part.scriptSrc === undefined ? [] : [` src: ${stringLiteral(part.scriptSrc)},`]),
948
- ];
949
-
950
- return [
951
- ` ${reactSuspenseOutOfOrderBoundaryHelperName}(${sinkName}, ${JSON.stringify(part.boundaryId)}, ${JSON.stringify(part.segmentId)}, (${part.valueCode}), async (${sinkName}, ${part.valueName}) => {`,
952
- emitNestedAppendStatements(part.parts, sinkName, compatRenderToStringHelperName),
953
- ` }, {`,
954
- ...options,
955
- ` });`,
956
- ].join("\n");
957
- }
958
-
959
868
  function emitNestedAppendStatements(
960
- parts: HtmlSyncPart[],
869
+ parts: readonly HtmlSyncPart[],
961
870
  sinkName: string,
962
871
  compatRenderToStringHelperName: string,
963
872
  ): string {
964
- return coalesceAdjacentStaticParts(parts)
873
+ return coalesceAdjacentStaticParts([...parts])
965
874
  .map((part) => emitSyncPartAsAppendStatement(part, sinkName, compatRenderToStringHelperName, " "))
966
875
  .join("\n");
967
876
  }
@@ -974,39 +883,39 @@ function emitNestedStreamAppendStatements(
974
883
  return coalesceAdjacentStaticParts(parts)
975
884
  .map((part) => {
976
885
  if (part.kind === "async-boundary") {
977
- return emitAsyncBoundary(
978
- part,
979
- sinkName,
980
- currentAsyncBoundaryHelperName,
886
+ return emitLoweredAsyncBoundary(part, {
887
+ asyncBoundaryHelperName: currentAsyncBoundaryHelperName,
981
888
  compatRenderToStringHelperName,
982
- ).replace(/^/gm, " ");
889
+ emitNestedAppendStatements,
890
+ sinkName,
891
+ }).replace(/^/gm, " ");
983
892
  }
984
893
 
985
894
  if (part.kind === "out-of-order-boundary") {
986
- return emitOutOfOrderBoundary(
987
- part,
988
- sinkName,
989
- currentOutOfOrderBoundaryHelperName,
895
+ return emitLoweredOutOfOrderBoundary(part, {
990
896
  compatRenderToStringHelperName,
991
- ).replace(/^/gm, " ");
897
+ emitNestedAppendStatements,
898
+ outOfOrderBoundaryHelperName: currentOutOfOrderBoundaryHelperName,
899
+ sinkName,
900
+ }).replace(/^/gm, " ");
992
901
  }
993
902
 
994
903
  if (part.kind === "react-suspense-boundary") {
995
- return emitReactSuspenseBoundary(
996
- part,
997
- sinkName,
998
- currentReactSuspenseBoundaryHelperName,
904
+ return emitLoweredReactSuspenseBoundary(part, {
999
905
  compatRenderToStringHelperName,
1000
- ).replace(/^/gm, " ");
906
+ emitNestedAppendStatements,
907
+ reactSuspenseBoundaryHelperName: currentReactSuspenseBoundaryHelperName,
908
+ sinkName,
909
+ }).replace(/^/gm, " ");
1001
910
  }
1002
911
 
1003
912
  if (part.kind === "react-suspense-out-of-order-boundary") {
1004
- return emitReactSuspenseOutOfOrderBoundary(
1005
- part,
1006
- sinkName,
1007
- currentReactSuspenseOutOfOrderBoundaryHelperName,
913
+ return emitLoweredReactSuspenseOutOfOrderBoundary(part, {
1008
914
  compatRenderToStringHelperName,
1009
- ).replace(/^/gm, " ");
915
+ emitNestedAppendStatements,
916
+ reactSuspenseOutOfOrderBoundaryHelperName: currentReactSuspenseOutOfOrderBoundaryHelperName,
917
+ sinkName,
918
+ }).replace(/^/gm, " ");
1010
919
  }
1011
920
 
1012
921
  return emitSyncPartAsAppendStatement(
@@ -7,6 +7,7 @@ import type {
7
7
  } from "./ir.js";
8
8
  import type { RuntimeImport, ServerEscapeOptions } from "./types.js";
9
9
  import { emitEscapeHtmlHelper } from "./emit-escape-helper.js";
10
+ import { createCodeBuilder } from "./emit-code-builder.js";
10
11
  import { escapeHtmlAttribute as escapeHtml } from "@reckona/mreact-shared/html-escape";
11
12
  import {
12
13
  htmlAttributeName,
@@ -131,9 +132,19 @@ export function emitServer(
131
132
  reactNodeRenderHelperName,
132
133
  );
133
134
  const moduleStatements = emitModuleStatements(ir);
135
+ const code = createCodeBuilder();
136
+ code.section(userImports);
137
+ code.section(escapeImport);
138
+ code.section(contextImport);
139
+ code.section(moduleStatements);
140
+ code.section(helper);
141
+ code.section(urlSafeBlock);
142
+ code.section(clientBoundaryBlock);
143
+ code.section(spreadAttributesBlock);
144
+ code.section(components);
134
145
 
135
146
  return {
136
- code: `${[userImports, escapeImport, contextImport, moduleStatements, helper, urlSafeBlock, clientBoundaryBlock, spreadAttributesBlock].filter(Boolean).join("\n\n")}\n\n${components}\n`,
147
+ code: code.toString(),
137
148
  imports: collectContextImports(
138
149
  contextProviderHelperName,
139
150
  contextConsumerHelperName,
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  } from "./internal.js";
19
19
  export type {
20
20
  StaticExportReference,
21
+ StaticExportSpecifierReference,
21
22
  FormActionExpressionReference,
22
23
  FormActionReference,
23
24
  StaticImportReference,
@@ -26,6 +27,21 @@ export type {
26
27
  ClientRouteStaticImportReference,
27
28
  TopLevelExportRenderInfo,
28
29
  } from "./internal.js";
30
+ export { analyzeBoundaryGraph } from "./boundary-graph.js";
31
+ export type {
32
+ BoundaryClassification,
33
+ BoundaryGraphClientBoundary,
34
+ BoundaryGraphEntry,
35
+ BoundaryGraphEntryKind,
36
+ BoundaryGraphExport,
37
+ BoundaryGraphInput,
38
+ BoundaryGraphModule,
39
+ BoundaryGraphResult,
40
+ BoundaryGraphServerActionSite,
41
+ BoundaryGraphTraceEvent,
42
+ BoundaryGraphTraceKind,
43
+ BoundaryGraphTraceReason,
44
+ } from "./boundary-graph.js";
29
45
  export { formatDiagnostic } from "./diagnostics.js";
30
46
  export { transform } from "./transform.js";
31
47
  export type {
package/src/internal.ts CHANGED
@@ -48,9 +48,15 @@ export interface ClientRouteStaticImportReference extends StaticImportReference
48
48
  export interface StaticExportReference {
49
49
  exportedNames: string[];
50
50
  exportAll: boolean;
51
+ specifiers: StaticExportSpecifierReference[];
51
52
  source: string;
52
53
  }
53
54
 
55
+ export interface StaticExportSpecifierReference {
56
+ exportedName: string;
57
+ localName: string;
58
+ }
59
+
54
60
  export interface TopLevelExportRenderInfo {
55
61
  calledComponentRoots: string[];
56
62
  clientRuntime: boolean;
@@ -1179,7 +1185,7 @@ function staticExportReference(statement: Record<string, unknown>): StaticExport
1179
1185
  const source = sourceValue(statement)[0];
1180
1186
  return source === undefined
1181
1187
  ? []
1182
- : [{ exportedNames: [], exportAll: true, source }];
1188
+ : [{ exportedNames: [], exportAll: true, specifiers: [], source }];
1183
1189
  }
1184
1190
 
1185
1191
  if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") {
@@ -1195,11 +1201,29 @@ function staticExportReference(statement: Record<string, unknown>): StaticExport
1195
1201
  {
1196
1202
  exportedNames: exportedNames(statement),
1197
1203
  exportAll: false,
1204
+ specifiers: staticExportSpecifierReferences(statement),
1198
1205
  source,
1199
1206
  },
1200
1207
  ];
1201
1208
  }
1202
1209
 
1210
+ function staticExportSpecifierReferences(
1211
+ statement: Record<string, unknown>,
1212
+ ): StaticExportSpecifierReference[] {
1213
+ const specifiers = Array.isArray(statement.specifiers)
1214
+ ? statement.specifiers.map(readObject)
1215
+ : [];
1216
+
1217
+ return specifiers.flatMap((specifier) => {
1218
+ const exportedName = exportedNameForSpecifier(specifier);
1219
+ const localName = localNameForExportSpecifier(specifier);
1220
+
1221
+ return exportedName === undefined || localName === undefined
1222
+ ? []
1223
+ : [{ exportedName, localName }];
1224
+ });
1225
+ }
1226
+
1203
1227
  function sourceValue(statement: Record<string, unknown>): string[] {
1204
1228
  const source = readOptionalObject(statement.source);
1205
1229
  const value = source?.value;