@mandujs/core 0.20.4 → 0.20.6
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 +1 -1
- package/src/runtime/server.ts +6 -2
- package/src/runtime/ssr.ts +13 -1
- package/src/runtime/streaming-ssr.ts +5 -2
package/package.json
CHANGED
package/src/runtime/server.ts
CHANGED
|
@@ -830,14 +830,18 @@ async function serveStaticFile(pathname: string, settings: ServerRegistrySetting
|
|
|
830
830
|
relativePath = pathname.slice("/public/".length);
|
|
831
831
|
allowedBaseDir = path.join(settings.rootDir, settings.publicDir);
|
|
832
832
|
}
|
|
833
|
-
// 3.
|
|
833
|
+
// 3. .well-known/ 디렉토리 (#178: RFC 8615 표준 — Chrome DevTools, ACME, etc.)
|
|
834
|
+
else if (pathname.startsWith("/.well-known/")) {
|
|
835
|
+
relativePath = pathname.slice(1); // ".well-known/..."
|
|
836
|
+
allowedBaseDir = path.join(settings.rootDir, settings.publicDir);
|
|
837
|
+
}
|
|
838
|
+
// 4. Public 폴더의 루트 파일 (favicon.ico, robots.txt 등)
|
|
834
839
|
else if (
|
|
835
840
|
pathname === "/favicon.ico" ||
|
|
836
841
|
pathname === "/robots.txt" ||
|
|
837
842
|
pathname === "/sitemap.xml" ||
|
|
838
843
|
pathname === "/manifest.json"
|
|
839
844
|
) {
|
|
840
|
-
// 고정된 파일명만 허용 (이미 위에서 정확히 매칭됨)
|
|
841
845
|
relativePath = path.basename(pathname);
|
|
842
846
|
allowedBaseDir = path.join(settings.rootDir, settings.publicDir);
|
|
843
847
|
} else {
|
package/src/runtime/ssr.ts
CHANGED
|
@@ -282,6 +282,17 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
282
282
|
devtoolsScript = generateDevtoolsScript();
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
// #179: body 내 <link> 태그를 <head>로 호이스팅
|
|
286
|
+
// React 컴포넌트(layout.tsx 등)에서 <link>를 렌더링하면 body 안에 위치하게 되는데,
|
|
287
|
+
// 폰트/스타일시트는 <head>에 있어야 FOUT 없이 로드됨
|
|
288
|
+
const linkTagPattern = /<link\s[^>]*(?:rel=["'](?:stylesheet|preconnect|preload|icon|dns-prefetch)["'][^>]*|href=["'][^"']+["'][^>]*)\/?\s*>/gi;
|
|
289
|
+
const hoistedLinks: string[] = [];
|
|
290
|
+
const bodyContent = content.replace(linkTagPattern, (match) => {
|
|
291
|
+
hoistedLinks.push(match);
|
|
292
|
+
return "";
|
|
293
|
+
});
|
|
294
|
+
const hoistedLinkTags = hoistedLinks.join("\n ");
|
|
295
|
+
|
|
285
296
|
return `<!doctype html>
|
|
286
297
|
<html lang="${escapeHtmlAttr(lang)}">
|
|
287
298
|
<head>
|
|
@@ -289,11 +300,12 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
|
|
|
289
300
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
290
301
|
<title>${escapeHtmlText(title)}</title>
|
|
291
302
|
${cssLinkTag}
|
|
303
|
+
${hoistedLinkTags}
|
|
292
304
|
${headTags}
|
|
293
305
|
${collectedHeadTags}
|
|
294
306
|
</head>
|
|
295
307
|
<body>
|
|
296
|
-
<div id="root">${
|
|
308
|
+
<div id="root">${bodyContent}</div>
|
|
297
309
|
${dataScript}
|
|
298
310
|
${routeScript}
|
|
299
311
|
${hydrationScripts}
|
|
@@ -540,12 +540,15 @@ function generateHTMLTailContent(options: StreamingSSROptions): string {
|
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
// 9.
|
|
543
|
+
// 9. #179: body 내 <link> 태그를 <head>로 호이스팅 (외부 폰트/스타일시트 지원)
|
|
544
|
+
scripts.push(`<script>document.querySelectorAll('#root link[rel="stylesheet"],#root link[rel="preconnect"],#root link[rel="preload"],#root link[rel="icon"],#root link[rel="dns-prefetch"]').forEach(function(l){document.head.appendChild(l)})</script>`);
|
|
545
|
+
|
|
546
|
+
// 10. HMR 스크립트 (개발 모드 — Zero-JS 페이지에서도 CSS 핫리로드 지원)
|
|
544
547
|
if (isDev && hmrPort) {
|
|
545
548
|
scripts.push(generateHMRScript(hmrPort));
|
|
546
549
|
}
|
|
547
550
|
|
|
548
|
-
//
|
|
551
|
+
// 11. DevTools 번들 로드 (개발 모드)
|
|
549
552
|
if (isDev) {
|
|
550
553
|
scripts.push(`<script type="module" src="/.mandu/client/_devtools.js"></script>`);
|
|
551
554
|
}
|