@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.54.15",
3
+ "version": "0.54.16",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -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");
@@ -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
- * Error Boundary 컴포넌트 (Class Component)
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
- let data = getServerData(id);
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
- export default island;
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
- "div",
100
- {
101
- "data-mandu-island": id,
102
- "data-mandu-src": target.src,
103
- "data-mandu-priority": target.priority,
104
- "data-hydrate": priorityToHydrate(target.priority),
105
- "data-props": serializeProps(element.props ?? {}),
106
- style: { display: "contents" },
107
- },
108
- element,
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
  };