@mandujs/core 0.54.12 → 0.54.14
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 +9 -1
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/run-audit.ts +15 -15
- package/src/agent/__tests__/context.test.ts +49 -1
- package/src/agent/context.ts +535 -535
- package/src/agent/index.ts +6 -6
- package/src/agent/plan.ts +282 -282
- package/src/agent/repair.ts +171 -171
- package/src/agent/sync.ts +200 -200
- package/src/agent/types.ts +8 -0
- package/src/agent/verify.ts +100 -2
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__tests__/build-runner.ts +36 -16
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +20 -20
- package/src/bundler/__tests__/fast-refresh.test.ts +24 -17
- package/src/bundler/analyzer.ts +15 -15
- package/src/bundler/build.test.ts +136 -48
- package/src/bundler/build.ts +193 -122
- package/src/bundler/css.ts +42 -42
- package/src/bundler/manifest-schema.ts +21 -21
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- package/src/bundler/plugins/block-generated-imports.ts +13 -13
- package/src/bundler/types.ts +31 -31
- package/src/client/island.ts +79 -79
- package/src/config/validate.ts +1 -1
- package/src/contract/schema.ts +7 -0
- package/src/deploy/inference/context.ts +82 -82
- package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- package/src/error/formatter.ts +10 -1
- package/src/experimental/index.ts +10 -0
- package/src/filling/context.ts +17 -17
- package/src/filling/filling.ts +22 -1
- package/src/filling/index.ts +15 -1
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.ts +210 -210
- package/src/guard/check.ts +9 -9
- package/src/guard/config-guard.ts +13 -13
- package/src/guard/fs-routes-policy.ts +51 -51
- package/src/guard/index.ts +11 -11
- package/src/index.ts +0 -10
- package/src/internal/index.ts +25 -0
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +6 -6
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/emit.ts +146 -146
- package/src/resource/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +84 -41
- package/src/router/client-entry.ts +156 -89
- package/src/router/fs-routes.test.ts +90 -0
- package/src/router/fs-routes.ts +37 -29
- package/src/router/fs-scanner.ts +81 -33
- package/src/router/fs-types.ts +8 -5
- package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +164 -103
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/image-feature.ts +15 -0
- package/src/runtime/observability-lifecycle.ts +290 -290
- package/src/runtime/page-render-response.ts +208 -106
- package/src/runtime/rate-limit.ts +231 -0
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/router.test.ts +4 -4
- package/src/runtime/router.ts +10 -12
- package/src/runtime/scheduler-lifecycle.ts +64 -0
- package/src/runtime/server.ts +148 -353
- package/src/runtime/ssr.ts +59 -59
- package/src/runtime/static-files.ts +289 -289
- package/src/runtime/streaming-ssr.ts +22 -22
- package/src/spec/schema.ts +4 -3
- package/src/watcher/__tests__/watcher.test.ts +59 -59
- package/src/watcher/watcher.ts +61 -61
package/src/runtime/ssr.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { BundleManifest } from "../bundler/types";
|
|
|
6
6
|
import { isSafeManduUrl } from "../bundler/manifest-schema";
|
|
7
7
|
import type { HydrationConfig, HydrationPriority } from "../spec/schema";
|
|
8
8
|
import { PORTS, TIMEOUTS } from "../constants";
|
|
9
|
-
import { decodeHtmlText, escapeHtmlAttr, escapeHtmlText, escapeJsonForInlineScript } from "./escape";
|
|
9
|
+
import { decodeHtmlText, escapeHtmlAttr, escapeHtmlText, escapeJsonForInlineScript } from "./escape";
|
|
10
10
|
import { REACT_INTERNALS_SHIM_SCRIPT } from "./shims";
|
|
11
11
|
import { generateFastRefreshPreamble } from "../bundler/dev";
|
|
12
12
|
import { PREFETCH_HELPER_SCRIPT } from "../client/prefetch-helper";
|
|
@@ -46,7 +46,7 @@ export interface SSROptions {
|
|
|
46
46
|
title?: string;
|
|
47
47
|
lang?: string;
|
|
48
48
|
/** 서버에서 로드한 데이터 (클라이언트로 전달) */
|
|
49
|
-
serverData?: unknown;
|
|
49
|
+
serverData?: unknown;
|
|
50
50
|
/** Hydration 설정 */
|
|
51
51
|
hydration?: HydrationConfig;
|
|
52
52
|
/** 번들 매니페스트 */
|
|
@@ -255,26 +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
|
-
}
|
|
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
|
-
}
|
|
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
|
+
}
|
|
278
278
|
|
|
279
279
|
// Runtime 로드 (hydrateIslands 실행 - dynamic import 사용)
|
|
280
280
|
if (manifest.shared.runtime) {
|
|
@@ -619,9 +619,9 @@ export async function resolveAsyncElement(node: ReactNode): Promise<ReactNode> {
|
|
|
619
619
|
return React.cloneElement(element, undefined, resolvedChildren);
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
export function renderToHTML(element: ReactElement, options: SSROptions = {}): string {
|
|
623
|
-
const hasExplicitTitle = options.title !== undefined;
|
|
624
|
-
const {
|
|
622
|
+
export function renderToHTML(element: ReactElement, options: SSROptions = {}): string {
|
|
623
|
+
const hasExplicitTitle = options.title !== undefined;
|
|
624
|
+
const {
|
|
625
625
|
title = "Mandu App",
|
|
626
626
|
lang = "ko",
|
|
627
627
|
serverData,
|
|
@@ -713,14 +713,14 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
713
713
|
const needsHydration =
|
|
714
714
|
hydration && hydration.strategy !== "none" && routeId && bundleManifest;
|
|
715
715
|
|
|
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
|
-
}
|
|
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
|
+
}
|
|
724
724
|
|
|
725
725
|
// Zero-JS 모드: island이 없는 페이지에서는 클라이언트 JS 번들을 전송하지 않음
|
|
726
726
|
// HMR/DevTools는 dev 환경에서만 유지 (CSS 핫리로드 등)
|
|
@@ -731,10 +731,10 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
731
731
|
|
|
732
732
|
if (needsHydration) {
|
|
733
733
|
// 서버 데이터 스크립트 (클라이언트 hydration에서 사용)
|
|
734
|
-
if (serverData !== undefined && routeId) {
|
|
735
|
-
const wrappedData = {
|
|
736
|
-
[routeId]: {
|
|
737
|
-
serverData,
|
|
734
|
+
if (serverData !== undefined && routeId) {
|
|
735
|
+
const wrappedData = {
|
|
736
|
+
[routeId]: {
|
|
737
|
+
serverData,
|
|
738
738
|
timestamp: Date.now(),
|
|
739
739
|
},
|
|
740
740
|
};
|
|
@@ -803,7 +803,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
803
803
|
? `<script>window.__MANDU_SPA__=false;</script>`
|
|
804
804
|
: "";
|
|
805
805
|
|
|
806
|
-
// #179: body 내 <link> 태그를 <head>로 호이스팅
|
|
806
|
+
// #179: body 내 <link> 태그를 <head>로 호이스팅
|
|
807
807
|
// React 컴포넌트(layout.tsx 등)에서 <link>를 렌더링하면 body 안에 위치하게 되는데,
|
|
808
808
|
// 폰트/스타일시트는 <head>에 있어야 FOUT 없이 로드됨
|
|
809
809
|
const linkTagPattern = /<link\s[^>]*(?:rel=["'](?:stylesheet|preconnect|preload|icon|dns-prefetch)["'][^>]*|href=["'][^"']+["'][^>]*)\/?\s*>/gi;
|
|
@@ -812,19 +812,19 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
812
812
|
hoistedLinks.push(match);
|
|
813
813
|
return "";
|
|
814
814
|
});
|
|
815
|
-
const hoistedLinkTags = hoistedLinks.join("\n ");
|
|
816
|
-
|
|
817
|
-
// #273 F15 — React 19 renders document metadata such as <title> from a
|
|
818
|
-
// page component into the body string in this SSR path. Hoist the first
|
|
819
|
-
// body title into <head> when no metadata/generateMetadata title was
|
|
820
|
-
// provided, and always strip body titles to avoid duplicate/invalid HTML.
|
|
821
|
-
let effectiveTitle = title;
|
|
822
|
-
const titleTagPattern = /<title(?:\s[^>]*)?>([\s\S]*?)<\/title>/i;
|
|
823
|
-
const bodyTitleMatch = bodyContent.match(titleTagPattern);
|
|
824
|
-
const bodyWithoutTitle = bodyContent.replace(/<title(?:\s[^>]*)?>[\s\S]*?<\/title>/gi, "");
|
|
825
|
-
if (!hasExplicitTitle && bodyTitleMatch) {
|
|
826
|
-
effectiveTitle = decodeHtmlText(bodyTitleMatch[1] ?? title);
|
|
827
|
-
}
|
|
815
|
+
const hoistedLinkTags = hoistedLinks.join("\n ");
|
|
816
|
+
|
|
817
|
+
// #273 F15 — React 19 renders document metadata such as <title> from a
|
|
818
|
+
// page component into the body string in this SSR path. Hoist the first
|
|
819
|
+
// body title into <head> when no metadata/generateMetadata title was
|
|
820
|
+
// provided, and always strip body titles to avoid duplicate/invalid HTML.
|
|
821
|
+
let effectiveTitle = title;
|
|
822
|
+
const titleTagPattern = /<title(?:\s[^>]*)?>([\s\S]*?)<\/title>/i;
|
|
823
|
+
const bodyTitleMatch = bodyContent.match(titleTagPattern);
|
|
824
|
+
const bodyWithoutTitle = bodyContent.replace(/<title(?:\s[^>]*)?>[\s\S]*?<\/title>/gi, "");
|
|
825
|
+
if (!hasExplicitTitle && bodyTitleMatch) {
|
|
826
|
+
effectiveTitle = decodeHtmlText(bodyTitleMatch[1] ?? title);
|
|
827
|
+
}
|
|
828
828
|
|
|
829
829
|
// Phase 18.α — Dev Error Overlay injection.
|
|
830
830
|
// Only emitted when `isDev` AND the user has not opted out (via
|
|
@@ -842,7 +842,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
842
842
|
<head>
|
|
843
843
|
<meta charset="UTF-8">
|
|
844
844
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
845
|
-
<title>${escapeHtmlText(effectiveTitle)}</title>
|
|
845
|
+
<title>${escapeHtmlText(effectiveTitle)}</title>
|
|
846
846
|
${cssLinkTag}
|
|
847
847
|
${viewTransitionTag}
|
|
848
848
|
${prefetchScriptTag}
|
|
@@ -854,7 +854,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
854
854
|
${devErrorOverlayTag}
|
|
855
855
|
</head>
|
|
856
856
|
<body>
|
|
857
|
-
<div id="root"${rootAttrs}>${bodyWithoutTitle}</div>
|
|
857
|
+
<div id="root"${rootAttrs}>${bodyWithoutTitle}</div>
|
|
858
858
|
${dataScript}
|
|
859
859
|
${routeScript}
|
|
860
860
|
${hydrationScripts}
|
|
@@ -871,11 +871,11 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
871
871
|
/**
|
|
872
872
|
* Client-side Routing: 현재 라우트 정보 스크립트 생성
|
|
873
873
|
*/
|
|
874
|
-
function generateRouteScript(
|
|
875
|
-
routeId: string,
|
|
876
|
-
pattern: string,
|
|
877
|
-
_serverData?: unknown
|
|
878
|
-
): string {
|
|
874
|
+
function generateRouteScript(
|
|
875
|
+
routeId: string,
|
|
876
|
+
pattern: string,
|
|
877
|
+
_serverData?: unknown
|
|
878
|
+
): string {
|
|
879
879
|
const routeInfo = {
|
|
880
880
|
id: routeId,
|
|
881
881
|
pattern,
|
|
@@ -1176,12 +1176,12 @@ export function renderSSR(element: ReactElement, options: SSROptions = {}): Resp
|
|
|
1176
1176
|
*/
|
|
1177
1177
|
export async function renderWithHydration(
|
|
1178
1178
|
element: ReactElement,
|
|
1179
|
-
options: SSROptions & {
|
|
1180
|
-
routeId: string;
|
|
1181
|
-
serverData: unknown;
|
|
1182
|
-
hydration: HydrationConfig;
|
|
1183
|
-
bundleManifest: BundleManifest;
|
|
1184
|
-
}
|
|
1179
|
+
options: SSROptions & {
|
|
1180
|
+
routeId: string;
|
|
1181
|
+
serverData: unknown;
|
|
1182
|
+
hydration: HydrationConfig;
|
|
1183
|
+
bundleManifest: BundleManifest;
|
|
1184
|
+
}
|
|
1185
1185
|
): Promise<Response> {
|
|
1186
1186
|
const html = renderToHTML(element, options);
|
|
1187
1187
|
// Phase 7.2 R1 Agent C (H1) — same CSP header logic as renderSSR.
|