From 3097083277954e5f9917aee69ea05d4d8ffd12d0 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Fri, 9 Jun 2017 14:50:57 -0700 Subject: [PATCH] feat(compiler-cli): new compiler api and command-line using TypeScript transformers --- packages/compiler-cli/index.ts | 5 + .../integrationtest/test/test_ngtools_api.ts | 14 +- .../third_party_src/tsconfig-build.json | 3 +- .../integrationtest/tsconfig-ngc-build.json | 38 ++ packages/compiler-cli/src/compiler_host.ts | 16 +- .../src/diagnostics/check_types.ts | 43 +- packages/compiler-cli/src/ngc.ts | 182 +++++ packages/compiler-cli/src/ngtools_api.ts | 2 +- packages/compiler-cli/src/transformers/api.ts | 227 +++++++ .../src/transformers/entry_points.ts | 24 + .../transformers/module_filename_resolver.ts | 285 ++++++++ .../src/transformers/node_emitter.ts | 14 +- .../transformers/node_emitter_transform.ts | 29 + .../compiler-cli/src/transformers/program.ts | 391 +++++++++++ .../test/diagnostics/check_types_spec.ts | 3 +- .../expression_diagnostics_spec.ts | 1 - .../compiler-cli/test/diagnostics/mocks.ts | 2 +- packages/compiler-cli/test/ngc_spec.ts | 629 ++++++++++++++++++ .../test/transformers/node_emitter_spec.ts | 3 +- packages/compiler-cli/tsconfig-build.json | 1 + packages/compiler/src/aot/compiler.ts | 77 ++- .../compiler/src/aot/summary_serializer.ts | 1 - packages/compiler/src/compile_metadata.ts | 4 - packages/compiler/src/compiler.ts | 2 +- packages/compiler/src/metadata_resolver.ts | 3 +- packages/compiler/src/parse_util.ts | 10 +- .../src/template_parser/template_parser.ts | 2 +- packages/compiler/src/util.ts | 20 +- packages/compiler/test/aot/emit_stubs_spec.ts | 9 +- tools/@angular/tsc-wrapped/index.ts | 2 +- tools/@angular/tsc-wrapped/src/main.ts | 63 +- 31 files changed, 1990 insertions(+), 115 deletions(-) create mode 100644 packages/compiler-cli/integrationtest/tsconfig-ngc-build.json create mode 100644 packages/compiler-cli/src/ngc.ts create mode 100644 packages/compiler-cli/src/transformers/api.ts create mode 100644 packages/compiler-cli/src/transformers/entry_points.ts create mode 100644 packages/compiler-cli/src/transformers/module_filename_resolver.ts create mode 100644 packages/compiler-cli/src/transformers/node_emitter_transform.ts create mode 100644 packages/compiler-cli/src/transformers/program.ts create mode 100644 packages/compiler-cli/test/ngc_spec.ts diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts index 9cb701fdfb..b5a4f2ae37 100644 --- a/packages/compiler-cli/index.ts +++ b/packages/compiler-cli/index.ts @@ -17,5 +17,10 @@ export {AstType, ExpressionDiagnosticsContext} from './src/diagnostics/expressio export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols'; export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './src/diagnostics/symbols'; +export * from './src/transformers/api'; +export * from './src/transformers/entry_points'; + +export {main as ngc} from './src/ngc'; + // TODO(hansl): moving to Angular 4 need to update this API. export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'; diff --git a/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts b/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts index 0949340663..f1ff609bc2 100644 --- a/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts +++ b/packages/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -15,7 +15,7 @@ import * as path from 'path'; import * as ts from 'typescript'; import * as assert from 'assert'; import {tsc} from '@angular/tsc-wrapped/src/tsc'; -import {NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli'; +import {__NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli'; const glob = require('glob'); @@ -51,7 +51,6 @@ function codeGenTest(forceError = false) { const wroteFiles: string[] = []; const config = tsc.readConfiguration(project, basePath); - const hostContext = new NodeCompilerHostContext(); const delegateHost = ts.createCompilerHost(config.parsed.options, true); const host: ts.CompilerHost = Object.assign( {}, delegateHost, @@ -79,7 +78,10 @@ function codeGenTest(forceError = false) { readResource: (fileName: string) => { readResources.push(fileName); - return hostContext.readResource(fileName); + if (!host.fileExists(fileName)) { + throw new Error(`Compilation failed. Resource file not found: ${fileName}`); + } + return Promise.resolve(host.readFile(fileName)); } }) .then(() => { @@ -128,7 +130,6 @@ function i18nTest() { const wroteFiles: string[] = []; const config = tsc.readConfiguration(project, basePath); - const hostContext = new NodeCompilerHostContext(); const delegateHost = ts.createCompilerHost(config.parsed.options, true); const host: ts.CompilerHost = Object.assign( {}, delegateHost, @@ -148,7 +149,10 @@ function i18nTest() { outFile: undefined, readResource: (fileName: string) => { readResources.push(fileName); - return hostContext.readResource(fileName); + if (!host.fileExists(fileName)) { + throw new Error(`Compilation failed. Resource file not found: ${fileName}`); + } + return Promise.resolve(host.readFile(fileName)); }, }) .then(() => { diff --git a/packages/compiler-cli/integrationtest/third_party_src/tsconfig-build.json b/packages/compiler-cli/integrationtest/third_party_src/tsconfig-build.json index 3544d7c598..d9f9f536c3 100644 --- a/packages/compiler-cli/integrationtest/third_party_src/tsconfig-build.json +++ b/packages/compiler-cli/integrationtest/third_party_src/tsconfig-build.json @@ -2,8 +2,7 @@ "angularCompilerOptions": { // For TypeScript 1.8, we have to lay out generated files // in the same source directory with your code. - "genDir": ".", - "debug": true + "skipTemplateCodegen": true }, "compilerOptions": { diff --git a/packages/compiler-cli/integrationtest/tsconfig-ngc-build.json b/packages/compiler-cli/integrationtest/tsconfig-ngc-build.json new file mode 100644 index 0000000000..b3f0e8d88a --- /dev/null +++ b/packages/compiler-cli/integrationtest/tsconfig-ngc-build.json @@ -0,0 +1,38 @@ +{ + "angularCompilerOptions": { + // For TypeScript 1.8, we have to lay out generated files + // in the same source directory with your code. + "genDir": ".", + "debug": true, + "enableSummariesForJit": true, + "alwaysCompileGeneratedCode": true + }, + + "compilerOptions": { + "target": "es5", + "experimentalDecorators": true, + "noImplicitAny": true, + "strictNullChecks": true, + "skipLibCheck": true, + "moduleResolution": "node", + "rootDir": "", + "declaration": true, + "lib": ["es6", "dom"], + "baseUrl": ".", + // Prevent scanning up the directory tree for types + "typeRoots": ["node_modules/@types"], + "noUnusedLocals": true, + "sourceMap": true + }, + + "files": [ + "src/module", + "src/bootstrap", + "test/all_spec", + "test/test_ngtools_api", + "benchmarks/src/tree/ng2/index_aot.ts", + "benchmarks/src/tree/ng2_switch/index_aot.ts", + "benchmarks/src/largetable/ng2/index_aot.ts", + "benchmarks/src/largetable/ng2_switch/index_aot.ts" + ] +} diff --git a/packages/compiler-cli/src/compiler_host.ts b/packages/compiler-cli/src/compiler_host.ts index 70ca33f1a2..03424e9ebc 100644 --- a/packages/compiler-cli/src/compiler_host.ts +++ b/packages/compiler-cli/src/compiler_host.ts @@ -21,7 +21,7 @@ const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsum const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/; export interface CompilerHostContext extends ts.ModuleResolutionHost { - readResource(fileName: string): Promise; + readResource?(fileName: string): Promise|string; assumeFileExists(fileName: string): void; } @@ -187,10 +187,11 @@ export class CompilerHost implements AotCompilerHost { getMetadataFor(filePath: string): ModuleMetadata[]|undefined { if (!this.context.fileExists(filePath)) { // If the file doesn't exists then we cannot return metadata for the file. - // This will occur if the user refernced a declared module for which no file + // This will occur if the user referenced a declared module for which no file // exists for the module (i.e. jQuery or angularjs). return; } + if (DTS.test(filePath)) { const metadataPath = filePath.replace(DTS, '.metadata.json'); if (this.context.fileExists(metadataPath)) { @@ -202,11 +203,11 @@ export class CompilerHost implements AotCompilerHost { return [this.upgradeVersion1Metadata( {'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)]; } - } else { - const sf = this.getSourceFile(filePath); - const metadata = this.metadataCollector.getMetadata(sf); - return metadata ? [metadata] : []; } + + const sf = this.getSourceFile(filePath); + const metadata = this.metadataCollector.getMetadata(sf); + return metadata ? [metadata] : []; } readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] { @@ -259,7 +260,8 @@ export class CompilerHost implements AotCompilerHost { } loadResource(filePath: string): Promise|string { - return this.context.readResource(filePath); + if (this.context.readResource) return this.context.readResource(filePath); + return this.context.readFile(filePath); } loadSummary(filePath: string): string|null { diff --git a/packages/compiler-cli/src/diagnostics/check_types.ts b/packages/compiler-cli/src/diagnostics/check_types.ts index b7eed5599b..3a2030043d 100644 --- a/packages/compiler-cli/src/diagnostics/check_types.ts +++ b/packages/compiler-cli/src/diagnostics/check_types.ts @@ -9,6 +9,8 @@ import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler'; import * as ts from 'typescript'; +import {Diagnostic, DiagnosticCategory} from '../transformers/api'; + interface FactoryInfo { source: ts.SourceFile; context: EmitterVisitorContext; @@ -16,17 +18,10 @@ interface FactoryInfo { type FactoryInfoMap = Map; -export enum DiagnosticKind { - Message, - Warning, - Error, -} - -export interface Diagnostic { - message: string; - kind: DiagnosticKind; - span: ParseSourceSpan; -} +const stubCancellationToken: ts.CancellationToken = { + isCancellationRequested(): boolean{return false;}, + throwIfCancellationRequested(): void{} +}; export class TypeChecker { private _aotCompiler: AotCompiler|undefined; @@ -35,6 +30,8 @@ export class TypeChecker { private _factoryNames: string[]|undefined; private _diagnosticProgram: ts.Program|undefined; private _diagnosticsByFile: Map|undefined; + private _currentCancellationToken: ts.CancellationToken = stubCancellationToken; + private _partial: boolean = false; constructor( private program: ts.Program, private tsOptions: ts.CompilerOptions, @@ -42,12 +39,19 @@ export class TypeChecker { private aotOptions: AotCompilerOptions, private _analyzedModules?: NgAnalyzedModules, private _generatedFiles?: GeneratedFile[]) {} - getDiagnostics(fileName?: string): Diagnostic[] { - return fileName ? - this.diagnosticsByFileName.get(fileName) || [] : - ([] as Diagnostic[]).concat(...Array.from(this.diagnosticsByFileName.values())); + getDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): Diagnostic[] { + this._currentCancellationToken = cancellationToken || stubCancellationToken; + try { + return fileName ? + this.diagnosticsByFileName.get(fileName) || [] : + ([] as Diagnostic[]).concat(...Array.from(this.diagnosticsByFileName.values())); + } finally { + this._currentCancellationToken = stubCancellationToken; + } } + get partialResults(): boolean { return this._partial; } + private get analyzedModules(): NgAnalyzedModules { return this._analyzedModules || (this._analyzedModules = this.aotCompiler.analyzeModulesSync( this.program.getSourceFiles().map(sf => sf.fileName))); @@ -130,6 +134,7 @@ export class TypeChecker { }; const program = this.diagnosticProgram; for (const factoryName of this.factoryNames) { + if (this._currentCancellationToken.isCancellationRequested()) return result; const sourceFile = program.getSourceFile(factoryName); for (const diagnostic of this.diagnosticProgram.getSemanticDiagnostics(sourceFile)) { const span = this.sourceSpanOf(diagnostic.file, diagnostic.start, diagnostic.length); @@ -138,7 +143,7 @@ export class TypeChecker { const diagnosticsList = diagnosticsFor(fileName); diagnosticsList.push({ message: diagnosticMessageToString(diagnostic.messageText), - kind: diagnosticKindConverter(diagnostic.category), span + category: diagnosticCategoryConverter(diagnostic.category), span }); } } @@ -158,12 +163,12 @@ export class TypeChecker { } function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string): string { - return typeof message === 'string' ? message : diagnosticMessageToString(message.messageText); + return ts.flattenDiagnosticMessageText(message, '\n'); } -function diagnosticKindConverter(kind: ts.DiagnosticCategory) { +function diagnosticCategoryConverter(kind: ts.DiagnosticCategory) { // The diagnostics kind matches ts.DiagnosticCategory. Review this code if this changes. - return kind as any as DiagnosticKind; + return kind as any as DiagnosticCategory; } function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo { diff --git a/packages/compiler-cli/src/ngc.ts b/packages/compiler-cli/src/ngc.ts new file mode 100644 index 0000000000..bc32f657d9 --- /dev/null +++ b/packages/compiler-cli/src/ngc.ts @@ -0,0 +1,182 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// Must be imported first, because Angular decorators throw on load. +import 'reflect-metadata'; + +import {isSyntaxError, syntaxError} from '@angular/compiler'; +import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as ts from 'typescript'; +import * as api from './transformers/api'; +import * as ng from './transformers/entry_points'; + +const TS_EXT = /\.ts$/; + +type Diagnostics = ts.Diagnostic[] | api.Diagnostic[]; + +function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] { + return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText); +} + +function formatDiagnostics(cwd: string, diags: Diagnostics): string { + if (diags && diags.length) { + if (isTsDiagnostics(diags)) { + return ts.formatDiagnostics(diags, { + getCurrentDirectory(): string{return cwd;}, + getCanonicalFileName(fileName: string): string{return fileName;}, + getNewLine(): string{return '\n';} + }); + } else { + return diags + .map(d => { + let res = api.DiagnosticCategory[d.category]; + if (d.span) { + res += + ` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`; + } + if (d.span && d.span.details) { + res += `: ${d.span.details}, ${d.message}\n`; + } else { + res += `: ${d.message}\n`; + } + return res; + }) + .join(); + } + } else + return ''; +} + +function check(cwd: string, ...args: Diagnostics[]) { + if (args.some(diags => !!(diags && diags[0]))) { + throw syntaxError(args.map(diags => { + if (diags && diags[0]) { + return formatDiagnostics(cwd, diags); + } + }) + .filter(message => !!message) + .join('')); + } +} + +function syntheticError(message: string): ts.Diagnostic { + return { + file: null as any as ts.SourceFile, + start: 0, + length: 0, + messageText: message, + category: ts.DiagnosticCategory.Error, + code: 0 + }; +} + +export function readConfiguration( + project: string, basePath: string, existingOptions?: ts.CompilerOptions) { + // Allow a directory containing tsconfig.json as the project value + // Note, TS@next returns an empty array, while earlier versions throw + const projectFile = + fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project; + let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); + + if (error) check(basePath, [error]); + const parseConfigHost = { + useCaseSensitiveFileNames: true, + fileExists: fs.existsSync, + readDirectory: ts.sys.readDirectory, + readFile: ts.sys.readFile + }; + const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions); + + check(basePath, parsed.errors); + + // Default codegen goes to the current directory + // Parsed options are already converted to absolute paths + const ngOptions = config.angularCompilerOptions || {}; + // Ignore the genDir option + ngOptions.genDir = basePath; + for (const key of Object.keys(parsed.options)) { + ngOptions[key] = parsed.options[key]; + } + + return {parsed, ngOptions}; +} + +export function main(args: string[], consoleError: (s: string) => void = console.error): number { + try { + const parsedArgs = require('minimist')(args); + const project = parsedArgs.p || parsedArgs.project || '.'; + + const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project; + + + // file names in tsconfig are resolved relative to this absolute path + const basePath = path.resolve(process.cwd(), projectDir); + + const {parsed, ngOptions} = readConfiguration(project, basePath); + ngOptions.basePath = basePath; + + let host = ts.createCompilerHost(parsed.options, true); + + const rootFileNames = parsed.fileNames.slice(0); + + const addGeneratedFileName = + (fileName: string) => { + if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) { + rootFileNames.push(fileName); + } + } + + if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) { + const {host: bundleHost, indexName, errors} = + createBundleIndexHost(ngOptions, rootFileNames, host); + if (errors) check(basePath, errors); + if (indexName) addGeneratedFileName(indexName); + host = bundleHost; + } + + const ngHost = ng.createHost({tsHost: host, options: ngOptions}); + + const ngProgram = + ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngOptions}); + + // Check parameter diagnostics + check(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics()); + + // Check syntactic diagnostics + check(basePath, ngProgram.getTsSyntacticDiagnostics()); + + // Check TypeScript semantic and Angular structure diagnostics + check(basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics()); + + // Check Angular semantic diagnostics + check(basePath, ngProgram.getNgSemanticDiagnostics()); + + ngProgram.emit({ + emitFlags: api.EmitFlags.Default | + ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) + }); + } catch (e) { + if (isSyntaxError(e)) { + consoleError(e.message); + return 1; + } else { + consoleError(e.stack); + consoleError('Compilation failed'); + return 2; + } + } + + return 0; +} + +// CLI entry point +if (require.main === module) { + process.exit(main(process.argv.slice(2), s => console.error(s))); +} diff --git a/packages/compiler-cli/src/ngtools_api.ts b/packages/compiler-cli/src/ngtools_api.ts index 766ce3394f..ed88264512 100644 --- a/packages/compiler-cli/src/ngtools_api.ts +++ b/packages/compiler-cli/src/ngtools_api.ts @@ -79,7 +79,6 @@ class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapte readResource(path: string) { return this._readResource(path); } } - /** * @internal * @private @@ -92,6 +91,7 @@ export class NgTools_InternalApi_NG_2 { static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise { const hostContext: CompilerHostContext = new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host); + const cliOptions: NgcCliOptions = { i18nFormat: options.i18nFormat !, i18nFile: options.i18nFile !, diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts new file mode 100644 index 0000000000..ea0919c3b5 --- /dev/null +++ b/packages/compiler-cli/src/transformers/api.ts @@ -0,0 +1,227 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ParseSourceSpan} from '@angular/compiler'; +import * as ts from 'typescript'; + +export enum DiagnosticCategory { + Warning = 0, + Error = 1, + Message = 2, +} + +export interface Diagnostic { + message: string; + span?: ParseSourceSpan; + category: DiagnosticCategory; +} + +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. + genDir?: string; + + // Path to the directory containing the tsconfig.json file. + basePath?: string; + + // Don't produce .metadata.json files (they don't work for bundled emit with --out) + skipMetadataEmit?: boolean; + + // Produce an error if the metadata written for a class would produce an error if used. + strictMetadataEmit?: boolean; + + // Don't produce .ngfactory.ts or .ngstyle.ts files + skipTemplateCodegen?: boolean; + + // Whether to generate a flat module index of the given name and the corresponding + // flat module metadata. This option is intended to be used when creating flat + // modules similar to how `@angular/core` and `@angular/common` are packaged. + // When this option is used the `package.json` for the library should refered to the + // generated flat module index instead of the library index file. When using this + // option only one .metadata.json file is produced that contains all the metadata + // necessary for symbols exported from the library index. + // In the generated .ngfactory.ts files flat module index is used to import symbols + // includes both the public API from the library index as well as shrowded internal + // symbols. + // By default the .ts file supplied in the `files` files field is assumed to be + // library index. If more than one is specified, uses `libraryIndex` to select the + // file to use. If more than on .ts file is supplied and no `libraryIndex` is supplied + // an error is produced. + // A flat module index .d.ts and .js will be created with the given `flatModuleOutFile` + // name in the same location as the library index .d.ts file is emitted. + // For example, if a library uses `public_api.ts` file as the library index of the + // module the `tsconfig.json` `files` field would be `["public_api.ts"]`. The + // `flatModuleOutFile` options could then be set to, for example `"index.js"`, which + // produces `index.d.ts` and `index.metadata.json` files. The library's + // `package.json`'s `module` field would be `"index.js"` and the `typings` field would + // be `"index.d.ts"`. + flatModuleOutFile?: string; + + // Preferred module id to use for importing flat module. References generated by `ngc` + // will use this module name when importing symbols from the flat module. This is only + // meaningful when `flatModuleOutFile` is also supplied. It is otherwise ignored. + flatModuleId?: string; + + // Whether to generate code for library code. + // If true, produce .ngfactory.ts and .ngstyle.ts files for .d.ts inputs. + // Default is true. + generateCodeForLibraries?: boolean; + + // Insert JSDoc type annotations needed by Closure Compiler + annotateForClosureCompiler?: boolean; + + // Modify how angular annotations are emitted to improve tree-shaking. + // Default is static fields. + // decorators: Leave the Decorators in-place. This makes compilation faster. + // TypeScript will emit calls to the __decorate helper. + // `--emitDecoratorMetadata` can be used for runtime reflection. + // However, the resulting code will not properly tree-shake. + // static fields: Replace decorators with a static field in the class. + // Allows advanced tree-shakers like Closure Compiler to remove + // unused classes. + annotationsAs?: 'decorators'|'static fields'; + + // Print extra information while running the compiler + trace?: boolean; + + // Whether to enable support for