From 7e7eb1be15c2efc5af6e83b0f1d5390e6416325e Mon Sep 17 00:00:00 2001 From: Jaybe Date: Sat, 15 Nov 2025 14:28:05 +0900 Subject: [PATCH] =?UTF-8?q?feat(phase6):=20=EC=9E=90=EC=82=B0=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EA=B5=AC=EC=A1=B0=20=EC=98=B5=EC=85=98(UI/?= =?UTF-8?q?=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0/Export)=20+=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=20preconnect=20=ED=86=A0=EA=B8=80=20+=20extras=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EB=B3=B4=EA=B0=95(robots/manifest)=20+=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/BuilderClientPage.tsx | 60 +++++++++++++++++-- .../assets.pathStrategy.integration.test.ts | 27 +++++++++ lib/exporter/extras.autoEnrich.test.ts | 44 ++++++++++++++ lib/exporter/extras.ts | 22 ++++++- 4 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 lib/exporter/assets.pathStrategy.integration.test.ts create mode 100644 lib/exporter/extras.autoEnrich.test.ts diff --git a/components/BuilderClientPage.tsx b/components/BuilderClientPage.tsx index d28bdff..7d3bfb8 100644 --- a/components/BuilderClientPage.tsx +++ b/components/BuilderClientPage.tsx @@ -58,6 +58,7 @@ export default function BuilderClientPage() { const [manifestThemeColor, setManifestThemeColor] = useState('') const [robotsDisallow, setRobotsDisallow] = useState('') const [sheetsMode, setSheetsMode] = useState<'auto' | 'include' | 'exclude'>('auto') + const [assetPathStrategy, setAssetPathStrategy] = useState<'flat' | 'grouped'>('flat') // Font optimization: allow toggling Google Fonts preconnect links in exported HTML const [fontPreconnect, setFontPreconnect] = useState(true) const [viewport, setViewport] = useState<'desktop' | 'mobile'>(() => { @@ -178,10 +179,22 @@ export default function BuilderClientPage() { try { if (typeof window !== 'undefined') { const w = window as unknown as { __exportPreview?: () => { html: string; css: string; js: string } } + // Build an effective AssetManager based on selected path strategy + const buildEffectiveAm = (): AssetManager | undefined => { + const base = am ?? undefined + if (!base) return undefined + if (assetPathStrategy === 'flat') return base + const clone = new AssetManager({ maxBytes: 10 * 1024 * 1024, allowedExts: ['png', 'jpg', 'jpeg', 'webp', 'svg'], pathStrategy: assetPathStrategy }) + for (const ref of base.list()) { + // re-add to apply new path strategy and recreate byOriginal mapping + clone.add({ name: ref.originalName, type: ref.mimeType, bytes: ref.bytes }) + } + return clone + } w.__exportPreview = () => { try { // Export preview with current optimization options - return exportPage(pageSchema.parse(pageData), { assetManager: am ?? undefined, fontPreconnect }) + return exportPage(pageSchema.parse(pageData), { assetManager: buildEffectiveAm(), fontPreconnect }) } catch { const fixed: Page = { ...pageData, @@ -191,12 +204,12 @@ export default function BuilderClientPage() { spamProtection: pageData.form?.spamProtection ?? { honeypotFieldName: '_hp', minSubmitSeconds: 2 }, }, } - return exportPage(pageSchema.parse(fixed), { assetManager: am ?? undefined, fontPreconnect }) + return exportPage(pageSchema.parse(fixed), { assetManager: buildEffectiveAm(), fontPreconnect }) } } } } catch {} - }, [pageData, am, fontPreconnect]) + }, [pageData, am, fontPreconnect, assetPathStrategy]) const doExport = useCallback(async () => { let valid = pageData as Page @@ -213,8 +226,19 @@ export default function BuilderClientPage() { } as Page valid = pageSchema.parse(fixed) } + // Build an effective AssetManager based on selected path strategy + const buildEffectiveAm = (): AssetManager | undefined => { + const base = am ?? undefined + if (!base) return undefined + if (assetPathStrategy === 'flat') return base + const clone = new AssetManager({ maxBytes: 10 * 1024 * 1024, allowedExts: ['png', 'jpg', 'jpeg', 'webp', 'svg'], pathStrategy: assetPathStrategy }) + for (const ref of base.list()) { + clone.add({ name: ref.originalName, type: ref.mimeType, bytes: ref.bytes }) + } + return clone + } // Respect font optimization toggle when exporting - const exported = exportPage(valid, { assetManager: am ?? undefined, fontPreconnect }) + const exported = exportPage(valid, { assetManager: buildEffectiveAm(), fontPreconnect }) try { if (typeof window !== 'undefined') { const w = window as unknown as { __captureExport?: boolean; __lastExport?: { html: string; css: string; js: string } } @@ -223,7 +247,17 @@ export default function BuilderClientPage() { } } } catch {} - const assets = am ? am.toZipStructure() : {} + const effectiveAm = (() => { + const base = am ?? undefined + if (!base) return undefined + if (assetPathStrategy === 'flat') return base + const clone = new AssetManager({ maxBytes: 10 * 1024 * 1024, allowedExts: ['png', 'jpg', 'jpeg', 'webp', 'svg'], pathStrategy: assetPathStrategy }) + for (const ref of base.list()) { + clone.add({ name: ref.originalName, type: ref.mimeType, bytes: ref.bytes }) + } + return clone + })() + const assets = effectiveAm ? effectiveAm.toZipStructure() : {} // Build overrides from UI options const manifestOverrideBase: Record = {} if (manifestName && manifestName.trim().length > 0) manifestOverrideBase.name = manifestName.trim() @@ -251,7 +285,7 @@ export default function BuilderClientPage() { } const zipBytes = await buildZip({ html: exported.html, css: exported.css, js: exported.js, assets, extras }) download('landing.zip', zipBytes) - }, [pageData, am, manifestName, manifestShortName, manifestStartUrl, manifestDisplay, manifestThemeColor, robotsDisallow, sheetsMode, fontPreconnect]) + }, [pageData, am, manifestName, manifestShortName, manifestStartUrl, manifestDisplay, manifestThemeColor, robotsDisallow, sheetsMode, fontPreconnect, assetPathStrategy]) return (
@@ -316,6 +350,20 @@ export default function BuilderClientPage() {

Export Options

+ {/* Asset path strategy: flat vs grouped */} + {/* Font optimization toggle for Google Fonts preconnect */}