From ffb15532828783e49436c3998a92009fb5bb0381 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Wed, 16 Aug 2017 15:35:19 -0700 Subject: [PATCH] refactor(compiler): make the new ngc API independent of tsickle (#18739) This changes `performCompile` / `program.emit` to not tsickle automatically, but allows to pass in an `emitCallback` in which tsickle can be executed. --- packages/compiler-cli/src/main.ts | 41 +++++++++- packages/compiler-cli/src/perform-compile.ts | 18 +++-- packages/compiler-cli/src/transformers/api.ts | 35 +++++---- .../compiler-cli/src/transformers/program.ts | 78 ++++++++++--------- tools/ngc-wrapped/index.ts | 2 +- 5 files changed, 116 insertions(+), 58 deletions(-) diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 28d9d86ab9..60b989bb42 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -14,6 +14,7 @@ import * as ts from 'typescript'; import * as tsc from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; +import * as tsickle from 'tsickle'; import * as api from './transformers/api'; import * as ngc from './transformers/entry_points'; import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile'; @@ -31,7 +32,8 @@ export function main( if (options.disableTransformerPipeline) { return disabledTransformerPipelineNgcMain(parsedArgs, consoleError); } - const {diagnostics: compileDiags} = performCompilation(rootNames, options); + const {diagnostics: compileDiags} = + performCompilation({rootNames, options, emitCallback: createEmitCallback(options)}); return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError)); } @@ -42,10 +44,45 @@ export function mainSync( if (configErrors.length) { return reportErrorsAndExit(options, configErrors, consoleError); } - const {diagnostics: compileDiags} = performCompilation(rootNames, options); + const {diagnostics: compileDiags} = + performCompilation({rootNames, options, emitCallback: createEmitCallback(options)}); return reportErrorsAndExit(options, compileDiags, consoleError); } +function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback { + const tsickleOptions: tsickle.TransformerOptions = { + googmodule: false, + untyped: true, + convertIndexImportShorthand: true, + transformDecorators: options.annotationsAs !== 'decorators', + transformTypesToClosure: options.annotateForClosureCompiler, + }; + + const tsickleHost: tsickle.TransformerHost = { + shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName), + pathToModuleName: (context, importPath) => '', + shouldIgnoreWarningsForPath: (filePath) => false, + fileNameToModuleId: (fileName) => fileName, + }; + + return ({ + program, + targetSourceFile, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers = {}, + host, + options + }) => + tsickle.emitWithTsickle( + program, tsickleHost, tsickleOptions, host, options, targetSourceFile, writeFile, + cancellationToken, emitOnlyDtsFiles, { + beforeTs: customTransformers.before, + afterTs: customTransformers.after, + }); +} + function readCommandLineAndConfiguration(args: any): ParsedConfiguration { const project = args.p || args.project || '.'; const allDiagnostics: Diagnostics = []; diff --git a/packages/compiler-cli/src/perform-compile.ts b/packages/compiler-cli/src/perform-compile.ts index 821720a7ce..2a81339840 100644 --- a/packages/compiler-cli/src/perform-compile.ts +++ b/packages/compiler-cli/src/perform-compile.ts @@ -105,10 +105,16 @@ export function readConfiguration( } export function performCompilation( - rootNames: string[], options: api.CompilerOptions, host?: api.CompilerHost, - oldProgram?: api.Program): { + {rootNames, options, host, oldProgram, emitCallback, customTransformers}: { + rootNames: string[], + options: api.CompilerOptions, + host?: api.CompilerHost, + oldProgram?: api.Program, + emitCallback?: api.TsEmitCallback, + customTransformers?: api.CustomTransformers + }): { program?: api.Program, - emitResult?: api.EmitResult, + emitResult?: ts.EmitResult, diagnostics: Diagnostics, } { const [major, minor] = ts.version.split('.'); @@ -128,7 +134,7 @@ export function performCompilation( } let program: api.Program|undefined; - let emitResult: api.EmitResult|undefined; + let emitResult: ts.EmitResult|undefined; try { if (!host) { host = ng.createCompilerHost({options}); @@ -155,7 +161,9 @@ export function performCompilation( shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics()); if (shouldEmit) { - const emitResult = program !.emit({ + emitResult = program !.emit({ + emitCallback, + customTransformers, emitFlags: api.EmitFlags.Default | ((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) }); diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 44fcd319f0..cc8860968c 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -167,17 +167,24 @@ export enum EmitFlags { All = DTS | JS | Metadata | I18nBundle | Summary } -// TODO(chuckj): Support CustomTransformers once we require TypeScript 2.3+ -// export interface CustomTransformers { -// beforeTs?: ts.TransformerFactory[]; -// afterTs?: ts.TransformerFactory[]; -// } - -export interface EmitResult extends ts.EmitResult { - modulesManifest: {modules: string[]; fileNames: string[];}; - externs: {[fileName: string]: string;}; +export interface CustomTransformers { + beforeTs?: ts.TransformerFactory[]; + afterTs?: ts.TransformerFactory[]; } +export interface TsEmitArguments { + program: ts.Program; + host: CompilerHost; + options: CompilerOptions; + targetSourceFile?: ts.SourceFile; + writeFile?: ts.WriteFileCallback; + cancellationToken?: ts.CancellationToken; + emitOnlyDtsFiles?: boolean; + customTransformers?: ts.CustomTransformers; +} + +export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } + export interface Program { /** * Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources. @@ -254,10 +261,10 @@ export interface Program { * * Angular structural information is required to emit files. */ - emit({// transformers, - emitFlags, cancellationToken}: { - emitFlags: EmitFlags, - // transformers?: CustomTransformers, // See TODO above + emit({emitFlags, cancellationToken, customTransformers, emitCallback}: { + emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken, - }): EmitResult; + customTransformers?: CustomTransformers, + emitCallback?: TsEmitCallback + }): ts.EmitResult; } diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 3bab5f51c3..ee81730f98 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -10,13 +10,12 @@ import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, NgAnaly import {createBundleIndexHost} from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; -import * as tsickle from 'tsickle'; import * as ts from 'typescript'; import {BaseAotCompilerHost} from '../compiler_host'; import {TypeChecker} from '../diagnostics/check_types'; -import {CompilerHost, CompilerOptions, Diagnostic, EmitFlags, EmitResult, Program} from './api'; +import {CompilerHost, CompilerOptions, CustomTransformers, Diagnostic, EmitFlags, Program, TsEmitArguments, TsEmitCallback} from './api'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {getAngularEmitterTransformFactory} from './node_emitter_transform'; @@ -30,6 +29,13 @@ const emptyModules: NgAnalyzedModules = { files: [] }; +const defaultEmitCallback: TsEmitCallback = + ({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, + customTransformers}) => + program.emit( + targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + + class AngularCompilerProgram implements Program { private tsProgram: ts.Program; private aotCompilerHost: AotCompilerHost; @@ -128,35 +134,35 @@ class AngularCompilerProgram implements Program { getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; } - emit({emitFlags = EmitFlags.Default, cancellationToken}: - {emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): EmitResult { + emit({emitFlags = EmitFlags.Default, cancellationToken, customTransformers, + emitCallback = defaultEmitCallback}: { + emitFlags?: EmitFlags, + cancellationToken?: ts.CancellationToken, + customTransformers?: CustomTransformers, + emitCallback?: TsEmitCallback + }): ts.EmitResult { const emitMap = new Map(); - const tsickleCompilerHostOptions: tsickle.TransformerOptions = { - googmodule: false, - untyped: true, - convertIndexImportShorthand: true, - transformDecorators: this.options.annotationsAs !== 'decorators', - transformTypesToClosure: this.options.annotateForClosureCompiler, - }; - - const tsickleHost: tsickle.TransformerHost = { - shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName), - pathToModuleName: (context, importPath) => '', - shouldIgnoreWarningsForPath: (filePath) => false, - fileNameToModuleId: (fileName) => fileName, - }; - const expectedOut = this.options.expectedOut ? this.options.expectedOut.map(f => path.resolve(process.cwd(), f)) : undefined; - const result = tsickle.emitWithTsickle( - this.programWithStubs, tsickleHost, tsickleCompilerHostOptions, this.host, this.options, - /* targetSourceFile */ undefined, - createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut), - cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, - this.calculateTransforms()); + // Ensure that expected output files exist. + for (const out of expectedOut || []) { + this.host.writeFile(out, '', false); + } + + const emitResult = emitCallback({ + program: this.programWithStubs, + host: this.host, + options: this.options, + targetSourceFile: undefined, + writeFile: + createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut), + cancellationToken, + emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, + customTransformers: this.calculateTransforms(customTransformers) + }); this.generatedFiles.forEach(file => { // In order not to replicate the TS calculation of the out folder for files @@ -174,12 +180,7 @@ class AngularCompilerProgram implements Program { } }); - // Ensure that expected output files exist. - for (const out of expectedOut || []) { - fs.appendFileSync(out, '', 'utf8'); - } - - return result; + return emitResult; } // Private members @@ -228,7 +229,7 @@ class AngularCompilerProgram implements Program { return this.generatedFiles && this._generatedFileDiagnostics !; } - private calculateTransforms(): tsickle.EmitTransformers { + private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers { const beforeTs: ts.TransformerFactory[] = []; if (!this.options.disableExpressionLowering) { beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache)); @@ -236,7 +237,11 @@ class AngularCompilerProgram implements Program { if (!this.options.skipTemplateCodegen) { beforeTs.push(getAngularEmitterTransformFactory(this.generatedFiles)); } - return {beforeTs}; + if (customTransformers && customTransformers.beforeTs) { + beforeTs.push(...customTransformers.beforeTs); + } + const afterTs = customTransformers ? customTransformers.afterTs : undefined; + return {before: beforeTs, after: afterTs}; } private catchAnalysisError(e: any): NgAnalyzedModules { @@ -370,7 +375,8 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions { } function writeMetadata( - emitFilePath: string, sourceFile: ts.SourceFile, metadataCache: LowerMetadataCache) { + host: ts.CompilerHost, emitFilePath: string, sourceFile: ts.SourceFile, + metadataCache: LowerMetadataCache) { if (/\.js$/.test(emitFilePath)) { const path = emitFilePath.replace(/\.js$/, '.metadata.json'); @@ -386,7 +392,7 @@ function writeMetadata( const metadata = metadataCache.getMetadata(collectableFile); if (metadata) { const metadataText = JSON.stringify([metadata]); - fs.writeFileSync(path, metadataText, {encoding: 'utf-8'}); + host.writeFile(path, metadataText, false); } } } @@ -412,7 +418,7 @@ function createWriteFileCallback( host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) { - writeMetadata(fileName, srcFile, metadataCache); + writeMetadata(host, fileName, srcFile, metadataCache); } } }; diff --git a/tools/ngc-wrapped/index.ts b/tools/ngc-wrapped/index.ts index 3a6c0fb37c..9fc36702a9 100644 --- a/tools/ngc-wrapped/index.ts +++ b/tools/ngc-wrapped/index.ts @@ -23,7 +23,7 @@ function main(args: string[]) { const {basePath} = calcProjectFileAndBasePath(project); const ngOptions = createNgCompilerOptions(basePath, config, tsOptions); - const {diagnostics} = performCompilation(files, ngOptions); + const {diagnostics} = performCompilation({rootNames: files, options: ngOptions}); if (diagnostics.length) { console.error(formatDiagnostics(ngOptions, diagnostics)); }