import { existsSync , readFileSync } from 'fs' ; import * as glob from 'glob' ; import * as path from 'path' ; import * as _ from 'lodash' ; import { ComponentCompiler } from '../component/component.compiler' ; import { SymbolTable } from '../symbolTable' ; import { NgAppBuilder } from './builders/ng-app.builder' ; interface DvConfig { name : string ; config ? : any ; gateway ? : { config ? : any ; } ; usedConcepts ? : { [ as ] : ConceptInfo ; } ; components ? : { app ? : ComponentsConfig ; } ; routes ? : { path : string ; component : string ; } [ ] ; } interface ComponentsConfig { readonly include ? : string [ ] ; readonly exclude ? : string [ ] ; } interface ConceptInfo { name ? : string ; config ? : any ; } const DEFAULT = [ '**' ] ; const IGNORE = [ 'node_modules/**' , 'dist/**' , 'pkg/**' , '**/!(*.html)' , '**/index.html' ] ; function filesToParse ( rootDirectory , componentsConfig ) { const globs = _ . get ( componentsConfig , 'include' , DEFAULT ) ; return htmlFilesFromGlobs ( globs , rootDirectory , componentsConfig ) ; } function htmlFilesFromGlobs ( globs , rootDirectory , componentsConfig ) { const globOptions = { cwd : rootDirectory , ignore : [ ... IGNORE , ... _ . get ( componentsConfig , 'exclude' , [ ] ) ] , nodir : true } ; return _ . flatMap ( globs , ( pattern ) => glob . sync ( pattern , globOptions ) ) ; } export class AppCompiler { static DVCONFIG_FILE_PATH = 'dvconfig.json' ; static PACKAGEJSON_FILE_PATH = 'package.json' ; private readonly componentCompiler = new ComponentCompiler ( ) ; private readonly symbolTable : SymbolTable = { } ; param projectDir param cacheDir param installDependencies static Compile ( projectDir , cacheDir , installDependencies = true ) { const compilerForProject = new AppCompiler ( projectDir , cacheDir , installDependencies ) ; compilerForProject . compile ( ) ; } private constructor ( private readonly projectDir , private readonly cacheDir , private readonly installDependencies ) { } compile ( ) { console . log ( `Using cache dir ${ this . cacheDir }` ) ; const packageJsonPath = path . join ( this . projectDir , AppCompiler . PACKAGEJSON_FILE_PATH ) ; const packageJsonContents = JSON . parse ( readFileSync ( packageJsonPath , 'utf8' ) ) ; const version = _ . get ( packageJsonContents , [ 'dependencies' , '@deja-vu/cli' ] ) ; if ( version === undefined ) { throw new Error ( `Couldn't figure out what verion of DV to use. Your ` + `package.json should have @deja-vu/cli under dependencies` ) ; } const dvConfigPath = path . join ( this . projectDir , AppCompiler . DVCONFIG_FILE_PATH ) ; const dvConfigContents = readFileSync ( dvConfigPath , 'utf8' ) ; const dvConfig = JSON . parse ( dvConfigContents ) ; const appName = dvConfig . name ; this . symbolTable [ appName ] = { kind : 'app' } ; const aliasToConceptNameMap = _ . mapValues ( dvConfig . usedConcepts , ( value , alias ) => { return { kind : 'concept' , conceptName : _ . get ( value , 'name' , alias ) } ; } ) ; _ . extend ( this . symbolTable , aliasToConceptNameMap ) ; const usedConcepts = _ . chain ( dvConfig . usedConcepts ) . toPairs ( ) . map ( ( [ conceptAlias , conceptConfig ] ) => _ . get ( conceptConfig , 'name' , conceptAlias ) ) . uniq ( ) . value ( ) ; const ngAppBuilder = new NgAppBuilder ( appName , version , dvConfigContents ) ; _ . each ( usedConcepts , ( usedConcept ) => { ngAppBuilder . addDependency ( usedConcept , version ) ; } ) ; if ( dvConfig . routes !== undefined ) { for ( const route of dvConfig . routes ) { const selector = `${ appName }-${ route . component }` ; ngAppBuilder . addRoute ( route . path , selector ) ; } } const globalStyleFileScss = path . join ( this . projectDir , 'src' , 'styles.scss' ) ; const globalStyleScss = existsSync ( globalStyleFileScss ) ? readFileSync ( globalStyleFileScss , 'utf8' ) : '' ; const globalStyleFileCss = path . join ( this . projectDir , 'src' , 'styles.css' ) ; const globalStyleCss = existsSync ( globalStyleFileCss ) ? readFileSync ( globalStyleFileCss , 'utf8' ) : '' ; ngAppBuilder . setGlobalStyle ( globalStyleScss . concat ( globalStyleCss ) ) ; const faviconFile = path . join ( this . projectDir , 'favicon.ico' ) ; if ( existsSync ( faviconFile ) ) { ngAppBuilder . setFavicon ( faviconFile ) ; } const assetsDir = path . join ( this . projectDir , 'src' , 'assets' ) ; if ( existsSync ( assetsDir ) ) { ngAppBuilder . setAppAssetsDir ( assetsDir ) ; } const componentsConfig = ( dvConfig . components !== undefined ) ? dvConfig . components . app : undefined ; const htmlFilesToParse = filesToParse ( this . projectDir , componentsConfig ) ; for ( const componentFilePath of htmlFilesToParse ) { try { const componentContents = readFileSync ( path . join ( this . projectDir , componentFilePath ) , 'utf8' ) ; const componentStylePath = path . join ( this . projectDir , _ . replace ( componentFilePath , '.html' , '.css' ) ) ; const componentStyle = existsSync ( componentStylePath ) ? readFileSync ( componentStylePath , 'utf8' ) : '' ; const pages = _ . map ( dvConfig . routes , 'component' ) ; const compiledComponent = this . componentCompiler . compile ( dvConfig . name , componentContents , this . symbolTable , componentStyle , pages ) ; ngAppBuilder . addComponent ( compiledComponent . name , compiledComponent . className , compiledComponent . ngComponent , compiledComponent . ngTemplate ) ; for ( const componentInput of compiledComponent . componentInputs ) { ngAppBuilder . addComponent ( componentInput . name , componentInput . className , componentInput . ngComponent , componentInput . ngTemplate ) ; } } catch ( e ) { console . error ( `Error in component ${ componentFilePath }` ) ; throw e ; } } ngAppBuilder . build ( this . cacheDir , this . installDependencies ) ; } }