import { AttrValue , Block , Configuration , Style , isAttrValue , isBlockClass , isStyle } from "@css-blocks/core" ; import { EmberAnalyzer } from "@css-blocks/ember-utils" ; import { SimpleAttribute , StyleMapping } from "@opticss/template-api" ; import { ObjectDictionary } from "@opticss/util" ; import * as debugGenerator from "debug" ; import { AggregateRewriteData , AndStyleExpression , BlockInfo , ConditionalStyle , ConditionalStyleExpression , GlobalBlockIndex , GlobalStyleIndex , ImpliedStyles , LocalStyleIndex , Operator , OptimizationEntry , StyleRequirements } from "./AggregateRewriteData" ; const debug = debugGenerator ( "css-blocks:ember-app:runtime-data-generator" ) ; export class RuntimeDataGenerator { blocks : Array < Block > ; styleIndices : Map < Style , number > ; blockIndices : Map < Block , number > ; sourceClassIndices : Map < string , number > ; outputClassIndices : Map < string , number > ; styleMapping : StyleMapping ; analyzer : EmberAnalyzer ; config : Configuration ; reservedClassNames : Set < string > ; constructor ( blocks , styleMapping , analyzer , config , reservedClassNames ) { this . blocks = blocks ; this . styleIndices = new Map < Style , number > ( ) ; this . blockIndices = new Map < Block , number > ( ) ; this . sourceClassIndices = new Map < string , number > ( ) ; this . outputClassIndices = new Map < string , number > ( ) ; this . styleMapping = styleMapping ; this . analyzer = analyzer ; this . config = config ; this . reservedClassNames = reservedClassNames ; } blockIndex ( block ) { if ( ! this . blockIndices . has ( block ) ) { this . blockIndices . set ( block , this . blockIndices . size ) ; } return this . blockIndices . get ( block ) ! ; } sourceClassIndex ( className ) { if ( ! this . sourceClassIndices . has ( className ) ) { throw new Error ( "[internal error] unknown class" ) ; } return this . sourceClassIndices . get ( className ) ! ; } cssClass ( style ) { return style . cssClass ( this . config , this . reservedClassNames ) ; } outputClassIndex ( classNameOrStyle ) { let className ; if ( isStyle ( classNameOrStyle ) ) { className = this . cssClass ( classNameOrStyle ) ; } else { className = classNameOrStyle ; } if ( ! this . outputClassIndices . has ( className ) ) { this . outputClassIndices . set ( className , this . outputClassIndices . size ) ; } return this . outputClassIndices . get ( className ) ! ; } styleIndex ( style ) ; styleIndex ( style ) ; styleIndex ( style ) { if ( ! style ) return null ; if ( ! this . styleIndices . has ( style ) ) { this . styleIndices . set ( style , this . styleIndices . size ) ; } return this . styleIndices . get ( style ) ! ; } generate ( ) { let blockIds = { } ; let blocks = new Array < BlockInfo > ( ) ; for ( let block of this . blocks ) { blockIds [ block . guid ] = this . blockIndex ( block ) ; } for ( let block of this . blocks ) { blocks [ this . blockIndex ( block ) ] = this . generateBlockInfo ( block ) ; } let stylesInUse = this . findStylesInUse ( ) ; debug ( `There are ${ stylesInUse . size } styles in use.` ) ; let styleRequirements = { } ; for ( let style of stylesInUse ) { this . sourceClassIndices . set ( this . cssClass ( style ) , this . styleIndex ( style ) ) ; if ( isAttrValue ( style ) ) { styleRequirements [ this . styleIndex ( style ) ] = [ Operator . AND , this . styleIndex ( style . blockClass ) ] ; } } let impliedStyles = this . getImpliedStyles ( stylesInUse ) ; let optimizations = this . getOptimizations ( stylesInUse ) ; let outputClassnames = new Array < string > ( this . outputClassIndices . size ) ; for ( let outputClass of this . outputClassIndices . keys ( ) ) { outputClassnames [ this . outputClassIndices . get ( outputClass ) ! ] = outputClass ; } return { blockIds , blocks , outputClassnames , styleRequirements , impliedStyles , optimizations , } ; } getOptimizations ( stylesInUse ) { let optimizations = new Array < OptimizationEntry > ( ) ; for ( let style of stylesInUse ) { let attr = { name : "class" , value : this . cssClass ( style ) } ; if ( this . styleMapping . isStyledAfterOptimization ( attr ) ) { optimizations . push ( [ this . outputClassIndex ( style ) , this . styleIndex ( style ) ] ) ; } if ( this . styleMapping . replacedAttributes . containsKey ( attr ) ) { let replacedWith = this . styleMapping . replacedAttributes . getValue ( attr ) ! ; optimizations . push ( [ this . outputClassIndex ( replacedWith . value ) , this . styleIndex ( style ) ] ) ; } if ( this . styleMapping . linkedAttributes . containsKey ( attr ) ) { let links = this . styleMapping . linkedAttributes . getValue ( attr ) ; for ( let link of links ) { let exceptions = link . unless . map ( u => this . sourceClassIndex ( ( < SimpleAttribute > u ) . value ) ) ; let expr = [ Operator . AND , this . styleIndex ( style ) , [ Operator . NOT , [ Operator . OR , ... exceptions ] ] ] ; optimizations . push ( [ this . outputClassIndex ( link . to . value ) , expr ] ) ; } } } return optimizations ; } getImpliedStyles ( stylesInUse ) { let impliedStyles = { } ; for ( let style of stylesInUse ) { let implied = new Array < GlobalStyleIndex | string | ConditionalStyle > ( ) ; let baseStyle = style . base ; if ( baseStyle ) { implied . push ( this . styleIndex ( baseStyle ) ) ; } let aliases = [ ... style . getStyleAliases ( ) ] ; implied . push ( ... aliases ) ; if ( isBlockClass ( style ) ) { for ( let composition of style . composedStyles ( ) ) { if ( composition . conditions . length > 0 ) { let styles = [ this . styleIndex ( composition . style ) ] ; if ( isAttrValue ( composition . style ) ) { styles . unshift ( this . styleIndex ( composition . style . blockClass ) ) ; } let conditions = [ Operator . AND , ... composition . conditions . map ( s => this . styleIndex ( s ) ) ] ; implied . push ( { styles , conditions } ) ; } else { if ( isAttrValue ( composition . style ) ) { implied . push ( this . styleIndex ( composition . style . blockClass ) ) ; } implied . push ( this . styleIndex ( composition . style ) ) ; } } } if ( implied . length > 0 ) { impliedStyles [ this . styleIndex ( style ) ] = implied ; } } return impliedStyles ; } findStylesInUse ( ) { let stylesInUse = new Set < Style > ( ) ; for ( let style of this . analyzer . stylesFound ) { for ( let s of style . resolveStyles ( ) ) { stylesInUse . add ( s ) ; } if ( isBlockClass ( style ) ) { for ( let c of style . resolveComposedStyles ( ) ) { stylesInUse . add ( c . style ) ; } } } return stylesInUse ; } generateBlockInfo ( block ) { let blockInterfaceStyles = { } ; let resolvedInterface = block . resolvedStyleInterface ( ) ; let resolvedStyles = Object . values ( resolvedInterface ) ; let attributes = resolvedStyles . filter ( s => isAttrValue ( s ) ) . map ( ( s ) => { let attrValue = < AttrValue > s ; let groupName = attrValue . attribute . asSource ( ) ; let attrName = attrValue . asSource ( ) ; return [ groupName , { [ attrValue . value ] : attrName } ] ; } ) ; let groups = attributes . reduce ( ( v , [ groupName , valueMap ] ) => { if ( ! v [ groupName ] ) v [ groupName ] = { } ; Object . assign ( v [ groupName ] , valueMap ) ; return v ; } , { } as ObjectDictionary < ObjectDictionary < string > > ) ; let styleNames = Object . keys ( resolvedInterface ) . sort ( ) ; let i = 0 ; for ( let styleName of styleNames ) { blockInterfaceStyles [ styleName ] = i ++ ; } let implementingBlocks = this . blocks . filter ( b => b . isImplementationOf ( block ) ) ; let implementations = { } ; for ( let impl of implementingBlocks ) { let implInterface = impl . resolvedStyleInterface ( ) ; implementations [ this . blockIndex ( impl ) ] = styleNames . map ( n => this . styleIndex ( implInterface [ n ] ) ) ; } return { groups , blockInterfaceStyles , implementations , } ; } }