feat(i18n): xtb serializer
This commit is contained in:
@ -84,14 +84,14 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
let filePaths =
|
||||
const filePaths =
|
||||
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
|
||||
let fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
let ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
let analyzedNgModules = this.compiler.analyzeModules(ngModules);
|
||||
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
|
||||
return Promise
|
||||
.all(fileMetas.map(
|
||||
(fileMeta) => this.compiler
|
||||
|
@ -20,25 +20,12 @@ export var CompileMetadataResolver: typeof _c.CompileMetadataResolver = _c.Compi
|
||||
export type HtmlParser = _c.HtmlParser;
|
||||
export var HtmlParser: typeof _c.HtmlParser = _c.HtmlParser;
|
||||
|
||||
export type I18nHtmlParser = _c.I18nHtmlParser;
|
||||
export var I18nHtmlParser: typeof _c.I18nHtmlParser = _c.I18nHtmlParser;
|
||||
|
||||
export type MessageExtractor = _c.MessageExtractor;
|
||||
export var MessageExtractor: typeof _c.MessageExtractor = _c.MessageExtractor;
|
||||
|
||||
export type ExtractionResult = _c.ExtractionResult;
|
||||
export var ExtractionResult: typeof _c.ExtractionResult = _c.ExtractionResult;
|
||||
|
||||
export type Message = _c.Message;
|
||||
export var Message: typeof _c.Message = _c.Message;
|
||||
|
||||
export var removeDuplicates: typeof _c.removeDuplicates = _c.removeDuplicates;
|
||||
export var serializeXmb: typeof _c.serializeXmb = _c.serializeXmb;
|
||||
export var deserializeXmb: typeof _c.deserializeXmb = _c.deserializeXmb;
|
||||
|
||||
export type ParseError = _c.ParseError;
|
||||
export var ParseError: typeof _c.ParseError = _c.ParseError;
|
||||
|
||||
export type InterpolationConfig = _c.InterpolationConfig;
|
||||
export var InterpolationConfig: typeof _c.InterpolationConfig = _c.InterpolationConfig;
|
||||
|
||||
export type DirectiveNormalizer = _c.DirectiveNormalizer;
|
||||
export var DirectiveNormalizer: typeof _c.DirectiveNormalizer = _c.DirectiveNormalizer;
|
||||
|
||||
|
@ -10,121 +10,124 @@
|
||||
|
||||
/**
|
||||
* Extract i18n messages from source code
|
||||
*
|
||||
* TODO(vicb): factorize code with the CodeGenerator
|
||||
*/
|
||||
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ComponentMetadata, NgModuleMetadata, ViewEncapsulation} from '@angular/core';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as path from 'path';
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private';
|
||||
import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, NgModuleCompiler, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler, ParseError} from './compiler_private';
|
||||
import {Console} from './core_private';
|
||||
|
||||
import {ReflectorHost} from './reflector_host';
|
||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||
|
||||
function extract(
|
||||
ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
|
||||
return Extractor.create(ngOptions, program, host).extract();
|
||||
const extractor = Extractor.create(ngOptions, program, host);
|
||||
const bundlePromise: Promise<compiler.i18n.MessageBundle> = extractor.extract();
|
||||
|
||||
return (bundlePromise).then(messageBundle => {
|
||||
const serializer = new compiler.i18n.Xmb();
|
||||
const dstPath = path.join(ngOptions.genDir, 'messages.xmb');
|
||||
host.writeFile(dstPath, messageBundle.write(serializer), false);
|
||||
});
|
||||
}
|
||||
|
||||
const _dirPaths = new Map<compiler.CompileDirectiveMetadata, string>();
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
|
||||
const _GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
|
||||
class Extractor {
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private _options: tsc.AngularCompilerOptions, private _program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private _resolver: CompileMetadataResolver, private _normalizer: DirectiveNormalizer,
|
||||
private _reflectorHost: ReflectorHost, private _extractor: MessageExtractor) {}
|
||||
private program: ts.Program, public host: ts.CompilerHost,
|
||||
private staticReflector: StaticReflector, private messageBundle: compiler.i18n.MessageBundle,
|
||||
private reflectorHost: ReflectorHost, private metadataResolver: CompileMetadataResolver,
|
||||
private directiveNormalizer: DirectiveNormalizer,
|
||||
private compiler: compiler.OfflineCompiler) {}
|
||||
|
||||
private _extractCmpMessages(components: compiler.CompileDirectiveMetadata[]): ExtractionResult {
|
||||
if (!components || !components.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let messages: Message[] = [];
|
||||
let errors: ParseError[] = [];
|
||||
components.forEach(metadata => {
|
||||
let url = _dirPaths.get(metadata);
|
||||
let result = this._extractor.extract(metadata.template.template, url);
|
||||
errors = errors.concat(result.errors);
|
||||
messages = messages.concat(result.messages);
|
||||
});
|
||||
|
||||
// Extraction Result might contain duplicate messages at this point
|
||||
return new ExtractionResult(messages, errors);
|
||||
}
|
||||
|
||||
private _readComponents(absSourcePath: string): Promise<compiler.CompileDirectiveMetadata>[] {
|
||||
const result: Promise<compiler.CompileDirectiveMetadata>[] = [];
|
||||
const metadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
||||
if (!metadata) {
|
||||
private readFileMetadata(absSourcePath: string): FileMetadata {
|
||||
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
||||
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${absSourcePath}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
const symbols = Object.keys(metadata['metadata']);
|
||||
const metadata = moduleMetadata['metadata'];
|
||||
const symbols = metadata && Object.keys(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.getDirectiveMetadata(<any>staticType, false);
|
||||
|
||||
if (directive && directive.isComponent) {
|
||||
let promise = this._normalizer.normalizeDirective(directive).asyncResult;
|
||||
promise.then(md => _dirPaths.set(md, absSourcePath));
|
||||
result.push(promise);
|
||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
||||
// Ignore symbols that are only included to record error information.
|
||||
continue;
|
||||
}
|
||||
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
|
||||
const annotations = this.staticReflector.annotations(staticType);
|
||||
annotations.forEach((annotation) => {
|
||||
if (annotation instanceof NgModuleMetadata) {
|
||||
result.ngModules.push(staticType);
|
||||
} else if (annotation instanceof ComponentMetadata) {
|
||||
result.components.push(staticType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
extract(): Promise<any> {
|
||||
_dirPaths.clear();
|
||||
extract(): Promise<compiler.i18n.MessageBundle> {
|
||||
const filePaths =
|
||||
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
const promises = this._program.getSourceFiles()
|
||||
.map(sf => sf.fileName)
|
||||
.filter(f => !_GENERATED_FILES.test(f))
|
||||
.map(
|
||||
(absSourcePath: string): Promise<any> =>
|
||||
Promise.all(this._readComponents(absSourcePath))
|
||||
.then(metadatas => this._extractCmpMessages(metadatas))
|
||||
.catch(e => console.error(e.stack)));
|
||||
let bundlePromise =
|
||||
Promise
|
||||
.all(fileMetas.map((fileMeta) => {
|
||||
const url = fileMeta.fileUrl;
|
||||
return Promise.all(fileMeta.components.map(compType => {
|
||||
const compMeta = this.metadataResolver.getDirectiveMetadata(<any>compType);
|
||||
const ngModule = analyzedNgModules.ngModuleByComponent.get(compType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Cannot determine the module for component ${compMeta.type.name}!`);
|
||||
}
|
||||
return Promise
|
||||
.all([compMeta, ...ngModule.transitiveModule.directives].map(
|
||||
dirMeta =>
|
||||
this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
|
||||
.then((normalizedCompWithDirectives) => {
|
||||
const compMeta = normalizedCompWithDirectives[0];
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, url, interpolationConfig));
|
||||
});
|
||||
}));
|
||||
}))
|
||||
.then(_ => this.messageBundle)
|
||||
.catch((e) => { console.error(e.stack); });
|
||||
|
||||
let messages: Message[] = [];
|
||||
let errors: ParseError[] = [];
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(extractionResults => {
|
||||
extractionResults.filter(result => !!result).forEach(result => {
|
||||
messages = messages.concat(result.messages);
|
||||
errors = errors.concat(result.errors);
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
messages = removeDuplicates(messages);
|
||||
|
||||
let genPath = path.join(this._options.genDir, 'messages.xmb');
|
||||
let msgBundle = serializeXmb(messages);
|
||||
|
||||
this.host.writeFile(genPath, msgBundle, false);
|
||||
});
|
||||
return bundlePromise;
|
||||
}
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost): Extractor {
|
||||
options: tsc.AngularCompilerOptions, program: ts.Program, compilerHost: ts.CompilerHost,
|
||||
reflectorHostContext?: ReflectorHostContext): Extractor {
|
||||
const xhr: compiler.XHR = {
|
||||
get: (s: string) => {
|
||||
if (!compilerHost.fileExists(s)) {
|
||||
@ -134,35 +137,49 @@ class Extractor {
|
||||
return Promise.resolve(compilerHost.readFile(s));
|
||||
}
|
||||
};
|
||||
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
const reflectorHost = new ReflectorHost(program, compilerHost, options);
|
||||
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser = new HtmlParser();
|
||||
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: true,
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
|
||||
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const console = new Console();
|
||||
const tmplParser =
|
||||
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
config, console, elementSchemaRegistry, staticReflector);
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config),
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(reflectorHost));
|
||||
|
||||
// TODO(vicb): handle implicit
|
||||
const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
let messageBundle = new compiler.i18n.MessageBundle(htmlParser, [], {});
|
||||
|
||||
return new Extractor(
|
||||
options, program, compilerHost, staticReflector, resolver, normalizer, reflectorHost,
|
||||
extractor);
|
||||
program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer,
|
||||
offlineCompiler);
|
||||
}
|
||||
}
|
||||
|
||||
interface FileMetadata {
|
||||
fileUrl: string;
|
||||
components: StaticSymbol[];
|
||||
ngModules: StaticSymbol[];
|
||||
}
|
||||
|
||||
// Entry point
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
@ -170,7 +187,7 @@ if (require.main === module) {
|
||||
.then(exitCode => process.exit(exitCode))
|
||||
.catch(e => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
console.error('Extraction failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user