@mandujs/core 0.18.2 → 0.18.3

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.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -205,11 +205,11 @@ async function loadAndHydrate(element, src) {
205
205
  // Dynamic import - 이 시점에 Island 모듈 로드
206
206
  const module = await import(src);
207
207
  const island = module.default;
208
+ const data = getServerData(id);
208
209
 
209
210
  // Mandu Island (preferred)
210
211
  if (island && island.__mandu_island === true) {
211
212
  const { definition } = island;
212
- const data = getServerData(id);
213
213
 
214
214
  // Island 컴포넌트 (Error Boundary + Loading 지원)
215
215
  function IslandComponent() {
@@ -258,11 +258,14 @@ async function loadAndHydrate(element, src) {
258
258
 
259
259
  console.log('[Mandu] Hydrated:', id);
260
260
  }
261
- // Plain React component fallback
261
+ // Plain React component fallback (e.g. "use client" pages)
262
262
  else if (typeof island === 'function' || React.isValidElement(island)) {
263
263
  console.warn('[Mandu] Plain component hydration:', id);
264
- const data = getServerData(id);
265
- const root = hydrateRoot(element, React.createElement(island, data));
264
+
265
+ const root = typeof island === 'function'
266
+ ? hydrateRoot(element, React.createElement(island, data))
267
+ : hydrateRoot(element, island);
268
+
266
269
  hydratedRoots.set(id, root);
267
270
 
268
271
  // 완료 표시
@@ -390,6 +393,8 @@ import React, {
390
393
  Suspense,
391
394
  StrictMode,
392
395
  Profiler,
396
+ // Misc
397
+ version,
393
398
  // Types
394
399
  Component,
395
400
  PureComponent,
@@ -401,15 +406,16 @@ import { jsx, jsxs } from 'react/jsx-runtime';
401
406
  import { jsxDEV } from 'react/jsx-dev-runtime';
402
407
 
403
408
  // React internals (ReactDOM이 내부적으로 접근 필요)
404
- const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
405
-
406
- // React 19 client internals (Playwright headless 환경 호환성)
409
+ // React 19+: __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
410
+ // React <=18: __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
407
411
  const __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE =
408
412
  React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE || {};
413
+ const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED =
414
+ React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED || {};
409
415
 
410
- // Null safety for Playwright headless browsers
416
+ // Null safety for Playwright headless browsers (React 19)
411
417
  if (__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.S == null) {
412
- __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.S = function() {};
418
+ __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.S = function () {};
413
419
  }
414
420
 
415
421
  // 전역 React 설정 (모든 모듈에서 동일 인스턴스 공유)
@@ -447,20 +453,18 @@ export {
447
453
  Suspense,
448
454
  StrictMode,
449
455
  Profiler,
456
+ version,
450
457
  Component,
451
458
  PureComponent,
452
459
  Children,
453
- __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
454
460
  __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
461
+ __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
455
462
  // JSX Runtime exports
456
463
  jsx,
457
464
  jsxs,
458
465
  jsxDEV,
459
466
  };
460
467
 
461
- // Version export (React 19 compatibility)
462
- export const version = React.version;
463
-
464
468
  // Default export
465
469
  export default React;
466
470
  `;
@@ -849,10 +853,8 @@ async function buildRuntime(
849
853
  },
850
854
  });
851
855
 
852
- // 소스 파일 정리
853
- await fs.unlink(runtimePath).catch(() => {});
854
-
855
856
  if (!result.success) {
857
+ // 실패 시 디버깅을 위해 소스 파일을 남겨둠 (_runtime.src.js)
856
858
  return {
857
859
  success: false,
858
860
  outputPath: "",
@@ -860,17 +862,28 @@ async function buildRuntime(
860
862
  };
861
863
  }
862
864
 
865
+ // 성공 시에만 소스 파일 정리
866
+ await fs.unlink(runtimePath).catch(() => {});
867
+
863
868
  return {
864
869
  success: true,
865
870
  outputPath: `/.mandu/client/${outputName}`,
866
871
  errors: [],
867
872
  };
868
- } catch (error) {
869
- await fs.unlink(runtimePath).catch(() => {});
873
+ } catch (error: any) {
874
+ // 예외 발생 시에도 디버깅을 위해 소스 파일을 남겨둠
875
+ const extra: string[] = [];
876
+ if (error?.errors && Array.isArray(error.errors)) {
877
+ extra.push(...error.errors.map((e: any) => String(e?.message || e)));
878
+ }
879
+ if (error?.logs && Array.isArray(error.logs)) {
880
+ extra.push(...error.logs.map((l: any) => String(l?.message || l)));
881
+ }
882
+
870
883
  return {
871
884
  success: false,
872
885
  outputPath: "",
873
- errors: [String(error)],
886
+ errors: [String(error), ...extra].filter(Boolean),
874
887
  };
875
888
  }
876
889
  }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Runtime Shims for React Compatibility
3
+ *
4
+ * React 19+ 호환성을 위한 런타임 shim 스크립트들을 제공합니다.
5
+ *
6
+ * @module runtime/shims
7
+ */
8
+
9
+ /**
10
+ * React 19 Client Internals Shim Script
11
+ *
12
+ * React 19에서 react-dom/client가 실행되기 전에 ReactSharedInternals.S가
13
+ * 존재하는지 확인하고 필요시 초기화하는 inline 스크립트입니다.
14
+ *
15
+ * **사용 목적**:
16
+ * - Playwright headless 환경에서 hydration 실패 방지
17
+ * - React 19의 __CLIENT_INTERNALS.S가 null일 수 있는 문제 해결
18
+ * - SSR HTML에 삽입하여 번들 로드 전에 실행
19
+ *
20
+ * **안전성**:
21
+ * - try-catch로 감싸져 있어 오류 발생 시에도 안전
22
+ * - 기존 값이 있으면 덮어쓰지 않음
23
+ * - React가 없거나 internals가 없어도 실패하지 않음
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // SSR HTML에 삽입
28
+ * const html = `
29
+ * ${hydrationScripts}
30
+ * ${REACT_INTERNALS_SHIM_SCRIPT}
31
+ * ${routerScript}
32
+ * `;
33
+ * ```
34
+ */
35
+ export const REACT_INTERNALS_SHIM_SCRIPT = `<script>
36
+ // React 19 internals shim: ensure ReactSharedInternals.S exists before react-dom/client runs.
37
+ // Some builds expect React.__CLIENT_INTERNALS... .S to be a function, but it may be null.
38
+ // This shim is safe: it only fills the slot if missing.
39
+ (function(){
40
+ try {
41
+ var React = window.React;
42
+ var i = React && React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
43
+ if (i && i.S == null) {
44
+ i.S = function(){};
45
+ }
46
+ } catch(e) {}
47
+ })();
48
+ </script>`;
@@ -6,6 +6,7 @@ import type { BundleManifest } from "../bundler/types";
6
6
  import type { HydrationConfig, HydrationPriority } from "../spec/schema";
7
7
  import { PORTS, TIMEOUTS } from "../constants";
8
8
  import { escapeHtmlAttr, escapeJsonForInlineScript } from "./escape";
9
+ import { REACT_INTERNALS_SHIM_SCRIPT } from "./shims";
9
10
 
10
11
  // Re-export streaming SSR utilities
11
12
  export {
@@ -251,6 +252,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
251
252
  ${dataScript}
252
253
  ${routeScript}
253
254
  ${hydrationScripts}
255
+ ${needsHydration ? REACT_INTERNALS_SHIM_SCRIPT : ""}
254
256
  ${routerScript}
255
257
  ${hmrScript}
256
258
  ${bodyEndTags}
@@ -19,6 +19,7 @@ import type { Metadata, MetadataItem } from "../seo/types";
19
19
  import { injectSEOIntoOptions, resolveSEO, type SEOOptions } from "../seo/integration/ssr";
20
20
  import { PORTS, TIMEOUTS } from "../constants";
21
21
  import { escapeHtmlAttr, escapeJsonForInlineScript, escapeJsString } from "./escape";
22
+ import { REACT_INTERNALS_SHIM_SCRIPT } from "./shims";
22
23
 
23
24
  // ========== Types ==========
24
25
 
@@ -507,6 +508,11 @@ function generateHTMLTailContent(options: StreamingSSROptions): string {
507
508
  scripts.push(`<script type="module" src="${escapeHtmlAttr(bundleManifest.shared.runtime)}"></script>`);
508
509
  }
509
510
 
511
+ // 7.5 React internals shim (must run before react-dom/client runs)
512
+ if (hydration && hydration.strategy !== "none") {
513
+ scripts.push(REACT_INTERNALS_SHIM_SCRIPT);
514
+ }
515
+
510
516
  // 8. Router 스크립트
511
517
  if (enableClientRouter && bundleManifest?.shared?.router) {
512
518
  scripts.push(`<script type="module" src="${escapeHtmlAttr(bundleManifest.shared.router)}"></script>`);