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:
parent
e26e4f922e
commit
4c26397937
18
build.sh
18
build.sh
|
@ -35,14 +35,8 @@ cd -
|
||||||
TSCONFIG=./modules/tsconfig.json
|
TSCONFIG=./modules/tsconfig.json
|
||||||
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
|
||||||
# compile ts code
|
# compile ts code
|
||||||
# TODO: Right now we have a cycle in that the compiler_cli depends on Angular
|
TSC="node dist/tools/tsc-wrapped/src/main"
|
||||||
# but we need it to compile Angular.
|
$TSC -p modules/tsconfig.json
|
||||||
# The solution right now is to do 2 compilation runs.
|
|
||||||
# Fix this by separating the metadata extraction into a separate binary that does
|
|
||||||
# not depend on Angular.
|
|
||||||
$(npm bin)/tsc -p ${TSCONFIG}
|
|
||||||
NG_TC="node dist/all/@angular/compiler_cli/src/main"
|
|
||||||
$NG_TC -p modules/tsconfig.json
|
|
||||||
|
|
||||||
rm -rf ./dist/packages-dist
|
rm -rf ./dist/packages-dist
|
||||||
|
|
||||||
|
@ -68,8 +62,8 @@ do
|
||||||
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
|
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||||
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
|
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
|
||||||
else
|
else
|
||||||
echo "====== COMPILING: ${NG_TC} -p ${SRCDIR}/tsconfig-es5.json ====="
|
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-es5.json ====="
|
||||||
$NG_TC -p ${SRCDIR}/tsconfig-es5.json
|
$TSC -p ${SRCDIR}/tsconfig-es5.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp ${SRCDIR}/package.json ${DESTDIR}/
|
cp ${SRCDIR}/package.json ${DESTDIR}/
|
||||||
|
@ -91,8 +85,8 @@ do
|
||||||
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||||
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json
|
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json
|
||||||
else
|
else
|
||||||
echo "====== (esm)COMPILING: $NG_TC -p ${SRCDIR}/tsconfig-es2015.json ====="
|
echo "====== (esm)COMPILING: $TSC -p ${SRCDIR}/tsconfig-es2015.json ====="
|
||||||
$NG_TC -p ${SRCDIR}/tsconfig-es2015.json
|
$TSC -p ${SRCDIR}/tsconfig-es2015.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "====== BUNDLING: ${SRCDIR} ====="
|
echo "====== BUNDLING: ${SRCDIR} ====="
|
||||||
|
|
|
@ -73,7 +73,7 @@ with the one already used in the plugin for TypeScript typechecking and emit.
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
At a high level, this program
|
At a high level, this program
|
||||||
- collects static metadata about the sources using the `ts-metadata-collector` package in angular2
|
- collects static metadata about the sources using the `tsc-wrapped` package in angular2
|
||||||
- uses the `OfflineCompiler` from `angular2/src/compiler/compiler` to codegen additional `.ts` files
|
- uses the `OfflineCompiler` from `angular2/src/compiler/compiler` to codegen additional `.ts` files
|
||||||
- these `.ts` files are written to the `genDir` path, then compiled together with the application.
|
- these `.ts` files are written to the `genDir` path, then compiled together with the application.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export {CodeGenerator} from './src/codegen';
|
export {CodeGenerator} from './src/codegen';
|
||||||
export {NodeReflectorHost} from './src/reflector_host';
|
export {NodeReflectorHost} from './src/reflector_host';
|
||||||
export {TsickleHost, MetadataWriterHost} from './src/compiler_host';
|
export * from 'tsc-wrapped';
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
"ngc": "./src/main.js"
|
"ngc": "./src/main.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ts-metadata-collector": "^0.1.0",
|
"tsc-wrapped": "^0.1.0",
|
||||||
"tsickle": "^0.1.2",
|
|
||||||
"reflect-metadata": "^0.1.2",
|
"reflect-metadata": "^0.1.2",
|
||||||
"parse5": "1.3.2"
|
"parse5": "1.3.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import {AngularCompilerOptions} from 'tsc-wrapped';
|
||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
|
@ -23,7 +24,6 @@ import {
|
||||||
|
|
||||||
import {Parse5DomAdapter} from '@angular/platform-server';
|
import {Parse5DomAdapter} from '@angular/platform-server';
|
||||||
|
|
||||||
import {MetadataCollector} from 'ts-metadata-collector';
|
|
||||||
import {NodeReflectorHost} from './reflector_host';
|
import {NodeReflectorHost} from './reflector_host';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
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 {
|
export class CodeGenerator {
|
||||||
constructor(private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions,
|
constructor(private options: AngularCompilerOptions,
|
||||||
private program: ts.Program, public host: ts.CompilerHost,
|
private program: ts.Program, public host: ts.CompilerHost,
|
||||||
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver,
|
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver,
|
||||||
private compiler: compiler.OfflineCompiler,
|
private compiler: compiler.OfflineCompiler,
|
||||||
|
@ -107,9 +85,9 @@ export class CodeGenerator {
|
||||||
|
|
||||||
// Write codegen in a directory structure matching the sources.
|
// Write codegen in a directory structure matching the sources.
|
||||||
private calculateEmitPath(filePath: string) {
|
private calculateEmitPath(filePath: string) {
|
||||||
let root = this.ngOptions.basePath;
|
let root = this.options.basePath;
|
||||||
for (let eachRootDir of this.options.rootDirs || []) {
|
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}`);
|
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||||
}
|
}
|
||||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
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
|
// TODO(tbosch): add a cache for shared css files
|
||||||
|
@ -170,11 +148,11 @@ export class CodeGenerator {
|
||||||
return Promise.all(stylesheetPromises.concat(compPromises));
|
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 {
|
compilerHost: ts.CompilerHost): CodeGenerator {
|
||||||
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))};
|
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))};
|
||||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
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);
|
const staticReflector = new StaticReflector(reflectorHost);
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
const htmlParser = new HtmlParser();
|
const htmlParser = new HtmlParser();
|
||||||
|
@ -190,7 +168,7 @@ export class CodeGenerator {
|
||||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||||
new compiler.ViewResolver(staticReflector), null, null, 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);
|
offlineCompiler, reflectorHost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,72 +3,19 @@
|
||||||
// Must be imported first, because angular2 decorators throws on load.
|
// Must be imported first, because angular2 decorators throws on load.
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {tsc, check} from './tsc';
|
import * as tsc from 'tsc-wrapped';
|
||||||
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
|
||||||
import {NodeReflectorHost} from './reflector_host';
|
|
||||||
import {CodeGenerator} from './codegen';
|
import {CodeGenerator} from './codegen';
|
||||||
import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
|
|
||||||
|
|
||||||
const DEBUG = false;
|
function codegen(ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
|
||||||
|
return CodeGenerator.create(ngOptions, program, host).codegen();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLI entry point
|
// CLI entry point
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
const args = require('minimist')(process.argv.slice(2));
|
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))
|
.then(exitCode => process.exit(exitCode))
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
|
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
|
||||||
import * as ts from 'typescript';
|
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 fs from 'fs';
|
||||||
import * as path from 'path';
|
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 EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
const DTS = /\.d\.ts$/;
|
const DTS = /\.d\.ts$/;
|
||||||
|
@ -12,24 +12,15 @@ const DTS = /\.d\.ts$/;
|
||||||
export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||||
private metadataCollector = new MetadataCollector();
|
private metadataCollector = new MetadataCollector();
|
||||||
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
|
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
|
||||||
private options: ts.CompilerOptions, private ngOptions: AngularCompilerOptions) {}
|
private options: AngularCompilerOptions) {}
|
||||||
|
|
||||||
angularImportLocations() {
|
angularImportLocations() {
|
||||||
if (this.ngOptions.legacyPackageLayout) {
|
return {
|
||||||
return {
|
coreDecorators: '@angular/core/src/metadata',
|
||||||
coreDecorators: 'angular2/src/core/metadata',
|
diDecorators: '@angular/core/src/di/decorators',
|
||||||
diDecorators: 'angular2/src/core/di/decorators',
|
diMetadata: '@angular/core/src/di/metadata',
|
||||||
diMetadata: 'angular2/src/core/di/metadata',
|
provider: '@angular/core/src/di/provider'
|
||||||
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'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private resolve(m: string, containingFile: string) {
|
private resolve(m: string, containingFile: string) {
|
||||||
const resolved =
|
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),
|
// 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!
|
// we still need to create it so that the `resolve` method works!
|
||||||
if (!this.compilerHost.fileExists(importedFile)) {
|
if (!this.compilerHost.fileExists(importedFile)) {
|
||||||
if (this.ngOptions.trace) {
|
if (this.options.trace) {
|
||||||
console.log(`Generating empty file ${importedFile} to allow resolution of import`);
|
console.log(`Generating empty file ${importedFile} to allow resolution of import`);
|
||||||
}
|
}
|
||||||
this.compilerHost.writeFile(importedFile, '', false);
|
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.");
|
throw new Error("Resolution of relative paths requires a containing file.");
|
||||||
}
|
}
|
||||||
// Any containing file gives the same result for absolute imports
|
// 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 {
|
try {
|
||||||
|
@ -175,18 +166,4 @@ export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||||
throw e;
|
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'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
|
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
|
||||||
"@angular/platform-server": ["../../../dist/packages-dist/platform-server"],
|
"@angular/platform-server": ["../../../dist/packages-dist/platform-server"],
|
||||||
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
||||||
"ts-metadata-collector": ["../../../dist/tools/ts-metadata-collector"]
|
"tsc-wrapped": ["../../../dist/tools/tsc-wrapped"]
|
||||||
},
|
},
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"selenium-webdriver": ["./@angular/typings/selenium-webdriver/selenium-webdriver.d.ts"],
|
"selenium-webdriver": ["./@angular/typings/selenium-webdriver/selenium-webdriver.d.ts"],
|
||||||
"rxjs/*": ["../node_modules/rxjs/*"],
|
"rxjs/*": ["../node_modules/rxjs/*"],
|
||||||
"@angular/*": ["./@angular/*"],
|
"@angular/*": ["./@angular/*"],
|
||||||
"ts-metadata-collector": ["../dist/tools/ts-metadata-collector"]
|
"tsc-wrapped": ["../dist/tools/tsc-wrapped"]
|
||||||
},
|
},
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
|
|
|
@ -11,13 +11,7 @@ cd ../..
|
||||||
|
|
||||||
$(npm bin)/tsc -p ./tools/tsconfig.json
|
$(npm bin)/tsc -p ./tools/tsconfig.json
|
||||||
|
|
||||||
# TODO: Right now we have a cycle in that the compiler_cli depends on Angular
|
node dist/tools/tsc-wrapped/src/main -p modules/tsconfig.json
|
||||||
# but we need it to compile Angular.
|
|
||||||
# The solution right now is to do 2 compilation runs.
|
|
||||||
# Fix this by separating the metadata extraction into a separate binary that does
|
|
||||||
# not depend on Angular.
|
|
||||||
$(npm bin)/tsc -p ./modules/tsconfig.json
|
|
||||||
node dist/all/@angular/compiler_cli/src/main -p modules/tsconfig.json
|
|
||||||
|
|
||||||
# Compile the compiler_cli integration tests
|
# Compile the compiler_cli integration tests
|
||||||
node dist/all/@angular/compiler_cli/src/main -p modules/@angular/compiler_cli/integrationtest
|
node dist/all/@angular/compiler_cli/src/main -p modules/@angular/compiler_cli/integrationtest
|
||||||
|
|
|
@ -3,7 +3,7 @@ import fse = require('fs-extra');
|
||||||
import path = require('path');
|
import path = require('path');
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
import {wrapDiffingPlugin, DiffingBroccoliPlugin, DiffResult} from './diffing-broccoli-plugin';
|
||||||
import {MetadataCollector} from '../ts-metadata-collector';
|
import {MetadataCollector} from '../tsc-wrapped';
|
||||||
|
|
||||||
type FileRegistry = ts.Map<{version: number}>;
|
type FileRegistry = ts.Map<{version: number}>;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ module.exports = function(gulp, plugins, config) {
|
||||||
symlink(relativeFolder, linkDir);
|
symlink(relativeFolder, linkDir);
|
||||||
});
|
});
|
||||||
// Also symlink tools we release independently to NPM, so tests can require metadata, etc.
|
// Also symlink tools we release independently to NPM, so tests can require metadata, etc.
|
||||||
symlink('../../tools/metadata', path.join(nodeModulesDir, 'ts-metadata-collector'));
|
symlink('../../tools/metadata', path.join(nodeModulesDir, 'tsc-wrapped'));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# TypeScript Decorator metadata collector
|
|
||||||
|
|
||||||
The `.d.ts` format does not preserve information about the Decorators applied to symbols.
|
|
||||||
Some tools, such as Angular 2 template compiler, need access to statically analyzable
|
|
||||||
information about Decorators, so this library allows programs to produce a `foo.metadata.json`
|
|
||||||
to accompany a `foo.d.ts` file, and preserves the information that was lost in the declaration
|
|
||||||
emit.
|
|
||||||
|
|
||||||
## Releasing
|
|
||||||
```
|
|
||||||
$ gulp build.tools
|
|
||||||
$ cp tools/metadata/package.json dist/tools/metadata/
|
|
||||||
$ npm login [angularcore]
|
|
||||||
$ npm publish dist/tools/metadata
|
|
||||||
```
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './src/collector';
|
|
||||||
export * from './src/schema';
|
|
|
@ -93,8 +93,7 @@ if (platform == 'node') {
|
||||||
error: 'error',
|
error: 'error',
|
||||||
complete: 'Compilation complete. Watching for file changes.',
|
complete: 'Compilation complete. Watching for file changes.',
|
||||||
onChangeCmds: [
|
onChangeCmds: [
|
||||||
['node', 'dist/tools/cjs-jasmine/index-tools', '--', 'ts-metadata-collector/**/*{_,.}spec.js'],
|
['node', 'dist/tools/cjs-jasmine/index-tools', '--', '{public_api_guard,tsc-wrapped}/**/*{_,.}spec.js']
|
||||||
['node', 'dist/tools/cjs-jasmine/index-tools', '--', 'public_api_guard/**/*{_,.}spec.js']
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# tsc-wrapped
|
||||||
|
|
||||||
|
This package is an internal dependency used by @angular/compiler-cli. Please use that instead.
|
||||||
|
|
||||||
|
This is a wrapper around TypeScript's `tsc` program that allows us to hook in extra extensions.
|
||||||
|
TypeScript will eventually have an extensibility model for arbitrary extensions. We don't want
|
||||||
|
to constrain their design with baggage from a legacy implementation, so this wrapper only
|
||||||
|
supports specific extensions developed by the Angular team:
|
||||||
|
|
||||||
|
- tsickle down-levels Decorators into Annotations so they can be tree-shaken
|
||||||
|
- tsickle can also optionally produce Closure Compiler-friendly code
|
||||||
|
- ./collector.ts emits an extra `.metadata.json` file for every `.d.ts` file written,
|
||||||
|
which retains metadata about decorators that is lost in the TS emit
|
||||||
|
- @angular/compiler-cli extends this library to additionally generate template code
|
||||||
|
|
||||||
|
## TypeScript Decorator metadata collector
|
||||||
|
|
||||||
|
The `.d.ts` format does not preserve information about the Decorators applied to symbols.
|
||||||
|
Some tools, such as Angular 2 template compiler, need access to statically analyzable
|
||||||
|
information about Decorators, so this library allows programs to produce a `foo.metadata.json`
|
||||||
|
to accompany a `foo.d.ts` file, and preserves the information that was lost in the declaration
|
||||||
|
emit.
|
||||||
|
|
||||||
|
## Releasing
|
||||||
|
```
|
||||||
|
$ $(npm bin)/tsc -p tools
|
||||||
|
$ cp tools/tsc-wrapped/package.json dist/tools/tsc-wrapped/
|
||||||
|
$ npm login [angularcore]
|
||||||
|
$ npm publish dist/tools/tsc-wrapped
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
export {TsickleHost, MetadataWriterHost} from './src/compiler_host';
|
||||||
|
export {main, CodegenExtension} from './src/main';
|
||||||
|
export {default as AngularCompilerOptions} from './src/options';
|
||||||
|
export * from './src/collector';
|
||||||
|
export * from './src/schema';
|
|
@ -1,16 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "ts-metadata-collector",
|
"name": "@angular/tsc-wrapped",
|
||||||
"version": "0.1.1",
|
"version": "0.1.0",
|
||||||
"description": "Collects static Decorator metadata from TypeScript sources",
|
"description": "Wraps the tsc CLI, allowing extensions.",
|
||||||
"homepage": "https://github.com/angular/angular/tree/master/tools/metadata",
|
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
|
||||||
"bugs": "https://github.com/angular/angular/issues",
|
"bugs": "https://github.com/angular/angular/issues",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
|
"Alex Eagle <alexeagle@google.com>",
|
||||||
"Chuck Jazdzewski <chuckj@google.com>"
|
"Chuck Jazdzewski <chuckj@google.com>"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
||||||
"devDependencies": {},
|
"dependencies": {
|
||||||
|
"tsickle": "^0.1.2"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^1.8.9 || ^1.9.0-dev"
|
"typescript": "^1.9.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as path from 'path';
|
import {writeFileSync} from 'fs';
|
||||||
import {convertDecorators} from 'tsickle';
|
import {convertDecorators} from 'tsickle';
|
||||||
import {NodeReflectorHost} from './reflector_host';
|
import {MetadataCollector} from './collector';
|
||||||
import {AngularCompilerOptions} from './codegen';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of CompilerHost that forwards all methods to another instance.
|
* Implementation of CompilerHost that forwards all methods to another instance.
|
||||||
|
@ -38,7 +37,7 @@ interface DecoratorInvocation {
|
||||||
args?: any[];
|
args?: any[];
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
constructor(delegate: ts.CompilerHost, private options: ts.CompilerOptions) { super(delegate); }
|
constructor(delegate: ts.CompilerHost) { super(delegate); }
|
||||||
|
|
||||||
getSourceFile =
|
getSourceFile =
|
||||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||||
|
@ -52,17 +51,29 @@ interface DecoratorInvocation {
|
||||||
newContent = converted.output + this.TSICKLE_SUPPORT;
|
newContent = converted.output + this.TSICKLE_SUPPORT;
|
||||||
}
|
}
|
||||||
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
||||||
|
|
||||||
export class MetadataWriterHost extends DelegatingHost {
|
export class MetadataWriterHost extends DelegatingHost {
|
||||||
private reflectorHost: NodeReflectorHost;
|
private metadataCollector = new MetadataCollector();
|
||||||
constructor(delegate: ts.CompilerHost, program: ts.Program, options: ts.CompilerOptions,
|
constructor(delegate: ts.CompilerHost, private program: ts.Program) {
|
||||||
ngOptions: AngularCompilerOptions) {
|
|
||||||
super(delegate);
|
super(delegate);
|
||||||
this.reflectorHost = new NodeReflectorHost(program, this, options, ngOptions);
|
}
|
||||||
|
|
||||||
|
private 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);
|
||||||
|
writeFileSync(path, metadataText, {encoding: 'utf-8'});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile: ts.WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean,
|
writeFile: ts.WriteFileCallback = (fileName: string, data: string, writeByteOrderMark: boolean,
|
||||||
|
@ -88,6 +99,6 @@ export class MetadataWriterHost extends DelegatingHost {
|
||||||
if (sourceFiles.length > 1) {
|
if (sourceFiles.length > 1) {
|
||||||
throw new Error('Bundled emit with --out is not supported');
|
throw new Error('Bundled emit with --out is not supported');
|
||||||
}
|
}
|
||||||
this.reflectorHost.writeMetadata(fileName, sourceFiles[0]);
|
this.writeMetadata(fileName, sourceFiles[0]);
|
||||||
}
|
};
|
||||||
}
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {tsc, check} from './tsc';
|
||||||
|
import NgOptions from './options';
|
||||||
|
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
||||||
|
|
||||||
|
export type CodegenExtension = (ngOptions: NgOptions, program: ts.Program, host: ts.CompilerHost) => Promise<any>;
|
||||||
|
|
||||||
|
export function main(project: string, basePath?: string,
|
||||||
|
codegen?: CodegenExtension): 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);
|
||||||
|
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||||
|
const errors = program.getOptionsDiagnostics();
|
||||||
|
check(errors);
|
||||||
|
|
||||||
|
if (ngOptions.skipTemplateCodegen || !codegen) {
|
||||||
|
codegen = () => Promise.resolve(null);
|
||||||
|
}
|
||||||
|
return codegen(ngOptions, program, host).then(() => {
|
||||||
|
tsc.typeCheck(host, program);
|
||||||
|
|
||||||
|
// Emit *.js with Decorators lowered to Annotations, and also *.js.map
|
||||||
|
const tsicklePreProcessor = new TsickleHost(host);
|
||||||
|
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);
|
||||||
|
tsc.emit(metadataWriter, program);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI entry point
|
||||||
|
if (require.main === module) {
|
||||||
|
const args = require('minimist')(process.argv.slice(2));
|
||||||
|
main(args.p || args.project || '.', args.basePath)
|
||||||
|
.then(exitCode => process.exit(exitCode))
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e.stack);
|
||||||
|
console.error("Compilation failed");
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
interface Options extends ts.CompilerOptions {
|
||||||
|
// 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 produce .metadata.json files (they don't work for bundled emit with --out)
|
||||||
|
skipMetadataEmit: boolean;
|
||||||
|
|
||||||
|
// Don't produce .ngfactory.ts or .css.shim.ts files
|
||||||
|
skipTemplateCodegen: boolean;
|
||||||
|
|
||||||
|
// Print extra information while running the compiler
|
||||||
|
trace: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Options;
|
|
@ -1,8 +1,6 @@
|
||||||
import * as ts from 'typescript';
|
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 * as path from 'path';
|
||||||
import {AngularCompilerOptions} from './codegen';
|
import AngularCompilerOptions from './options';
|
||||||
import {TsickleHost} from './compiler_host';
|
import {TsickleHost} from './compiler_host';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +17,6 @@ export interface CompilerInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
const SOURCE_EXTENSION = /\.[jt]s$/;
|
|
||||||
|
|
||||||
function debug(msg: string, ...o: any[]) {
|
function debug(msg: string, ...o: any[]) {
|
||||||
if (DEBUG) console.log(msg, ...o);
|
if (DEBUG) console.log(msg, ...o);
|
||||||
|
@ -50,19 +47,24 @@ export class Tsc implements CompilerInterface {
|
||||||
public parsed: ts.ParsedCommandLine;
|
public parsed: ts.ParsedCommandLine;
|
||||||
private basePath: string;
|
private basePath: string;
|
||||||
|
|
||||||
|
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {}
|
||||||
|
|
||||||
readConfiguration(project: string, basePath: string) {
|
readConfiguration(project: string, basePath: string) {
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
|
|
||||||
// Allow a directory containing tsconfig.json as the project value
|
// Allow a directory containing tsconfig.json as the project value
|
||||||
if (lstatSync(project).isDirectory()) {
|
try {
|
||||||
|
this.readDirectory(project);
|
||||||
project = path.join(project, "tsconfig.json");
|
project = path.join(project, "tsconfig.json");
|
||||||
|
} catch (e) {
|
||||||
|
// Was not a directory, continue on assuming it's a file
|
||||||
}
|
}
|
||||||
|
|
||||||
const {config, error} = ts.readConfigFile(project, ts.sys.readFile);
|
const {config, error} = ts.readConfigFile(project, this.readFile);
|
||||||
check([error]);
|
check([error]);
|
||||||
|
|
||||||
this.parsed =
|
this.parsed =
|
||||||
ts.parseJsonConfigFileContent(config, {readDirectory: ts.sys.readDirectory}, basePath);
|
ts.parseJsonConfigFileContent(config, {readDirectory: this.readDirectory}, basePath);
|
||||||
|
|
||||||
check(this.parsed.errors);
|
check(this.parsed.errors);
|
||||||
|
|
||||||
|
@ -70,6 +72,9 @@ export class Tsc implements CompilerInterface {
|
||||||
// Parsed options are already converted to absolute paths
|
// Parsed options are already converted to absolute paths
|
||||||
this.ngOptions = config.angularCompilerOptions || {};
|
this.ngOptions = config.angularCompilerOptions || {};
|
||||||
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
|
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
|
||||||
|
for (const key of Object.keys(this.parsed.options)) {
|
||||||
|
this.ngOptions[key] = this.parsed.options[key];
|
||||||
|
}
|
||||||
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {Tsc} from '../src/tsc';
|
||||||
|
|
||||||
|
describe('options parsing', () => {
|
||||||
|
|
||||||
|
const tsc = new Tsc(() => `
|
||||||
|
{
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"googleClosureOutput": true
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "built"
|
||||||
|
}
|
||||||
|
}`, () => ['tsconfig.json']);
|
||||||
|
|
||||||
|
it('should combine all options into ngOptions', () => {
|
||||||
|
const {parsed, ngOptions} = tsc.readConfiguration('projectDir', 'basePath');
|
||||||
|
|
||||||
|
expect(ngOptions).toEqual({
|
||||||
|
genDir:'basePath',
|
||||||
|
googleClosureOutput: true,
|
||||||
|
module: ts.ModuleKind.CommonJS,
|
||||||
|
outDir: 'basePath/built',
|
||||||
|
configFilePath: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue