@mandujs/core 0.54.2 → 0.54.4
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 +5 -2
- package/scripts/postinstall-lock.ts +153 -0
- package/src/agent/__tests__/context.test.ts +237 -0
- package/src/agent/context.ts +535 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/plan.ts +283 -0
- package/src/agent/repair.ts +172 -0
- package/src/agent/sync.ts +200 -0
- package/src/agent/types.ts +308 -0
- package/src/agent/verify.ts +406 -0
- package/src/bundler/__tests__/build-runner.ts +33 -13
- package/src/bundler/__tests__/cold-start.test.ts +35 -7
- package/src/bundler/__tests__/css.test.ts +20 -0
- package/src/bundler/analyzer.ts +15 -7
- package/src/bundler/build.test.ts +53 -11
- package/src/bundler/build.ts +447 -185
- package/src/bundler/css.ts +42 -12
- package/src/bundler/manifest-schema.ts +21 -14
- package/src/bundler/types.ts +31 -14
- package/src/client/island.ts +79 -29
- package/src/guard/config-guard.ts +13 -7
- package/src/guard/fs-routes-policy.ts +51 -0
- package/src/guard/index.ts +11 -6
- package/src/index.ts +3 -2
- package/src/router/client-entry.ts +71 -0
- package/src/router/fs-routes.ts +16 -8
- package/src/router/fs-scanner.ts +4 -3
- package/src/runtime/__tests__/page-render-response.test.ts +49 -0
- package/src/runtime/page-render-response.ts +1 -5
- package/src/runtime/ssr.ts +39 -30
- package/src/runtime/streaming-ssr.ts +22 -13
package/src/runtime/ssr.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface SSROptions {
|
|
|
46
46
|
title?: string;
|
|
47
47
|
lang?: string;
|
|
48
48
|
/** 서버에서 로드한 데이터 (클라이언트로 전달) */
|
|
49
|
-
serverData?:
|
|
49
|
+
serverData?: unknown;
|
|
50
50
|
/** Hydration 설정 */
|
|
51
51
|
hydration?: HydrationConfig;
|
|
52
52
|
/** 번들 매니페스트 */
|
|
@@ -255,19 +255,26 @@ function generateHydrationScripts(
|
|
|
255
255
|
? Object.values(manifest.islands).filter((ib) => ib.route === routeId)
|
|
256
256
|
: [];
|
|
257
257
|
|
|
258
|
-
if (routeIslands.length > 0) {
|
|
259
|
-
for (const ib of routeIslands) {
|
|
260
|
-
const cacheBust = `${ib.js}${ib.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
261
|
-
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
262
|
-
}
|
|
258
|
+
if (routeIslands.length > 0) {
|
|
259
|
+
for (const ib of routeIslands) {
|
|
260
|
+
const cacheBust = `${ib.js}${ib.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
261
|
+
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
262
|
+
}
|
|
263
263
|
} else {
|
|
264
264
|
// Fallback: route-level bundle (backward compat)
|
|
265
265
|
const bundle = manifest.bundles[routeId];
|
|
266
266
|
if (bundle) {
|
|
267
267
|
const cacheBust = `${bundle.js}${bundle.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
268
|
-
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
268
|
+
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (manifest.partials) {
|
|
273
|
+
for (const partial of Object.values(manifest.partials)) {
|
|
274
|
+
const cacheBust = `${partial.js}${partial.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
275
|
+
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
271
278
|
|
|
272
279
|
// Runtime 로드 (hydrateIslands 실행 - dynamic import 사용)
|
|
273
280
|
if (manifest.shared.runtime) {
|
|
@@ -706,12 +713,14 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
706
713
|
const needsHydration =
|
|
707
714
|
hydration && hydration.strategy !== "none" && routeId && bundleManifest;
|
|
708
715
|
|
|
709
|
-
if (needsHydration && !islandPreWrapped) {
|
|
710
|
-
// v0.8.0: bundleSrc를 data-mandu-src 속성으로 전달 (Runtime이 dynamic import로 로드)
|
|
711
|
-
const bundle = bundleManifest.bundles[routeId];
|
|
712
|
-
const bundleSrc = bundle?.js;
|
|
713
|
-
|
|
714
|
-
|
|
716
|
+
if (needsHydration && !islandPreWrapped) {
|
|
717
|
+
// v0.8.0: bundleSrc를 data-mandu-src 속성으로 전달 (Runtime이 dynamic import로 로드)
|
|
718
|
+
const bundle = bundleManifest.bundles[routeId];
|
|
719
|
+
const bundleSrc = bundle?.js;
|
|
720
|
+
if (bundleSrc) {
|
|
721
|
+
content = wrapWithIsland(content, routeId, hydration.priority, bundleSrc);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
715
724
|
|
|
716
725
|
// Zero-JS 모드: island이 없는 페이지에서는 클라이언트 JS 번들을 전송하지 않음
|
|
717
726
|
// HMR/DevTools는 dev 환경에서만 유지 (CSS 핫리로드 등)
|
|
@@ -722,10 +731,10 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
722
731
|
|
|
723
732
|
if (needsHydration) {
|
|
724
733
|
// 서버 데이터 스크립트 (클라이언트 hydration에서 사용)
|
|
725
|
-
if (serverData && routeId) {
|
|
726
|
-
const wrappedData = {
|
|
727
|
-
[routeId]: {
|
|
728
|
-
serverData,
|
|
734
|
+
if (serverData !== undefined && routeId) {
|
|
735
|
+
const wrappedData = {
|
|
736
|
+
[routeId]: {
|
|
737
|
+
serverData,
|
|
729
738
|
timestamp: Date.now(),
|
|
730
739
|
},
|
|
731
740
|
};
|
|
@@ -862,11 +871,11 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
862
871
|
/**
|
|
863
872
|
* Client-side Routing: 현재 라우트 정보 스크립트 생성
|
|
864
873
|
*/
|
|
865
|
-
function generateRouteScript(
|
|
866
|
-
routeId: string,
|
|
867
|
-
pattern: string,
|
|
868
|
-
_serverData?:
|
|
869
|
-
): string {
|
|
874
|
+
function generateRouteScript(
|
|
875
|
+
routeId: string,
|
|
876
|
+
pattern: string,
|
|
877
|
+
_serverData?: unknown
|
|
878
|
+
): string {
|
|
870
879
|
const routeInfo = {
|
|
871
880
|
id: routeId,
|
|
872
881
|
pattern,
|
|
@@ -1167,12 +1176,12 @@ export function renderSSR(element: ReactElement, options: SSROptions = {}): Resp
|
|
|
1167
1176
|
*/
|
|
1168
1177
|
export async function renderWithHydration(
|
|
1169
1178
|
element: ReactElement,
|
|
1170
|
-
options: SSROptions & {
|
|
1171
|
-
routeId: string;
|
|
1172
|
-
serverData:
|
|
1173
|
-
hydration: HydrationConfig;
|
|
1174
|
-
bundleManifest: BundleManifest;
|
|
1175
|
-
}
|
|
1179
|
+
options: SSROptions & {
|
|
1180
|
+
routeId: string;
|
|
1181
|
+
serverData: unknown;
|
|
1182
|
+
hydration: HydrationConfig;
|
|
1183
|
+
bundleManifest: BundleManifest;
|
|
1184
|
+
}
|
|
1176
1185
|
): Promise<Response> {
|
|
1177
1186
|
const html = renderToHTML(element, options);
|
|
1178
1187
|
// Phase 7.2 R1 Agent C (H1) — same CSP header logic as renderSSR.
|
|
@@ -613,13 +613,16 @@ function generateHTMLShell(options: StreamingSSROptions): string {
|
|
|
613
613
|
}
|
|
614
614
|
</style>`;
|
|
615
615
|
|
|
616
|
-
let islandOpenTag = "";
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
616
|
+
let islandOpenTag = "";
|
|
617
|
+
const hasRouteBundle = !!(needsHydration && bundleManifest.bundles[routeId]?.js);
|
|
618
|
+
if (needsHydration) {
|
|
619
|
+
const bundle = bundleManifest.bundles[routeId];
|
|
620
|
+
const bundleSrc = bundle?.js ? `${bundle.js}?t=${Date.now()}` : "";
|
|
621
|
+
const priority = hydration.priority || "visible";
|
|
622
|
+
if (hasRouteBundle) {
|
|
623
|
+
islandOpenTag = `<div data-mandu-island="${escapeHtmlAttr(routeId)}" data-mandu-src="${escapeHtmlAttr(bundleSrc)}" data-mandu-priority="${escapeHtmlAttr(priority)}" style="display:contents">`;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
623
626
|
|
|
624
627
|
// Phase 7.1 R2 Agent D: Fast Refresh preamble. Must land in <head>
|
|
625
628
|
// BEFORE any island script evaluates — the stubs it installs for
|
|
@@ -697,7 +700,7 @@ function generateHTMLTailContent(options: StreamingSSROptions): string {
|
|
|
697
700
|
// 1~8: hydration이 필요한 경우에만 클라이언트 JS 관련 스크립트 삽입
|
|
698
701
|
if (needsHydration) {
|
|
699
702
|
// 1. Critical 데이터 스크립트 (즉시 사용 가능)
|
|
700
|
-
if (criticalData && routeId) {
|
|
703
|
+
if (criticalData !== undefined && routeId) {
|
|
701
704
|
const wrappedData = {
|
|
702
705
|
[routeId]: {
|
|
703
706
|
serverData: criticalData,
|
|
@@ -746,10 +749,16 @@ function generateHTMLTailContent(options: StreamingSSROptions): string {
|
|
|
746
749
|
|
|
747
750
|
// 6. Island modulepreload
|
|
748
751
|
const bundle = bundleManifest.bundles[routeId];
|
|
749
|
-
if (bundle) {
|
|
750
|
-
const cacheBust = `${bundle.js}${bundle.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
751
|
-
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
752
|
-
}
|
|
752
|
+
if (bundle) {
|
|
753
|
+
const cacheBust = `${bundle.js}${bundle.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
754
|
+
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
755
|
+
}
|
|
756
|
+
if (bundleManifest.partials) {
|
|
757
|
+
for (const partial of Object.values(bundleManifest.partials)) {
|
|
758
|
+
const cacheBust = `${partial.js}${partial.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
|
|
759
|
+
scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
753
762
|
|
|
754
763
|
// 7. Runtime 로드
|
|
755
764
|
if (bundleManifest.shared.runtime) {
|
|
@@ -783,7 +792,7 @@ function generateHTMLTailContent(options: StreamingSSROptions): string {
|
|
|
783
792
|
}
|
|
784
793
|
|
|
785
794
|
// Island wrapper 닫기 (hydration이 필요한 경우)
|
|
786
|
-
const islandCloseTag = needsHydration ? "</div>" : "";
|
|
795
|
+
const islandCloseTag = needsHydration && bundleManifest.bundles[routeId]?.js ? "</div>" : "";
|
|
787
796
|
|
|
788
797
|
return `${islandCloseTag}</div>
|
|
789
798
|
${scripts.join("\n ")}`;
|