import { ObjectDictionary } from "@opticss/util" ; import * as debugGenerator from "debug" ; import { postcss } from "opticss" ; import * as path from "path" ; import { Block } from "../BlockTree" ; import { Options , ResolvedConfiguration } from "../configuration" ; import { CssBlockError } from "../errors" ; import { FileIdentifier , ImportedCompiledCssFile , ImportedFile } from "../importing" ; import { BlockFactoryBase , sourceMapFromProcessedFile } from "./BlockFactoryBase" ; import { BlockParser , ParsedSource } from "./BlockParser" ; import { PreprocessorSync , PreprocessorsSync , ProcessedFile , Syntax , annotateCssContentWithSourceMap , syntaxName } from "./preprocessing" ; const debug = debugGenerator ( "css-blocks:BlockFactorySync" ) ; interface ErrorWithErrNum { code ? : string ; message : string ; } /**
 * This factory ensures that instances of a block are re-used when blocks are
 * going to be compiled/optimized together. Multiple instances of the same
 * block will result in analysis and optimization bugs.
 *
 * This also ensures that importers and preprocessors are correctly used when loading a block file.
 */ export class BlockFactorySync extends BlockFactoryBase { parser : BlockParser ; preprocessors : PreprocessorsSync ; private blocks : ObjectDictionary < Block > ; private paths : ObjectDictionary < string > ; get isSync ( ) : true { return true ; } constructor ( options , postcssImpl = postcss , faultTolerant = false ) { super ( options , postcssImpl , faultTolerant ) ; this . preprocessors = this . configuration . preprocessorsSync ; this . parser = new BlockParser ( options , this ) ; this . blocks = { } ; this . paths = { } ; this . faultTolerant = faultTolerant ; } /**
     * Toss out any caches in this BlockFactory. Any future requests for a block
     * or block path will be loaded fresh from persistent storage.
     */ reset ( ) { super . reset ( ) ; this . blocks = { } ; this . paths = { } ; } param root param identifier param name param isDfnFile returns parseRootFaultTolerant ( root , identifier , name , isDfnFile = false , expectedGuid ? ) { return this . parser . parseSync ( root , identifier , name , isDfnFile , expectedGuid ) ; } param root param identifier param name param isDfnFile returns parseRoot ( root , identifier , name , isDfnFile = false , expectedGuid ? ) { const block = this . parseRootFaultTolerant ( root , identifier , name , isDfnFile , expectedGuid ) ; return this . _surfaceBlockErrors ( block ) ; } /**
     * This method doesn't do anything, but it's provided for parity with
     * `BlockFactory`.
     */ prepareForExit ( ) { } param filePath returns getBlockFromPath ( filePath ) { if ( ! path . isAbsolute ( filePath ) ) { throw new Error ( `An absolute path is required. Got: ${ filePath }.` ) ; } filePath = path . resolve ( filePath ) ; let identifier = this . paths [ filePath ] || this . importer . identifier ( null , filePath , this . configuration ) ; return this . getBlock ( identifier ) ; } param identifier returns getBlock ( identifier ) { if ( this . blocks [ identifier ] ) { return this . blocks [ identifier ] ; } return this . _getBlock ( identifier ) ; } param identifier returns private _getBlock ( identifier ) { let file = this . importer . importSync ( identifier , this . configuration ) ; let block ; if ( file . type === "ImportedCompiledCssFile" ) { block = this . _reconstituteCompiledCssSource ( file ) ; } else { block = this . _importAndPreprocessBlock ( file ) ; } debug ( `Finalizing Block object for "${ block . identifier }"` ) ; if ( this . blocks [ block . identifier ] ) { return this . blocks [ block . identifier ] ; } const uniqueName = this . getUniqueBlockName ( block . name , block . identifier , file . type === "ImportedCompiledCssFile" ) ; if ( uniqueName === null ) { block . addError ( new CssBlockError ( `Block uses a name that has already been used by ${ this . blockNames [ block . name ] }` , { filename : block . identifier , } ) ) ; block . setName ( block . name ) ; } else { block . setName ( uniqueName ) ; } if ( block . isValid ( ) ) { const guidRegResult = this . registerGuid ( block . guid ) ; if ( ! guidRegResult ) { block . addError ( new CssBlockError ( "Block uses a GUID that has already been used! Check dependencies for conflicting GUIDs and/or increase the number of significant characters used to generate GUIDs." , { filename : block . identifier , } ) ) ; } } this . _surfaceBlockErrors ( block ) ; this . blocks [ block . identifier ] = block ; return block ; } param file returns private _importAndPreprocessBlock ( file ) { let realFilename = this . importer . filesystemPath ( file . identifier , this . configuration ) ; if ( realFilename ) { if ( this . paths [ realFilename ] && this . paths [ realFilename ] !== file . identifier ) { throw new Error ( `The same block file was returned with different identifiers: ${ this . paths [ realFilename ] } and ${ file . identifier }` ) ; } else { this . paths [ realFilename ] = file . identifier ; } } if ( this . blocks [ file . identifier ] ) { debug ( `Using pre-compiled Block for "${ file . identifier }"` ) ; return this . blocks [ file . identifier ] ; } let filename = realFilename || this . importer . debugIdentifier ( file . identifier , this . configuration ) ; let preprocessor = this . preprocessor ( file ) ; debug ( `Preprocessing "${ filename }"` ) ; let preprocessResult = preprocessor ( filename , file . contents , this . configuration ) ; debug ( `Generating PostCSS AST for "${ filename }"` ) ; let sourceMap = sourceMapFromProcessedFile ( preprocessResult ) ; let content = preprocessResult . content ; if ( sourceMap ) { content = annotateCssContentWithSourceMap ( this . configuration , filename , content , sourceMap ) ; } let root = this . postcssImpl . parse ( content , { from : filename } ) ; if ( this . blocks [ file . identifier ] ) { return this . blocks [ file . identifier ] ; } debug ( `Parsing Block object for "${ filename }"` ) ; let source = { identifier : file . identifier , defaultName : file . defaultName , parseResult : root , originalSource : file . contents , originalSyntax : file . syntax , dependencies : preprocessResult . dependencies || [ ] , } ; return this . parser . parseSourceSync ( source ) ; } private _reconstituteCompiledCssSource ( file ) { if ( this . blocks [ file . identifier ] ) { debug ( `Using pre-compiled Block for "${ file . identifier }"` ) ; return this . blocks [ file . identifier ] ; } const { definitionAst , cssContentsAst } = this . _prepareDefinitionASTs ( file ) ; const block = this . parser . parseDefinitionSourceSync ( definitionAst , file . identifier , file . blockId , file . defaultName ) ; block . precompiledStylesheet = cssContentsAst ; block . precompiledStylesheetUnedited = file . rawCssContents ; this . _mergeCssRulesIntoDefinitionBlock ( block , cssContentsAst , file ) ; return block ; } param fromIdentifier param importPath returns getBlockRelative ( fromIdentifier , importPath ) { let importer = this . importer ; let fromPath = importer . debugIdentifier ( fromIdentifier , this . configuration ) ; let identifier = importer . identifier ( fromIdentifier , importPath , this . configuration ) ; try { return this . getBlock ( identifier ) ; } catch ( err ) { if ( ( < ErrorWithErrNum > err ) . code === "ENOENT" ) { err . message = `From ${ fromPath }: ${ err . message }` ; } throw err ; } } preprocessor ( file ) { let syntax = file . syntax ; let firstPreprocessor = this . preprocessors [ syntax ] ; let preprocessor = null ; if ( firstPreprocessor ) { if ( syntax !== Syntax . css && this . preprocessors . css && ! this . configuration . disablePreprocessChaining ) { let cssProcessor = this . preprocessors . css ; preprocessor = ( fullPath , content , configuration ) : ProcessedFile => { let result = firstPreprocessor ! ( fullPath , content , configuration ) ; let content2 = result . content . toString ( ) ; let result2 = cssProcessor ( fullPath , content2 , configuration , sourceMapFromProcessedFile ( result ) ) ; return { content : result2 . content , sourceMap : sourceMapFromProcessedFile ( result2 ) , dependencies : ( result . dependencies || [ ] ) . concat ( result2 . dependencies || [ ] ) , } ; } ; } else { preprocessor = firstPreprocessor ; } } else if ( syntax !== Syntax . css ) { throw new Error ( `No preprocessor provided for ${ syntaxName ( syntax ) }.` ) ; } else { preprocessor = ( _fullPath , content , _options ) : ProcessedFile => { return { content , } ; } ; } return preprocessor ; } }