import * as CleanCSS from 'clean-css' ; import { OptimizationLevel , optimizationLevelFrom } from 'clean-css/lib/options/optimization-level' ; import { Options as HTMLOptions , minify } from 'html-minifier' ; import { TemplatePart } from 'parse-literals' ; template O template C export interface Strategy < O = any , C = any > { param parts returns getPlaceholder ( parts ) : string ; param parts param placeholder returns combineHTMLStrings ( parts , placeholder ) : string ; param html param options returns minifyHTML ( html , options ? ) : string ; param css param options returns minifyCSS ? ( css , options ? ) : string ; param html param placeholder returns splitHTMLByPlaceholder ( html , placeholder ) : string [ ] ; } /**
 * The default <code>clean-css</code> options, optimized for production
 * minification.
 */ export const defaultMinifyCSSOptions = { } ; /**
 * The default <code>html-minifier</code> options, optimized for production
 * minification.
 */ export const defaultMinifyOptions = { caseSensitive : true , collapseWhitespace : true , decodeEntities : true , minifyCSS : defaultMinifyCSSOptions , minifyJS : true , processConditionalComments : true , removeAttributeQuotes : false , removeComments : true , removeEmptyAttributes : true , removeScriptTypeAttributes : true , removeStyleLinkTypeAttributes : true , useShortDoctype : true } ; /**
 * The default strategy. This uses <code>html-minifier</code> to minify HTML and
 * <code>clean-css</code> to minify CSS.
 */ export const defaultStrategy = { getPlaceholder ( parts ) { const suffix = '();' ; let placeholder = '@TEMPLATE_EXPRESSION' ; while ( parts . some ( part => part . text . includes ( placeholder + suffix ) ) ) { placeholder += '_' ; } return placeholder + suffix ; } , combineHTMLStrings ( parts , placeholder ) { return parts . map ( part => part . text ) . join ( placeholder ) ; } , minifyHTML ( html , options = { } ) { let minifyCSSOptions ; if ( options . minifyCSS ) { if ( options . minifyCSS !== true && typeof options . minifyCSS !== 'function' ) { minifyCSSOptions = { ... options . minifyCSS } ; } else { minifyCSSOptions = { } ; } } else { minifyCSSOptions = false ; } let adjustedMinifyCSSOptions = false ; if ( minifyCSSOptions ) { adjustedMinifyCSSOptions = adjustMinifyCSSOptions ( minifyCSSOptions ) ; } let result = minify ( html , { ... options , minifyCSS : adjustedMinifyCSSOptions } ) ; if ( options . collapseWhitespace ) { const matches = Array . from ( result . matchAll ( /<svg/g ) ) . reverse ( ) ; for ( const match of matches ) { const startTagIndex = match . index ! ; const closeTagIndex = result . indexOf ( '</svg' , startTagIndex ) ; if ( closeTagIndex < 0 ) { continue ; } const start = result . substring ( 0 , startTagIndex ) ; let svg = result . substring ( startTagIndex , closeTagIndex ) ; const end = result . substring ( closeTagIndex ) ; svg = svg . replace ( /\r?\n/g , '' ) ; result = start + svg + end ; } } if ( adjustedMinifyCSSOptions && adjustedMinifyCSSOptions . level [ OptimizationLevel . One ] . tidySelectors ) { result = fixCleanCssTidySelectors ( html , result ) ; } return result ; } , minifyCSS ( css , options = { } ) { const adjustedOptions = adjustMinifyCSSOptions ( options ) ; const output = new CleanCSS ( adjustedOptions ) . minify ( css ) ; if ( output . errors && output . errors . length ) { throw new Error ( output . errors . join ( '\n\n' ) ) ; } if ( adjustedOptions . level [ OptimizationLevel . One ] . tidySelectors ) { output . styles = fixCleanCssTidySelectors ( css , output . styles ) ; } return output . styles ; } , splitHTMLByPlaceholder ( html , placeholder ) { const parts = html . split ( placeholder ) ; if ( placeholder . endsWith ( ';' ) ) { const withoutSemicolon = placeholder . substring ( 0 , placeholder . length - 1 ) ; for ( let i = parts . length - 1 ; i >= 0 ; i -- ) { parts . splice ( i , 1 , ... parts [ i ] . split ( withoutSemicolon ) ) ; } } return parts ; } } ; export function adjustMinifyCSSOptions ( options = { } ) { const level = optimizationLevelFrom ( options . level ) ; const originalTransform = typeof options . level === 'object' && options . level [ 1 ] && options . level [ 1 ] . transform ; level [ OptimizationLevel . One ] . transform = ( property , value ) => { if ( value . startsWith ( '@TEMPLATE_EXPRESSION' ) && ! value . endsWith ( ';' ) ) { return ( value = `${ value };` ) ; } return originalTransform ? originalTransform ( property , value ) : value ; } ; return { ... options , level } ; } function fixCleanCssTidySelectors ( original , result ) { const regex = /(::?.+\((.*)\))[\s\r\n]*{/gm ; let match ; while ( ( match = regex . exec ( original ) ) != null ) { const pseudoClass = match [ 1 ] ; const parameters = match [ 2 ] ; if ( ! parameters . match ( /\s/ ) ) { continue ; } const parametersWithoutSpaces = parameters . replace ( /\s/g , '' ) ; const resultPseudoClass = pseudoClass . replace ( parameters , parametersWithoutSpaces ) ; const resultStartIndex = result . indexOf ( resultPseudoClass ) ; if ( resultStartIndex < 0 ) { continue ; } const resultEndIndex = resultStartIndex + resultPseudoClass . length ; result = result . substring ( 0 , resultStartIndex ) + pseudoClass + result . substring ( resultEndIndex ) ; } return result ; }