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.
This commit is contained in:
Tobias Bosch 2017-08-16 15:35:19 -07:00 committed by Miško Hevery
parent 56a5b02d04
commit ffb1553282
5 changed files with 116 additions and 58 deletions

View File

@ -14,6 +14,7 @@ import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as tsickle from 'tsickle';
import * as api from './transformers/api'; import * as api from './transformers/api';
import * as ngc from './transformers/entry_points'; import * as ngc from './transformers/entry_points';
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile'; import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
@ -31,7 +32,8 @@ export function main(
if (options.disableTransformerPipeline) { if (options.disableTransformerPipeline) {
return disabledTransformerPipelineNgcMain(parsedArgs, consoleError); 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)); return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError));
} }
@ -42,10 +44,45 @@ export function mainSync(
if (configErrors.length) { if (configErrors.length) {
return reportErrorsAndExit(options, configErrors, consoleError); 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); 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 { function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
const project = args.p || args.project || '.'; const project = args.p || args.project || '.';
const allDiagnostics: Diagnostics = []; const allDiagnostics: Diagnostics = [];

View File

@ -105,10 +105,16 @@ export function readConfiguration(
} }
export function performCompilation( export function performCompilation(
rootNames: string[], options: api.CompilerOptions, host?: api.CompilerHost, {rootNames, options, host, oldProgram, emitCallback, customTransformers}: {
oldProgram?: api.Program): { rootNames: string[],
options: api.CompilerOptions,
host?: api.CompilerHost,
oldProgram?: api.Program,
emitCallback?: api.TsEmitCallback,
customTransformers?: api.CustomTransformers
}): {
program?: api.Program, program?: api.Program,
emitResult?: api.EmitResult, emitResult?: ts.EmitResult,
diagnostics: Diagnostics, diagnostics: Diagnostics,
} { } {
const [major, minor] = ts.version.split('.'); const [major, minor] = ts.version.split('.');
@ -128,7 +134,7 @@ export function performCompilation(
} }
let program: api.Program|undefined; let program: api.Program|undefined;
let emitResult: api.EmitResult|undefined; let emitResult: ts.EmitResult|undefined;
try { try {
if (!host) { if (!host) {
host = ng.createCompilerHost({options}); host = ng.createCompilerHost({options});
@ -155,7 +161,9 @@ export function performCompilation(
shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics()); shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics());
if (shouldEmit) { if (shouldEmit) {
const emitResult = program !.emit({ emitResult = program !.emit({
emitCallback,
customTransformers,
emitFlags: api.EmitFlags.Default | emitFlags: api.EmitFlags.Default |
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) ((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
}); });

View File

@ -167,17 +167,24 @@ export enum EmitFlags {
All = DTS | JS | Metadata | I18nBundle | Summary All = DTS | JS | Metadata | I18nBundle | Summary
} }
// TODO(chuckj): Support CustomTransformers once we require TypeScript 2.3+ export interface CustomTransformers {
// export interface CustomTransformers { beforeTs?: ts.TransformerFactory<ts.SourceFile>[];
// beforeTs?: ts.TransformerFactory<ts.SourceFile>[]; afterTs?: ts.TransformerFactory<ts.SourceFile>[];
// afterTs?: ts.TransformerFactory<ts.SourceFile>[];
// }
export interface EmitResult extends ts.EmitResult {
modulesManifest: {modules: string[]; fileNames: string[];};
externs: {[fileName: string]: string;};
} }
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 { export interface Program {
/** /**
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources. * 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. * Angular structural information is required to emit files.
*/ */
emit({// transformers, emit({emitFlags, cancellationToken, customTransformers, emitCallback}: {
emitFlags, cancellationToken}: { emitFlags?: EmitFlags,
emitFlags: EmitFlags,
// transformers?: CustomTransformers, // See TODO above
cancellationToken?: ts.CancellationToken, cancellationToken?: ts.CancellationToken,
}): EmitResult; customTransformers?: CustomTransformers,
emitCallback?: TsEmitCallback
}): ts.EmitResult;
} }

View File

@ -10,13 +10,12 @@ import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, NgAnaly
import {createBundleIndexHost} from '@angular/tsc-wrapped'; import {createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as tsickle from 'tsickle';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {BaseAotCompilerHost} from '../compiler_host'; import {BaseAotCompilerHost} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types'; 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 {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {getAngularEmitterTransformFactory} from './node_emitter_transform';
@ -30,6 +29,13 @@ const emptyModules: NgAnalyzedModules = {
files: [] files: []
}; };
const defaultEmitCallback: TsEmitCallback =
({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
customTransformers}) =>
program.emit(
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
class AngularCompilerProgram implements Program { class AngularCompilerProgram implements Program {
private tsProgram: ts.Program; private tsProgram: ts.Program;
private aotCompilerHost: AotCompilerHost; private aotCompilerHost: AotCompilerHost;
@ -128,35 +134,35 @@ class AngularCompilerProgram implements Program {
getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; } getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; }
emit({emitFlags = EmitFlags.Default, cancellationToken}: emit({emitFlags = EmitFlags.Default, cancellationToken, customTransformers,
{emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): EmitResult { emitCallback = defaultEmitCallback}: {
emitFlags?: EmitFlags,
cancellationToken?: ts.CancellationToken,
customTransformers?: CustomTransformers,
emitCallback?: TsEmitCallback
}): ts.EmitResult {
const emitMap = new Map<string, string>(); const emitMap = new Map<string, string>();
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 ? const expectedOut = this.options.expectedOut ?
this.options.expectedOut.map(f => path.resolve(process.cwd(), f)) : this.options.expectedOut.map(f => path.resolve(process.cwd(), f)) :
undefined; undefined;
const result = tsickle.emitWithTsickle( // Ensure that expected output files exist.
this.programWithStubs, tsickleHost, tsickleCompilerHostOptions, this.host, this.options, for (const out of expectedOut || []) {
/* targetSourceFile */ undefined, 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), createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut),
cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, cancellationToken,
this.calculateTransforms()); emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(customTransformers)
});
this.generatedFiles.forEach(file => { this.generatedFiles.forEach(file => {
// In order not to replicate the TS calculation of the out folder for files // 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. return emitResult;
for (const out of expectedOut || []) {
fs.appendFileSync(out, '', 'utf8');
}
return result;
} }
// Private members // Private members
@ -228,7 +229,7 @@ class AngularCompilerProgram implements Program {
return this.generatedFiles && this._generatedFileDiagnostics !; return this.generatedFiles && this._generatedFileDiagnostics !;
} }
private calculateTransforms(): tsickle.EmitTransformers { private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = []; const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
if (!this.options.disableExpressionLowering) { if (!this.options.disableExpressionLowering) {
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache)); beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
@ -236,7 +237,11 @@ class AngularCompilerProgram implements Program {
if (!this.options.skipTemplateCodegen) { if (!this.options.skipTemplateCodegen) {
beforeTs.push(getAngularEmitterTransformFactory(this.generatedFiles)); 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 { private catchAnalysisError(e: any): NgAnalyzedModules {
@ -370,7 +375,8 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
} }
function writeMetadata( function writeMetadata(
emitFilePath: string, sourceFile: ts.SourceFile, metadataCache: LowerMetadataCache) { host: ts.CompilerHost, emitFilePath: string, sourceFile: ts.SourceFile,
metadataCache: LowerMetadataCache) {
if (/\.js$/.test(emitFilePath)) { if (/\.js$/.test(emitFilePath)) {
const path = emitFilePath.replace(/\.js$/, '.metadata.json'); const path = emitFilePath.replace(/\.js$/, '.metadata.json');
@ -386,7 +392,7 @@ function writeMetadata(
const metadata = metadataCache.getMetadata(collectableFile); const metadata = metadataCache.getMetadata(collectableFile);
if (metadata) { if (metadata) {
const metadataText = JSON.stringify([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); host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) { if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) {
writeMetadata(fileName, srcFile, metadataCache); writeMetadata(host, fileName, srcFile, metadataCache);
} }
} }
}; };

View File

@ -23,7 +23,7 @@ function main(args: string[]) {
const {basePath} = calcProjectFileAndBasePath(project); const {basePath} = calcProjectFileAndBasePath(project);
const ngOptions = createNgCompilerOptions(basePath, config, tsOptions); const ngOptions = createNgCompilerOptions(basePath, config, tsOptions);
const {diagnostics} = performCompilation(files, ngOptions); const {diagnostics} = performCompilation({rootNames: files, options: ngOptions});
if (diagnostics.length) { if (diagnostics.length) {
console.error(formatDiagnostics(ngOptions, diagnostics)); console.error(formatDiagnostics(ngOptions, diagnostics));
} }