From 8d3b6d7f1cf7b290e16d853f8ddcdec406b8be54 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 2 Aug 2017 15:04:18 -0700 Subject: [PATCH] refactor(compiler-cli): fixup! --- integration/i18n/closure.conf | 30 ++++++ integration/i18n/e2e/app.e2e-spec.ts | 10 ++ integration/i18n/e2e/browser.config.json | 15 +++ integration/i18n/e2e/protractor.config.js | 16 +++ integration/i18n/e2e/tsconfig.json | 8 ++ integration/i18n/package.json | 32 ++++++ integration/i18n/src/app.ts | 11 +++ integration/i18n/src/hello-world.component.ts | 9 ++ integration/i18n/src/index.html | 18 ++++ integration/i18n/src/main.ts | 4 + integration/i18n/tsconfig.json | 30 ++++++ .../integrationtest/alt/src/bootstrap.ts | 12 --- .../integrationtest/test/all_spec.ts | 8 ++ .../integrationtest/test/basic_spec.ts | 11 +-- .../integrationtest/test/source_map_spec.ts | 3 +- .../integrationtest/test/util_alt.ts | 33 ------- .../integrationtest/tsconfig-build-alt.json | 32 ------ .../integrationtest/tsconfig-build.json | 4 +- packages/compiler-cli/package.json | 2 +- packages/compiler-cli/src/main.ts | 23 ++++- packages/compiler-cli/src/main2.ts | 15 --- packages/compiler-cli/src/ngc.ts | 30 +++++- packages/compiler-cli/src/perform-compile.ts | 97 +++++++++++-------- packages/compiler-cli/src/transformers/api.ts | 31 +++++- .../src/transformers/node_emitter.ts | 32 ++++-- .../compiler-cli/src/transformers/program.ts | 51 ++++++++-- packages/compiler-cli/test/ngc_spec.ts | 10 +- .../test/transformers/node_emitter_spec.ts | 6 +- packages/compiler-cli/tsconfig-build.json | 1 - scripts/ci/offline_compiler_test.sh | 5 +- tools/ngc-wrapped/index.ts | 6 +- 31 files changed, 407 insertions(+), 188 deletions(-) create mode 100644 integration/i18n/closure.conf create mode 100644 integration/i18n/e2e/app.e2e-spec.ts create mode 100644 integration/i18n/e2e/browser.config.json create mode 100644 integration/i18n/e2e/protractor.config.js create mode 100644 integration/i18n/e2e/tsconfig.json create mode 100644 integration/i18n/package.json create mode 100644 integration/i18n/src/app.ts create mode 100644 integration/i18n/src/hello-world.component.ts create mode 100644 integration/i18n/src/index.html create mode 100644 integration/i18n/src/main.ts create mode 100644 integration/i18n/tsconfig.json delete mode 100644 packages/compiler-cli/integrationtest/alt/src/bootstrap.ts delete mode 100644 packages/compiler-cli/integrationtest/test/util_alt.ts delete mode 100644 packages/compiler-cli/integrationtest/tsconfig-build-alt.json delete mode 100644 packages/compiler-cli/src/main2.ts diff --git a/integration/i18n/closure.conf b/integration/i18n/closure.conf new file mode 100644 index 00000000000000..8393a38b294080 --- /dev/null +++ b/integration/i18n/closure.conf @@ -0,0 +1,30 @@ +--compilation_level=ADVANCED_OPTIMIZATIONS +--language_out=ES5 +--js_output_file=dist/bundle.js +--output_manifest=dist/manifest.MF +--variable_renaming_report=dist/variable_renaming_report +--property_renaming_report=dist/property_renaming_report +--create_source_map=%outname%.map + +--warning_level=QUIET +--dependency_mode=STRICT +--rewrite_polyfills=false + +node_modules/zone.js/dist/zone_externs.js + +--js node_modules/rxjs/**.js +--process_common_js_modules +--module_resolution=node + +node_modules/@angular/core/@angular/core.js +--js_module_root=node_modules/@angular/core +node_modules/@angular/core/src/testability/testability.externs.js + +node_modules/@angular/common/@angular/common.js +--js_module_root=node_modules/@angular/common + +node_modules/@angular/platform-browser/@angular/platform-browser.js +--js_module_root=node_modules/@angular/platform-browser + +--js built/**.js +--entry_point=built/src/main diff --git a/integration/i18n/e2e/app.e2e-spec.ts b/integration/i18n/e2e/app.e2e-spec.ts new file mode 100644 index 00000000000000..afcc36025a61ec --- /dev/null +++ b/integration/i18n/e2e/app.e2e-spec.ts @@ -0,0 +1,10 @@ +import { browser, element, by } from 'protractor'; + +describe('i18n E2E Tests', function () { + it('remove i18n attributes', function () { + browser.get(''); + const div = element(by.css('div')); + expect(div.getAttribute('title')).not.toBe(null); + expect(div.getAttribute('i18n')).toBe(null); + }); +}); diff --git a/integration/i18n/e2e/browser.config.json b/integration/i18n/e2e/browser.config.json new file mode 100644 index 00000000000000..dcdae630083520 --- /dev/null +++ b/integration/i18n/e2e/browser.config.json @@ -0,0 +1,15 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "src", + "routes": { + "/dist": "dist", + "/node_modules": "node_modules" + }, + "middleware": { + "0": null + } + } +} \ No newline at end of file diff --git a/integration/i18n/e2e/protractor.config.js b/integration/i18n/e2e/protractor.config.js new file mode 100644 index 00000000000000..5bc4f6e640d190 --- /dev/null +++ b/integration/i18n/e2e/protractor.config.js @@ -0,0 +1,16 @@ +exports.config = { + specs: [ + '../built/e2e/*.e2e-spec.js' + ], + capabilities: { + browserName: 'chrome', + chromeOptions: { + args: ['--no-sandbox'], + binary: process.env.CHROME_BIN, + } + }, + directConnect: true, + baseUrl: 'http://localhost:8080/', + framework: 'jasmine', + useAllAngular2AppRoots: true +}; diff --git a/integration/i18n/e2e/tsconfig.json b/integration/i18n/e2e/tsconfig.json new file mode 100644 index 00000000000000..e112859422e720 --- /dev/null +++ b/integration/i18n/e2e/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "outDir": "../built/e2e", + "types": ["jasmine"], + // TODO(alexeagle): was required for Protractor 4.0.11 + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/integration/i18n/package.json b/integration/i18n/package.json new file mode 100644 index 00000000000000..14d555606d0f5c --- /dev/null +++ b/integration/i18n/package.json @@ -0,0 +1,32 @@ +{ + "name": "angular-integration", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@angular/animations": "file:../../dist/packages-dist/animations", + "@angular/common": "file:../../dist/packages-dist/common", + "@angular/compiler": "file:../../dist/packages-dist/compiler", + "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", + "@angular/core": "file:../../dist/packages-dist/core", + "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", + "@angular/platform-server": "file:../../dist/packages-dist/platform-server", + "@angular/tsc-wrapped": "file:../../dist/packages-dist/tsc-wrapped", + "google-closure-compiler": "20170409.0.0", + "rxjs": "5.3.1", + "typescript": "~2.3.1", + "zone.js": "0.8.6" + }, + "devDependencies": { + "@types/jasmine": "2.5.41", + "concurrently": "3.4.0", + "lite-server": "2.2.2", + "protractor": "file:../../node_modules/protractor" + }, + "scripts": { + "closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf", + "test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first", + "serve": "lite-server -c e2e/browser.config.json", + "preprotractor": "tsc -p e2e", + "protractor": "protractor e2e/protractor.config.js" + } +} \ No newline at end of file diff --git a/integration/i18n/src/app.ts b/integration/i18n/src/app.ts new file mode 100644 index 00000000000000..31ecac712b0d23 --- /dev/null +++ b/integration/i18n/src/app.ts @@ -0,0 +1,11 @@ +import {HelloWorldComponent} from './hello-world.component'; + +import {NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; + +@NgModule({ + declarations: [HelloWorldComponent], + bootstrap: [HelloWorldComponent], + imports: [BrowserModule], +}) +export class AppModule {} diff --git a/integration/i18n/src/hello-world.component.ts b/integration/i18n/src/hello-world.component.ts new file mode 100644 index 00000000000000..5aabbddd93099f --- /dev/null +++ b/integration/i18n/src/hello-world.component.ts @@ -0,0 +1,9 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'hello-world-app', + template: `
Hello {{ name }}!
`, +}) +export class HelloWorldComponent { + name: string = 'world'; +} diff --git a/integration/i18n/src/index.html b/integration/i18n/src/index.html new file mode 100644 index 00000000000000..ffbd2616e9d0bc --- /dev/null +++ b/integration/i18n/src/index.html @@ -0,0 +1,18 @@ + + + + + + Hello World + + + + + Loading... + + + + + + + \ No newline at end of file diff --git a/integration/i18n/src/main.ts b/integration/i18n/src/main.ts new file mode 100644 index 00000000000000..81d2aa7b15f7a5 --- /dev/null +++ b/integration/i18n/src/main.ts @@ -0,0 +1,4 @@ +import {platformBrowser} from '@angular/platform-browser'; +import {AppModuleNgFactory} from './app.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/integration/i18n/tsconfig.json b/integration/i18n/tsconfig.json new file mode 100644 index 00000000000000..34cc0b9fb285dc --- /dev/null +++ b/integration/i18n/tsconfig.json @@ -0,0 +1,30 @@ +{ + "angularCompilerOptions": { + "annotationsAs": "static fields", + "annotateForClosureCompiler": true, + "alwaysCompileGeneratedCode": true + }, + + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node", + // TODO(i): strictNullChecks should turned on but are temporarily disabled due to #15432 + "strictNullChecks": false, + "target": "es6", + "noImplicitAny": false, + "sourceMap": false, + "experimentalDecorators": true, + "outDir": "built", + "rootDir": ".", + "declaration": true, + "types": [] + }, + + "exclude": [ + "vendor", + "node_modules", + "built", + "dist", + "e2e" + ] +} \ No newline at end of file diff --git a/packages/compiler-cli/integrationtest/alt/src/bootstrap.ts b/packages/compiler-cli/integrationtest/alt/src/bootstrap.ts deleted file mode 100644 index d6d7aedc7f6b80..00000000000000 --- a/packages/compiler-cli/integrationtest/alt/src/bootstrap.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {BasicComp} from '../../src/basic'; -import {MainModuleNgFactory} from './module.ngfactory'; - -MainModuleNgFactory.create(null).instance.appRef.bootstrap(BasicComp); diff --git a/packages/compiler-cli/integrationtest/test/all_spec.ts b/packages/compiler-cli/integrationtest/test/all_spec.ts index a44aee27b91d68..6fdd35ba6b6e4d 100644 --- a/packages/compiler-cli/integrationtest/test/all_spec.ts +++ b/packages/compiler-cli/integrationtest/test/all_spec.ts @@ -18,3 +18,11 @@ import './projection_spec'; import './query_spec'; import './source_map_spec'; import './jit_summaries_spec'; + +import * as ts from 'typescript'; + +const [major, minor] = ts.version.split('.'); + +if (+major < 2 || (+major === 2 && +minor < 3)) { + throw new Error('Must use TypeScript > 2.3 to have transformer support'); +} diff --git a/packages/compiler-cli/integrationtest/test/basic_spec.ts b/packages/compiler-cli/integrationtest/test/basic_spec.ts index e48e60074d1632..e9e7f0ac46777b 100644 --- a/packages/compiler-cli/integrationtest/test/basic_spec.ts +++ b/packages/compiler-cli/integrationtest/test/basic_spec.ts @@ -12,7 +12,6 @@ import * as path from 'path'; import {MultipleComponentsMyComp} from '../src/a/multiple_components'; import {BasicComp} from '../src/basic'; import {createComponent} from './util'; -import {createComponentAlt} from './util_alt'; describe('template codegen output', () => { const outDir = 'src'; @@ -37,9 +36,9 @@ describe('template codegen output', () => { expect(fs.readFileSync(dtsOutput, {encoding: 'utf-8'})).toContain('Basic'); }); - it('should write .ngfactory.ts for .d.ts inputs', () => { + it('should write .ngfactory.js for .d.ts inputs', () => { const factoryOutput = - path.join('node_modules', '@angular2-material', 'button', 'button.ngfactory.ts'); + path.join('node_modules', '@angular2-material', 'button', 'button.ngfactory.js'); expect(fs.existsSync(factoryOutput)).toBeTruthy(); }); @@ -95,11 +94,5 @@ describe('template codegen output', () => { expect(containerElement.attributes['title']).toBe('käännä teksti'); expect(containerElement.attributes['i18n-title']).toBeUndefined(); }); - - it('should have removed i18n markup event without translations', () => { - const containerElement = createComponentAlt(BasicComp).debugElement.children[0]; - expect(containerElement.attributes['title']).toBe('translate me'); - expect(containerElement.attributes['i18n-title']).toBeUndefined(); - }); }); }); diff --git a/packages/compiler-cli/integrationtest/test/source_map_spec.ts b/packages/compiler-cli/integrationtest/test/source_map_spec.ts index 431cccc4dd1804..5fd368b4016dc7 100644 --- a/packages/compiler-cli/integrationtest/test/source_map_spec.ts +++ b/packages/compiler-cli/integrationtest/test/source_map_spec.ts @@ -10,7 +10,8 @@ import './init'; import {BindingErrorComp} from '../src/errors'; import {createComponent} from './util'; -describe('source maps', () => { +// source maps does not currently work with the transformer pipeline +xdescribe('source maps', () => { it('should report source location for binding errors', () => { const comp = createComponent(BindingErrorComp); let error: any; diff --git a/packages/compiler-cli/integrationtest/test/util_alt.ts b/packages/compiler-cli/integrationtest/test/util_alt.ts deleted file mode 100644 index 972dc03ac9bc66..00000000000000 --- a/packages/compiler-cli/integrationtest/test/util_alt.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {NgModuleRef} from '@angular/core'; -import {ComponentFixture} from '@angular/core/testing'; -import {platformServerTesting} from '@angular/platform-server/testing'; - -import {MainModuleNgFactory} from '../alt/src/module.ngfactory'; -import {MainModule} from '../src/module'; - -let mainModuleRef: NgModuleRef = null !; -beforeEach((done) => { - platformServerTesting().bootstrapModuleFactory(MainModuleNgFactory).then((moduleRef: any) => { - mainModuleRef = moduleRef; - done(); - }); -}); - -export function createModule(): NgModuleRef { - return mainModuleRef; -} - -export function createComponentAlt(comp: {new (...args: any[]): C}): ComponentFixture { - const moduleRef = createModule(); - const compRef = - moduleRef.componentFactoryResolver.resolveComponentFactory(comp).create(moduleRef.injector); - return new ComponentFixture(compRef, null, false); -} diff --git a/packages/compiler-cli/integrationtest/tsconfig-build-alt.json b/packages/compiler-cli/integrationtest/tsconfig-build-alt.json deleted file mode 100644 index 2a1553a619883e..00000000000000 --- a/packages/compiler-cli/integrationtest/tsconfig-build-alt.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "angularCompilerOptions": { - // For TypeScript 1.8, we have to lay out generated files - // in the same source directory with your code. - "genDir": "./alt", - "debug": true, - "enableSummariesForJit": true, - "alwaysCompileGeneratedCode": true - }, - - "compilerOptions": { - "target": "es5", - "experimentalDecorators": true, - "noImplicitAny": true, - "strictNullChecks": true, - "skipLibCheck": true, - "moduleResolution": "node", - "rootDir": "", - "declaration": true, - "lib": ["es6", "dom"], - "baseUrl": ".", - // Prevent scanning up the directory tree for types - "typeRoots": ["node_modules/@types"], - "noUnusedLocals": true, - "sourceMap": true - }, - - "files": [ - "src/module", - "alt/src/bootstrap" - ] -} diff --git a/packages/compiler-cli/integrationtest/tsconfig-build.json b/packages/compiler-cli/integrationtest/tsconfig-build.json index 2928fa0a65b3d0..5c753f89bae75b 100644 --- a/packages/compiler-cli/integrationtest/tsconfig-build.json +++ b/packages/compiler-cli/integrationtest/tsconfig-build.json @@ -5,7 +5,9 @@ "genDir": ".", "debug": true, "enableSummariesForJit": true, - "alwaysCompileGeneratedCode": true + "alwaysCompileGeneratedCode": true, + "locale": "fi", + "i18nFormat": "xlf" }, "compilerOptions": { diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 487a14ea9a21fa..157562d82d6c1d 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -5,7 +5,7 @@ "main": "index.js", "typings": "index.d.ts", "bin": { - "ngc": "./src/main2.js", + "ngc": "./src/main.js", "ng-xi18n": "./src/extract_i18n.js" }, "dependencies": { diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 1d68f0d1a20cd2..9e4f1717603d02 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -12,8 +12,14 @@ import 'reflect-metadata'; import * as ts from 'typescript'; import * as tsc from '@angular/tsc-wrapped'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as ngc from './ngc'; + import {isSyntaxError} from '@angular/compiler'; +import {readConfiguration} from './perform-compile'; + import {CodeGenerator} from './codegen'; function codegen( @@ -45,6 +51,19 @@ export function main( // CLI entry point if (require.main === module) { - const args = require('minimist')(process.argv.slice(2)); - main(args).then((exitCode: number) => process.exit(exitCode)); + const args = process.argv.slice(2); + const parsedArgs = require('minimist')(args); + const project = parsedArgs.p || parsedArgs.project || '.'; + + const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project; + + // file names in tsconfig are resolved relative to this absolute path + const basePath = path.resolve(process.cwd(), projectDir); + const {ngOptions} = readConfiguration(project, basePath); + + if (ngOptions.disableTransformerPipeline) { + main(parsedArgs).then((exitCode: number) => process.exit(exitCode)); + } else { + process.exit(ngc.main(args, s => console.error(s))); + } } diff --git a/packages/compiler-cli/src/main2.ts b/packages/compiler-cli/src/main2.ts deleted file mode 100644 index 58f3ee79b6a8fd..00000000000000 --- a/packages/compiler-cli/src/main2.ts +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {main} from './ngc'; - -// CLI entry point -if (require.main === module) { - process.exit(main(process.argv.slice(2), s => console.error(s))); -} diff --git a/packages/compiler-cli/src/ngc.ts b/packages/compiler-cli/src/ngc.ts index 457ae8df30f06e..779d23e0bd5a6d 100644 --- a/packages/compiler-cli/src/ngc.ts +++ b/packages/compiler-cli/src/ngc.ts @@ -9,11 +9,12 @@ // Must be imported first, because Angular decorators throw on load. import 'reflect-metadata'; -import {isSyntaxError, syntaxError} from '@angular/compiler'; +import {isSyntaxError} from '@angular/compiler'; import * as fs from 'fs'; import * as path from 'path'; import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile'; +import {CompilerOptions} from './transformers/api'; export function main( args: string[], consoleError: (s: string) => void = console.error, @@ -28,11 +29,13 @@ export function main( const basePath = path.resolve(process.cwd(), projectDir); const {parsed, ngOptions} = readConfiguration(project, basePath, checkFunc); - const result = - performCompilation(basePath, parsed.fileNames, parsed.options, ngOptions, checkFunc); + // CLI arguments can override the i18n options + const ngcOptions = mergeCommandLine(parsedArgs, ngOptions); - return result.diagnostics.length === 0 ? 0 : 1; + const res = performCompilation( + basePath, parsed.fileNames, parsed.options, ngcOptions, consoleError, checkFunc); + return res.errorCode; } catch (e) { if (isSyntaxError(e)) { consoleError(e.message); @@ -44,3 +47,22 @@ export function main( return 2; } } + +// Merge command line parameters +function mergeCommandLine( + parsedArgs: {[k: string]: string}, ngOptions: CompilerOptions): CompilerOptions { + if (parsedArgs.i18nFile) ngOptions.i18nInFile = parsedArgs.i18nFile; + if (parsedArgs.i18nFormat) ngOptions.i18nInFormat = parsedArgs.i18nFormat; + if (parsedArgs.locale) ngOptions.i18nLocale = parsedArgs.locale; + const mt = parsedArgs.missingTranslation; + if (mt === 'error' || mt === 'warning' || mt === 'ignore') { + ngOptions.i18nMissingTranslations = mt; + } + + return ngOptions; +} + +// CLI entry point +if (require.main === module) { + process.exit(main(process.argv.slice(2), s => console.error(s))); +} \ No newline at end of file diff --git a/packages/compiler-cli/src/perform-compile.ts b/packages/compiler-cli/src/perform-compile.ts index 05502744c0fa22..0e7a9a047ad419 100644 --- a/packages/compiler-cli/src/perform-compile.ts +++ b/packages/compiler-cli/src/perform-compile.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {syntaxError} from '@angular/compiler'; +import {isSyntaxError, syntaxError} from '@angular/compiler'; import {createBundleIndexHost} from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; + import * as api from './transformers/api'; import * as ng from './transformers/entry_points'; @@ -100,52 +101,72 @@ export function readConfiguration( return {parsed, ngOptions}; } +/** + * Returns an object with two properties: + * - `errorCode` is 0 when the compilation was successful, + * - `result` is an `EmitResult` when the errorCode is 0, `undefined` otherwise. + */ export function performCompilation( - basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any, - checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics, - tsCompilerHost?: ts.CompilerHost): tsickle.EmitResult { - ngOptions.basePath = basePath; - ngOptions.genDir = basePath; - - let host = tsCompilerHost || ts.createCompilerHost(options, true); - host.realpath = p => p; + basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: api.CompilerOptions, + consoleError: (s: string) => void = console.error, + checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics, + tsCompilerHost?: ts.CompilerHost): {errorCode: number, result?: api.EmitResult} { + try { + ngOptions.basePath = basePath; + ngOptions.genDir = basePath; + + let host = tsCompilerHost || ts.createCompilerHost(options, true); + host.realpath = p => p; + + const rootFileNames = files.map(f => path.normalize(f)); + + const addGeneratedFileName = (fileName: string) => { + if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) { + rootFileNames.push(fileName); + } + }; + + if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) { + const {host: bundleHost, indexName, errors} = + createBundleIndexHost(ngOptions, rootFileNames, host); + if (errors) checkFunc(basePath, errors); + if (indexName) addGeneratedFileName(indexName); + host = bundleHost; + } - const rootFileNames = files.map(f => path.normalize(f)); + const ngHostOptions = {...options, ...ngOptions}; + const ngHost = ng.createHost({tsHost: host, options: ngHostOptions}); - const addGeneratedFileName = (fileName: string) => { - if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) { - rootFileNames.push(fileName); - } - }; + const ngProgram = + ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions}); - if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) { - const {host: bundleHost, indexName, errors} = - createBundleIndexHost(ngOptions, rootFileNames, host); - if (errors) checkFunc(basePath, errors); - if (indexName) addGeneratedFileName(indexName); - host = bundleHost; - } + // Check parameter diagnostics + checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics()); - const ngHostOptions = {...options, ...ngOptions}; - const ngHost = ng.createHost({tsHost: host, options: ngHostOptions}); + // Check syntactic diagnostics + checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics()); - const ngProgram = - ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions}); + // Check TypeScript semantic and Angular structure diagnostics + checkFunc( + basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics()); - // Check parameter diagnostics - checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics()); + // Check Angular semantic diagnostics + checkFunc(basePath, ngProgram.getNgSemanticDiagnostics()); - // Check syntactic diagnostics - checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics()); + const result = ngProgram.emit({ + emitFlags: api.EmitFlags.Default | + ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) + }); - // Check TypeScript semantic and Angular structure diagnostics - checkFunc(basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics()); + checkFunc(basePath, result.diagnostics); - // Check Angular semantic diagnostics - checkFunc(basePath, ngProgram.getNgSemanticDiagnostics()); + return {errorCode: 0, result}; + } catch (e) { + if (isSyntaxError(e)) { + consoleError(e.message); + return {errorCode: 1}; + } - return ngProgram.emit({ - emitFlags: api.EmitFlags.Default | - ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) - }); + throw e; + } } \ No newline at end of file diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 653b18a6e68140..18f28a79c5d450 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -7,7 +7,6 @@ */ import {ParseSourceSpan} from '@angular/compiler'; -import * as tsickle from 'tsickle'; import * as ts from 'typescript'; export enum DiagnosticCategory { @@ -25,6 +24,7 @@ export interface Diagnostic { export interface CompilerOptions extends ts.CompilerOptions { // Absolute path to a directory where generated file structure is written. // If unspecified, generated files will be written alongside sources. + // @deprecated - no effect genDir?: string; // Path to the directory containing the tsconfig.json file. @@ -96,6 +96,22 @@ export interface CompilerOptions extends ts.CompilerOptions { // Whether to enable lowering expressions lambdas and expressions in a reference value // position. disableExpressionLowering?: boolean; + + // Locale of the application + i18nLocale?: string; + // Export format (xlf, xlf2 or xmb) + i18nFormat?: string; + // Path to the extracted message file + i18nOutFile?: string; + + // Import format if different from `i18nFormat` + i18nInFormat?: string; + // Locale of the imported translations + i18nInLocale?: string; + // Path to the translation file + i18nInFile?: string; + // How to handle missing messages + i18nMissingTranslations?: 'error'|'warning'|'ignore'; } export interface ModuleFilenameResolver { @@ -147,6 +163,11 @@ export enum EmitFlags { // afterTs?: ts.TransformerFactory[]; // } +export interface EmitResult extends ts.EmitResult { + modulesManifest: {modules: string[]; fileNames: string[];}; + externs: {[fileName: string]: string;}; +} + export interface Program { /** * Retrieve the TypeScript program used to produce semantic diagnostics and emit the sources. @@ -156,7 +177,7 @@ export interface Program { getTsProgram(): ts.Program; /** - * Retreive options diagnostics for the TypeScript options used to create the program. This is + * Retrieve options diagnostics for the TypeScript options used to create the program. This is * faster than calling `getTsProgram().getOptionsDiagnostics()` since it does not need to * collect Angular structural information to produce the errors. */ @@ -168,7 +189,7 @@ export interface Program { getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[]; /** - * Retrive the syntax diagnostics from TypeScript. This is faster than calling + * Retrieve the syntax diagnostics from TypeScript. This is faster than calling * `getTsProgram().getSyntacticDiagnostics()` since it does not need to collect Angular structural * information to produce the errors. */ @@ -189,7 +210,7 @@ export interface Program { getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[]; /** - * Retreive the semantic diagnostics from TypeScript. This is equivilent to calling + * Retrieve the semantic diagnostics from TypeScript. This is equivilent to calling * `getTsProgram().getSemanticDiagnostics()` directly and is included for completeness. */ getTsSemanticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken): @@ -228,5 +249,5 @@ export interface Program { emitFlags: EmitFlags, // transformers?: CustomTransformers, // See TODO above cancellationToken?: ts.CancellationToken, - }): tsickle.EmitResult; + }): EmitResult; } diff --git a/packages/compiler-cli/src/transformers/node_emitter.ts b/packages/compiler-cli/src/transformers/node_emitter.ts index 8adc7e8f6402f3..29cd86863c7bc7 100644 --- a/packages/compiler-cli/src/transformers/node_emitter.ts +++ b/packages/compiler-cli/src/transformers/node_emitter.ts @@ -19,8 +19,10 @@ export class TypeScriptNodeEmitter { updateSourceFile(sourceFile: ts.SourceFile, stmts: Statement[], preamble?: string): [ts.SourceFile, Map] { const converter = new _NodeEmitterVisitor(); - const statements = - stmts.map(stmt => stmt.visitStatement(converter, null)).filter(stmt => stmt != null); + // [].concat flattens the result so that each `visit...` method can also return an array of + // stmts. + const statements: any[] = [].concat( + ...stmts.map(stmt => stmt.visitStatement(converter, null)).filter(stmt => stmt != null)); const newSourceFile = ts.updateSourceFileNode( sourceFile, [...converter.getReexports(), ...converter.getImports(), ...statements]); if (preamble) { @@ -118,20 +120,30 @@ class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor { } } - return this.record( - stmt, ts.createVariableStatement( - this.getModifiers(stmt), - ts.createVariableDeclarationList([ts.createVariableDeclaration( - ts.createIdentifier(stmt.name), - /* type */ undefined, - (stmt.value && stmt.value.visitExpression(this, null)) || undefined)]))); + const varDeclList = ts.createVariableDeclarationList([ts.createVariableDeclaration( + ts.createIdentifier(stmt.name), + /* type */ undefined, + (stmt.value && stmt.value.visitExpression(this, null)) || undefined)]); + + if (stmt.hasModifier(StmtModifier.Exported)) { + // Note: We need to add an explicit variable and export declaration so that + // the variable can be referred in the same file as well. + const tsVarStmt = + this.record(stmt, ts.createVariableStatement(/* modifiers */[], varDeclList)); + const exportStmt = this.record( + stmt, ts.createExportDeclaration( + /*decorators*/ undefined, /*modifiers*/ undefined, + ts.createNamedExports([ts.createExportSpecifier(stmt.name, stmt.name)]))); + return [tsVarStmt, exportStmt]; + } + return this.record(stmt, ts.createVariableStatement(this.getModifiers(stmt), varDeclList)); } visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) { return this.record( stmt, ts.createFunctionDeclaration( /* decorators */ undefined, this.getModifiers(stmt), - /* astrictToken */ undefined, stmt.name, /* typeParameters */ undefined, + /* asteriskToken */ undefined, stmt.name, /* typeParameters */ undefined, stmt.params.map( p => ts.createParameter( /* decorators */ undefined, /* modifiers */ undefined, diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 483867bfb76e3d..a6084eb0ac548a 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -6,17 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompiler, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler'; -import {MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped'; -import {writeFileSync} from 'fs'; +import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler'; +import {MissingTranslationStrategy} from '@angular/core'; +import * as fs from 'fs'; import * as path from 'path'; import * as tsickle from 'tsickle'; import * as ts from 'typescript'; -import {CompilerHost as AotCompilerHost, CompilerHostContext} from '../compiler_host'; +import {CompilerHost as AotCompilerHost} from '../compiler_host'; import {TypeChecker} from '../diagnostics/check_types'; -import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, Program} from './api'; +import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, EmitResult, Program} from './api'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {getAngularEmitterTransformFactory} from './node_emitter_transform'; @@ -62,8 +62,39 @@ class AngularCompilerProgram implements Program { if (host.readResource) { this.aotCompilerHost.loadResource = host.readResource.bind(host); } - const {compiler} = createAotCompiler(this.aotCompilerHost, options); - this.compiler = compiler; + + let missingTranslation = MissingTranslationStrategy.Warning; + + switch (options.i18nMissingTranslations) { + case 'ignore': + missingTranslation = MissingTranslationStrategy.Ignore; + break; + case 'error': + missingTranslation = MissingTranslationStrategy.Error; + break; + } + + let translations: string = ''; + + if (options.i18nInFile) { + if (!options.locale) { + throw new Error(`The translation file (${options.i18nInFile}) locale must be provided.`); + } + translations = fs.readFileSync(options.i18nInFile, 'utf8'); + } else { + // No translations are provided, ignore any errors + // We still go through i18n to remove i18n attributes + missingTranslation = MissingTranslationStrategy.Ignore; + } + + const aotOptions: AotCompilerOptions = { + locale: options.i18nInLocale, + i18nFormat: options.i18nInFormat || options.i18nFormat, translations, missingTranslation, + enableLegacyTemplate: options.enableLegacyTemplate, + enableSummariesForJit: true, + }; + + this.compiler = createAotCompiler(this.aotCompilerHost, aotOptions).compiler; } // Program implementation @@ -116,14 +147,14 @@ class AngularCompilerProgram implements Program { getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; } emit({emitFlags = EmitFlags.Default, cancellationToken}: - {emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): tsickle.EmitResult { + {emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): EmitResult { const emitMap = new Map(); const tsickleCompilerHostOptions: tsickle.TransformerOptions = { googmodule: false, untyped: true, convertIndexImportShorthand: true, - transformDecorators: this.options.annotationsAs === 'static fields', + transformDecorators: this.options.annotationsAs !== 'decorators', transformTypesToClosure: this.options.annotateForClosureCompiler, }; @@ -303,7 +334,7 @@ function writeMetadata( const metadata = metadataCache.getMetadata(collectableFile); if (metadata) { const metadataText = JSON.stringify([metadata]); - writeFileSync(path, metadataText, {encoding: 'utf-8'}); + fs.writeFileSync(path, metadataText, {encoding: 'utf-8'}); } } } diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 23d811f3948aaa..4f08bacb017f82 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -339,7 +339,7 @@ describe('ngc command-line', () => { const mymodulejs = path.resolve(outDir, 'mymodule.js'); const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); expect(mymoduleSource).not.toContain('@fileoverview added by tsickle'); - expect(mymoduleSource).toContain('MyComp = __decorate(['); + expect(mymoduleSource).toContain('MyComp.decorators = ['); }); it('should add closure annotations', () => { @@ -372,11 +372,11 @@ describe('ngc command-line', () => { expect(mymoduleSource).toContain('@param {?} p'); }); - it('should add metadata as static fields', () => { + it('should add metadata as decorators', () => { writeConfig(`{ "extends": "./tsconfig-base.json", "angularCompilerOptions": { - "annotationsAs": "static fields" + "annotationsAs": "decorators" }, "files": ["mymodule.ts"] }`); @@ -398,7 +398,7 @@ describe('ngc command-line', () => { const mymodulejs = path.resolve(outDir, 'mymodule.js'); const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('MyComp.decorators = ['); + expect(mymoduleSource).toContain('MyComp = __decorate(['); }); }); @@ -642,7 +642,7 @@ describe('ngc command-line', () => { }); - expect(emitResult.diagnostics.length).toEqual(0); + expect(emitResult.errorCode).toEqual(0); shouldExist('index.js'); shouldExist('index.metadata.json'); }); diff --git a/packages/compiler-cli/test/transformers/node_emitter_spec.ts b/packages/compiler-cli/test/transformers/node_emitter_spec.ts index 2918e70535bdc1..f7b169c2a719e9 100644 --- a/packages/compiler-cli/test/transformers/node_emitter_spec.ts +++ b/packages/compiler-cli/test/transformers/node_emitter_spec.ts @@ -63,7 +63,7 @@ describe('TypeScriptNodeEmitter', () => { expect(emitStmt(someVar.set(o.literal(1)).toDeclStmt(null, [o.StmtModifier.Final]))) .toEqual(`var someVar = 1;`); expect(emitStmt(someVar.set(o.literal(1)).toDeclStmt(null, [o.StmtModifier.Exported]))) - .toEqual(`exports.someVar = 1;`); + .toEqual(`var someVar = 1; exports.someVar = someVar;`); }); describe('declare variables with ExternExpressions as values', () => { @@ -71,7 +71,7 @@ describe('TypeScriptNodeEmitter', () => { // identifier is in the same module -> no reexport expect(emitStmt(someVar.set(o.importExpr(sameModuleIdentifier)).toDeclStmt(null, [ o.StmtModifier.Exported - ]))).toEqual('exports.someVar = someLocalId;'); + ]))).toEqual('var someVar = someLocalId; exports.someVar = someVar;'); }); it('should create no reexport if the variable is not exported', () => { @@ -84,7 +84,7 @@ describe('TypeScriptNodeEmitter', () => { expect(emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)) .toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Exported]))) .toEqual( - `const i0 = require("/somePackage/someOtherPath"); exports.someVar = i0.someExternalId;`); + `const i0 = require("/somePackage/someOtherPath"); var someVar = i0.someExternalId; exports.someVar = someVar;`); }); it('should create a reexport', () => { diff --git a/packages/compiler-cli/tsconfig-build.json b/packages/compiler-cli/tsconfig-build.json index cab8e9b5ea9441..6937d14a2617a9 100644 --- a/packages/compiler-cli/tsconfig-build.json +++ b/packages/compiler-cli/tsconfig-build.json @@ -32,7 +32,6 @@ "files": [ "index.ts", "src/main.ts", - "src/main2.ts", "src/ngc.ts", "src/extract_i18n.ts", "../../node_modules/@types/node/index.d.ts", diff --git a/scripts/ci/offline_compiler_test.sh b/scripts/ci/offline_compiler_test.sh index f419884f850060..b8a07035570cd6 100755 --- a/scripts/ci/offline_compiler_test.sh +++ b/scripts/ci/offline_compiler_test.sh @@ -8,7 +8,7 @@ LINKABLE_PKGS=( $(pwd)/dist/packages-dist/{common,forms,core,compiler,compiler-cli,platform-{browser,server},platform-browser-dynamic,router,http,animations,tsc-wrapped} ) -TYPESCRIPT_2_1=typescript@2.1.5 +TYPESCRIPT_2_3=typescript@2.3.x PKGS=( reflect-metadata@0.1.8 zone.js@0.6.25 @@ -33,7 +33,7 @@ cp -v package.json $TMP ( cd $TMP set -ex -o pipefail - npm install ${PKGS[*]} $TYPESCRIPT_2_1 + npm install ${PKGS[*]} $TYPESCRIPT_2_3 # TODO(alexeagle): allow this to be npm link instead npm install ${LINKABLE_PKGS[*]} @@ -54,7 +54,6 @@ cp -v package.json $TMP # Copy the html files from source to the emitted output cp flat_module/src/*.html node_modules/flat_module/src - ./node_modules/.bin/ngc -p tsconfig-build-alt.json --missingTranslation=error --i18nFormat=xlf ./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf ./node_modules/.bin/ng-xi18n -p tsconfig-xi18n.json --i18nFormat=xlf --locale=fr diff --git a/tools/ngc-wrapped/index.ts b/tools/ngc-wrapped/index.ts index 37d88317dd265d..6e56a5f223cbe8 100644 --- a/tools/ngc-wrapped/index.ts +++ b/tools/ngc-wrapped/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -// TODO(chuckj): Remove the requirment for a fake 'reflect` implementation from +// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from // the compiler import 'reflect-metadata'; import {performCompilation} from '@angular/compiler-cli'; @@ -28,7 +28,7 @@ function main(args: string[]) { const basePath = path.resolve(process.cwd(), projectDir); const result = performCompilation(basePath, files, options, ngOptions, undefined); - if (result === 0) { + if (result.errorCode === 0) { // Ensure that expected output files exist. if (ngOptions && ngOptions.expectedOut) { for (const out of ngOptions.expectedOut) { @@ -37,7 +37,7 @@ function main(args: string[]) { } } - return result; + return result.errorCode; } if (require.main === module) {