/** * Transform template html and css into executable code. * Intended to be used in a build step. */ import * as ts from 'typescript'; import * as path from 'path'; import * as compiler from 'angular2/compiler'; import {StaticReflector} from './static_reflector'; import {CompileMetadataResolver} from 'angular2/src/compiler/metadata_resolver'; import {HtmlParser} from 'angular2/src/compiler/html_parser'; import {DirectiveNormalizer} from 'angular2/src/compiler/directive_normalizer'; import {Lexer} from 'angular2/src/compiler/expression_parser/lexer'; import {Parser} from 'angular2/src/compiler/expression_parser/parser'; import {TemplateParser} from 'angular2/src/compiler/template_parser'; import {DomElementSchemaRegistry} from 'angular2/src/compiler/schema/dom_element_schema_registry'; import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; import {ViewCompiler} from 'angular2/src/compiler/view_compiler/view_compiler'; import {TypeScriptEmitter} from 'angular2/src/compiler/output/ts_emitter'; import {RouterLinkTransform} from 'angular2/src/router/directives/router_link_transform'; import {Parse5DomAdapter} from 'angular2/platform/server'; import {MetadataCollector} from 'ts-metadata-collector'; import {NodeReflectorHost} from './reflector_host'; import {wrapCompilerHost, CodeGeneratorHost} from './compiler_host'; const SOURCE_EXTENSION = /\.[jt]s$/; const PREAMBLE = `/** * This file is generated by the Angular 2 template compiler. * Do not edit. */ `; export interface AngularCompilerOptions { // Absolute path to a directory where generated file structure is written genDir: string; } export class CodeGenerator { constructor(private ngOptions: AngularCompilerOptions, private basePath: string, public program: ts.Program, public host: CodeGeneratorHost, private staticReflector: StaticReflector, private resolver: CompileMetadataResolver, private compiler: compiler.OfflineCompiler, private reflectorHost: NodeReflectorHost) {} private generateSource(metadatas: compiler.CompileDirectiveMetadata[]) { const normalize = (metadata: compiler.CompileDirectiveMetadata) => { const directiveType = metadata.type.runtime; const directives = this.resolver.getViewDirectivesMetadata(directiveType); const pipes = this.resolver.getViewPipesMetadata(directiveType); return new compiler.NormalizedComponentWithViewDirectives(metadata, directives, pipes); }; return this.compiler.compileTemplates(metadatas.map(normalize)); } private readComponents(absSourcePath: string) { const result: Promise[] = []; const metadata = this.staticReflector.getModuleMetadata(absSourcePath); if (!metadata) { console.log(`WARNING: no metadata found for ${absSourcePath}`); return result; } const symbols = Object.keys(metadata['metadata']); if (!symbols || !symbols.length) { return result; } for (const symbol of symbols) { const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath); let directive: compiler.CompileDirectiveMetadata; directive = this.resolver.maybeGetDirectiveMetadata(staticType); if (!directive || !directive.isComponent) { continue; } result.push(this.compiler.normalizeDirectiveMetadata(directive)); } return result; } codegen(): Promise { Parse5DomAdapter.makeCurrent(); const generateOneFile = (absSourcePath: string) => Promise.all(this.readComponents(absSourcePath)) .then((metadatas: compiler.CompileDirectiveMetadata[]) => { if (!metadatas || !metadatas.length) { return; } const generated = this.generateSource(metadatas); const sourceFile = this.program.getSourceFile(absSourcePath); // Write codegen in a directory structure matching the sources. // TODO(alexeagle): maybe use generated.moduleUrl instead of hardcoded ".ngfactory.ts" // TODO(alexeagle): relativize paths by the rootDirs option const emitPath = path.join(this.ngOptions.genDir, path.relative(this.basePath, absSourcePath)) .replace(SOURCE_EXTENSION, '.ngfactory.ts'); this.host.writeFile(emitPath, PREAMBLE + generated.source, false, () => {}, [sourceFile]); }) .catch((e) => { console.error(e.stack); }); return Promise.all(this.program.getRootFileNames() .filter(f => !/\.ngfactory\.ts$/.test(f)) .map(generateOneFile)); } static create(ngOptions: AngularCompilerOptions, parsed: ts.ParsedCommandLine, basePath: string, compilerHost: ts.CompilerHost): {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 urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); const staticReflector = new StaticReflector(reflectorHost); const htmlParser = new HtmlParser(); const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser); const parser = new Parser(new Lexer()); const tmplParser = new TemplateParser(parser, new DomElementSchemaRegistry(), htmlParser, /*console*/ null, [new RouterLinkTransform(parser)]); const offlineCompiler = new compiler.OfflineCompiler( normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(new compiler.CompilerConfig(true, true, true)), new TypeScriptEmitter()); const resolver = new CompileMetadataResolver( new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.ViewResolver(staticReflector), null, null, staticReflector); return { generator: new CodeGenerator(ngOptions, basePath, program, wrapCompilerHost(compilerHost, parsed.options), staticReflector, resolver, offlineCompiler, reflectorHost) }; } }