feat(offline compiler): add metadata emit
Also add a configuration switch to disable the codegen, so we can still use the metadata emit and tsickle pre-processing in the build pipeline for angular itself.
This commit is contained in:
parent
2e1f3f003d
commit
072446aed3
18
gulpfile.js
18
gulpfile.js
|
@ -1056,11 +1056,19 @@ gulp.task('test.typings', ['build.js.cjs'],
|
||||||
gulp.task('!build.compiler_cli', ['build.js.cjs'],
|
gulp.task('!build.compiler_cli', ['build.js.cjs'],
|
||||||
function(done) { runTsc('tools/compiler_cli/src', done); });
|
function(done) { runTsc('tools/compiler_cli/src', done); });
|
||||||
|
|
||||||
|
gulp.task('!clean.compiler_cli', function(done) {
|
||||||
|
fse.remove(path.join('dist', 'tools', 'compiler_cli', 'test'),
|
||||||
|
fse.remove(path.join('tools', 'compiler_cli', 'test', 'src', '*.ngfactory.ts'),
|
||||||
|
fse.remove(path.join('tools', 'compiler_cli', 'test', 'src', 'a',
|
||||||
|
'*.ngfactory.ts'),
|
||||||
|
done)));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('!test.compiler_cli.codegen', function(done) {
|
gulp.task('!test.compiler_cli.codegen', function(done) {
|
||||||
try {
|
try {
|
||||||
require('./dist/js/cjs/compiler_cli/main')
|
require('./dist/js/cjs/compiler_cli/main')
|
||||||
.main("tools/compiler_cli/test")
|
.main("tools/compiler_cli/test")
|
||||||
.then(function() { runTsc('tools/compiler_cli/test', done); })
|
.then(done)
|
||||||
.catch(function(rej) { done(new Error(rej)); });
|
.catch(function(rej) { done(new Error(rej)); });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
done(err);
|
done(err);
|
||||||
|
@ -1070,11 +1078,17 @@ gulp.task('!test.compiler_cli.codegen', function(done) {
|
||||||
gulp.task('!test.compiler_cli.unit',
|
gulp.task('!test.compiler_cli.unit',
|
||||||
function(done) { runJasmineTests(['dist/js/cjs/compiler_cli/**/*_spec.js'], done) });
|
function(done) { runJasmineTests(['dist/js/cjs/compiler_cli/**/*_spec.js'], done) });
|
||||||
|
|
||||||
|
// This task overwrites our careful tsickle-lowered Decorators with normal .js emit.
|
||||||
|
// So it should only be run after asserting on the .js file content.
|
||||||
|
gulp.task('!test.compiler_cli.verify_codegen',
|
||||||
|
function(done) { runTsc('tools/compiler_cli/test', done); });
|
||||||
|
|
||||||
// End-to-end test for compiler CLI.
|
// End-to-end test for compiler CLI.
|
||||||
// Calls the compiler using its command-line interface, then compiles the app with the codegen.
|
// Calls the compiler using its command-line interface, then compiles the app with the codegen.
|
||||||
// TODO(alexeagle): wire up the playground tests with offline compilation, similar to dart.
|
// TODO(alexeagle): wire up the playground tests with offline compilation, similar to dart.
|
||||||
gulp.task('test.compiler_cli', ['!build.compiler_cli'], function(done) {
|
gulp.task('test.compiler_cli', ['!build.compiler_cli'], function(done) {
|
||||||
runSequence('!test.compiler_cli.unit', '!test.compiler_cli.codegen', sequenceComplete(done));
|
runSequence('!clean.compiler_cli', '!test.compiler_cli.codegen', '!test.compiler_cli.unit',
|
||||||
|
'!test.compiler_cli.verify_codegen', sequenceComplete(done));
|
||||||
});
|
});
|
||||||
|
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|
|
@ -13,7 +13,7 @@ and then downloading only the executable JS to the client.
|
||||||
## Install and use
|
## Install and use
|
||||||
|
|
||||||
```
|
```
|
||||||
$ npm install angular2-template-compiler typescript rxjs
|
$ npm install angular2-template-compiler typescript@next rxjs @angular/compiler
|
||||||
# Optional sanity check, make sure TypeScript can compile
|
# Optional sanity check, make sure TypeScript can compile
|
||||||
$ ./node_modules/.bin/tsc -p path/to/project
|
$ ./node_modules/.bin/tsc -p path/to/project
|
||||||
$ ./node_modules/.bin/ng2tc -p path/to/project
|
$ ./node_modules/.bin/ng2tc -p path/to/project
|
||||||
|
|
|
@ -8,14 +8,16 @@
|
||||||
"ng2tc": "./main.js"
|
"ng2tc": "./main.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular2": "angular/angular#2.0.0-build.cacdead.js",
|
|
||||||
"ts-metadata-collector": "^0.1.0",
|
"ts-metadata-collector": "^0.1.0",
|
||||||
"tsickle": "^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"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^1.8.0 || ^1.9.0-dev"
|
"typescript": "^1.8.0 || ^1.9.0-dev",
|
||||||
|
"@angular/compiler": "0.0.0-5",
|
||||||
|
"@angular/platform-server": "0.0.0-5",
|
||||||
|
"@angular/core": "0.0.0-5"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/// <reference path="../../typings/jasmine/jasmine.d.ts" />
|
||||||
|
/// <reference path="../../typings/node/node.d.ts" />
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
describe("template codegen output", () => {
|
||||||
|
const outDir = path.join('dist', 'tools', 'compiler_cli', 'test', 'built');
|
||||||
|
|
||||||
|
it("should lower Decorators without reflect-metadata", () => {
|
||||||
|
const jsOutput = path.join(outDir, 'basic.js');
|
||||||
|
expect(fs.existsSync(jsOutput)).toBeTruthy();
|
||||||
|
expect(fs.readFileSync(jsOutput, {encoding: 'utf-8'})).not.toContain('Reflect.decorate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should produce metadata.json outputs", () => {
|
||||||
|
const metadataOutput = path.join(outDir, 'basic.metadata.json');
|
||||||
|
expect(fs.existsSync(metadataOutput)).toBeTruthy();
|
||||||
|
const output = fs.readFileSync(metadataOutput, {encoding: 'utf-8'});
|
||||||
|
expect(output).toContain('"decorators":');
|
||||||
|
expect(output).toContain('"name":"Component","module":"angular2/core"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should write .d.ts files", () => {
|
||||||
|
const dtsOutput = path.join(outDir, 'basic.d.ts');
|
||||||
|
expect(fs.existsSync(dtsOutput)).toBeTruthy();
|
||||||
|
expect(fs.readFileSync(dtsOutput, {encoding: 'utf-8'})).toContain('Basic');
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,7 +21,6 @@ import {Parse5DomAdapter} from '@angular/platform-server';
|
||||||
|
|
||||||
import {MetadataCollector} from 'ts-metadata-collector';
|
import {MetadataCollector} from 'ts-metadata-collector';
|
||||||
import {NodeReflectorHost} from './reflector_host';
|
import {NodeReflectorHost} from './reflector_host';
|
||||||
import {wrapCompilerHost, CodeGeneratorHost} from './compiler_host';
|
|
||||||
|
|
||||||
const SOURCE_EXTENSION = /\.[jt]s$/;
|
const SOURCE_EXTENSION = /\.[jt]s$/;
|
||||||
const PREAMBLE = `/**
|
const PREAMBLE = `/**
|
||||||
|
@ -33,11 +32,23 @@ const PREAMBLE = `/**
|
||||||
export interface AngularCompilerOptions {
|
export interface AngularCompilerOptions {
|
||||||
// Absolute path to a directory where generated file structure is written
|
// Absolute path to a directory where generated file structure is written
|
||||||
genDir: string;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CodeGenerator {
|
export class CodeGenerator {
|
||||||
constructor(private ngOptions: AngularCompilerOptions, private basePath: string,
|
constructor(private ngOptions: AngularCompilerOptions, private basePath: string,
|
||||||
public program: ts.Program, public host: CodeGeneratorHost,
|
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,
|
||||||
private reflectorHost: NodeReflectorHost) {}
|
private reflectorHost: NodeReflectorHost) {}
|
||||||
|
@ -105,20 +116,11 @@ export class CodeGenerator {
|
||||||
.map(generateOneFile));
|
.map(generateOneFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(ngOptions: AngularCompilerOptions, parsed: ts.ParsedCommandLine, basePath: string,
|
static create(ngOptions: AngularCompilerOptions, program: ts.Program, options: ts.CompilerOptions,
|
||||||
compilerHost: ts.CompilerHost):
|
compilerHost: ts.CompilerHost): CodeGenerator {
|
||||||
{errors?: ts.Diagnostic[], generator?: CodeGenerator} {
|
|
||||||
const program = ts.createProgram(parsed.fileNames, parsed.options, compilerHost);
|
|
||||||
const errors = program.getOptionsDiagnostics();
|
|
||||||
if (errors && errors.length) {
|
|
||||||
return {errors};
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadataCollector = new MetadataCollector();
|
|
||||||
const reflectorHost =
|
|
||||||
new NodeReflectorHost(program, metadataCollector, compilerHost, parsed.options);
|
|
||||||
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 staticReflector = new StaticReflector(reflectorHost);
|
const staticReflector = new StaticReflector(reflectorHost);
|
||||||
const htmlParser = new HtmlParser();
|
const htmlParser = new HtmlParser();
|
||||||
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser);
|
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser);
|
||||||
|
@ -132,10 +134,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 {
|
return new CodeGenerator(ngOptions, ngOptions.basePath, program, compilerHost, staticReflector,
|
||||||
generator: new CodeGenerator(ngOptions, basePath, program,
|
resolver, offlineCompiler, reflectorHost);
|
||||||
wrapCompilerHost(compilerHost, parsed.options), staticReflector,
|
|
||||||
resolver, offlineCompiler, reflectorHost)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {convertDecorators} from 'tsickle';
|
import {convertDecorators} from 'tsickle';
|
||||||
|
import {NodeReflectorHost} from './reflector_host';
|
||||||
|
import {AngularCompilerOptions} from './codegen';
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
function debug(msg: string, ...o: any[]) {
|
function debug(msg: string, ...o: any[]) {
|
||||||
|
@ -31,14 +33,15 @@ export abstract class DelegatingHost implements ts.CompilerHost {
|
||||||
trace = (s: string) => this.delegate.trace(s);
|
trace = (s: string) => this.delegate.trace(s);
|
||||||
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
||||||
}
|
}
|
||||||
const TSICKLE_SUPPORT = `interface DecoratorInvocation {
|
|
||||||
type: Function;
|
export class TsickleHost extends DelegatingHost {
|
||||||
args?: any[];
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export class CodeGeneratorHost extends DelegatingHost {
|
|
||||||
// Additional diagnostics gathered by pre- and post-emit transformations.
|
// Additional diagnostics gathered by pre- and post-emit transformations.
|
||||||
public diagnostics: ts.Diagnostic[] = [];
|
public diagnostics: ts.Diagnostic[] = [];
|
||||||
|
private TSICKLE_SUPPORT = `interface DecoratorInvocation {
|
||||||
|
type: Function;
|
||||||
|
args?: any[];
|
||||||
|
}
|
||||||
|
`;
|
||||||
constructor(delegate: ts.CompilerHost, private options: ts.CompilerOptions) { super(delegate); }
|
constructor(delegate: ts.CompilerHost, private options: ts.CompilerOptions) { super(delegate); }
|
||||||
|
|
||||||
getSourceFile =
|
getSourceFile =
|
||||||
|
@ -50,14 +53,40 @@ export class CodeGeneratorHost extends DelegatingHost {
|
||||||
if (converted.diagnostics) {
|
if (converted.diagnostics) {
|
||||||
this.diagnostics.push(...converted.diagnostics);
|
this.diagnostics.push(...converted.diagnostics);
|
||||||
}
|
}
|
||||||
newContent = TSICKLE_SUPPORT + converted.output;
|
newContent = this.TSICKLE_SUPPORT + converted.output;
|
||||||
debug(newContent);
|
debug(newContent);
|
||||||
}
|
}
|
||||||
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapCompilerHost(delegate: ts.CompilerHost,
|
export class MetadataWriterHost extends DelegatingHost {
|
||||||
options: ts.CompilerOptions): CodeGeneratorHost {
|
private reflectorHost: NodeReflectorHost;
|
||||||
return new CodeGeneratorHost(delegate, options);
|
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 (!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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export {CodeGenerator} from './codegen';
|
export {CodeGenerator} from './codegen';
|
||||||
export {NodeReflectorHost} from './reflector_host';
|
export {NodeReflectorHost} from './reflector_host';
|
||||||
export {wrapCompilerHost, CodeGeneratorHost} from './compiler_host';
|
export {TsickleHost, MetadataWriterHost} from './compiler_host';
|
||||||
|
|
|
@ -10,8 +10,10 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {tsc, check} from './tsc';
|
import {tsc, check} from './tsc';
|
||||||
|
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;
|
const DEBUG = false;
|
||||||
|
|
||||||
|
@ -20,37 +22,61 @@ function debug(msg: string, ...o: any[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main(project: string, basePath?: string): Promise<any> {
|
export function main(project: string, basePath?: string): Promise<any> {
|
||||||
// file names in tsconfig are resolved relative to this absolute path
|
try {
|
||||||
basePath = path.join(process.cwd(), basePath || project);
|
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
|
// read the configuration options from wherever you store them
|
||||||
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
||||||
|
ngOptions.basePath = basePath;
|
||||||
|
|
||||||
const host = ts.createCompilerHost(parsed.options, true);
|
const host = ts.createCompilerHost(parsed.options, true);
|
||||||
const {errors, generator} = CodeGenerator.create(ngOptions, parsed, basePath, host);
|
|
||||||
check(errors);
|
|
||||||
|
|
||||||
return generator.codegen()
|
let codegenStep: Promise<any>;
|
||||||
// use our compiler host, which wraps the built-in one from TypeScript
|
|
||||||
// This allows us to add features like --stripDesignTimeDecorators to optimize your
|
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||||
// application more.
|
const errors = program.getOptionsDiagnostics();
|
||||||
.then(() => tsc.typeCheckAndEmit(generator.host, generator.program))
|
check(errors);
|
||||||
.catch(rejected => {
|
|
||||||
console.error('Compile failed\n', rejected.message);
|
const doCodegen =
|
||||||
throw new Error(rejected);
|
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));
|
||||||
try {
|
main(args.p || args.project || '.', args.basePath)
|
||||||
main(args.p || args.project || '.', args.basePath)
|
.then(exitCode => process.exit(exitCode))
|
||||||
.then(exitCode => process.exit(exitCode))
|
.catch(e => {
|
||||||
.catch(r => { process.exit(1); });
|
console.error(e.stack);
|
||||||
} catch (e) {
|
console.error("Compilation failed");
|
||||||
console.error(e.stack);
|
process.exit(1);
|
||||||
console.error("Compilation failed");
|
});
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,33 @@ import * as ts from 'typescript';
|
||||||
import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
|
import {MetadataCollector, ModuleMetadata} from 'ts-metadata-collector';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import {AngularCompilerOptions} from './codegen';
|
||||||
|
|
||||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
const DTS = /\.d\.ts$/;
|
const DTS = /\.d\.ts$/;
|
||||||
|
|
||||||
export class NodeReflectorHost implements StaticReflectorHost {
|
export class NodeReflectorHost implements StaticReflectorHost {
|
||||||
constructor(private program: ts.Program, private metadataCollector: MetadataCollector,
|
private metadataCollector = new MetadataCollector();
|
||||||
private compilerHost: ts.CompilerHost, private options: ts.CompilerOptions) {}
|
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
|
||||||
|
private options: ts.CompilerOptions, private ngOptions: 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'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
private resolve(m: string, containingFile: string) {
|
private resolve(m: string, containingFile: string) {
|
||||||
const resolved =
|
const resolved =
|
||||||
ts.resolveModuleName(m, containingFile, this.options, this.compilerHost).resolvedModule;
|
ts.resolveModuleName(m, containingFile, this.options, this.compilerHost).resolvedModule;
|
||||||
|
@ -51,7 +70,7 @@ export class NodeReflectorHost implements StaticReflectorHost {
|
||||||
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.compilerHost.getCurrentDirectory(), 'index.ts');
|
containingFile = path.join(this.ngOptions.basePath, 'index.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -134,8 +153,10 @@ export class NodeReflectorHost implements StaticReflectorHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||||
if (DTS.test(emitFilePath)) {
|
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||||
const path = emitFilePath.replace(DTS, '.metadata.json');
|
// released
|
||||||
|
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
|
||||||
|
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
|
||||||
const metadata =
|
const metadata =
|
||||||
this.metadataCollector.getMetadata(sourceFile, this.program.getTypeChecker());
|
this.metadataCollector.getMetadata(sourceFile, this.program.getTypeChecker());
|
||||||
if (metadata && metadata.metadata) {
|
if (metadata && metadata.metadata) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {StringMapWrapper, ListWrapper} from '@angular/facade/src/collection';
|
import {StringMapWrapper, ListWrapper} from '@angular/core/src/facade/collection';
|
||||||
import {
|
import {
|
||||||
isArray,
|
isArray,
|
||||||
isPresent,
|
isPresent,
|
||||||
|
@ -6,7 +6,7 @@ import {
|
||||||
isPrimitive,
|
isPrimitive,
|
||||||
isStringMap,
|
isStringMap,
|
||||||
FunctionWrapper
|
FunctionWrapper
|
||||||
} from '@angular/facade/src/lang';
|
} from '@angular/core/src/facade/lang';
|
||||||
import {
|
import {
|
||||||
AttributeMetadata,
|
AttributeMetadata,
|
||||||
DirectiveMetadata,
|
DirectiveMetadata,
|
||||||
|
@ -58,6 +58,9 @@ export interface StaticReflectorHost {
|
||||||
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
|
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
|
||||||
|
|
||||||
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol;
|
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol;
|
||||||
|
|
||||||
|
angularImportLocations():
|
||||||
|
{coreDecorators: string, diDecorators: string, diMetadata: string, provider: string};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,6 +120,9 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
public parameters(type: StaticSymbol): any[] {
|
public parameters(type: StaticSymbol): any[] {
|
||||||
|
if (!(type instanceof StaticSymbol)) {
|
||||||
|
throw new Error(`parameters recieved ${JSON.stringify(type)} which is not a StaticSymbol`);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let parameters = this.parameterCache.get(type);
|
let parameters = this.parameterCache.get(type);
|
||||||
if (!isPresent(parameters)) {
|
if (!isPresent(parameters)) {
|
||||||
|
@ -148,7 +154,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
return parameters;
|
return parameters;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Failed on type ${type} with error ${e}`);
|
console.log(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,10 +176,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeConversionMap(): void {
|
private initializeConversionMap(): void {
|
||||||
let coreDecorators = 'angular2/src/core/metadata';
|
const {coreDecorators, diDecorators, diMetadata, provider} = this.host.angularImportLocations();
|
||||||
let diDecorators = 'angular2/src/core/di/decorators';
|
|
||||||
let diMetadata = 'angular2/src/core/di/metadata';
|
|
||||||
let provider = 'angular2/src/core/di/provider';
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(provider, 'Provider'), Provider);
|
this.registerDecoratorOrConstructor(this.host.findDeclaration(provider, 'Provider'), Provider);
|
||||||
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'),
|
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'),
|
||||||
|
|
|
@ -241,6 +241,14 @@ describe('StaticReflector', () => {
|
||||||
class MockReflectorHost implements StaticReflectorHost {
|
class MockReflectorHost implements StaticReflectorHost {
|
||||||
private staticTypeCache = new Map<string, StaticSymbol>();
|
private staticTypeCache = new Map<string, StaticSymbol>();
|
||||||
|
|
||||||
|
angularImportLocations() {
|
||||||
|
return {
|
||||||
|
coreDecorators: 'angular2/src/core/metadata',
|
||||||
|
diDecorators: 'angular2/src/core/di/decorators',
|
||||||
|
diMetadata: 'angular2/src/core/di/metadata',
|
||||||
|
provider: 'angular2/src/core/di/provider'
|
||||||
|
};
|
||||||
|
}
|
||||||
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol {
|
getStaticSymbol(moduleId: string, declarationFile: string, name: string): StaticSymbol {
|
||||||
var cacheKey = `${declarationFile}:${name}`;
|
var cacheKey = `${declarationFile}:${name}`;
|
||||||
var result = this.staticTypeCache.get(cacheKey);
|
var result = this.staticTypeCache.get(cacheKey);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as ts from 'typescript';
|
||||||
import {lstatSync} from 'fs';
|
import {lstatSync} from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {AngularCompilerOptions} from './codegen';
|
import {AngularCompilerOptions} from './codegen';
|
||||||
import {CodeGeneratorHost} from './compiler_host';
|
import {TsickleHost} from './compiler_host';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our interface to the TypeScript standard compiler.
|
* Our interface to the TypeScript standard compiler.
|
||||||
|
@ -14,7 +14,8 @@ export interface CompilerInterface {
|
||||||
readConfiguration(
|
readConfiguration(
|
||||||
project: string,
|
project: string,
|
||||||
basePath: string): {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
basePath: string): {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
||||||
typeCheckAndEmit(compilerHost: CodeGeneratorHost, oldProgram?: ts.Program): number;
|
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
|
||||||
|
emit(compilerHost: ts.CompilerHost, program: ts.Program): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
|
@ -72,27 +73,32 @@ export class Tsc implements CompilerInterface {
|
||||||
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
||||||
}
|
}
|
||||||
|
|
||||||
typeCheckAndEmit(compilerHost: CodeGeneratorHost, oldProgram?: ts.Program): number {
|
typeCheck(compilerHost: ts.CompilerHost, oldProgram: ts.Program): void {
|
||||||
|
// Create a new program since codegen files were created after making the old program
|
||||||
const program =
|
const program =
|
||||||
ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost, oldProgram);
|
ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost, oldProgram);
|
||||||
debug("Checking global diagnostics...");
|
debug("Checking global diagnostics...");
|
||||||
check(program.getGlobalDiagnostics());
|
check(program.getGlobalDiagnostics());
|
||||||
|
|
||||||
|
let diagnostics: ts.Diagnostic[] = [];
|
||||||
debug("Type checking...");
|
debug("Type checking...");
|
||||||
{
|
|
||||||
let diagnostics: ts.Diagnostic[] = [];
|
for (let sf of program.getSourceFiles()) {
|
||||||
for (let sf of program.getSourceFiles()) {
|
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
|
||||||
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
|
|
||||||
}
|
|
||||||
check(diagnostics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("Emitting outputs...");
|
|
||||||
|
|
||||||
const {diagnostics, emitSkipped} = program.emit();
|
|
||||||
check(diagnostics);
|
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);
|
check(compilerHost.diagnostics);
|
||||||
return emitSkipped ? 1 : 0;
|
return emitResult.emitSkipped ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export var tsc: CompilerInterface = new Tsc();
|
export var tsc: CompilerInterface = new Tsc();
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
// For TypeScript 1.8, we have to lay out generated files
|
// For TypeScript 1.8, we have to lay out generated files
|
||||||
// in the same source directory with your code.
|
// in the same source directory with your code.
|
||||||
"genDir": "."
|
"genDir": ".",
|
||||||
|
"legacyPackageLayout": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "../../../dist/tools/compiler_cli/test/built",
|
"outDir": "../../../dist/tools/compiler_cli/test/built",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
"declaration": true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These options are only needed because the test depends
|
* These options are only needed because the test depends
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ts-metadata-collector",
|
"name": "ts-metadata-collector",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"description": "Collects static Decorator metadata from TypeScript sources",
|
"description": "Collects static Decorator metadata from TypeScript sources",
|
||||||
"homepage": "https://github.com/angular/angular/tree/master/tools/metadata",
|
"homepage": "https://github.com/angular/angular/tree/master/tools/metadata",
|
||||||
"bugs": "https://github.com/angular/angular/issues",
|
"bugs": "https://github.com/angular/angular/issues",
|
||||||
|
@ -11,6 +11,6 @@
|
||||||
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^1.8.9 || ^1.9"
|
"typescript": "^1.8.9 || ^1.9.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue