@mandujs/core 0.54.15 → 0.54.16
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.
package/package.json
CHANGED
|
@@ -207,6 +207,8 @@ describe("buildClientBundles vendor shims", () => {
|
|
|
207
207
|
const runtimeSource = await readFile(path.join(rootDir, ".mandu", "client", "_runtime.js"), "utf-8");
|
|
208
208
|
expect(runtimeSource).toContain("function readManduData");
|
|
209
209
|
expect(runtimeSource).toContain("document.getElementById(\"__MANDU_DATA__\")");
|
|
210
|
+
expect(runtimeSource).toContain("function parsePropsScript");
|
|
211
|
+
expect(runtimeSource).toContain("data-mandu-props");
|
|
210
212
|
expect(runtimeSource).toContain("deserializeManduProps");
|
|
211
213
|
expect(runtimeSource).toContain("new Date");
|
|
212
214
|
expect(runtimeSource).toContain("new Map");
|
package/src/bundler/build.ts
CHANGED
|
@@ -624,10 +624,48 @@ function readManduData() {
|
|
|
624
624
|
return window.__MANDU_DATA__;
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
-
const getServerData = (id) => readManduData()[id]?.serverData || {};
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
627
|
+
const getServerData = (id) => readManduData()[id]?.serverData || {};
|
|
628
|
+
|
|
629
|
+
function findPropsScript(id) {
|
|
630
|
+
const scripts = document.querySelectorAll('script[data-mandu-props]');
|
|
631
|
+
for (const script of scripts) {
|
|
632
|
+
if (script.getAttribute('data-mandu-props') === id) {
|
|
633
|
+
return script;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function parsePropsScript(id) {
|
|
640
|
+
const script = findPropsScript(id);
|
|
641
|
+
if (!script || !script.textContent) return null;
|
|
642
|
+
try {
|
|
643
|
+
return deserializeManduProps(script.textContent);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
console.warn('[Mandu] Failed to parse data-mandu-props for island ' + id + ':', error);
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function readDataProps(element) {
|
|
651
|
+
const propsEl = element.hasAttribute('data-props')
|
|
652
|
+
? element
|
|
653
|
+
: element.querySelector('[data-props]');
|
|
654
|
+
if (!propsEl) return null;
|
|
655
|
+
try {
|
|
656
|
+
return deserializeManduProps(propsEl.getAttribute('data-props') || '{}');
|
|
657
|
+
} catch (error) {
|
|
658
|
+
console.warn('[Mandu] Failed to parse data-props fallback:', error);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function getIslandProps(id, element) {
|
|
664
|
+
return parsePropsScript(id) || readDataProps(element) || getServerData(id);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Error Boundary 컴포넌트 (Class Component)
|
|
631
669
|
* Island의 errorBoundary 옵션을 지원
|
|
632
670
|
*/
|
|
633
671
|
class IslandErrorBoundary extends Component {
|
|
@@ -872,23 +910,7 @@ async function loadAndHydrate(element, src) {
|
|
|
872
910
|
// Dynamic import - 이 시점에 Island 모듈 로드
|
|
873
911
|
const module = await import(src);
|
|
874
912
|
const island = module.default;
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
// Fallback: read data-props from the island root or a child element if
|
|
878
|
-
// __MANDU_DATA__ is empty. Inline partials put their serialized props on
|
|
879
|
-
// the root marker itself.
|
|
880
|
-
if (!data || Object.keys(data).length === 0) {
|
|
881
|
-
const propsEl = element.hasAttribute('data-props')
|
|
882
|
-
? element
|
|
883
|
-
: element.querySelector('[data-props]');
|
|
884
|
-
if (propsEl) {
|
|
885
|
-
try {
|
|
886
|
-
data = deserializeManduProps(propsEl.getAttribute('data-props') || '{}');
|
|
887
|
-
} catch (e) {
|
|
888
|
-
console.warn('[Mandu] Failed to parse data-props fallback:', e);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
913
|
+
const data = getIslandProps(id, element);
|
|
892
914
|
|
|
893
915
|
// Mandu Island (preferred)
|
|
894
916
|
if (island && island.__mandu_island === true) {
|
|
@@ -1582,6 +1604,7 @@ function generateIslandEntry(routeId: string, clientModulePath: string, exportNa
|
|
|
1582
1604
|
* Mandu Island: ${commentRouteId} (Generated)
|
|
1583
1605
|
* Pure export - no side effects
|
|
1584
1606
|
*/
|
|
1607
|
+
import React from "react";
|
|
1585
1608
|
import * as islandModule from ${importSpecifier};
|
|
1586
1609
|
|
|
1587
1610
|
const candidateExportNames = ${JSON.stringify(candidates)};
|
|
@@ -1600,7 +1623,13 @@ function resolveIslandExport(mod) {
|
|
|
1600
1623
|
}
|
|
1601
1624
|
|
|
1602
1625
|
const island = resolveIslandExport(islandModule);
|
|
1603
|
-
|
|
1626
|
+
const exportedIsland = island && island.__mandu_island === true
|
|
1627
|
+
? island
|
|
1628
|
+
: function ManduGeneratedIsland(props) {
|
|
1629
|
+
return React.createElement(island, props || {});
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
export default exportedIsland;
|
|
1604
1633
|
`;
|
|
1605
1634
|
}
|
|
1606
1635
|
|
|
@@ -181,7 +181,9 @@ describe("runtime page render response orchestration", () => {
|
|
|
181
181
|
const html = await response.text();
|
|
182
182
|
expect(html).toContain('data-mandu-island="candidates-$id--0"');
|
|
183
183
|
expect(html).toContain('data-mandu-src="/.mandu/client/candidates-$id.island.js"');
|
|
184
|
+
expect(html).toContain('type="application/json" data-mandu-props="candidates-$id--0"');
|
|
184
185
|
expect(html).toContain(""pledges"");
|
|
186
|
+
expect(html).toContain('"pledges"');
|
|
185
187
|
expect(html).toContain("Public transit");
|
|
186
188
|
expect(html).not.toContain('data-mandu-island="candidates-$id"');
|
|
187
189
|
});
|
|
@@ -4,6 +4,7 @@ import type { HydrationConfig } from "../spec/schema";
|
|
|
4
4
|
import type { CookieManager } from "../filling/context";
|
|
5
5
|
import { renderSSR, renderStreamingResponse, resolveAsyncElement } from "./ssr";
|
|
6
6
|
import { serializeProps } from "../client/serialize";
|
|
7
|
+
import { escapeJsonForInlineScript } from "./escape";
|
|
7
8
|
|
|
8
9
|
export interface InlineClientHydrationTarget {
|
|
9
10
|
routeId: string;
|
|
@@ -94,18 +95,31 @@ async function resolveAndWrapInlineClientHydration(
|
|
|
94
95
|
|
|
95
96
|
if (type === target.component) {
|
|
96
97
|
const id = `${target.routeId}--${counter.value++}`;
|
|
98
|
+
const props = element.props ?? {};
|
|
99
|
+
const serializedProps = serializeProps(props);
|
|
97
100
|
return {
|
|
98
101
|
node: React.createElement(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
React.Fragment,
|
|
103
|
+
null,
|
|
104
|
+
React.createElement(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
"data-mandu-island": id,
|
|
108
|
+
"data-mandu-src": target.src,
|
|
109
|
+
"data-mandu-priority": target.priority,
|
|
110
|
+
"data-hydrate": priorityToHydrate(target.priority),
|
|
111
|
+
"data-props": serializedProps,
|
|
112
|
+
style: { display: "contents" },
|
|
113
|
+
},
|
|
114
|
+
element,
|
|
115
|
+
),
|
|
116
|
+
React.createElement("script", {
|
|
117
|
+
type: "application/json",
|
|
118
|
+
"data-mandu-props": id,
|
|
119
|
+
dangerouslySetInnerHTML: {
|
|
120
|
+
__html: escapeJsonForInlineScript(serializedProps),
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
109
123
|
),
|
|
110
124
|
didWrap: true,
|
|
111
125
|
};
|