refactor(compiler-cli): use the transformer based compiler by default

The source map does not currently work with the transformer pipeline.
It will be re-enabled after TypeScript 2.4 is made the min version.

To revert to the former compiler, use the `disableTransformerPipeline` in
tsconfig.json:

```
{
  "angularCompilerOptions": {
    "disableTransformerPipeline": true
  }
}
```
This commit is contained in:
Victor Berchet
2017-08-02 11:20:07 -07:00
committed by Hans
parent 06faac8b5c
commit 679608db65
32 changed files with 569 additions and 247 deletions

View File

@ -24,6 +24,7 @@ export interface Diagnostic {
export interface CompilerOptions extends ts.CompilerOptions {
// Absolute path to a directory where generated file structure is written.
// If unspecified, generated files will be written alongside sources.
// @deprecated - no effect
genDir?: string;
// Path to the directory containing the tsconfig.json file.
@ -95,6 +96,27 @@ export interface CompilerOptions extends ts.CompilerOptions {
// Whether to enable lowering expressions lambdas and expressions in a reference value
// position.
disableExpressionLowering?: boolean;
// The list of expected files, when provided:
// - extra files are filtered out,
// - missing files are created empty.
expectedOut?: string[];
// Locale of the application
i18nOutLocale?: string;
// Export format (xlf, xlf2 or xmb)
i18nOutFormat?: string;
// Path to the extracted message file
i18nOutFile?: string;
// Import format if different from `i18nFormat`
i18nInFormat?: string;
// Locale of the imported translations
i18nInLocale?: string;
// Path to the translation file
i18nInFile?: string;
// How to handle missing messages
i18nInMissingTranslations?: 'error'|'warning'|'ignore';
}
export interface ModuleFilenameResolver {
@ -146,6 +168,11 @@ export enum EmitFlags {
// afterTs?: ts.TransformerFactory<ts.SourceFile>[];
// }
export interface EmitResult extends ts.EmitResult {
modulesManifest: {modules: string[]; fileNames: string[];};
externs: {[fileName: string]: string;};
}
export interface Program {
/**
* Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources.
@ -155,7 +182,7 @@ export interface Program {
getTsProgram(): ts.Program;
/**
* Retreive options diagnostics for the TypeScript options used to create the program. This is
* Retrieve options diagnostics for the TypeScript options used to create the program. This is
* faster than calling `getTsProgram().getOptionsDiagnostics()` since it does not need to
* collect Angular structural information to produce the errors.
*/
@ -167,7 +194,7 @@ export interface Program {
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[];
/**
* Retrive the syntax diagnostics from TypeScript. This is faster than calling
* Retrieve the syntax diagnostics from TypeScript. This is faster than calling
* `getTsProgram().getSyntacticDiagnostics()` since it does not need to collect Angular structural
* information to produce the errors.
*/
@ -188,7 +215,7 @@ export interface Program {
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[];
/**
* Retreive the semantic diagnostics from TypeScript. This is equivilent to calling
* Retrieve the semantic diagnostics from TypeScript. This is equivilent to calling
* `getTsProgram().getSemanticDiagnostics()` directly and is included for completeness.
*/
getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
@ -227,5 +254,5 @@ export interface Program {
emitFlags: EmitFlags,
// transformers?: CustomTransformers, // See TODO above
cancellationToken?: ts.CancellationToken,
}): void;
}): EmitResult;
}

View File

@ -55,19 +55,26 @@ function transformSourceFile(
context: ts.TransformationContext): ts.SourceFile {
const inserts: DeclarationInsert[] = [];
// Calculate the range of intersting locations. The transform will only visit nodes in this
// Calculate the range of interesting locations. The transform will only visit nodes in this
// range to improve the performance on large files.
const locations = Array.from(requests.keys());
const min = Math.min(...locations);
const max = Math.max(...locations);
// Visit nodes matching the request and synthetic nodes added by tsickle
function shouldVisit(pos: number, end: number): boolean {
return (pos <= max && end >= min) || pos == -1;
}
function visitSourceFile(sourceFile: ts.SourceFile): ts.SourceFile {
function topLevelStatement(node: ts.Node): ts.Node {
const declarations: Declaration[] = [];
function visitNode(node: ts.Node): ts.Node {
const nodeRequest = requests.get(node.pos);
if (nodeRequest && nodeRequest.kind == node.kind && nodeRequest.end == node.end) {
// Get the original node before tsickle
const {pos, end, kind} = ts.getOriginalNode(node);
const nodeRequest = requests.get(pos);
if (nodeRequest && nodeRequest.kind == kind && nodeRequest.end == end) {
// This node is requested to be rewritten as a reference to the exported name.
// Record that the node needs to be moved to an exported variable with the given name
const name = nodeRequest.name;
@ -75,14 +82,16 @@ function transformSourceFile(
return ts.createIdentifier(name);
}
let result = node;
if (node.pos <= max && node.end >= min && !isLexicalScope(node)) {
if (shouldVisit(pos, end) && !isLexicalScope(node)) {
result = ts.visitEachChild(node, visitNode, context);
}
return result;
}
const result =
(node.pos <= max && node.end >= min) ? ts.visitEachChild(node, visitNode, context) : node;
// Get the original node before tsickle
const {pos, end} = ts.getOriginalNode(node);
const result = shouldVisit(pos, end) ? ts.visitEachChild(node, visitNode, context) : node;
if (declarations.length) {
inserts.push({priorTo: result, declarations});
@ -91,6 +100,7 @@ function transformSourceFile(
}
const traversedSource = ts.visitEachChild(sourceFile, topLevelStatement, context);
if (inserts.length) {
// Insert the declarations before the rewritten statement that references them.
const insertMap = toMap(inserts, i => i.priorTo);

View File

@ -19,8 +19,10 @@ export class TypeScriptNodeEmitter {
updateSourceFile(sourceFile: ts.SourceFile, stmts: Statement[], preamble?: string):
[ts.SourceFile, Map<ts.Node, Node>] {
const converter = new _NodeEmitterVisitor();
const statements =
stmts.map(stmt => stmt.visitStatement(converter, null)).filter(stmt => stmt != null);
// [].concat flattens the result so that each `visit...` method can also return an array of
// stmts.
const statements: any[] = [].concat(
...stmts.map(stmt => stmt.visitStatement(converter, null)).filter(stmt => stmt != null));
const newSourceFile = ts.updateSourceFileNode(
sourceFile, [...converter.getReexports(), ...converter.getImports(), ...statements]);
if (preamble) {
@ -118,20 +120,30 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
}
return this.record(
stmt, ts.createVariableStatement(
this.getModifiers(stmt),
ts.createVariableDeclarationList([ts.createVariableDeclaration(
ts.createIdentifier(stmt.name),
/* type */ undefined,
(stmt.value && stmt.value.visitExpression(this, null)) || undefined)])));
const varDeclList = ts.createVariableDeclarationList([ts.createVariableDeclaration(
ts.createIdentifier(stmt.name),
/* type */ undefined,
(stmt.value && stmt.value.visitExpression(this, null)) || undefined)]);
if (stmt.hasModifier(StmtModifier.Exported)) {
// Note: We need to add an explicit variable and export declaration so that
// the variable can be referred in the same file as well.
const tsVarStmt =
this.record(stmt, ts.createVariableStatement(/* modifiers */[], varDeclList));
const exportStmt = this.record(
stmt, ts.createExportDeclaration(
/*decorators*/ undefined, /*modifiers*/ undefined,
ts.createNamedExports([ts.createExportSpecifier(stmt.name, stmt.name)])));
return [tsVarStmt, exportStmt];
}
return this.record(stmt, ts.createVariableStatement(this.getModifiers(stmt), varDeclList));
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) {
return this.record(
stmt, ts.createFunctionDeclaration(
/* decorators */ undefined, this.getModifiers(stmt),
/* astrictToken */ undefined, stmt.name, /* typeParameters */ undefined,
/* asteriskToken */ undefined, stmt.name, /* typeParameters */ undefined,
stmt.params.map(
p => ts.createParameter(
/* decorators */ undefined, /* modifiers */ undefined,

View File

@ -6,20 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompiler, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
import {MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import {writeFileSync} from 'fs';
import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
import {MissingTranslationStrategy} from '@angular/core';
import * as fs from 'fs';
import * as path from 'path';
import * as tsickle from 'tsickle';
import * as ts from 'typescript';
import {CompilerHost as AotCompilerHost, CompilerHostContext} from '../compiler_host';
import {CompilerHost as AotCompilerHost} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types';
import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, Program} from './api';
import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, EmitResult, Program} from './api';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
const GENERATED_FILES = /\.ngfactory\.js$|\.ngstyle\.js$|\.ngsummary\.js$/;
const SUMMARY_JSON_FILES = /\.ngsummary.json$/;
const emptyModules: NgAnalyzedModules = {
@ -52,17 +54,20 @@ class AngularCompilerProgram implements Program {
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
private oldProgram?: Program) {
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram);
this.srcNames = this.tsProgram.getSourceFiles().map(sf => sf.fileName);
this.srcNames =
this.tsProgram.getSourceFiles()
.map(sf => sf.fileName)
.filter(f => !f.match(/\.ngfactory\.[\w.]+$|\.ngstyle\.[\w.]+$|\.ngsummary\.[\w.]+$/));
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
this.aotCompilerHost = new AotCompilerHost(
this.tsProgram, options, host, /* collectorOptions */ undefined, this.metadataCache);
if (host.readResource) {
this.aotCompilerHost.loadResource = host.readResource.bind(host);
}
const {compiler} = createAotCompiler(this.aotCompilerHost, options);
this.compiler = compiler;
const aotOptions = getAotCompilerOptions(options);
this.compiler = createAotCompiler(this.aotCompilerHost, aotOptions).compiler;
}
// Program implementation
@ -115,25 +120,56 @@ class AngularCompilerProgram implements Program {
getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; }
emit({emitFlags = EmitFlags.Default, cancellationToken}:
{emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): ts.EmitResult {
{emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): EmitResult {
const emitMap = new Map<string, string>();
const result = this.programWithStubs.emit(
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),
createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut),
cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
this.calculateTransforms());
this.generatedFiles.forEach(file => {
// In order not to replicate the TS calculation of the out folder for files
// derive the out location for .json files from the out location of the .ts files
if (file.source && file.source.length && SUMMARY_JSON_FILES.test(file.genFileUrl)) {
// If we have emitted the ngsummary.ts file, ensure the ngsummary.json file is emitted to
// the same location.
const emittedFile = emitMap.get(file.srcFileUrl);
const fileName = emittedFile ?
path.join(path.dirname(emittedFile), path.basename(file.genFileUrl)) :
file.genFileUrl;
this.host.writeFile(fileName, file.source, false, error => {});
if (emittedFile) {
const fileName = path.join(path.dirname(emittedFile), path.basename(file.genFileUrl));
this.host.writeFile(fileName, file.source, false, error => {});
}
}
});
// Ensure that expected output files exist.
for (const out of expectedOut || []) {
fs.appendFileSync(out, '', 'utf8');
}
return result;
}
@ -183,19 +219,15 @@ class AngularCompilerProgram implements Program {
return this.generatedFiles && this._generatedFileDiagnostics !;
}
private calculateTransforms(): ts.CustomTransformers {
const before: ts.TransformerFactory<ts.SourceFile>[] = [];
const after: ts.TransformerFactory<ts.SourceFile>[] = [];
private calculateTransforms(): tsickle.EmitTransformers {
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
if (!this.options.disableExpressionLowering) {
before.push(getExpressionLoweringTransformFactory(this.metadataCache));
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
}
if (!this.options.skipTemplateCodegen) {
after.push(getAngularEmitterTransformFactory(this.generatedFiles));
beforeTs.push(getAngularEmitterTransformFactory(this.generatedFiles));
}
const result: ts.CustomTransformers = {};
if (before.length) result.before = before;
if (after.length) result.after = after;
return result;
return {beforeTs};
}
private catchAnalysisError(e: any): NgAnalyzedModules {
@ -228,8 +260,8 @@ class AngularCompilerProgram implements Program {
private generateStubs() {
return this.options.skipTemplateCodegen ? [] :
this.options.generateCodeForLibraries === false ?
this.compiler.emitAllStubs(this.analyzedModules) :
this.compiler.emitPartialStubs(this.analyzedModules);
this.compiler.emitPartialStubs(this.analyzedModules) :
this.compiler.emitAllStubs(this.analyzedModules);
}
private generateFiles() {
@ -270,6 +302,40 @@ export function createProgram(
return new AngularCompilerProgram(rootNames, options, host, oldProgram);
}
// Compute the AotCompiler options
function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
let missingTranslation = MissingTranslationStrategy.Warning;
switch (options.i18nInMissingTranslations) {
case 'ignore':
missingTranslation = MissingTranslationStrategy.Ignore;
break;
case 'error':
missingTranslation = MissingTranslationStrategy.Error;
break;
}
let translations: string = '';
if (options.i18nInFile) {
if (!options.locale) {
throw new Error(`The translation file (${options.i18nInFile}) locale must be provided.`);
}
translations = fs.readFileSync(options.i18nInFile, 'utf8');
} else {
// No translations are provided, ignore any errors
// We still go through i18n to remove i18n attributes
missingTranslation = MissingTranslationStrategy.Ignore;
}
return {
locale: options.i18nInLocale,
i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation,
enableLegacyTemplate: options.enableLegacyTemplate,
enableSummariesForJit: true,
};
}
function writeMetadata(
emitFilePath: string, sourceFile: ts.SourceFile, metadataCache: LowerMetadataCache) {
if (/\.js$/.test(emitFilePath)) {
@ -287,38 +353,36 @@ function writeMetadata(
const metadata = metadataCache.getMetadata(collectableFile);
if (metadata) {
const metadataText = JSON.stringify([metadata]);
writeFileSync(path, metadataText, {encoding: 'utf-8'});
fs.writeFileSync(path, metadataText, {encoding: 'utf-8'});
}
}
}
function createWriteFileCallback(
emitFlags: EmitFlags, host: ts.CompilerHost, metadataCache: LowerMetadataCache,
emitMap: Map<string, string>) {
const withMetadata =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
const generatedFile = GENERATED_FILES.test(fileName);
if (!generatedFile || data != '') {
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}
if (!generatedFile && sourceFiles && sourceFiles.length == 1) {
emitMap.set(sourceFiles[0].fileName, fileName);
writeMetadata(fileName, sourceFiles[0], metadataCache);
}
};
const withoutMetadata =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
const generatedFile = GENERATED_FILES.test(fileName);
if (!generatedFile || data != '') {
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}
if (!generatedFile && sourceFiles && sourceFiles.length == 1) {
emitMap.set(sourceFiles[0].fileName, fileName);
}
};
return (emitFlags & EmitFlags.Metadata) != 0 ? withMetadata : withoutMetadata;
emitMap: Map<string, string>, expectedOut?: string[]) {
return (fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
let srcFile: ts.SourceFile|undefined;
if (sourceFiles && sourceFiles.length == 1) {
srcFile = sourceFiles[0];
emitMap.set(srcFile.fileName, fileName);
}
const absFile = path.resolve(process.cwd(), fileName);
const generatedFile = GENERATED_FILES.test(fileName);
// Don't emit unexpected files nor empty generated files
if ((!expectedOut || expectedOut.indexOf(absFile) > -1) && (!generatedFile || data)) {
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) {
writeMetadata(fileName, srcFile, metadataCache);
}
}
};
}
function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {