@@ -7,15 +7,12 @@ import {
77 parseHTMLContent ,
88 stripEmbeddedRuntimeScripts ,
99} from "./htmlDocument" ;
10- import {
11- rewriteAssetPaths ,
12- rewriteCssAssetUrls ,
13- rewriteInlineStyleAssetUrls ,
14- } from "./rewriteSubCompPaths" ;
10+ // rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
1511import { scopeCssToComposition , wrapScopedCompositionScript } from "./compositionScoping" ;
1612import { validateHyperframeHtmlContract } from "./staticGuard" ;
1713import { getHyperframeRuntimeScript } from "../generated/runtime-inline" ;
1814import { readDeclaredDefaults } from "../runtime/getVariables" ;
15+ import { inlineSubCompositions } from "./inlineSubCompositions" ;
1916
2017/** Resolve a relative path within projectDir, rejecting traversal outside it. */
2118function safePath ( projectDir : string , relativePath : string ) : string | null {
@@ -581,144 +578,36 @@ export async function bundleToSingleHtml(
581578 }
582579 }
583580
584- // Inline sub-compositions
585- const compStyleChunks : string [ ] = [ ] ;
586- const compScriptChunks : string [ ] = [ ] ;
587- const compExternalScriptSrcs : string [ ] = [ ] ;
588- const compVariablesByComp : Record < string , Record < string , unknown > > = { } ;
581+ // Inline sub-compositions (via shared function)
589582 const trackedCompositionHosts = getBundledTrackedCompositionHosts ( document ) ;
590583 const hostIdentityByElement = assignBundledRuntimeCompositionIds ( trackedCompositionHosts ) ;
591584 const subCompositionHosts = trackedCompositionHosts . filter ( ( host ) =>
592585 host . hasAttribute ( "data-composition-src" ) ,
593586 ) ;
594- for ( const hostEl of subCompositionHosts ) {
595- const src = hostEl . getAttribute ( "data-composition-src" ) ;
596- if ( ! src || ! isRelativeUrl ( src ) ) continue ;
597- const compPath = safePath ( projectDir , src ) ;
598- const compHtml = compPath ? safeReadFile ( compPath ) : null ;
599- if ( compHtml == null ) {
600- console . warn ( `[Bundler] Composition file not found: ${ src } ` ) ;
601- continue ;
602- }
603-
604- const compDoc = parseHTMLContent ( compHtml ) ;
605- const hostIdentity = hostIdentityByElement . get ( hostEl ) ;
606- const compId = hostIdentity ?. authoredCompositionId || null ;
607- const runtimeCompId = hostIdentity ?. runtimeCompositionId || compId || "" ;
608- const contentRoot = compDoc . querySelector ( "template" ) ;
609- const contentHtml = contentRoot ? contentRoot . innerHTML || "" : compDoc . body . innerHTML || "" ;
610- const contentDoc = parseHTMLContent ( contentHtml ) ;
611- const innerRoot = compId
612- ? contentDoc . querySelector ( `[data-composition-id="${ compId } "]` )
613- : contentDoc . querySelector ( "[data-composition-id]" ) ;
614- const inferredCompId = innerRoot ?. getAttribute ( "data-composition-id" ) ?. trim ( ) || "" ;
615- const authoredRootId = innerRoot ?. getAttribute ( "id" ) ?. trim ( ) || null ;
616- const scopeCompId = compId || inferredCompId ;
617- const runtimeScope = runtimeCompId
618- ? cssAttributeSelector ( "data-composition-id" , runtimeCompId )
619- : "" ;
620- const mergedVariables = runtimeCompId
621- ? {
622- ...readDeclaredDefaults ( compDoc . documentElement ) ,
623- ...parseHostVariableValues ( hostEl ) ,
624- }
625- : { } ;
626- if ( runtimeCompId && Object . keys ( mergedVariables ) . length > 0 ) {
627- compVariablesByComp [ runtimeCompId ] = mergedVariables ;
628- }
629-
630- // When a sub-composition is a full HTML document (no <template>), styles
631- // and scripts in <head> are not part of contentDoc (which only has body
632- // content). Extract them so backgrounds, positioning, fonts, and library
633- // scripts (e.g. GSAP CDN) are not silently dropped.
634- if ( ! contentRoot && compDoc . head ) {
635- for ( const s of [ ...compDoc . head . querySelectorAll ( "style" ) ] ) {
636- const css = rewriteCssAssetUrls ( s . textContent || "" , src ) ;
637- compStyleChunks . push (
638- scopeCompId ? scopeCssToComposition ( css , scopeCompId , runtimeScope , authoredRootId ) : css ,
639- ) ;
640- }
641- for ( const s of [ ...compDoc . head . querySelectorAll ( "script" ) ] ) {
642- const externalSrc = ( s . getAttribute ( "src" ) || "" ) . trim ( ) ;
643- if ( externalSrc && ! compExternalScriptSrcs . includes ( externalSrc ) ) {
644- compExternalScriptSrcs . push ( externalSrc ) ;
645- }
646- }
647- }
648-
649- for ( const s of [ ...contentDoc . querySelectorAll ( "style" ) ] ) {
650- const css = rewriteCssAssetUrls ( s . textContent || "" , src ) ;
651- compStyleChunks . push (
652- scopeCompId ? scopeCssToComposition ( css , scopeCompId , runtimeScope , authoredRootId ) : css ,
653- ) ;
654- s . remove ( ) ;
655- }
656- for ( const s of [ ...contentDoc . querySelectorAll ( "script" ) ] ) {
657- const externalSrc = ( s . getAttribute ( "src" ) || "" ) . trim ( ) ;
658- if ( externalSrc ) {
659- // External CDN/remote script — collect for deduped injection into the document.
660- // Do NOT try to inline the content (external scripts have no innerHTML).
661- if ( ! compExternalScriptSrcs . includes ( externalSrc ) ) {
662- compExternalScriptSrcs . push ( externalSrc ) ;
663- }
664- } else {
665- compScriptChunks . push (
666- scopeCompId
667- ? wrapScopedCompositionScript (
668- s . textContent || "" ,
669- scopeCompId ,
670- "[HyperFrames] composition script error:" ,
671- runtimeScope ,
672- runtimeCompId || scopeCompId ,
673- authoredRootId ,
674- )
675- : `(function(){ try { ${ s . textContent || "" } } catch (_err) { console.error('[HyperFrames] composition script error:', _err); } })();` ,
676- ) ;
677- }
678- s . remove ( ) ;
679- }
680-
681- // Rewrite relative asset paths before inlining so ../foo.svg from
682- // compositions/ resolves correctly when the content moves to root.
683- const assetEls = innerRoot
684- ? innerRoot . querySelectorAll ( "[src], [href]" )
685- : contentDoc . querySelectorAll ( "[src], [href]" ) ;
686- rewriteAssetPaths (
687- assetEls ,
688- src ,
689- ( el : Element , attr : string ) => el . getAttribute ( attr ) ,
690- ( el : Element , attr : string , val : string ) => {
691- el . setAttribute ( attr , val ) ;
692- } ,
693- ) ;
694- const styledEls = innerRoot
695- ? innerRoot . querySelectorAll ( "[style]" )
696- : contentDoc . querySelectorAll ( "[style]" ) ;
697- rewriteInlineStyleAssetUrls (
698- styledEls ,
699- src ,
700- ( el : Element ) => el . getAttribute ( "style" ) ,
701- ( el : Element , val : string ) => {
702- el . setAttribute ( "style" , val ) ;
703- } ,
704- ) ;
705-
706- if ( innerRoot ) {
707- const innerW = innerRoot . getAttribute ( "data-width" ) ;
708- const innerH = innerRoot . getAttribute ( "data-height" ) ;
709- if ( innerW && ! hostEl . getAttribute ( "data-width" ) ) hostEl . setAttribute ( "data-width" , innerW ) ;
710- if ( innerH && ! hostEl . getAttribute ( "data-height" ) ) hostEl . setAttribute ( "data-height" , innerH ) ;
711- innerRoot . setAttribute ( "data-composition-file" , src ) ;
712- for ( const child of [ ...innerRoot . querySelectorAll ( "style, script" ) ] ) child . remove ( ) ;
713- const preparedInnerRoot = prepareFlattenedInnerRoot ( innerRoot ) ;
714- hostEl . innerHTML = preparedInnerRoot . outerHTML || "" ;
715- } else {
716- for ( const child of [ ...contentDoc . querySelectorAll ( "style, script" ) ] ) child . remove ( ) ;
717- hostEl . innerHTML = contentDoc . body . innerHTML || "" ;
718- }
719- hostEl . setAttribute ( "data-composition-file" , src ) ;
720- hostEl . removeAttribute ( "data-composition-src" ) ;
721- }
587+ const subCompResult = inlineSubCompositions ( document , subCompositionHosts , {
588+ resolveHtml : ( srcPath : string ) => {
589+ if ( ! isRelativeUrl ( srcPath ) ) return null ;
590+ const compPath = safePath ( projectDir , srcPath ) ;
591+ return compPath ? safeReadFile ( compPath ) : null ;
592+ } ,
593+ parseHtml : parseHTMLContent ,
594+ hostIdentityMap : hostIdentityByElement ,
595+ rewriteInlineStyles : true ,
596+ flattenInnerRoot : prepareFlattenedInnerRoot ,
597+ readVariableDefaults : readDeclaredDefaults ,
598+ parseHostVariables : parseHostVariableValues ,
599+ buildScopeSelector : ( compId : string ) => cssAttributeSelector ( "data-composition-id" , compId ) ,
600+ scriptErrorLabel : "[HyperFrames] composition script error:" ,
601+ onMissingComposition : ( srcPath : string ) => {
602+ console . warn ( `[Bundler] Composition file not found: ${ srcPath } ` ) ;
603+ } ,
604+ } ) ;
605+ const compStyleChunks : string [ ] = [ ...subCompResult . styles ] ;
606+ const compScriptChunks : string [ ] = [ ...subCompResult . scripts ] ;
607+ const compExternalScriptSrcs : string [ ] = [ ...subCompResult . externalScriptSrcs ] ;
608+ const compVariablesByComp : Record < string , Record < string , unknown > > = {
609+ ...subCompResult . variablesByComp ,
610+ } ;
722611
723612 // Inline template compositions: inject <template id="X-template"> content into
724613 // matching empty host elements with data-composition-id="X" (no data-composition-src)
0 commit comments