diff --git a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html index 5a532db930..56549a9fba 100644 --- a/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html +++ b/modules/@angular/compiler-cli/integrationtest/ngtools_src/app.component.html @@ -1,5 +1,5 @@
-

hello world

+

hello world

lazy
diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts index 66f89b6baf..99d1a31116 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -19,7 +19,6 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler const glob = require('glob'); - /** * Main method. * Standalone program that executes codegen using the ngtools API and tests that files were @@ -30,6 +29,7 @@ function main() { Promise.resolve() .then(() => codeGenTest()) + .then(() => i18nTest()) .then(() => lazyRoutesTest()) .then(() => { console.log('All done!'); @@ -42,7 +42,6 @@ function main() { }); } - function codeGenTest() { const basePath = path.join(__dirname, '../ngtools_src'); const project = path.join(basePath, 'tsconfig-build.json'); @@ -109,6 +108,67 @@ function codeGenTest() { }); } +function i18nTest() { + const basePath = path.join(__dirname, '../ngtools_src'); + const project = path.join(basePath, 'tsconfig-build.json'); + const readResources: string[] = []; + const wroteFiles: string[] = []; + + const config = tsc.readConfiguration(project, basePath); + const hostContext = new NodeCompilerHostContext(); + const delegateHost = ts.createCompilerHost(config.parsed.options, true); + const host: ts.CompilerHost = Object.assign( + {}, delegateHost, + {writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }}); + const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); + + config.ngOptions.basePath = basePath; + + console.log(`>>> running i18n extraction for ${project}`); + return __NGTOOLS_PRIVATE_API_2 + .extractI18n({ + basePath, + compilerOptions: config.parsed.options, program, host, + angularCompilerOptions: config.ngOptions, + i18nFormat: 'xlf', + readResource: (fileName: string) => { + readResources.push(fileName); + return hostContext.readResource(fileName); + }, + }) + .then(() => { + console.log(`>>> i18n extraction done, asserting read and wrote files`); + + const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true}); + + assert(wroteFiles.length == 1, `Expected a single message bundle file.`); + + assert( + wroteFiles[0].endsWith('/ngtools_src/messages.xlf'), + `Expected the bundle file to be "message.xlf".`); + + allFiles.forEach((fileName: string) => { + // Skip tsconfig. + if (fileName.match(/tsconfig-build.json$/)) { + return; + } + + // Assert that file was read. + if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) { + assert( + readResources.indexOf(fileName) != -1, + `Expected resource "${fileName}" to be read.`); + } + }); + + console.log(`done, no errors.`); + }) + .catch((e: any) => { + console.error(e.stack); + console.error('Extraction failed'); + throw e; + }); +} function lazyRoutesTest() { const basePath = path.join(__dirname, '../ngtools_src'); diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index 5562150ab5..0a137ff529 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -14,41 +14,15 @@ // Must be imported first, because angular2 decorators throws on load. import 'reflect-metadata'; -import * as compiler from '@angular/compiler'; import * as tsc from '@angular/tsc-wrapped'; -import * as path from 'path'; import * as ts from 'typescript'; import {Extractor} from './extractor'; function extract( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, - program: ts.Program, host: ts.CompilerHost) { - const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host); - - const bundlePromise: Promise = extractor.extract(); - - return (bundlePromise).then(messageBundle => { - let ext: string; - let serializer: compiler.Serializer; - const format = (cliOptions.i18nFormat || 'xlf').toLowerCase(); - - switch (format) { - case 'xmb': - ext = 'xmb'; - serializer = new compiler.Xmb(); - break; - case 'xliff': - case 'xlf': - default: - ext = 'xlf'; - serializer = new compiler.Xliff(); - break; - } - - const dstPath = path.join(ngOptions.genDir, `messages.${ext}`); - host.writeFile(dstPath, messageBundle.write(serializer), false); - }); + program: ts.Program, host: ts.CompilerHost): Promise { + return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat); } // Entry point diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index af49d16372..b51e1ce0d7 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -15,27 +15,70 @@ import 'reflect-metadata'; import * as compiler from '@angular/compiler'; import * as tsc from '@angular/tsc-wrapped'; +import * as path from 'path'; import * as ts from 'typescript'; -import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host'; +import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; +import {PathMappedCompilerHost} from './path_mapped_compiler_host'; export class Extractor { constructor( - private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost, + private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor, + public host: ts.CompilerHost, private ngCompilerHost: CompilerHost, private program: ts.Program) {} - extract(): Promise { - return this.ngExtractor.extract(this.program.getSourceFiles().map( - sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))); + extract(formatName: string): Promise { + // Checks the format and returns the extension + const ext = this.getExtension(formatName); + + const files = this.program.getSourceFiles().map( + sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)); + + const promiseBundle = this.ngExtractor.extract(files); + + return promiseBundle.then(bundle => { + const content = this.serialize(bundle, ext); + const dstPath = path.join(this.options.genDir, `messages.${ext}`); + this.host.writeFile(dstPath, content, false); + }); + } + + serialize(bundle: compiler.MessageBundle, ext: string): string { + let serializer: compiler.Serializer; + + switch (ext) { + case 'xmb': + serializer = new compiler.Xmb(); + break; + case 'xlf': + default: + serializer = new compiler.Xliff(); + } + + return bundle.write(serializer); + } + + getExtension(formatName: string): string { + const format = (formatName || 'xlf').toLowerCase(); + + if (format === 'xmb') return 'xmb'; + if (format === 'xlf' || format === 'xlif') return 'xlf'; + + throw new Error('Unsupported format "${formatName}"'); } static create( - options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, - moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor { - if (!ngCompilerHost) - ngCompilerHost = - new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost)); + options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost, + compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor { + if (!ngCompilerHost) { + const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; + const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost); + ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) : + new CompilerHost(program, options, context); + } + const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost); - return new Extractor(ngExtractor, ngCompilerHost, program); + + return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program); } } diff --git a/modules/@angular/compiler-cli/src/ngtools_api.ts b/modules/@angular/compiler-cli/src/ngtools_api.ts index 486a72f595..4fa9ab56e9 100644 --- a/modules/@angular/compiler-cli/src/ngtools_api.ts +++ b/modules/@angular/compiler-cli/src/ngtools_api.ts @@ -19,10 +19,10 @@ import * as ts from 'typescript'; import {CodeGenerator} from './codegen'; import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; +import {Extractor} from './extractor'; import {listLazyRoutesOfModule} from './ngtools_impl'; import {PathMappedCompilerHost} from './path_mapped_compiler_host'; - export interface NgTools_InternalApi_NG2_CodeGen_Options { basePath: string; compilerOptions: ts.CompilerOptions; @@ -50,9 +50,18 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options { // Every new property under this line should be optional. } - export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; } +export interface NgTools_InternalApi_NG2_ExtractI18n_Options { + basePath: string; + compilerOptions: ts.CompilerOptions; + program: ts.Program; + host: ts.CompilerHost; + angularCompilerOptions: AngularCompilerOptions; + i18nFormat: string; + readResource: (fileName: string) => Promise; + // Every new property under this line should be optional. +} /** * A ModuleResolutionHostAdapter that overrides the readResource() method with the one @@ -94,7 +103,6 @@ export class NgTools_InternalApi_NG_2 { return codeGenerator.codegen(); } - /** * @internal * @private @@ -124,4 +132,19 @@ export class NgTools_InternalApi_NG_2 { }, {}); } + + /** + * @internal + * @private + */ + static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise { + const hostContext: CompilerHostContext = + new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host); + + // Create the i18n extractor. + const extractor = Extractor.create( + options.angularCompilerOptions, options.program, options.host, hostContext); + + return extractor.extract(options.i18nFormat); + } } diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index 16e92c6499..a46e641b59 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -28,7 +28,6 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {NgModuleResolver} from '../ng_module_resolver'; import {ParseError} from '../parse_util'; import {PipeResolver} from '../pipe_resolver'; -import {Console} from '../private_import_core'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {createOfflineCompileUrlResolver} from '../url_resolver';