license import * as ng from '@angular/compiler-cli' ; import { BazelOptions , CachedFileLoader , CompilerHost , FileCache , FileLoader , UncachedFileLoader , constructManifest , debug , parseTsconfig , resolveNormalizedPath , runAsWorker , runWorkerLoop } from '@bazel/typescript' ; import * as fs from 'fs' ; import * as path from 'path' ; import * as tsickle from 'tsickle' ; import * as ts from 'typescript' ; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/ ; const NGC_GEN_FILES = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/ ; const NGC_ASSETS = /\.(css|html|ngsummary\.json)$/ ; const BAZEL_BIN = /\b(blaze|bazel)-out\b.*?\bbin\b/ ; const ALL_DEPS_COMPILED_WITH_BAZEL = false ; const NODE_MODULES = 'node_modules/' ; export function main ( args ) { if ( runAsWorker ( args ) ) { runWorkerLoop ( runOneBuild ) ; } else { return runOneBuild ( args ) ? 0 : 1 ; } return 0 ; } /** The one FileCache instance used in this process. */ const fileCache = new FileCache < ts . SourceFile > ( debug ) ; export function runOneBuild ( args , inputs ? ) { if ( args [ 0 ] === '-p' ) args . shift ( ) ; const project = args [ 0 ] . replace ( /^@+/ , '' ) ; const [ parsedOptions , errors ] = parseTsconfig ( project ) ; if ( errors && errors . length ) { console . error ( ng . formatDiagnostics ( errors ) ) ; return false ; } const { options : tsOptions , bazelOpts , files , config } = parsedOptions ; const angularCompilerOptions = config [ 'angularCompilerOptions' ] || { } ; if ( config [ 'extends' ] ) { let userConfigFile = resolveNormalizedPath ( path . dirname ( project ) , config [ 'extends' ] ) ; if ( ! userConfigFile . endsWith ( '.json' ) ) userConfigFile += '.json' ; const { config : userConfig , error } = ts . readConfigFile ( userConfigFile , ts . sys . readFile ) ; if ( error ) { console . error ( ng . formatDiagnostics ( [ error ] ) ) ; return false ; } if ( userConfig . angularCompilerOptions ) { angularCompilerOptions [ 'diagnostics' ] = angularCompilerOptions [ 'diagnostics' ] || userConfig . angularCompilerOptions . diagnostics ; angularCompilerOptions [ 'trace' ] = angularCompilerOptions [ 'trace' ] || userConfig . angularCompilerOptions . trace ; angularCompilerOptions [ 'disableExpressionLowering' ] = angularCompilerOptions [ 'disableExpressionLowering' ] || userConfig . angularCompilerOptions . disableExpressionLowering ; angularCompilerOptions [ 'disableTypeScriptVersionCheck' ] = angularCompilerOptions [ 'disableTypeScriptVersionCheck' ] || userConfig . angularCompilerOptions . disableTypeScriptVersionCheck ; angularCompilerOptions [ 'i18nOutLocale' ] = angularCompilerOptions [ 'i18nOutLocale' ] || userConfig . angularCompilerOptions . i18nOutLocale ; angularCompilerOptions [ 'i18nOutFormat' ] = angularCompilerOptions [ 'i18nOutFormat' ] || userConfig . angularCompilerOptions . i18nOutFormat ; angularCompilerOptions [ 'i18nOutFile' ] = angularCompilerOptions [ 'i18nOutFile' ] || userConfig . angularCompilerOptions . i18nOutFile ; angularCompilerOptions [ 'i18nInFormat' ] = angularCompilerOptions [ 'i18nInFormat' ] || userConfig . angularCompilerOptions . i18nInFormat ; angularCompilerOptions [ 'i18nInLocale' ] = angularCompilerOptions [ 'i18nInLocale' ] || userConfig . angularCompilerOptions . i18nInLocale ; angularCompilerOptions [ 'i18nInFile' ] = angularCompilerOptions [ 'i18nInFile' ] || userConfig . angularCompilerOptions . i18nInFile ; angularCompilerOptions [ 'i18nInMissingTranslations' ] = angularCompilerOptions [ 'i18nInMissingTranslations' ] || userConfig . angularCompilerOptions . i18nInMissingTranslations ; angularCompilerOptions [ 'i18nUseExternalIds' ] = angularCompilerOptions [ 'i18nUseExternalIds' ] || userConfig . angularCompilerOptions . i18nUseExternalIds ; angularCompilerOptions [ 'preserveWhitespaces' ] = angularCompilerOptions [ 'preserveWhitespaces' ] || userConfig . angularCompilerOptions . preserveWhitespaces ; angularCompilerOptions . createExternalSymbolFactoryReexports = angularCompilerOptions . createExternalSymbolFactoryReexports || userConfig . angularCompilerOptions . createExternalSymbolFactoryReexports ; } } const expectedOuts = config [ 'angularCompilerOptions' ] [ 'expectedOut' ] ; const { basePath } = ng . calcProjectFileAndBasePath ( project ) ; const compilerOpts = ng . createNgCompilerOptions ( basePath , config , tsOptions ) ; const tsHost = ts . createCompilerHost ( compilerOpts , true ) ; const { diagnostics } = compile ( { allDepsCompiledWithBazel : ALL_DEPS_COMPILED_WITH_BAZEL , compilerOpts , tsHost , bazelOpts , files , inputs , expectedOuts } ) ; if ( diagnostics . length ) { console . error ( ng . formatDiagnostics ( diagnostics ) ) ; } return diagnostics . every ( d => d . category !== ts . DiagnosticCategory . Error ) ; } export function relativeToRootDirs ( filePath , rootDirs ) { if ( ! filePath ) return filePath ; for ( let i = 0 ; i < rootDirs . length ; i ++ ) { const dir = rootDirs [ i ] ; const rel = path . posix . relative ( dir , filePath ) ; if ( rel . indexOf ( '.' ) != 0 ) return rel ; } return filePath ; } export function compile ( { allDepsCompiledWithBazel = true , compilerOpts , tsHost , bazelOpts , files , inputs , expectedOuts , gatherDiagnostics } ) { let fileLoader ; if ( bazelOpts . maxCacheSizeMb !== undefined ) { const maxCacheSizeBytes = bazelOpts . maxCacheSizeMb * ( 1 << 20 ) ; fileCache . setMaxCacheSize ( maxCacheSizeBytes ) ; } else { fileCache . resetMaxCacheSize ( ) ; } if ( inputs ) { fileLoader = new CachedFileLoader ( fileCache ) ; const resolvedInputs = { } ; const inputKeys = Object . keys ( inputs ) ; for ( let i = 0 ; i < inputKeys . length ; i ++ ) { const key = inputKeys [ i ] ; resolvedInputs [ resolveNormalizedPath ( key ) ] = inputs [ key ] ; } fileCache . updateCache ( resolvedInputs ) ; } else { fileLoader = new UncachedFileLoader ( ) ; } if ( ! bazelOpts . es5Mode ) { compilerOpts . annotateForClosureCompiler = true ; compilerOpts . annotationsAs = 'static fields' ; } const isInIvyMode = compilerOpts . enableIvy === 'ngtsc' || compilerOpts . enableIvy === 'tsc' ; if ( isInIvyMode ) { if ( compilerOpts . enableIvy === 'tsc' ) { compilerOpts . annotateForClosureCompiler = false ; } compilerOpts . annotationsAs = 'decorators' ; } if ( ! compilerOpts . rootDirs ) { throw new Error ( 'rootDirs is not set!' ) ; } const bazelBin = compilerOpts . rootDirs . find ( rootDir => BAZEL_BIN . test ( rootDir ) ) ; if ( ! bazelBin ) { throw new Error ( `Couldn't find bazel bin in the rootDirs: ${ compilerOpts . rootDirs }` ) ; } const writtenExpectedOuts = expectedOuts . map ( p => p . replace ( /\\/g , '/' ) ) ; const originalWriteFile = tsHost . writeFile . bind ( tsHost ) ; tsHost . writeFile = ( fileName , content , writeByteOrderMark , onError ? , sourceFiles ? ) => { const relative = relativeToRootDirs ( fileName . replace ( /\\/g , '/' ) , [ compilerOpts . rootDir ] ) ; const expectedIdx = writtenExpectedOuts . findIndex ( o => o === relative ) ; if ( expectedIdx >= 0 ) { writtenExpectedOuts . splice ( expectedIdx , 1 ) ; originalWriteFile ( fileName , content , writeByteOrderMark , onError , sourceFiles ) ; } } ; const generatedFileModuleResolverHost = Object . create ( tsHost ) ; generatedFileModuleResolverHost . fileExists = ( fileName ) => { const match = NGC_GEN_FILES . exec ( fileName ) ; if ( match ) { const [ , file , suffix , ext ] = match ; if ( ext !== '.ts' && ext !== '.d.ts' ) return false ; if ( suffix . indexOf ( 'ngstyle' ) >= 0 ) { fileName = file ; } else { fileName = file + ( ext || '' ) ; } } return tsHost . fileExists ( fileName ) ; } ; function generatedFileModuleResolver ( moduleName , containingFile , compilerOptions ) { return ts . resolveModuleName ( moduleName , containingFile , compilerOptions , generatedFileModuleResolverHost ) ; } const bazelHost = new CompilerHost ( files , compilerOpts , bazelOpts , tsHost , fileLoader , generatedFileModuleResolver ) ; if ( isInIvyMode ) { bazelHost . transformDecorators = false ; } bazelHost . transformTypesToClosure = compilerOpts . annotateForClosureCompiler ; const origBazelHostFileExist = bazelHost . fileExists ; bazelHost . fileExists = ( fileName ) => { if ( NGC_ASSETS . test ( fileName ) ) { return tsHost . fileExists ( fileName ) ; } return origBazelHostFileExist . call ( bazelHost , fileName ) ; } ; const origBazelHostShouldNameModule = bazelHost . shouldNameModule . bind ( bazelHost ) ; bazelHost . shouldNameModule = ( fileName ) => { const flatModuleOutPath = path . posix . join ( bazelOpts . package , compilerOpts . flatModuleOutFile + '.ts' ) ; if ( fileName === path . posix . join ( compilerOpts . baseUrl , flatModuleOutPath ) ) return true ; const targetWorkspace = bazelOpts . target . split ( '/' ) [ 0 ] . replace ( /^@/ , '' ) ; if ( targetWorkspace && fileName === path . posix . join ( compilerOpts . baseUrl , 'external' , targetWorkspace , flatModuleOutPath ) ) return true ; return origBazelHostShouldNameModule ( fileName ) || NGC_GEN_FILES . test ( fileName ) ; } ; const ngHost = ng . createCompilerHost ( { options : compilerOpts , tsHost : bazelHost } ) ; const fileNameToModuleNameCache = new Map < string , string > ( ) ; ngHost . fileNameToModuleName = ( importedFilePath , containingFilePath ) => { if ( fileNameToModuleNameCache . has ( importedFilePath ) ) { return fileNameToModuleNameCache . get ( importedFilePath ) ; } const result = doFileNameToModuleName ( importedFilePath ) ; fileNameToModuleNameCache . set ( importedFilePath , result ) ; return result ; } ; function doFileNameToModuleName ( importedFilePath ) { try { const sourceFile = ngHost . getSourceFile ( importedFilePath , ts . ScriptTarget . Latest ) ; if ( sourceFile && sourceFile . moduleName ) { return sourceFile . moduleName ; } } catch ( err ) { } if ( importedFilePath . indexOf ( 'node_modules' ) >= 0 ) { const maybeMetadataFile = importedFilePath . replace ( EXT , '' ) + '.metadata.json' ; if ( fs . existsSync ( maybeMetadataFile ) ) { const moduleName = JSON . parse ( fs . readFileSync ( maybeMetadataFile , { encoding : 'utf-8' } ) ) . importAs ; if ( moduleName ) { return moduleName ; } } } if ( ( compilerOpts . module === ts . ModuleKind . UMD || compilerOpts . module === ts . ModuleKind . AMD ) && ngHost . amdModuleName ) { return ngHost . amdModuleName ( { fileName : importedFilePath } as ts . SourceFile ) ; } const result = relativeToRootDirs ( importedFilePath , compilerOpts . rootDirs ) . replace ( EXT , '' ) ; if ( result . startsWith ( NODE_MODULES ) ) { return result . substr ( NODE_MODULES . length ) ; } return bazelOpts . workspaceName + '/' + result ; } ngHost . toSummaryFileName = ( fileName , referringSrcFileName ) => path . posix . join ( bazelOpts . workspaceName , relativeToRootDirs ( fileName , compilerOpts . rootDirs ) . replace ( EXT , '' ) ) ; if ( allDepsCompiledWithBazel ) { ngHost . fromSummaryFileName = ( fileName , referringLibFileName ) => { const workspaceRelative = fileName . split ( '/' ) . splice ( 1 ) . join ( '/' ) ; return resolveNormalizedPath ( bazelBin , workspaceRelative ) + '.d.ts' ; } ; } ( ngHost as any ) . reportMissingResource = ( resourceName ) => { console . error ( `\nAsset not found:\n  ${ resourceName }` ) ; console . error ( 'Check that it\'s included in the `assets` attribute of the `ng_module` rule.\n' ) ; } ; const emitCallback = ( { program , targetSourceFile , writeFile , cancellationToken , emitOnlyDtsFiles , customTransformers = { } , } ) => tsickle . emitWithTsickle ( program , bazelHost , bazelHost , compilerOpts , targetSourceFile , writeFile , cancellationToken , emitOnlyDtsFiles , { beforeTs : customTransformers . before , afterTs : customTransformers . after , afterDeclarations : customTransformers . afterDeclarations } ) ; if ( ! gatherDiagnostics ) { gatherDiagnostics = ( program ) => gatherDiagnosticsForInputsOnly ( compilerOpts , bazelOpts , program ) ; } const { diagnostics , emitResult , program } = ng . performCompilation ( { rootNames : files , options : compilerOpts , host : ngHost , emitCallback , mergeEmitResultsCallback : tsickle . mergeEmitResults , gatherDiagnostics } ) ; const tsickleEmitResult = emitResult as tsickle . EmitResult ; let externs = '/** @externs */\n' ; if ( ! diagnostics . length ) { if ( bazelOpts . tsickleGenerateExterns ) { externs += tsickle . getGeneratedExterns ( tsickleEmitResult . externs ) ; } if ( bazelOpts . manifest ) { const manifest = constructManifest ( tsickleEmitResult . modulesManifest , bazelHost ) ; fs . writeFileSync ( bazelOpts . manifest , manifest ) ; } } if ( ! program ) return { program , diagnostics } ; if ( ! bazelOpts . nodeModulesPrefix ) { generateMetadataJson ( program . getTsProgram ( ) , files , compilerOpts . rootDirs , bazelBin , tsHost ) ; } if ( bazelOpts . tsickleExternsPath ) { fs . writeFileSync ( bazelOpts . tsickleExternsPath , externs ) ; } for ( let i = 0 ; i < writtenExpectedOuts . length ; i ++ ) { originalWriteFile ( writtenExpectedOuts [ i ] , '' , false ) ; } return { program , diagnostics } ; } /**
 * Generate metadata.json for the specified `files`. By default, metadata.json
 * is only generated by the compiler if --flatModuleOutFile is specified. But
 * if compiled under blaze, we want the metadata to be generated for each
 * Angular component.
 */ function generateMetadataJson ( program , files , rootDirs , bazelBin , tsHost ) { const collector = new ng . MetadataCollector ( ) ; for ( let i = 0 ; i < files . length ; i ++ ) { const file = files [ i ] ; const sourceFile = program . getSourceFile ( file ) ; if ( sourceFile ) { const metadata = collector . getMetadata ( sourceFile ) ; if ( metadata ) { const relative = relativeToRootDirs ( file , rootDirs ) ; const shortPath = relative . replace ( EXT , '.metadata.json' ) ; const outFile = resolveNormalizedPath ( bazelBin , shortPath ) ; const data = JSON . stringify ( metadata ) ; tsHost . writeFile ( outFile , data , false , undefined , [ ] ) ; } } } } function isCompilationTarget ( bazelOpts , sf ) { return ! NGC_GEN_FILES . test ( sf . fileName ) && ( bazelOpts . compilationTargetSrc . indexOf ( sf . fileName ) !== - 1 ) ; } function gatherDiagnosticsForInputsOnly ( options , bazelOpts , ngProgram ) { const tsProgram = ngProgram . getTsProgram ( ) ; const diagnostics = [ ] ; diagnostics . push ( ... tsProgram . getOptionsDiagnostics ( ) ) ; diagnostics . push ( ... tsProgram . getGlobalDiagnostics ( ) ) ; const programFiles = tsProgram . getSourceFiles ( ) . filter ( f => isCompilationTarget ( bazelOpts , f ) ) ; for ( let i = 0 ; i < programFiles . length ; i ++ ) { const sf = programFiles [ i ] ; diagnostics . push ( ... tsProgram . getSyntacticDiagnostics ( sf ) ) ; diagnostics . push ( ... tsProgram . getSemanticDiagnostics ( sf ) ) ; } if ( ! diagnostics . length ) { diagnostics . push ( ... ngProgram . getNgStructuralDiagnostics ( ) ) ; diagnostics . push ( ... ngProgram . getNgSemanticDiagnostics ( ) ) ; } return diagnostics ; } if ( require . main === module ) { process . exitCode = main ( process . argv . slice ( 2 ) ) ; }