chore(ngc): refactor out tsc-wrapped
This allows angular's build to depend on some extensions, but not on code generation, and breaks a cycle in the angular build We now merge ts-metadata-collector into tsc-wrapped and stop publishing the former.
This commit is contained in:
@ -4,6 +4,7 @@
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
import * as path from 'path';
|
||||
import {AngularCompilerOptions} from 'tsc-wrapped';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
@ -23,7 +24,6 @@ import {
|
||||
|
||||
import {Parse5DomAdapter} from '@angular/platform-server';
|
||||
|
||||
import {MetadataCollector} from 'ts-metadata-collector';
|
||||
import {NodeReflectorHost} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
|
||||
@ -35,30 +35,8 @@ const PREAMBLE = `/**
|
||||
*/
|
||||
`;
|
||||
|
||||
// TODO(alexeagle): we end up passing options and ngOptions everywhere.
|
||||
// Maybe this should extend ts.CompilerOptions so we only need this one.
|
||||
export interface AngularCompilerOptions {
|
||||
// Absolute path to a directory where generated file structure is written
|
||||
genDir: string;
|
||||
|
||||
// Path to the directory containing the tsconfig.json file.
|
||||
basePath: string;
|
||||
|
||||
// Don't do the template code generation
|
||||
skipTemplateCodegen: boolean;
|
||||
|
||||
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
||||
skipMetadataEmit: boolean;
|
||||
|
||||
// Lookup angular's symbols using the old angular2/... npm namespace.
|
||||
legacyPackageLayout: boolean;
|
||||
|
||||
// Print extra information while running the compiler
|
||||
trace: boolean;
|
||||
}
|
||||
|
||||
export class CodeGenerator {
|
||||
constructor(private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions,
|
||||
constructor(private options: AngularCompilerOptions,
|
||||
private program: ts.Program, public host: ts.CompilerHost,
|
||||
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver,
|
||||
private compiler: compiler.OfflineCompiler,
|
||||
@ -107,9 +85,9 @@ export class CodeGenerator {
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string) {
|
||||
let root = this.ngOptions.basePath;
|
||||
let root = this.options.basePath;
|
||||
for (let eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.ngOptions.trace) {
|
||||
if (this.options.trace) {
|
||||
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
@ -117,7 +95,7 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
return path.join(this.ngOptions.genDir, path.relative(root, filePath));
|
||||
return path.join(this.options.genDir, path.relative(root, filePath));
|
||||
}
|
||||
|
||||
// TODO(tbosch): add a cache for shared css files
|
||||
@ -170,11 +148,11 @@ export class CodeGenerator {
|
||||
return Promise.all(stylesheetPromises.concat(compPromises));
|
||||
}
|
||||
|
||||
static create(ngOptions: AngularCompilerOptions, program: ts.Program, options: ts.CompilerOptions,
|
||||
static create(options: AngularCompilerOptions, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost): CodeGenerator {
|
||||
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))};
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
const reflectorHost = new NodeReflectorHost(program, compilerHost, options, ngOptions);
|
||||
const reflectorHost = new NodeReflectorHost(program, compilerHost, options);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser = new HtmlParser();
|
||||
@ -190,7 +168,7 @@ export class CodeGenerator {
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
new compiler.ViewResolver(staticReflector), null, null, staticReflector);
|
||||
|
||||
return new CodeGenerator(options, ngOptions, program, compilerHost, staticReflector, resolver,
|
||||
return new CodeGenerator(options, program, compilerHost, staticReflector, resolver,
|
||||
offlineCompiler, reflectorHost);
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +0,0 @@
|
||||
import * as ts from 'typescript';
|
||||
import * as path from 'path';
|
||||
import {convertDecorators} from 'tsickle';
|
||||
import {NodeReflectorHost} from './reflector_host';
|
||||
import {AngularCompilerOptions} from './codegen';
|
||||
|
||||
/**
|
||||
* Implementation of CompilerHost that forwards all methods to another instance.
|
||||
* Useful for partial implementations to override only methods they care about.
|
||||
*/
|
||||
export abstract class DelegatingHost implements ts.CompilerHost {
|
||||
constructor(protected delegate: ts.CompilerHost) {}
|
||||
getSourceFile =
|
||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) =>
|
||||
this.delegate.getSourceFile(fileName, languageVersion, onError);
|
||||
|
||||
getCancellationToken = () => this.delegate.getCancellationToken();
|
||||
getDefaultLibFileName = (options: ts.CompilerOptions) =>
|
||||
this.delegate.getDefaultLibFileName(options);
|
||||
getDefaultLibLocation = () => this.delegate.getDefaultLibLocation();
|
||||
writeFile: ts.WriteFileCallback = this.delegate.writeFile;
|
||||
getCurrentDirectory = () => this.delegate.getCurrentDirectory();
|
||||
getCanonicalFileName = (fileName: string) => this.delegate.getCanonicalFileName(fileName);
|
||||
useCaseSensitiveFileNames = () => this.delegate.useCaseSensitiveFileNames();
|
||||
getNewLine = () => this.delegate.getNewLine();
|
||||
fileExists = (fileName: string) => this.delegate.fileExists(fileName);
|
||||
readFile = (fileName: string) => this.delegate.readFile(fileName);
|
||||
trace = (s: string) => this.delegate.trace(s);
|
||||
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
export class TsickleHost extends DelegatingHost {
|
||||
// Additional diagnostics gathered by pre- and post-emit transformations.
|
||||
public diagnostics: ts.Diagnostic[] = [];
|
||||
private TSICKLE_SUPPORT = `
|
||||
interface DecoratorInvocation {
|
||||
type: Function;
|
||||
args?: any[];
|
||||
}
|
||||
`;
|
||||
constructor(delegate: ts.CompilerHost, private options: ts.CompilerOptions) { super(delegate); }
|
||||
|
||||
getSourceFile =
|
||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||
const originalContent = this.delegate.readFile(fileName);
|
||||
let newContent = originalContent;
|
||||
if (!/\.d\.ts$/.test(fileName)) {
|
||||
const converted = convertDecorators(fileName, originalContent);
|
||||
if (converted.diagnostics) {
|
||||
this.diagnostics.push(...converted.diagnostics);
|
||||
}
|
||||
newContent = converted.output + this.TSICKLE_SUPPORT;
|
||||
}
|
||||
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
||||
}
|
||||
}
|
||||
|
||||
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
||||
|
||||
export class MetadataWriterHost extends DelegatingHost {
|
||||
private reflectorHost: NodeReflectorHost;
|
||||
constructor(delegate: ts.CompilerHost, program: ts.Program, options: ts.CompilerOptions,
|
||||
ngOptions: AngularCompilerOptions) {
|
||||
super(delegate);
|
||||
this.reflectorHost = new NodeReflectorHost(program, this, options, ngOptions);
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void,
|
||||
sourceFiles?: ts.SourceFile[]) => {
|
||||
if (/\.d\.ts$/.test(fileName)) {
|
||||
// Let the original file be written first; this takes care of creating parent directories
|
||||
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
|
||||
// TODO: remove this early return after https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||
// released
|
||||
return;
|
||||
}
|
||||
|
||||
if (IGNORED_FILES.test(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceFiles) {
|
||||
throw new Error('Metadata emit requires the sourceFiles are passed to WriteFileCallback. ' +
|
||||
'Update to TypeScript ^1.9.0-dev');
|
||||
}
|
||||
if (sourceFiles.length > 1) {
|
||||
throw new Error('Bundled emit with --out is not supported');
|
||||
}
|
||||
this.reflectorHost.writeMetadata(fileName, sourceFiles[0]);
|
||||
}
|
||||
}
|
@ -3,72 +3,19 @@
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import {tsc, check} from './tsc';
|
||||
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
||||
import {NodeReflectorHost} from './reflector_host';
|
||||
import * as tsc from 'tsc-wrapped';
|
||||
|
||||
import {CodeGenerator} from './codegen';
|
||||
import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
function debug(msg: string, ...o: any[]) {
|
||||
if (DEBUG) console.log(msg, ...o);
|
||||
}
|
||||
|
||||
export function main(project: string, basePath?: string): Promise<any> {
|
||||
try {
|
||||
let projectDir = project;
|
||||
if (fs.lstatSync(project).isFile()) {
|
||||
projectDir = path.dirname(project);
|
||||
}
|
||||
// file names in tsconfig are resolved relative to this absolute path
|
||||
basePath = path.join(process.cwd(), basePath || projectDir);
|
||||
|
||||
// read the configuration options from wherever you store them
|
||||
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
||||
ngOptions.basePath = basePath;
|
||||
|
||||
const host = ts.createCompilerHost(parsed.options, true);
|
||||
|
||||
let codegenStep: Promise<any>;
|
||||
|
||||
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||
const errors = program.getOptionsDiagnostics();
|
||||
check(errors);
|
||||
|
||||
const doCodegen = ngOptions.skipTemplateCodegen ?
|
||||
Promise.resolve(null) :
|
||||
CodeGenerator.create(ngOptions, program, parsed.options, host).codegen();
|
||||
|
||||
return doCodegen.then(() => {
|
||||
tsc.typeCheck(host, program);
|
||||
|
||||
// Emit *.js with Decorators lowered to Annotations, and also *.js.map
|
||||
const tsicklePreProcessor = new TsickleHost(host, parsed.options);
|
||||
tsc.emit(tsicklePreProcessor, program);
|
||||
|
||||
if (!ngOptions.skipMetadataEmit) {
|
||||
// Emit *.metadata.json and *.d.ts
|
||||
// Not in the same emit pass with above, because tsickle erases
|
||||
// decorators which we want to read or document.
|
||||
// Do this emit second since TypeScript will create missing directories for us
|
||||
// in the standard emit.
|
||||
const metadataWriter = new MetadataWriterHost(host, program, parsed.options, ngOptions);
|
||||
tsc.emit(metadataWriter, program);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
function codegen(ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
|
||||
return CodeGenerator.create(ngOptions, program, host).codegen();
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
main(args.p || args.project || '.', args.basePath)
|
||||
tsc.main(args.p || args.project || '.', args.basePath, codegen)
|
||||
.then(exitCode => process.exit(exitCode))
|
||||
.catch(e => {
|
||||
console.error(e.stack);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
|
||||
import * as ts from 'typescript';
|
||||
import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
|
||||
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from 'tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {AngularCompilerOptions} from './codegen';
|
||||
import {ImportGenerator, AssetUrl} from './compiler_private';
|
||||
import {ImportGenerator, AssetUrl} from './compiler_private'
|
||||
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
@ -12,24 +12,15 @@ const DTS = /\.d\.ts$/;
|
||||
export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
private metadataCollector = new MetadataCollector();
|
||||
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
|
||||
private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions) {}
|
||||
private options: AngularCompilerOptions) {}
|
||||
|
||||
angularImportLocations() {
|
||||
if (this.ngOptions.legacyPackageLayout) {
|
||||
return {
|
||||
coreDecorators: 'angular2/src/core/metadata',
|
||||
diDecorators: 'angular2/src/core/di/decorators',
|
||||
diMetadata: 'angular2/src/core/di/metadata',
|
||||
provider: 'angular2/src/core/di/provider'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/decorators',
|
||||
diMetadata: '@angular/core/src/di/metadata',
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
}
|
||||
return {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/decorators',
|
||||
diMetadata: '@angular/core/src/di/metadata',
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
}
|
||||
private resolve(m: string, containingFile: string) {
|
||||
const resolved =
|
||||
@ -63,7 +54,7 @@ export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
// TODO(tbosch): if a file does not yet exist (because we compile it later),
|
||||
// we still need to create it so that the `resolve` method works!
|
||||
if (!this.compilerHost.fileExists(importedFile)) {
|
||||
if (this.ngOptions.trace) {
|
||||
if (this.options.trace) {
|
||||
console.log(`Generating empty file ${importedFile} to allow resolution of import`);
|
||||
}
|
||||
this.compilerHost.writeFile(importedFile, '', false);
|
||||
@ -92,7 +83,7 @@ export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
throw new Error("Resolution of relative paths requires a containing file.");
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join(this.ngOptions.basePath, 'index.ts');
|
||||
containingFile = path.join(this.options.basePath, 'index.ts');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -175,18 +166,4 @@ export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||
// released
|
||||
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
|
||||
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
|
||||
const metadata =
|
||||
this.metadataCollector.getMetadata(sourceFile, this.program.getTypeChecker());
|
||||
if (metadata && metadata.metadata) {
|
||||
const metadataText = JSON.stringify(metadata);
|
||||
fs.writeFileSync(path, metadataText, {encoding: 'utf-8'});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
import * as ts from 'typescript';
|
||||
// Don't import from fs in general, that's the CompilerHost's job
|
||||
import {lstatSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
import {AngularCompilerOptions} from './codegen';
|
||||
import {TsickleHost} from './compiler_host';
|
||||
|
||||
/**
|
||||
* Our interface to the TypeScript standard compiler.
|
||||
* If you write an Angular compiler plugin for another build tool,
|
||||
* you should implement a similar interface.
|
||||
*/
|
||||
export interface CompilerInterface {
|
||||
readConfiguration(
|
||||
project: string,
|
||||
basePath: string): {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
||||
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
|
||||
emit(compilerHost: ts.CompilerHost, program: ts.Program): number;
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
const SOURCE_EXTENSION = /\.[jt]s$/;
|
||||
|
||||
function debug(msg: string, ...o: any[]) {
|
||||
if (DEBUG) console.log(msg, ...o);
|
||||
}
|
||||
|
||||
export function formatDiagnostics(diags: ts.Diagnostic[]): string {
|
||||
return diags.map((d) => {
|
||||
let res = ts.DiagnosticCategory[d.category];
|
||||
if (d.file) {
|
||||
res += ' at ' + d.file.fileName + ':';
|
||||
const {line, character} = d.file.getLineAndCharacterOfPosition(d.start);
|
||||
res += (line + 1) + ':' + (character + 1) + ':';
|
||||
}
|
||||
res += ' ' + ts.flattenDiagnosticMessageText(d.messageText, '\n');
|
||||
return res;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
export function check(diags: ts.Diagnostic[]) {
|
||||
if (diags && diags.length && diags[0]) {
|
||||
throw new Error(formatDiagnostics(diags));
|
||||
}
|
||||
}
|
||||
|
||||
export class Tsc implements CompilerInterface {
|
||||
public ngOptions: AngularCompilerOptions;
|
||||
public parsed: ts.ParsedCommandLine;
|
||||
private basePath: string;
|
||||
|
||||
readConfiguration(project: string, basePath: string) {
|
||||
this.basePath = basePath;
|
||||
|
||||
// Allow a directory containing tsconfig.json as the project value
|
||||
if (lstatSync(project).isDirectory()) {
|
||||
project = path.join(project, "tsconfig.json");
|
||||
}
|
||||
|
||||
const {config, error} = ts.readConfigFile(project, ts.sys.readFile);
|
||||
check([error]);
|
||||
|
||||
this.parsed =
|
||||
ts.parseJsonConfigFileContent(config, {readDirectory: ts.sys.readDirectory}, basePath);
|
||||
|
||||
check(this.parsed.errors);
|
||||
|
||||
// Default codegen goes to the current directory
|
||||
// Parsed options are already converted to absolute paths
|
||||
this.ngOptions = config.angularCompilerOptions || {};
|
||||
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
|
||||
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
||||
}
|
||||
|
||||
typeCheck(compilerHost: ts.CompilerHost, oldProgram: ts.Program): void {
|
||||
// Create a new program since codegen files were created after making the old program
|
||||
const program =
|
||||
ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost, oldProgram);
|
||||
debug("Checking global diagnostics...");
|
||||
check(program.getGlobalDiagnostics());
|
||||
|
||||
let diagnostics: ts.Diagnostic[] = [];
|
||||
debug("Type checking...");
|
||||
|
||||
for (let sf of program.getSourceFiles()) {
|
||||
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
|
||||
}
|
||||
check(diagnostics);
|
||||
}
|
||||
|
||||
emit(compilerHost: TsickleHost, oldProgram: ts.Program): number {
|
||||
// Create a new program since the host may be different from the old program.
|
||||
const program = ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost);
|
||||
debug("Emitting outputs...");
|
||||
const emitResult = program.emit();
|
||||
let diagnostics: ts.Diagnostic[] = [];
|
||||
diagnostics.push(...emitResult.diagnostics);
|
||||
|
||||
check(compilerHost.diagnostics);
|
||||
return emitResult.emitSkipped ? 1 : 0;
|
||||
}
|
||||
}
|
||||
export var tsc: CompilerInterface = new Tsc();
|
Reference in New Issue
Block a user