diff --git a/CHANGELOG.md b/CHANGELOG.md index b46f8db760..e062bedab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,127 @@ + +# [5.0.0-beta.4](https://github.com/angular/angular/compare/5.0.0-beta.3...5.0.0-beta.4) (2017-08-16) + + +### Bug Fixes + +* **aio:** fix compilation error by using the correct type for `providers` ([4d523fd](https://github.com/angular/angular/commit/4d523fd)) +* **aio:** skip PWA test when redeploying non-public commit ([06faac8](https://github.com/angular/angular/commit/06faac8)) +* **compiler:** Don't strip CSS source maps ([64b4be9](https://github.com/angular/angular/commit/64b4be9)) +* **forms:** re-assigning options should not clear select ([32ff21c](https://github.com/angular/angular/commit/32ff21c)), closes [#18330](https://github.com/angular/angular/issues/18330) +* **language-service:** remove tsickle dependency ([bc22ff1](https://github.com/angular/angular/commit/bc22ff1)) + + +### Features + +* **common:** mark NgTemplateOutlet API as stable ([0a73e8d](https://github.com/angular/angular/commit/0a73e8d)) +* **forms:** add status to `AbstractControlDirective` ([233ef93](https://github.com/angular/angular/commit/233ef93)) +* **forms:** add updateOn support to ngModelOptions ([1cfa79c](https://github.com/angular/angular/commit/1cfa79c)) + + +### Performance Improvements + +* **aio:** update to new version of build-optimizer ([088532b](https://github.com/angular/angular/commit/088532b)) +* **core:** add option to remove blank text nodes from compiled templates ([d2c0d98](https://github.com/angular/angular/commit/d2c0d98)) +* **core:** Remove decorator DSL which depends on Reflect ([cac130e](https://github.com/angular/angular/commit/cac130e)) + + + + +## [4.3.5](https://github.com/angular/angular/compare/4.3.4...4.3.5) (2017-08-16) + + +### Bug Fixes + +* **aio:** skip PWA test when redeploying non-public commit ([b9c1c91](https://github.com/angular/angular/commit/b9c1c91)) +* **core:** forbid destroyed views to be inserted or moved in VC ([972538b](https://github.com/angular/angular/commit/972538b)), closes [#18615](https://github.com/angular/angular/issues/18615) +* **forms:** re-assigning options should not clear select ([a1624f2](https://github.com/angular/angular/commit/a1624f2)), closes [#18330](https://github.com/angular/angular/issues/18330) + + +### Performance Improvements + +* **aio:** update to new version of build-optimizer ([d7be4f1](https://github.com/angular/angular/commit/d7be4f1)) + + + + +## [4.3.4](https://github.com/angular/angular/compare/4.3.3...4.3.4) (2017-08-10) + +### Bug Fixes + +* **animations:** revert container/queried animations accordingly during cancel ([#18516](https://github.com/angular/angular/issues/18516)) ([5a165eb](https://github.com/angular/angular/commit/5a165eb)) +* **animations:** support persisting dynamic styles within animation states ([#18468](https://github.com/angular/angular/issues/18468)) ([e0660b1](https://github.com/angular/angular/commit/e0660b1)), closes [#18423](https://github.com/angular/angular/issues/18423) [#17505](https://github.com/angular/angular/issues/17505) +* **benchpress:** compile cleanly with TS 2.4 ([#18455](https://github.com/angular/angular/issues/18455)) ([5afc7ab](https://github.com/angular/angular/commit/5afc7ab)) +* **compiler:** cleanly compile with TypeScript 2.4 ([#18456](https://github.com/angular/angular/issues/18456)) ([5e4054b](https://github.com/angular/angular/commit/5e4054b)) +* **compiler:** ignore [@import](https://github.com/import) in multi-line css ([#18452](https://github.com/angular/angular/issues/18452)) ([e7e7622](https://github.com/angular/angular/commit/e7e7622)), closes [#18038](https://github.com/angular/angular/issues/18038) + + + +# [5.0.0-beta.3](https://github.com/angular/angular/compare/5.0.0-beta.2...5.0.0-beta.3) (2017-08-09) + + +### Bug Fixes + +* **animations:** revert container/queried animations accordingly during cancel ([#18516](https://github.com/angular/angular/issues/18516)) ([c0c03dc](https://github.com/angular/angular/commit/c0c03dc)) +* **animations:** support persisting dynamic styles within animation states ([#18468](https://github.com/angular/angular/issues/18468)) ([05472cb](https://github.com/angular/angular/commit/05472cb)), closes [#18423](https://github.com/angular/angular/issues/18423) [#17505](https://github.com/angular/angular/issues/17505) +* **benchpress:** compile cleanly with TS 2.4 ([#18455](https://github.com/angular/angular/issues/18455)) ([e25b3dd](https://github.com/angular/angular/commit/e25b3dd)) +* **common:** don't recreate view when context shape doesn't change ([#18277](https://github.com/angular/angular/issues/18277)) ([685cc26](https://github.com/angular/angular/commit/685cc26)), closes [#13407](https://github.com/angular/angular/issues/13407) +* **compiler:** cleanly compile with TypeScript 2.4 ([#18456](https://github.com/angular/angular/issues/18456)) ([7c47b62](https://github.com/angular/angular/commit/7c47b62)) +* **compiler:** ignore [@import](https://github.com/import) in multi-line css ([#18452](https://github.com/angular/angular/issues/18452)) ([1dca575](https://github.com/angular/angular/commit/1dca575)), closes [#18038](https://github.com/angular/angular/issues/18038) +* **compiler-cli:** disable buggy expression lowering ([#18513](https://github.com/angular/angular/issues/18513)) ([ca695e0](https://github.com/angular/angular/commit/ca695e0)) +* **compiler-cli:** fix and re-enable expression lowering ([#18570](https://github.com/angular/angular/issues/18570)) ([6f2038c](https://github.com/angular/angular/commit/6f2038c)), closes [#18388](https://github.com/angular/angular/issues/18388) +* **compiler-cli:** modified ngc to throw all errors, not just syntax ([#18388](https://github.com/angular/angular/issues/18388)) ([5651e4a](https://github.com/angular/angular/commit/5651e4a)) +* **compiler-cli:** remove minimist dependency of compiler-cli/index ([#18532](https://github.com/angular/angular/issues/18532)) ([5b7432b](https://github.com/angular/angular/commit/5b7432b)) +* **core:** fix platform-browser-dynamic ([#18576](https://github.com/angular/angular/issues/18576)) ([f0a5501](https://github.com/angular/angular/commit/f0a5501)) +* **core:** forbid destroyed views to be inserted or moved in VC ([#18568](https://github.com/angular/angular/issues/18568)) ([e54bd59](https://github.com/angular/angular/commit/e54bd59)), closes [#17780](https://github.com/angular/angular/issues/17780) + +### Features + +* **core:** Create StaticInjector which does not depend on Reflect polyfill. ([d9d00bd](https://github.com/angular/angular/commit/d9d00bd)) +* **forms:** add default updateOn values for groups and arrays ([#18536](https://github.com/angular/angular/issues/18536)) ([ff5c58b](https://github.com/angular/angular/commit/ff5c58b)) +* **forms:** add updateOn blur option to FormControls ([#18408](https://github.com/angular/angular/issues/18408)) ([333a708](https://github.com/angular/angular/commit/333a708)), closes [#7113](https://github.com/angular/angular/issues/7113) +* **forms:** add updateOn submit option to FormControls ([#18514](https://github.com/angular/angular/issues/18514)) ([f69561b](https://github.com/angular/angular/commit/f69561b)) + +### Performance Improvements + +* switch angular to use StaticInjector instead of ReflectiveInjector ([fcadbf4](https://github.com/angular/angular/commit/fcadbf4)), closes [#18496](https://github.com/angular/angular/issues/18496) + + +### BREAKING CHANGES + +* `platformXXXX()` no longer accepts providers which depend on reflection. +Specifically the method signature when from `Provider[]` to +`StaticProvider[]`. + +Example: +Before: +``` +[ + MyClass, + {provide: ClassA, useClass: SubClassA} +] + +``` + +After: +``` +[ + {provide: MyClass, deps: [Dep1,...]}, + {provide: ClassA, useClass: SubClassA, deps: [Dep1,...]} +] +``` + +NOTE: This only applies to platform creation and providers for the JIT +compiler. It does not apply to `@Component` or `@NgModule` provides +declarations. + +Benchpress note: Previously Benchpress also supported reflective +provides, which now require static providers. + +DEPRECATION: + +- `ReflectiveInjector` is now deprecated as it will be remove. Use + `Injector.create` as a replacement. + # [5.0.0-beta.2](https://github.com/angular/angular/compare/5.0.0-beta.1...5.0.0-beta.2) (2017-08-02) @@ -14,17 +138,14 @@ * **router:** add events tracking activation of individual routes ([49cd851](https://github.com/angular/angular/commit/49cd851)) - ## [4.3.3](https://github.com/angular/angular/compare/4.3.2...4.3.3) (2017-08-02) - ### Bug Fixes * **compiler:** fix for element needing implicit parent placed in top-level ng-container ([f5cbc2e](https://github.com/angular/angular/commit/f5cbc2e)), closes [#18314](https://github.com/angular/angular/issues/18314) - # [5.0.0-beta.1](https://github.com/angular/angular/compare/5.0.0-beta.0...5.0.0-beta.1) (2017-07-27) diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts index 183bf9a196..cfb3ba616f 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/upload-server/build-creator.ts @@ -32,7 +32,8 @@ export class BuildCreator extends EventEmitter { then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])). then(([prDirExisted, shaDirExisted]) => { if (shaDirExisted) { - throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`); + const publicOrNot = isPublic ? 'public' : 'non-public'; + throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); } dirToRemoveOnError = prDirExisted ? shaDir : prDir; diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts index 6191a495f4..002d93de3e 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/lib/verify-setup/upload-server.e2e.ts @@ -110,6 +110,7 @@ describe('upload-server (on HTTP)', () => { const authorizationHeader2 = isPublic ? authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`; const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`); + const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`); it('should not overwrite existing builds', done => { @@ -120,7 +121,7 @@ describe('upload-server (on HTTP)', () => { expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content'); h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`). - then(h.verifyResponse(409, /^Request to overwrite existing directory/)). + then(h.verifyResponse(409, overwriteRe)). then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')). then(done); }); @@ -141,7 +142,7 @@ describe('upload-server (on HTTP)', () => { expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content'); h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9Almost}`). - then(h.verifyResponse(409, /^Request to overwrite existing directory/)). + then(h.verifyResponse(409, overwriteRe)). then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')). then(done); }); @@ -310,7 +311,7 @@ describe('upload-server (on HTTP)', () => { expect(h.buildExists(pr, sha0, isPublic)).toBe(false); uploadBuild(sha0). - then(h.verifyResponse(409, /^Request to overwrite existing directory/)). + then(h.verifyResponse(409, overwriteRe)). then(() => { checkPrVisibility(isPublic); expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts index 6125205ba3..d5b1f91576 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/test/upload-server/build-creator.spec.ts @@ -153,7 +153,8 @@ describe('BuildCreator', () => { it('should abort and skip further operations if the build does already exist', done => { existsValues[shaDir] = true; bc.create(pr, sha, archive, isPublic).catch(err => { - expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`); + const publicOrNot = isPublic ? 'public' : 'non-public'; + expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled(); @@ -169,7 +170,8 @@ describe('BuildCreator', () => { expect(bcExistsSpy(shaDir)).toBe(false); bc.create(pr, sha, archive, isPublic).catch(err => { - expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`); + const publicOrNot = isPublic ? 'public' : 'non-public'; + expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled(); diff --git a/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json b/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json index d773f53668..59d3ff1d9f 100644 --- a/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json +++ b/aio/aio-builds-setup/dockerbuild/scripts-js/tslint.json @@ -6,7 +6,7 @@ "interface-name": [true, "never-prefix"], "max-classes-per-file": [true, 4], "no-consecutive-blank-lines": [true, 2], - "no-console": false, + "no-console": [false], "no-namespace": [true, "allow-declarations"], "no-string-literal": false, "quotemark": [true, "single"], diff --git a/aio/aio-builds-setup/docs/overview--http-status-codes.md b/aio/aio-builds-setup/docs/overview--http-status-codes.md index 54b06f66e8..1a041ffbcf 100644 --- a/aio/aio-builds-setup/docs/overview--http-status-codes.md +++ b/aio/aio-builds-setup/docs/overview--http-status-codes.md @@ -46,8 +46,8 @@ with a bried explanation of what they mean: Request method other than POST. - **409 (Conflict)**: - Request to overwrite existing directory (e.g. deploy existing build or change PR visibility when - the destination directory does already exist). + Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or + change PR visibility when the destination directory does already exist). - **413 (Payload Too Large)**: Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`. @@ -71,7 +71,8 @@ with a bried explanation of what they mean: Request method other than POST. - **409 (Conflict)**: - Request to overwrite existing directory (i.e. directories for both visibilities exist). + Request to overwrite existing (public or non-public) directory (i.e. directories for both + visibilities exist). (Normally, this should not happen.) diff --git a/aio/content/examples/.gitignore b/aio/content/examples/.gitignore index 3a57bb712c..7833b847ed 100644 --- a/aio/content/examples/.gitignore +++ b/aio/content/examples/.gitignore @@ -55,10 +55,6 @@ dist/ !testing/src/browser-test-shim.js !testing/karma*.js -# TS to JS -!ts-to-js/js*/**/*.js -ts-to-js/js*/**/system*.js - # webpack !webpack/**/config/*.js !webpack/**/*webpack*.js diff --git a/aio/content/examples/i18n/src/app/i18n-providers.ts b/aio/content/examples/i18n/src/app/i18n-providers.ts index 0e820bbcfd..3c74d0e8e0 100644 --- a/aio/content/examples/i18n/src/app/i18n-providers.ts +++ b/aio/content/examples/i18n/src/app/i18n-providers.ts @@ -1,15 +1,15 @@ // #docplaster // #docregion without-missing-translation -import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, MissingTranslationStrategy } from '@angular/core'; +import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, MissingTranslationStrategy, StaticProvider } from '@angular/core'; import { CompilerConfig } from '@angular/compiler'; -export function getTranslationProviders(): Promise { +export function getTranslationProviders(): Promise { // Get the locale id from the global const locale = document['locale'] as string; // return no providers if fail to get translation file for locale - const noProviders: Object[] = []; + const noProviders: StaticProvider[] = []; // No locale or U.S. English: no translation providers if (!locale || locale === 'en-US') { diff --git a/aio/content/examples/ts-to-js/e2e-spec.ts b/aio/content/examples/ts-to-js/e2e-spec.ts deleted file mode 100644 index bc67bac8f0..0000000000 --- a/aio/content/examples/ts-to-js/e2e-spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; // necessary for es6 output in node - -import { browser, element, by } from 'protractor'; - -describe('TypeScript to Javascript tests', function () { - - beforeAll(function () { - browser.get(''); - }); - - it('should display the basic component example', function () { - testTag('hero-view', 'Hero Detail: Windstorm'); - }); - - it('should display the component example with lifecycle methods', function () { - testTag('hero-lifecycle', 'Hero: Windstorm'); - }); - - it('should display component with DI example', function () { - testTag('hero-di', 'Hero: Windstorm'); - }); - - it('should display component with DI using @Inject example', function () { - testTag('hero-di-inject', 'Hero: Windstorm'); - }); - - it('should support optional, attribute, and query injections', function () { - let app = element(by.css('hero-di-inject-additional')); - let h1 = app.element(by.css('h1')); - let okMsg = app.element(by.css('p')); - - expect(h1.getText()).toBe('Tour of Heroes'); - app.element(by.buttonText('OK')).click(); - expect(okMsg.getText()).toBe('OK!'); - }); - - it('should support component with inputs and outputs', function () { - let app = element(by.css('hero-io')); - let confirmComponent = app.element(by.css('app-confirm')); - - confirmComponent.element(by.buttonText('OK')).click(); - expect(app.element(by.cssContainingText('span', 'OK clicked')).isPresent()).toBe(true); - - confirmComponent.element(by.buttonText('Cancel')).click(); - expect(app.element(by.cssContainingText('span', 'Cancel clicked')).isPresent()).toBe(true); - }); - - it('should support host bindings and host listeners', function() { - let app = element(by.css('hero-host')); - let h1 = app.element(by.css('h1')); - - expect(app.getAttribute('class')).toBe('heading'); - expect(app.getAttribute('title')).toContain('Tooltip'); - - h1.click(); - expect(h1.getAttribute('class')).toBe('active'); - - h1.click(); - browser.actions().doubleClick(h1.getWebElement()).perform(); - expect(h1.getAttribute('class')).toBe('active'); - }); - - it('should support content and view queries', function() { - let app = element(by.css('hero-queries')); - let windstorm = app.element(by.css('view-child:first-child')); - - app.element(by.css('button')).click(); - expect(windstorm.element(by.css('h2')).getAttribute('class')).toBe('active'); - expect(windstorm.element(by.css('content-child')).getText()).toBe('Active'); - }); - - function testTag(selector: string, expectedText: string) { - let component = element(by.css(selector)); - expect(component.getText()).toBe(expectedText); - } - -}); diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/example-config.json b/aio/content/examples/ts-to-js/js-es6-decorators/example-config.json deleted file mode 100644 index 81f31aaf0d..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/example-config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "build": "build:babel" -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/plnkr.json b/aio/content/examples/ts-to-js/js-es6-decorators/plnkr.json deleted file mode 100644 index 447fc5f605..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/plnkr.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "TypeScript to JavaScript", - "basePath": "src/", - "files":[ - "!**/*.d.ts", - "!**/*.js" - ], - "tags":["cookbook"] -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/.babelrc b/aio/content/examples/ts-to-js/js-es6-decorators/src/.babelrc deleted file mode 100644 index 3aaf507508..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/.babelrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "presets": [ - "es2015", - "angular2" - ] -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.es6 deleted file mode 100644 index d425788f46..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.es6 +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'my-app', - templateUrl: './app.component.html', - styles: [ - // See hero-di-inject-additional.component - 'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}', - '.heading {font-style: italic}' - ] -}) -export class AppComponent { - title = 'ES6 JavaScript with Decorators'; -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.html b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.html deleted file mode 100644 index 995645073a..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.component.html +++ /dev/null @@ -1,31 +0,0 @@ - -

{{title}}

-Classes and Class Metadata
-Input and Output Decorators
-Dependency Injection
-Host Metadata
-View and Child Metadata
- -
-

Classes and Class Metadata

- - - -
-

Input and Output Metadata

- - -
-

Dependency Injection

- - - - -
-

Host Metadata

- - - -
-

View and Child Metadata

- diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.module.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.module.es6 deleted file mode 100644 index 9c248a7ad3..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/app.module.es6 +++ /dev/null @@ -1,55 +0,0 @@ -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; - -import { AppComponent } from './app.component'; -import { ConfirmComponent } from './confirm.component'; -// #docregion appimport -import { HeroComponent } from './hero.component'; -// #enddocregion appimport -import { HeroComponent as HeroDIComponent } from './hero-di.component'; -import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; -import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; -import { HeroHostComponent } from './hero-host.component'; -import { HeroHostMetaComponent } from './hero-host-meta.component'; -import { HeroIOComponent } from './hero-io.component'; -import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; -import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; -import { HeroTitleComponent } from './hero-title.component'; - -import { DataService } from './data.service'; - -@NgModule({ - imports: [ - BrowserModule - ], - declarations: [ - AppComponent, - ConfirmComponent, - HeroComponent, - HeroDIComponent, - HeroDIInjectComponent, - HeroDIInjectAdditionalComponent, - HeroHostComponent, HeroHostMetaComponent, - HeroIOComponent, - HeroLifecycleComponent, - HeroQueriesComponent, ViewChildComponent, ContentChildComponent, - HeroTitleComponent - ], - providers: [ - DataService, - { provide: 'heroName', useValue: 'Windstorm' } - ], - bootstrap: [ AppComponent ], - - // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging -}) -export class AppModule { } - -/* tslint:disable no-unused-variable */ -// #docregion ng2import -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { - LocationStrategy, - HashLocationStrategy -} from '@angular/common'; -// #enddocregion ng2import diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.es6 deleted file mode 100644 index f01fa4de40..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.es6 +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -// #docregion -@Component({ - selector: 'app-confirm', - templateUrl: './confirm.component.html' -}) -export class ConfirmComponent { - @Input() okMsg = ''; - @Input('cancelMsg') notOkMsg = ''; - @Output() ok = new EventEmitter(); - @Output('cancel') notOk = new EventEmitter(); - - onOkClick() { - this.ok.emit(true); - } - onNotOkClick() { - this.notOk.emit(true); - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.html b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.html deleted file mode 100644 index 45275d218a..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/confirm.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/data.service.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/data.service.es6 deleted file mode 100644 index cd7f9e1aae..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/data.service.es6 +++ /dev/null @@ -1,10 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class DataService { - constructor() { } - - getHeroName() { - return 'Windstorm'; - } -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 deleted file mode 100644 index ec460a9dbc..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-di-inject-additional', - template: `` -}) -export class HeroComponent { } diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 deleted file mode 100644 index 94b42f956a..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Inject } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-di-inject', - template: `

Hero: {{name}}

` -}) -export class HeroComponent { - constructor(@Inject('heroName') name) { - this.name = name; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 deleted file mode 100644 index 3a17abd281..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -import { Component } from '@angular/core'; -import { DataService } from './data.service'; - -@Component({ - selector: 'hero-di', - template: `

Hero: {{name}}

` -}) -export class HeroComponent { - name = ''; - constructor(dataService: DataService) { - this.name = dataService.getHeroName(); - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 deleted file mode 100644 index fefe4a5470..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 +++ /dev/null @@ -1,44 +0,0 @@ -import { Component } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-host-meta', - template: ` -

Hero Host in Metadata

-
Heading clicks: {{clicks}}
- `, - host: { - // HostBindings to the element - '[title]': 'title', - '[class.heading]': 'headingClass', - - // HostListeners on the entire element - '(click)': 'clicked()', - '(mouseenter)': 'enter($event)', - '(mouseleave)': 'leave($event)' - }, - // Styles within (but excluding) the element - styles: ['.active {background-color: coral;}'] -}) -export class HeroHostMetaComponent { - title = 'Hero Host in Metadata Tooltip'; - headingClass = true; - - active = false; - clicks = 0; - - clicked() { - this.clicks += 1; - } - - enter(event: Event) { - this.active = true; - this.headingClass = false; - } - - leave(event: Event) { - this.active = false; - this.headingClass = true; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 deleted file mode 100644 index e8d72233c8..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, HostBinding, HostListener } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-host', - template: ` -

Hero Host in Decorators

-
Heading clicks: {{clicks}}
- `, - // Styles within (but excluding) the element - styles: ['.active {background-color: yellow;}'] -}) -export class HeroHostComponent { - // HostBindings to the element - @HostBinding() title = 'Hero Host in Decorators Tooltip'; - @HostBinding('class.heading') headingClass = true; - - active = false; - clicks = 0; - - // HostListeners on the entire element - @HostListener('click') - clicked() { - this.clicks += 1; - } - - @HostListener('mouseenter', ['$event']) - enter(event: Event) { - this.active = true; - this.headingClass = false; - } - - @HostListener('mouseleave', ['$event']) - leave(event: Event) { - this.active = false; - this.headingClass = true; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 deleted file mode 100644 index 4b36411e78..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 +++ /dev/null @@ -1,26 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-io', - template: ` - - - OK clicked - Cancel clicked - ` -}) -export class HeroIOComponent { - okClicked = false; - cancelClicked = false; - - onOk() { - this.okClicked = true; - } - - onCancel() { - this.cancelClicked = true; - } -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 deleted file mode 100644 index 2539266597..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 +++ /dev/null @@ -1,14 +0,0 @@ -// #docregion -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-lifecycle', - template: `

Hero: {{name}}

` -}) -export class HeroComponent { - name = ''; - ngOnInit() { - // todo: fetch from server async - setTimeout(() => this.name = 'Windstorm', 0); - } -} diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 deleted file mode 100644 index fced43d4d7..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 +++ /dev/null @@ -1,81 +0,0 @@ -import { - Component, - ContentChild, - Input, - QueryList, - ViewChildren -} from '@angular/core'; - -@Component({ - selector: 'content-child', - template: ` - - Active - ` -}) -export class ContentChildComponent { - active = false; - - activate() { - this.active = true; - } -} - -//////////////////// - -// #docregion content -@Component({ - selector: 'view-child', - template: ` -

- {{hero.name}} - -

`, - styles: ['.active {font-weight: bold; background-color: skyblue;}'] -}) -export class ViewChildComponent { - @Input() hero; - active = false; - - @ContentChild(ContentChildComponent) content; - - activate() { - this.active = !this.active; - this.content.activate(); - } -} -// #enddocregion content - -//////////////////// - -// #docregion view -@Component({ - selector: 'hero-queries', - template: ` - - - - - ` -}) -export class HeroQueriesComponent { - active = false; - heroData = [ - {id: 1, name: 'Windstorm'}, - {id: 2, name: 'LaughingGas'} - ]; - - @ViewChildren(ViewChildComponent) views; - - activate() { - this.active = !this.active; - this.views.forEach( - view => view.activate() - ); - } - - get buttonLabel() { - return this.active ? 'Deactivate' : 'Activate'; - } -} -// #enddocregion view diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 deleted file mode 100644 index 04c14a9631..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 +++ /dev/null @@ -1,25 +0,0 @@ -import { Attribute, Component, Inject, Optional } from '@angular/core'; - -// #docregion -// #docregion templateUrl -@Component({ - selector: 'hero-title', - templateUrl: './hero-title.component.html' -}) -// #enddocregion templateUrl -export class HeroTitleComponent { - msg = ''; - constructor( - @Inject('titlePrefix') @Optional() titlePrefix, - @Attribute('title') title - ) { - this.titlePrefix = titlePrefix; - this.title = title; - } - - ok() { - this.msg = 'OK!'; - } -} -// #enddocregion - diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.html b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.html deleted file mode 100644 index 164683cb7c..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero-title.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -

{{titlePrefix}} {{title}}

- -

{{ msg }}

diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero.component.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero.component.es6 deleted file mode 100644 index 2976ec605e..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/app/hero.component.es6 +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -// #docregion metadata -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-view', - template: '

{{title}}: {{getName()}}

' -}) -// #docregion appexport, class -export class HeroComponent { - title = 'Hero Detail'; - getName() {return 'Windstorm'; } -} -// #enddocregion appexport, class -// #enddocregion metadata diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/index.html b/aio/content/examples/ts-to-js/js-es6-decorators/src/index.html deleted file mode 100644 index 2a94db38cc..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - TypeScript to JavaScript - - - - - - - - - - - - - Loading... - - - diff --git a/aio/content/examples/ts-to-js/js-es6-decorators/src/main.es6 b/aio/content/examples/ts-to-js/js-es6-decorators/src/main.es6 deleted file mode 100644 index f22933ba8e..0000000000 --- a/aio/content/examples/ts-to-js/js-es6-decorators/src/main.es6 +++ /dev/null @@ -1,4 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; - -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ts-to-js/js-es6/example-config.json b/aio/content/examples/ts-to-js/js-es6/example-config.json deleted file mode 100644 index 81f31aaf0d..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/example-config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "build": "build:babel" -} diff --git a/aio/content/examples/ts-to-js/js-es6/plnkr.json b/aio/content/examples/ts-to-js/js-es6/plnkr.json deleted file mode 100644 index 447fc5f605..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/plnkr.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "TypeScript to JavaScript", - "basePath": "src/", - "files":[ - "!**/*.d.ts", - "!**/*.js" - ], - "tags":["cookbook"] -} diff --git a/aio/content/examples/ts-to-js/js-es6/src/.babelrc b/aio/content/examples/ts-to-js/js-es6/src/.babelrc deleted file mode 100644 index 3c078e9f99..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/.babelrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "presets": [ - "es2015" - ] -} diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/app.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/app.component.es6 deleted file mode 100644 index 6079f7f246..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/app.component.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import { Component } from '@angular/core'; - -export class AppComponent { - constructor() { - this.title = 'Plain ES6 JavaScript'; - } -} - -AppComponent.annotations = [ - new Component({ - selector: 'my-app', - templateUrl: './app.component.html', - styles: [ - // See hero-di-inject-additional.component - 'hero-host { border: 1px dashed black; display: block; padding: 4px;}', - '.heading {font-style: italic}' - ] - }) -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/app.component.html b/aio/content/examples/ts-to-js/js-es6/src/app/app.component.html deleted file mode 100644 index 52b9b4580e..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/app.component.html +++ /dev/null @@ -1,30 +0,0 @@ - -

{{title}}

-Classes and Class Metadata
-Input and Output Metadata
-Dependency Injection
-Host Metadata
-View and Child Metadata
- -
-

Classes and Class Metadata

- - - -
-

Input and Output Metadata

- - -
-

Dependency Injection

- - - - -
-

Host Metadata

- - -
-

View and Child Metadata

- diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/app.module.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/app.module.es6 deleted file mode 100644 index f31141e78f..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/app.module.es6 +++ /dev/null @@ -1,56 +0,0 @@ -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; - -import { AppComponent } from './app.component'; -import { ConfirmComponent } from './confirm.component'; -// #docregion appimport -import { HeroComponent } from './hero.component'; - -// #enddocregion appimport -import { HeroComponent as HeroDIComponent } from './hero-di.component'; -import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; -import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; -import { HeroHostComponent } from './hero-host.component'; -import { HeroIOComponent } from './hero-io.component'; -import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; -import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; -import { HeroTitleComponent } from './hero-title.component'; - -import { DataService } from './data.service'; - -export class AppModule { } - -AppModule.annotations = [ - new NgModule({ - imports: [ BrowserModule], - declarations: [ - AppComponent, - ConfirmComponent, - HeroComponent, - HeroDIComponent, - HeroDIInjectComponent, - HeroDIInjectAdditionalComponent, - HeroHostComponent, - HeroIOComponent, - HeroLifecycleComponent, - HeroQueriesComponent, ViewChildComponent, ContentChildComponent, - HeroTitleComponent - ], - providers: [ - DataService, - { provide: 'heroName', useValue: 'Windstorm' } - ], - bootstrap: [ AppComponent ], - - // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging - }) -] - -/* tslint:disable no-unused-variable */ -// #docregion ng2import -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { - LocationStrategy, - HashLocationStrategy -} from '@angular/common'; -// #enddocregion ng2import diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.es6 deleted file mode 100644 index 296268dda4..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.es6 +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, EventEmitter } from '@angular/core'; - -// #docregion -export class ConfirmComponent { - constructor(){ - this.ok = new EventEmitter(); - this.notOk = new EventEmitter(); - } - onOkClick() { - this.ok.emit(true); - } - onNotOkClick() { - this.notOk.emit(true); - } -} - -ConfirmComponent.annotations = [ - new Component({ - selector: 'app-confirm', - templateUrl: './confirm.component.html', - inputs: [ - 'okMsg', - 'notOkMsg: cancelMsg' - ], - outputs: [ - 'ok', - 'notOk: cancel' - ] - }) -]; -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.html b/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.html deleted file mode 100644 index 45275d218a..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/confirm.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/data.service.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/data.service.es6 deleted file mode 100644 index de023ce5a0..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/data.service.es6 +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable } from '@angular/core'; - -export class DataService { - constructor() { - } - getHeroName() { - return 'Windstorm'; - } -} - -DataService.annotations = [ - new Injectable() -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 deleted file mode 100644 index 5eb9bab815..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -export class HeroComponent { } - -HeroComponent.annotations = [ - new Component({ - selector: 'hero-di-inject-additional', - template: `` - }) -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject.component.es6 deleted file mode 100644 index 2f95a0b981..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di-inject.component.es6 +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Inject } from '@angular/core'; - -// #docregion -export class HeroComponent { - constructor(name) { - this.name = name; - } -} - -HeroComponent.annotations = [ - new Component({ - selector: 'hero-di-inject', - template: `

Hero: {{name}}

` - }) -]; - -HeroComponent.parameters = [ - [new Inject('heroName')] -]; -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-di.component.es6 deleted file mode 100644 index a18b1ba777..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-di.component.es6 +++ /dev/null @@ -1,21 +0,0 @@ -// #docregion -import { Component } from '@angular/core'; -import { DataService } from './data.service'; - -export class HeroComponent { - constructor(dataService) { - this.name = dataService.getHeroName(); - } -} - -HeroComponent.annotations = [ - new Component({ - selector: 'hero-di', - template: `

Hero: {{name}}

` - }) -]; - -HeroComponent.parameters = [ - [DataService] -]; -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-host.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-host.component.es6 deleted file mode 100644 index b06701ee8d..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-host.component.es6 +++ /dev/null @@ -1,50 +0,0 @@ -import { Component } from '@angular/core'; - -// #docregion -export class HeroHostComponent { - constructor() { - this.active = false; - this.clicks = 0; - this.headingClass = true; - this.title = 'Hero Host Tooltip'; - } - - clicked() { - this.clicks += 1; - } - - enter(event) { - this.active = true; - this.headingClass = false; - } - - leave(event) { - this.active = false; - this.headingClass = true; - } -} - -// #docregion metadata -HeroHostComponent.annotations = [ - new Component({ - selector: 'hero-host', - template: ` -

Hero Host

-
Heading clicks: {{clicks}}
- `, - host: { - // HostBindings to the element - '[title]': 'title', - '[class.heading]': 'headingClass', - '(click)': 'clicked()', - - // HostListeners on the entire element - '(mouseenter)': 'enter($event)', - '(mouseleave)': 'leave($event)' - }, - // Styles within (but excluding) the element - styles: ['.active {background-color: yellow;}'] - }) -]; -// #enddocregion metadata -// #enddocregion diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-io.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-io.component.es6 deleted file mode 100644 index 0dc8c08f2d..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-io.component.es6 +++ /dev/null @@ -1,31 +0,0 @@ -import { Component } from '@angular/core'; - -export class HeroIOComponent { - constructor() { - this.okClicked = false; - this.cancelClicked = false; - } - - onOk() { - this.okClicked = true; - } - - onCancel() { - this.cancelClicked = true; - } -} - -HeroIOComponent.annotations = [ - new Component({ - selector: 'hero-io', - template: ` - - - OK clicked - Cancel clicked - ` - }) -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 deleted file mode 100644 index b394ff59aa..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -import { Component } from '@angular/core'; -export class HeroComponent { - ngOnInit() { - // todo: fetch from server async - setTimeout(() => this.name = 'Windstorm', 0); - } -} - -HeroComponent.annotations = [ - new Component({ - selector: 'hero-lifecycle', - template: `

Hero: {{name}}

` - }) -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-queries.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-queries.component.es6 deleted file mode 100644 index bf3b914406..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-queries.component.es6 +++ /dev/null @@ -1,97 +0,0 @@ -import { - Component, - ContentChild, - Input, - QueryList, - ViewChildren -} from '@angular/core'; - -export class ContentChildComponent { - constructor() { - this.active = false; - } - - activate() { - this.active = !this.active; - } -} - -ContentChildComponent.annotations = [ - new Component({ - selector: 'content-child', - template: ` - - Active - ` - }) -]; - -//////////////////// - -// #docregion content -export class ViewChildComponent { - constructor() { - this.active = false; - } - - activate() { - this.active = !this.active; - this.content.activate(); - } -} - -ViewChildComponent.annotations = [ - new Component({ - selector: 'view-child', - template: `

- {{hero.name}} - -

`, - styles: ['.active {font-weight: bold; background-color: skyblue;}'], - inputs: ['hero'], - queries: { - content: new ContentChild(ContentChildComponent) - } - }) -]; -// #enddocregion content - -//////////////////// - -// #docregion view -export class HeroQueriesComponent { - constructor(){ - this.active = false; - this.heroData = [ - {id: 1, name: 'Windstorm'}, - {id: 2, name: 'LaughingGas'} - ]; - } - - activate() { - this.active = !this.active; - this.views.forEach( - view => view.activate() - ); - } - - get buttonLabel() { - return this.active ? 'Deactivate' : 'Activate'; - } -} - -HeroQueriesComponent.annotations = [ - new Component({ - selector: 'hero-queries', - template: ` - - - - - `, - queries: { - views: new ViewChildren(ViewChildComponent) - } - }) -]; -// #enddocregion view diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.es6 deleted file mode 100644 index 4b89dfba83..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.es6 +++ /dev/null @@ -1,28 +0,0 @@ -import { Attribute, Component, Inject, Optional } from '@angular/core'; - -// #docregion -export class HeroTitleComponent { - constructor(titlePrefix, title) { - this.titlePrefix = titlePrefix; - this.title = title; - this.msg = ''; - } - - ok() { - this.msg = 'OK!'; - } -} - -// #docregion templateUrl -HeroTitleComponent.annotations = [ - new Component({ - selector: 'hero-title', - templateUrl: './hero-title.component.html' - }) -]; -// #enddocregion templateUrl - -HeroTitleComponent.parameters = [ - [new Optional(), new Inject('titlePrefix')], - [new Attribute('title')] -]; diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.html b/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.html deleted file mode 100644 index 164683cb7c..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero-title.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -

{{titlePrefix}} {{title}}

- -

{{ msg }}

diff --git a/aio/content/examples/ts-to-js/js-es6/src/app/hero.component.es6 b/aio/content/examples/ts-to-js/js-es6/src/app/hero.component.es6 deleted file mode 100644 index 350f946460..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/app/hero.component.es6 +++ /dev/null @@ -1,21 +0,0 @@ -// #docplaster -// #docregion -// #docregion metadata -import { Component } from '@angular/core'; - -// #docregion appexport, class -export class HeroComponent { - constructor() { - this.title = 'Hero Detail'; - } - getName() {return 'Windstorm'; } -} -// #enddocregion appexport, class - -HeroComponent.annotations = [ - new Component({ - selector: 'hero-view', - template: '

{{title}}: {{getName()}}

' - }) -]; -// #enddocregion metadata diff --git a/aio/content/examples/ts-to-js/js-es6/src/index.html b/aio/content/examples/ts-to-js/js-es6/src/index.html deleted file mode 100644 index 2a94db38cc..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - TypeScript to JavaScript - - - - - - - - - - - - - Loading... - - - diff --git a/aio/content/examples/ts-to-js/js-es6/src/main.es6 b/aio/content/examples/ts-to-js/js-es6/src/main.es6 deleted file mode 100644 index f22933ba8e..0000000000 --- a/aio/content/examples/ts-to-js/js-es6/src/main.es6 +++ /dev/null @@ -1,4 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; - -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ts-to-js/js/example-config.json b/aio/content/examples/ts-to-js/js/example-config.json deleted file mode 100644 index 81f31aaf0d..0000000000 --- a/aio/content/examples/ts-to-js/js/example-config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "build": "build:babel" -} diff --git a/aio/content/examples/ts-to-js/js/plnkr.json b/aio/content/examples/ts-to-js/js/plnkr.json deleted file mode 100644 index 5e1eb188be..0000000000 --- a/aio/content/examples/ts-to-js/js/plnkr.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "TypeScript to JavaScript", - "basePath": "src/", - "files":[ - "!**/karma*.*" - ], - "tags":["cookbook"] -} diff --git a/aio/content/examples/ts-to-js/js/src/app/app.component.html b/aio/content/examples/ts-to-js/js/src/app/app.component.html deleted file mode 100644 index 4ca26f2657..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/app.component.html +++ /dev/null @@ -1,47 +0,0 @@ - -

{{title}}

-Classes and Class Metadata
-Interfaces
-Input and Output Metadata
-Dependency Injection
-Host Metadata
-View and Child Metadata
- -
-

Classes and Class Metadata

- -

Classes and Class Metadata (DSL)

- - -
-

Interfaces

- -

Interfaces (DSL)

- - -
-

Input and Output Metadata

- -

Input and Output Metadata (DSL)

- - -
-

Dependency Injection

- - - - -

Dependency Injection (DSL)

- - - - -
-

Host Metadata

- -

Host Metadata (DSL)

- - -
-

View and Child Metadata (DSL)

- diff --git a/aio/content/examples/ts-to-js/js/src/app/app.component.js b/aio/content/examples/ts-to-js/js/src/app/app.component.js deleted file mode 100644 index b6d1ec29b0..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/app.component.js +++ /dev/null @@ -1,20 +0,0 @@ -(function(app) { - -app.AppComponent = AppComponent; -function AppComponent() { - this.title = 'ES5 JavaScript'; -} - -AppComponent.annotations = [ - new ng.core.Component({ - selector: 'my-app', - templateUrl: 'app/app.component.html', - styles: [ - // See hero-di-inject-additional.component - 'hero-host, hero-host-dsl { border: 1px dashed black; display: block; padding: 4px;}', - '.heading {font-style: italic}' - ] - }) -]; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/app.module.js b/aio/content/examples/ts-to-js/js/src/app/app.module.js deleted file mode 100644 index fc2300a89b..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/app.module.js +++ /dev/null @@ -1,46 +0,0 @@ -(function(app) { - -app.AppModule = AppModule; -function AppModule() { } - -AppModule.annotations = [ - new ng.core.NgModule({ - imports: [ ng.platformBrowser.BrowserModule ], - declarations: [ - app.AppComponent, - app.ConfirmComponent, app.ConfirmDslComponent, - app.HeroComponent, app.HeroDslComponent, - app.HeroDIComponent, app.HeroDIDslComponent, - app.HeroDIInjectComponent, app.HeroDIInjectDslComponent, - app.HeroDIInjectAdditionalComponent, app.HeroDIInjectAdditionalDslComponent, - app.HeroHostComponent, app.HeroHostDslComponent, - app.HeroIOComponent, app.HeroIODslComponent, - app.HeroLifecycleComponent, app.HeroLifecycleDslComponent, - app.heroQueries.HeroQueriesComponent, app.heroQueries.ViewChildComponent, app.heroQueries.ContentChildComponent, - app.HeroTitleComponent, app.HeroTitleDslComponent - ], - providers: [ - app.DataService, - { provide: 'heroName', useValue: 'Windstorm' } - ], - bootstrap: [ app.AppComponent ], - - // schemas: [ ng.core.NO_ERRORS_SCHEMA ] // helpful for debugging! - }) -] - -})(window.app = window.app || {}); - - -///// For documentation only ///// -(function () { - // #docregion appimport - var HeroComponent = app.HeroComponent; - // #enddocregion appimport - - // #docregion ng2import - var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; - var LocationStrategy = ng.common.LocationStrategy; - var HashLocationStrategy = ng.common.HashLocationStrategy; - // #enddocregion ng2import -}) diff --git a/aio/content/examples/ts-to-js/js/src/app/confirm.component.html b/aio/content/examples/ts-to-js/js/src/app/confirm.component.html deleted file mode 100644 index 45275d218a..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/confirm.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/aio/content/examples/ts-to-js/js/src/app/confirm.component.js b/aio/content/examples/ts-to-js/js/src/app/confirm.component.js deleted file mode 100644 index b76d3f54aa..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/confirm.component.js +++ /dev/null @@ -1,75 +0,0 @@ -(function(app) { - - // #docregion - app.ConfirmComponent = ConfirmComponent; - - ConfirmComponent.annotations = [ - new ng.core.Component({ - selector: 'app-confirm', - templateUrl: 'app/confirm.component.html', - inputs: [ - 'okMsg', - 'notOkMsg: cancelMsg' - ], - outputs: [ - 'ok', - 'notOk: cancel' - ] - }) - ]; - - function ConfirmComponent() { - this.ok = new ng.core.EventEmitter(); - this.notOk = new ng.core.EventEmitter(); - } - - ConfirmComponent.prototype.onOkClick = function() { - this.ok.emit(true); - } - - ConfirmComponent.prototype.onNotOkClick = function() { - this.notOk.emit(true); - } - // #enddocregion - -})(window.app = window.app || {}); - -/////// DSL version //////// - -(function(app) { - - var old = app.ConfirmComponent; - - // #docregion dsl - app.ConfirmComponent = ng.core.Component({ - selector: 'app-confirm-dsl', - templateUrl: 'app/confirm.component.html', - inputs: [ - 'okMsg', - 'notOkMsg: cancelMsg' - ], - outputs: [ - 'ok', - 'notOk: cancel' - ] - }) - .Class({ - constructor: function ConfirmComponent() { - this.ok = new ng.core.EventEmitter(); - this.notOk = new ng.core.EventEmitter(); - }, - - onOkClick: function() { - this.ok.emit(true); - }, - - onNotOkClick: function() { - this.notOk.emit(true); - } - }); - // #enddocregion dsl - - app.ConfirmDslComponent = app.ConfirmComponent; - app.ConfirmComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/data.service.js b/aio/content/examples/ts-to-js/js/src/app/data.service.js deleted file mode 100644 index 643bb57dca..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/data.service.js +++ /dev/null @@ -1,10 +0,0 @@ -(function(app) { - - app.DataService = DataService; - function DataService() { } - - DataService.prototype.getHeroName = function() { - return 'Windstorm'; - }; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-di-inject-additional.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-di-inject-additional.component.js deleted file mode 100644 index 450cc53847..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-di-inject-additional.component.js +++ /dev/null @@ -1,36 +0,0 @@ -(function(app) { - - var old = app.HeroComponent; - - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-di-inject-additional', - template: '' - }) - ]; - - function HeroComponent() {} - - app.HeroDIInjectAdditionalComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -////// DSL Version ///////// -(function(app) { - - var old = app.HeroComponent; - - app.HeroComponent = ng.core.Component({ - selector: 'hero-di-inject-additional-dsl', - template: '' - }).Class({ - constructor: function HeroComponent() { } - }); - - app.HeroDIInjectAdditionalDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-di-inject.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-di-inject.component.js deleted file mode 100644 index 879365208c..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-di-inject.component.js +++ /dev/null @@ -1,51 +0,0 @@ -(function(app) { - - var old = app.HeroComponent; - - // #docregion - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-di-inject', - template: '

Hero: {{name}}

' - }) - ]; - - HeroComponent.parameters = [ 'heroName' ]; - - function HeroComponent(name) { - this.name = name; - } - // #enddocregion - - app.HeroDIInjectComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -/////// DSL version //////// - -(function(app) { - - var old = app.HeroComponent; - - // #docregion dsl - app.HeroComponent = ng.core.Component({ - selector: 'hero-di-inject-dsl', - template: '

Hero: {{name}}

' - }) - .Class({ - constructor: [ - new ng.core.Inject('heroName'), - function HeroComponent(name) { - this.name = name; - } - ] - }); - // #enddocregion dsl - - app.HeroDIInjectDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-di.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-di.component.js deleted file mode 100644 index 2f49a85079..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-di.component.js +++ /dev/null @@ -1,51 +0,0 @@ -(function(app) { - - var old = app.HeroComponent; - - // #docregion - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-di', - template: '

Hero: {{name}}

' - }) - ]; - - HeroComponent.parameters = [ app.DataService ]; - - function HeroComponent(dataService) { - this.name = dataService.getHeroName(); - } - // #enddocregion - - app.HeroDIComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -////// DSL Version ///// - -(function(app) { - - var old = app.HeroComponent; - - // #docregion dsl - app.HeroComponent = ng.core.Component({ - selector: 'hero-di-dsl', - template: '

Hero: {{name}}

' - }) - .Class({ - constructor: [ - app.DataService, - function HeroComponent(service) { - this.name = service.getHeroName(); - } - ] - }); - // #enddocregion dsl - - app.HeroDIDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-host.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-host.component.js deleted file mode 100644 index b73e50aae0..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-host.component.js +++ /dev/null @@ -1,107 +0,0 @@ -(function(app) { - - var old = app.HeroComponent - - // #docregion - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-host', - template: - '

Hero Host

' + - '
Heading clicks: {{clicks}}
', - host: { - // HostBindings to the element - '[title]': 'title', - '[class.heading]': 'headingClass', - '(click)': 'clicked()', - - // HostListeners on the entire element - '(mouseenter)': 'enter($event)', - '(mouseleave)': 'leave($event)' - }, - // Styles within (but excluding) the element - styles: ['.active {background-color: yellow;}'] - }) - ]; - - function HeroComponent() { - this.clicks = 0; - this.headingClass = true; - this.title = 'Hero Host Tooltip content'; - } - - HeroComponent.prototype.clicked = function() { - this.clicks += 1; - } - - HeroComponent.prototype.enter = function(event) { - this.active = true; - this.headingClass = false; - } - - HeroComponent.prototype.leave = function(event) { - this.active = false; - this.headingClass = true; - } - // #enddocregion - - app.HeroHostComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -//// DSL Version //// - -(function(app) { - - var old = app.HeroComponent; - - // #docregion dsl - app.HeroComponent = ng.core.Component({ - selector: 'hero-host-dsl', - template: ` -

Hero Host (DSL)

-
Heading clicks: {{clicks}}
- `, - host: { - // HostBindings to the element - '[title]': 'title', - '[class.heading]': 'headingClass', - '(click)': 'clicked()', - - // HostListeners on the entire element - '(mouseenter)': 'enter($event)', - '(mouseleave)': 'leave($event)' - }, - // Styles within (but excluding) the element - styles: ['.active {background-color: coral;}'] - }) - .Class({ - constructor: function HeroComponent() { - this.clicks = 0; - this.headingClass = true; - this.title = 'Hero Host Tooltip DSL content'; - }, - - clicked() { - this.clicks += 1; - }, - - enter(event) { - this.active = true; - this.headingClass = false; - }, - - leave(event) { - this.active = false; - this.headingClass = true; - } - }); - // #enddocregion dsl - - app.HeroHostDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-io-dsl.component.html b/aio/content/examples/ts-to-js/js/src/app/hero-io-dsl.component.html deleted file mode 100644 index c8023ccb45..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-io-dsl.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - -OK clicked -Cancel clicked diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-io.component.html b/aio/content/examples/ts-to-js/js/src/app/hero-io.component.html deleted file mode 100644 index 215ddccf92..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-io.component.html +++ /dev/null @@ -1,7 +0,0 @@ - - -OK clicked -Cancel clicked diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-io.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-io.component.js deleted file mode 100644 index b09208ce68..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-io.component.js +++ /dev/null @@ -1,52 +0,0 @@ -(function(app) { - - var old = app.HeroComponent - - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-io', - templateUrl: 'app/hero-io.component.html' - }) - ]; - - function HeroComponent() { } - - HeroComponent.prototype.onOk = function() { - this.okClicked = true; - } - - HeroComponent.prototype.onCancel = function() { - this.cancelClicked = true; - } - - app.HeroIOComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -///// DSL Version //// - -(function(app) { - - var old = app.HeroComponent - - app.HeroComponent = ng.core.Component({ - selector: 'hero-io-dsl', - templateUrl: 'app/hero-io-dsl.component.html' - }) - .Class({ - constructor: function HeroComponent() { }, - onOk: function() { - this.okClicked = true; - }, - onCancel: function() { - this.cancelClicked = true; - } - }); - - app.HeroIODslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-lifecycle.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-lifecycle.component.js deleted file mode 100644 index 2a68288f92..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-lifecycle.component.js +++ /dev/null @@ -1,52 +0,0 @@ -// #docplaster -(function(app) { - - var old = app.HeroComponent; - - // #docregion - app.HeroComponent = HeroComponent; - - HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-lifecycle', - template: '

Hero: {{name}}

' - }) - ]; - - function HeroComponent() { } - - HeroComponent.prototype.ngOnInit = function() { - // todo: fetch from server async - setTimeout(() => this.name = 'Windstorm', 0); - }; - // #enddocregion - - app.HeroLifecycleComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); - -/////// DSL version //// - -(function(app) { - - var old = app.HeroComponent; - - // #docregion dsl - app.HeroComponent = ng.core.Component({ - selector: 'hero-lifecycle-dsl', - template: '

Hero: {{name}}

' - }) - .Class({ - constructor: function HeroComponent() { }, - ngOnInit: function() { - // todo: fetch from server async - setTimeout(() => this.name = 'Windstorm', 0); - } - }); - // #enddocregion dsl - - app.HeroLifecycleDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-queries.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-queries.component.js deleted file mode 100644 index 5e8de58d41..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-queries.component.js +++ /dev/null @@ -1,92 +0,0 @@ -(function(app) { - - app.heroQueries = app.heroQueries || {}; - - app.heroQueries.ContentChildComponent = ng.core.Component({ - selector: 'content-child', - template: - '' + - 'Active' + - '' - }).Class({ - constructor: function ContentChildComponent() { - this.active = false; - }, - - activate: function() { - this.active = !this.active; - } - }); - - //////////////////// - - // #docregion content - app.heroQueries.ViewChildComponent = ng.core.Component({ - selector: 'view-child', - template: - '

' + - '{{hero.name}} ' + - '' + - '

', - styles: ['.active {font-weight: bold; background-color: skyblue;}'], - inputs: ['hero'], - queries: { - content: new ng.core.ContentChild(app.heroQueries.ContentChildComponent) - } - }) - .Class({ - constructor: function HeroQueriesHeroComponent() { - this.active = false; - }, - - activate: function() { - this.active = !this.active; - this.content.activate(); - } - }); - // #enddocregion content - - //////////////////// - - // #docregion view - app.heroQueries.HeroQueriesComponent = ng.core.Component({ - selector: 'hero-queries', - template: - '' + - '' + - '' + - '', - queries: { - views: new ng.core.ViewChildren(app.heroQueries.ViewChildComponent) - } - }) - .Class({ - constructor: function HeroQueriesComponent() { - this.active = false; - this.heroData = [ - {id: 1, name: 'Windstorm'}, - {id: 2, name: 'LaughingGas'} - ]; - }, - - activate: function() { - this.active = !this.active; - this.views.forEach(function(view) { - view.activate(); - }); - }, - }); - - // #docregion defined-property - // add prototype property w/ getter outside the DSL - var proto = app.heroQueries.HeroQueriesComponent.prototype; - Object.defineProperty(proto, "buttonLabel", { - get: function () { - return this.active ? 'Deactivate' : 'Activate'; - }, - enumerable: true - }); - // #enddocregion defined-property - // #enddocregion view - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-title.component.html b/aio/content/examples/ts-to-js/js/src/app/hero-title.component.html deleted file mode 100644 index 164683cb7c..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-title.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -

{{titlePrefix}} {{title}}

- -

{{ msg }}

diff --git a/aio/content/examples/ts-to-js/js/src/app/hero-title.component.js b/aio/content/examples/ts-to-js/js/src/app/hero-title.component.js deleted file mode 100644 index f0770e1228..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero-title.component.js +++ /dev/null @@ -1,64 +0,0 @@ -(function(app) { - - // #docregion - app.HeroTitleComponent = HeroTitleComponent; - - // #docregion templateUrl - HeroTitleComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-title', - templateUrl: 'app/hero-title.component.html' - }) - ]; - // #enddocregion templateUrl - - function HeroTitleComponent(titlePrefix, title) { - this.titlePrefix = titlePrefix; - this.title = title; - this.msg = ''; - } - - HeroTitleComponent.prototype.ok = function() { - this.msg = 'OK!'; - } - - HeroTitleComponent.parameters = [ - [new ng.core.Optional(), new ng.core.Inject('titlePrefix')], - [new ng.core.Attribute('title')] - ]; - // #enddocregion - -})(window.app = window.app || {}); - -////////// DSL version //////////// - -(function(app) { - - var old = app.HeroTitleComponent; - - // #docregion dsl - app.HeroTitleComponent = ng.core.Component({ - selector: 'hero-title-dsl', - templateUrl: 'app/hero-title.component.html' - }) - .Class({ - constructor: [ - [ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ], - new ng.core.Attribute('title'), - function HeroTitleComponent(titlePrefix, title) { - this.titlePrefix = titlePrefix; - this.title = title; - this.msg = ''; - } - ], - - ok: function() { - this.msg = 'OK!'; - } - }); - // #enddocregion dsl - - app.HeroTitleDslComponent = app.HeroTitleComponent; - app.HeroTitleComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/app/hero.component.js b/aio/content/examples/ts-to-js/js/src/app/hero.component.js deleted file mode 100644 index e1407b2635..0000000000 --- a/aio/content/examples/ts-to-js/js/src/app/hero.component.js +++ /dev/null @@ -1,53 +0,0 @@ -// #docplaster -(function(app) { - -// #docregion -// #docregion appexport -// #docregion metadata -app.HeroComponent = HeroComponent; // "export" - -HeroComponent.annotations = [ - new ng.core.Component({ - selector: 'hero-view', - template: '

{{title}}: {{getName()}}

' - }) -]; - -// #docregion constructorproto -function HeroComponent() { - this.title = "Hero Detail"; -} - -HeroComponent.prototype.getName = function() { return 'Windstorm'; }; -// #enddocregion constructorproto - -// #enddocregion metadata -// #enddocregion appexport -// #enddocregion - -})(window.app = window.app || {}); - -//////////// DSL version /////////// - -(function(app) { - - var old = app.HeroComponent; - - // #docregion dsl - app.HeroComponent = ng.core.Component({ - selector: 'hero-view-dsl', - template: '

{{title}}: {{getName()}}

', - }) - .Class({ - constructor: function HeroComponent() { - this.title = "Hero Detail"; - }, - - getName: function() { return 'Windstorm'; } - }); - // #enddocregion dsl - - app.HeroDslComponent = app.HeroComponent; - app.HeroComponent = old; - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/js/src/index.html b/aio/content/examples/ts-to-js/js/src/index.html deleted file mode 100644 index 00338aa55a..0000000000 --- a/aio/content/examples/ts-to-js/js/src/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - TypeScript to JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Loading... - - - diff --git a/aio/content/examples/ts-to-js/js/src/main.js b/aio/content/examples/ts-to-js/js/src/main.js deleted file mode 100644 index fd3e737c9e..0000000000 --- a/aio/content/examples/ts-to-js/js/src/main.js +++ /dev/null @@ -1,9 +0,0 @@ -(function(app) { - -var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; - -document.addEventListener('DOMContentLoaded', function() { - platformBrowserDynamic().bootstrapModule(app.AppModule); -}); - -})(window.app = window.app || {}); diff --git a/aio/content/examples/ts-to-js/ts/example-config.json b/aio/content/examples/ts-to-js/ts/example-config.json deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aio/content/examples/ts-to-js/ts/plnkr.json b/aio/content/examples/ts-to-js/ts/plnkr.json deleted file mode 100644 index 447fc5f605..0000000000 --- a/aio/content/examples/ts-to-js/ts/plnkr.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "TypeScript to JavaScript", - "basePath": "src/", - "files":[ - "!**/*.d.ts", - "!**/*.js" - ], - "tags":["cookbook"] -} diff --git a/aio/content/examples/ts-to-js/ts/src/app/app.component.html b/aio/content/examples/ts-to-js/ts/src/app/app.component.html deleted file mode 100644 index 995645073a..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/app.component.html +++ /dev/null @@ -1,31 +0,0 @@ - -

{{title}}

-Classes and Class Metadata
-Input and Output Decorators
-Dependency Injection
-Host Metadata
-View and Child Metadata
- -
-

Classes and Class Metadata

- - - -
-

Input and Output Metadata

- - -
-

Dependency Injection

- - - - -
-

Host Metadata

- - - -
-

View and Child Metadata

- diff --git a/aio/content/examples/ts-to-js/ts/src/app/app.component.ts b/aio/content/examples/ts-to-js/ts/src/app/app.component.ts deleted file mode 100644 index 912c9e1a80..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/app.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'my-app', - templateUrl: './app.component.html', - styles: [ - // See hero-di-inject-additional.component - 'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}', - '.heading {font-style: italic}' - ] -}) -export class AppComponent { - title = 'TypeScript'; -} diff --git a/aio/content/examples/ts-to-js/ts/src/app/app.module.ts b/aio/content/examples/ts-to-js/ts/src/app/app.module.ts deleted file mode 100644 index 992c3c3514..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/app.module.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* tslint:disable-next-line:no-unused-variable */ -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; - -import { AppComponent } from './app.component'; -import { ConfirmComponent } from './confirm.component'; -// #docregion appimport -import { HeroComponent } from './hero.component'; -// #enddocregion appimport -import { HeroComponent as HeroDIComponent } from './hero-di.component'; -import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; -import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; -import { HeroHostComponent } from './hero-host.component'; -import { HeroHostMetaComponent } from './hero-host-meta.component'; -import { HeroIOComponent } from './hero-io.component'; -import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; -import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; -import { HeroTitleComponent } from './hero-title.component'; - -import { DataService } from './data.service'; - -@NgModule({ - imports: [ - BrowserModule - ], - declarations: [ - AppComponent, - ConfirmComponent, - HeroComponent, - HeroDIComponent, - HeroDIInjectComponent, - HeroDIInjectAdditionalComponent, - HeroHostComponent, HeroHostMetaComponent, - HeroIOComponent, - HeroLifecycleComponent, - HeroQueriesComponent, ViewChildComponent, ContentChildComponent, - HeroTitleComponent - ], - providers: [ - DataService, - { provide: 'heroName', useValue: 'Windstorm' } - ], - bootstrap: [ AppComponent ], - - // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging! -}) -export class AppModule { } - -/* tslint:disable no-unused-variable */ -// #docregion ng2import -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { - LocationStrategy, - HashLocationStrategy -} from '@angular/common'; -// #enddocregion ng2import diff --git a/aio/content/examples/ts-to-js/ts/src/app/confirm.component.html b/aio/content/examples/ts-to-js/ts/src/app/confirm.component.html deleted file mode 100644 index 45275d218a..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/confirm.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/aio/content/examples/ts-to-js/ts/src/app/confirm.component.ts b/aio/content/examples/ts-to-js/ts/src/app/confirm.component.ts deleted file mode 100644 index f01fa4de40..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/confirm.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -// #docregion -@Component({ - selector: 'app-confirm', - templateUrl: './confirm.component.html' -}) -export class ConfirmComponent { - @Input() okMsg = ''; - @Input('cancelMsg') notOkMsg = ''; - @Output() ok = new EventEmitter(); - @Output('cancel') notOk = new EventEmitter(); - - onOkClick() { - this.ok.emit(true); - } - onNotOkClick() { - this.notOk.emit(true); - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/data.service.ts b/aio/content/examples/ts-to-js/ts/src/app/data.service.ts deleted file mode 100644 index cd7f9e1aae..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/data.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class DataService { - constructor() { } - - getHeroName() { - return 'Windstorm'; - } -} diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject-additional.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject-additional.component.ts deleted file mode 100644 index ec460a9dbc..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject-additional.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-di-inject-additional', - template: `` -}) -export class HeroComponent { } diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject.component.ts deleted file mode 100644 index c583a1b0f6..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-di-inject.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, Inject } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-di-inject', - template: `

Hero: {{name}}

` -}) -export class HeroComponent { - constructor(@Inject('heroName') private name: string) { } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-di.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-di.component.ts deleted file mode 100644 index 3a17abd281..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-di.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -import { Component } from '@angular/core'; -import { DataService } from './data.service'; - -@Component({ - selector: 'hero-di', - template: `

Hero: {{name}}

` -}) -export class HeroComponent { - name = ''; - constructor(dataService: DataService) { - this.name = dataService.getHeroName(); - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-host-meta.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-host-meta.component.ts deleted file mode 100644 index fefe4a5470..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-host-meta.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-host-meta', - template: ` -

Hero Host in Metadata

-
Heading clicks: {{clicks}}
- `, - host: { - // HostBindings to the element - '[title]': 'title', - '[class.heading]': 'headingClass', - - // HostListeners on the entire element - '(click)': 'clicked()', - '(mouseenter)': 'enter($event)', - '(mouseleave)': 'leave($event)' - }, - // Styles within (but excluding) the element - styles: ['.active {background-color: coral;}'] -}) -export class HeroHostMetaComponent { - title = 'Hero Host in Metadata Tooltip'; - headingClass = true; - - active = false; - clicks = 0; - - clicked() { - this.clicks += 1; - } - - enter(event: Event) { - this.active = true; - this.headingClass = false; - } - - leave(event: Event) { - this.active = false; - this.headingClass = true; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-host.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-host.component.ts deleted file mode 100644 index e8d72233c8..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-host.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, HostBinding, HostListener } from '@angular/core'; - -// #docregion -@Component({ - selector: 'hero-host', - template: ` -

Hero Host in Decorators

-
Heading clicks: {{clicks}}
- `, - // Styles within (but excluding) the element - styles: ['.active {background-color: yellow;}'] -}) -export class HeroHostComponent { - // HostBindings to the element - @HostBinding() title = 'Hero Host in Decorators Tooltip'; - @HostBinding('class.heading') headingClass = true; - - active = false; - clicks = 0; - - // HostListeners on the entire element - @HostListener('click') - clicked() { - this.clicks += 1; - } - - @HostListener('mouseenter', ['$event']) - enter(event: Event) { - this.active = true; - this.headingClass = false; - } - - @HostListener('mouseleave', ['$event']) - leave(event: Event) { - this.active = false; - this.headingClass = true; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-io.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-io.component.ts deleted file mode 100644 index 4b36411e78..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-io.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-io', - template: ` - - - OK clicked - Cancel clicked - ` -}) -export class HeroIOComponent { - okClicked = false; - cancelClicked = false; - - onOk() { - this.okClicked = true; - } - - onCancel() { - this.cancelClicked = true; - } -} diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-lifecycle.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-lifecycle.component.ts deleted file mode 100644 index 2629c85a1a..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-lifecycle.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -// #docregion -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'hero-lifecycle', - template: `

Hero: {{name}}

` -}) -export class HeroComponent implements OnInit { - name: string; - ngOnInit() { - // todo: fetch from server async - setTimeout(() => this.name = 'Windstorm', 0); - } -} diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-queries.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-queries.component.ts deleted file mode 100644 index 8b2d91ea85..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-queries.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - Component, - ContentChild, - Input, - QueryList, - ViewChildren -} from '@angular/core'; - -@Component({ - selector: 'content-child', - template: ` - - Active - ` -}) -export class ContentChildComponent { - active = false; - - activate() { - this.active = !this.active; - } -} - -//////////////////// - -// #docregion content -@Component({ - selector: 'view-child', - template: ` -

- {{hero.name}} - -

`, - styles: ['.active {font-weight: bold; background-color: skyblue;}'] -}) -export class ViewChildComponent { - @Input() hero: any; - active = false; - - @ContentChild(ContentChildComponent) content: ContentChildComponent; - - activate() { - this.active = !this.active; - this.content.activate(); - } -} -// #enddocregion content - -//////////////////// - -// #docregion view -@Component({ - selector: 'hero-queries', - template: ` - - - - - ` -}) -export class HeroQueriesComponent { - active = false; - heroData = [ - {id: 1, name: 'Windstorm'}, - {id: 2, name: 'LaughingGas'} - ]; - - @ViewChildren(ViewChildComponent) views: QueryList; - - activate() { - this.active = !this.active; - this.views.forEach( - view => view.activate() - ); - } - - // #docregion defined-property - get buttonLabel() { - return this.active ? 'Deactivate' : 'Activate'; - } - // #enddocregion defined-property -} -// #enddocregion view diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.html b/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.html deleted file mode 100644 index 164683cb7c..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.html +++ /dev/null @@ -1,4 +0,0 @@ - -

{{titlePrefix}} {{title}}

- -

{{ msg }}

diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.ts deleted file mode 100644 index 078421ecc7..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero-title.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Attribute, Component, Inject, Optional } from '@angular/core'; - -// #docregion -// #docregion templateUrl -@Component({ - selector: 'hero-title', - templateUrl: './hero-title.component.html' -}) -// #enddocregion templateUrl -export class HeroTitleComponent { - msg = ''; - constructor( - @Inject('titlePrefix') @Optional() private titlePrefix: string, - @Attribute('title') private title: string - ) { } - - ok() { - this.msg = 'OK!'; - } -} -// #enddocregion diff --git a/aio/content/examples/ts-to-js/ts/src/app/hero.component.ts b/aio/content/examples/ts-to-js/ts/src/app/hero.component.ts deleted file mode 100644 index 2976ec605e..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/app/hero.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -// #docregion -// #docregion metadata -import { Component } from '@angular/core'; - -@Component({ - selector: 'hero-view', - template: '

{{title}}: {{getName()}}

' -}) -// #docregion appexport, class -export class HeroComponent { - title = 'Hero Detail'; - getName() {return 'Windstorm'; } -} -// #enddocregion appexport, class -// #enddocregion metadata diff --git a/aio/content/examples/ts-to-js/ts/src/index.html b/aio/content/examples/ts-to-js/ts/src/index.html deleted file mode 100644 index d9ad1f7aef..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/index.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - TypeScript to JavaScript - - - - - - - - - - - - - Loading... - - - diff --git a/aio/content/examples/ts-to-js/ts/src/main.ts b/aio/content/examples/ts-to-js/ts/src/main.ts deleted file mode 100644 index f22933ba8e..0000000000 --- a/aio/content/examples/ts-to-js/ts/src/main.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; - -platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/guide/aot-compiler.md b/aio/content/guide/aot-compiler.md index ceb4fb49a6..0159810e84 100644 --- a/aio/content/guide/aot-compiler.md +++ b/aio/content/guide/aot-compiler.md @@ -609,6 +609,10 @@ Add the following _npm_ convenience script to the `package.json` so you can comp 把下列*npm*便利脚本添加到`package.json`中,以便用一条命令就可以完成编译和Rollup打包工作。 + + "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js", + + Open a terminal window and try it. 打开终端窗口,并试一下。 @@ -869,10 +873,15 @@ AOT编译假设上面介绍的一些支持文件都以准备好。 -Extend the `scripts` section of the `package.json` with these npm scripts: +With the following npm script in the `scripts` section of the `package.json`, you can easily serve +the AOT-compiled application: 使用下面的npm脚本,扩展`package.json`文件的`scripts`部分: + + "serve:aot": "lite-server -c bs-config.aot.json", + + Copy the AOT distribution files into the `/aot` folder with the node script: @@ -890,7 +899,7 @@ Copy the AOT distribution files into the `/aot` folder with the node script: -Now AOT-compile the app and launch it with the `lite-server`: +Now AOT-compile the app and launch: 现在AOT编译应用,并使用`lite`服务器启动它: diff --git a/aio/content/guide/change-log.md b/aio/content/guide/change-log.md index 382b76ad52..2f7425e5d8 100644 --- a/aio/content/guide/change-log.md +++ b/aio/content/guide/change-log.md @@ -244,8 +244,8 @@ The documentation for the version prior to v.2.2.0 has been removed. ## 在“从TypeScript到JavaScript”增加ES6的描述 (2016-11-14) -The updated [TypeScript to JavaScript](guide/ts-to-js) guide -now explains how to write apps in ES6/7 +The updated TypeScript to JavaScript guide (removed August 2017, PR #18694) +explains how to write apps in ES6/7 更新了“[从TypeScript到JavaScript](guide/ts-to-js)”烹饪宝典,解释如何使用ES6/7编写应用 diff --git a/aio/content/guide/metadata.md b/aio/content/guide/metadata.md new file mode 100644 index 0000000000..25608db86a --- /dev/null +++ b/aio/content/guide/metadata.md @@ -0,0 +1,874 @@ +# Angular Metadata and AOT + +The Angular **AOT compiler** turns your TypeScript source code into runnable JavaScript. +As part of that process, the compiler extracts and interprets **metadata** about the parts of the application that Angular is supposed to manage. + +You write metadata in a _subset_ of TypeScript. This guide explains why a subset is necessary, describes the subset constraints, and what happens when you step outside of those constraints. + +## Angular metadata +Angular metadata tells Angular how to construct instances of your application classes and interact with them at runtime. + +You specify the metadata with **decorators** such as `@Component()` and `@Input()`. +You also specify metadata implicitly in the constructor declarations of these decorated classes. + +In the following example, the `@Component()` metadata object and the class constructor tell Angular how to create and display an instance of `TypicalComponent`. + +```typescript +@Component({ + selector: 'app-typical', + template: '
A typical component for {{data.name}}
' +)} +export class TypicalComponent { + @Input() data: TypicalData; + constructor(private someService: SomeService) { ... } +} +``` + +The Angular compiler extracts the metadata _once_ and generates a _factory_ for `TypicalComponent`. +When it needs to create a `TypicalComponent` instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency. + +## Compile ahead-of-time (AOT) + +You should use AOT to compile an application that must launch quickly. +With AOT, there is no runtime compile step. +The client doesn't need the compiler library at all and excluding it significantly reduces the total payload. +The browser downloads a smaller set of safely-compiled, application module(s) and libraries that it can parse quickly and run almost immediately. + +The AOT compiler produces a number of files, including the application JavaScript that ultimately runs in the browser. It then statically analyzes your source code and interprets the Angular metadata without actually running the application. + +To compile the app, run the `ngc` stand-alone tool as part of your build process. +When using the CLI, run the `ng build` command. + +For more information on AOT, see [Ahead-of-Time Compilation](guide/aot-compiler). + +## Metadata restrictions + +Angular metadata expressions must conform to the following general constraints: + +1. Limit [expression syntax](#expression-syntax) to the supported subset of JavaScript. +2. Only reference exported symbols after [code folding](#folding). +3. Only call [functions supported](#supported-functions) by the compiler. +4. Decorated and data-bound class members must be public. + +The next sections elaborate on these points. + +## How AOT works + +It helps to think of the AOT compiler as having two phases: a code analysis phase in which it simply records a representation of the source; and a code generation phase in which the compiler's `StaticReflector` handles the interpretation as well as places restrictions on what it interprets. + +## Phase 1: analysis + +The TypeScript compiler does some of the analytic work of the first phase. It emits the `.d.ts` _type definition files_ with type information that the AOT compiler needs to generate application code. + +At the same time, the AOT **_collector_** analyzes the metadata recorded in the Angular decorators and outputs metadata information in **`.metadata.json`** files, one per `.d.ts` file. + +You can think of `.metadata.json` as a diagram of the overall structure of a decorator's metadata, represented as an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree). + +
+ +Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/tsc-wrapped/src/schema.ts) +describes the JSON format as a collection of TypeScript interfaces. + +
+ +{@a expression-syntax} +### Expression syntax + +The _collector_ only understands a subset of JavaScript. +Define metadata objects with the following limited syntax: + +Syntax | Example +-----------------------------------|----------------------------------- +Literal object | `{cherry: true, apple: true, mincemeat: false}` +Literal array | `['cherries', 'flour', 'sugar']` +Spread in literal array | `['apples', 'flour', ...the_rest]` +Calls | `bake(ingredients)` +New | `new Oven()` +Property access | `pie.slice` +Array index | `ingredients[0]` +Identifier reference | `Component` +A template string | `pie is ${multiplier} times better than cake` +Literal string | `'pi'` +Literal number | `3.14153265` +Literal boolean | `true` +Literal null | `null` +Supported prefix operator | `!cake` +Supported Binary operator | `a + b` +Conditional operator | `a ? b : c` +Parentheses | `(a + b)` + +If an expression uses unsupported syntax, the _collector_ writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that +piece of metadata to generate the application code. + +
+ + If you want `ngc` to report syntax errors immediately rather than produce a `.metadata.json` file with errors, set the `strictMetadataEmit` option in `tsconfig`. + +``` + "angularCompilerOptions": { + ... + "strictMetadataEmit" : true + } + ``` + +Angular libraries have this option to ensure that all Angular `.metadata.json` files are clean and it is a best practice to do the same when building your own libraries. + +
+ +{@a function-expression} +{@a arror-functions} +### No arrow functions + +The AOT compiler does not support [function expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function) +and [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), also called _lambda_ functions. + +Consider the following component decorator: + +```ts +@Component({ + ... + providers: [{provide: server, useFactory: () => new Server()}] +}) +``` + +The AOT _collector_ does not support the arrow function, `() => new Server()`, in a metadata expression. +It generates an error node in place of the function. + +When the compiler later interprets this node, it reports an error that invites you to turn the arrow function into an _exported function_. + +You can fix the error by converting to this: + +```ts +export function serverFactory() { + return new Server(); +} + +@Component({ + ... + providers: [{provide: server, useFactory: serverFactory}] +}) +``` + +### Limited function calls + +The _collector_ can represent a function call or object creation with `new` as long as the syntax is valid. The _collector_ only cares about proper syntax. + +But beware. The compiler may later refuse to generate a call to a _particular_ function or creation of a _particular_ object. +The compiler only supports calls to a small set of functions and will use `new` for only a few designated classes. These functions and classes are in a table of [below](#supported-functions). + + +### Folding +{@a exported-symbols} +The compiler can only resolve references to **_exported_** symbols. +Fortunately, the _collector_ enables limited use of non-exported symbols through _folding_. + +The _collector_ may be able to evaluate an expression during collection and record the result in the `.metadata.json` instead of the original expression. + +For example, the _collector_ can evaluate the expression `1 + 2 + 3 + 4` and replace it with the result, `10`. + +This process is called _folding_. An expression that can be reduced in this manner is _foldable_. + +{@a var-declaration} +The collector can evaluate references to +module-local `const` declarations and initialized `var` and `let` declarations, effectively removing them from the `.metadata.json` file. + +Consider the following component definition: + +```ts +const template = '
{{hero.name}}
'; + +@Component({ + selector: 'app-hero', + template: template +}) +class HeroComponent { + @Input() hero: Hero; +} +``` + +The compiler could not refer to the `template` constant because it isn't exported. + +But the _collector_ can _fold_ the `template` constant into the metadata definition by inlining its contents. +The effect is the same as if you had written: + +```TypeScript +@Component({ + selector: 'app-hero', + template: '
{{hero.name}}
' +}) +class HeroComponent { + @Input() hero: Hero; +} +``` + +There is no longer a reference to `template` and, therefore, nothing to trouble the compiler when it later interprets the _collector's_ output in `.metadata.json`. + +You can take this example a step further by including the `template` constant in another expression: + +```TypeScript +const template = '
{{hero.name}}
'; + +@Component({ + selector: 'app-hero', + template: template + '
{{hero.title}}
' +}) +class HeroComponent { + @Input() hero: Hero; +} +``` + +The _collector_ reduces this expression to its equivalent _folded_ string: + +`'
{{hero.name}}
{{hero.title}}
'`. + +#### Foldable syntax + +The following table describes which expressions the _collector_ can and cannot fold: + +Syntax | Foldable +-----------------------------------|----------------------------------- +Literal object | yes +Literal array | yes +Spread in literal array | no +Calls | no +New | no +Property access | yes, if target is foldable +Array index | yes, if target and index are foldable +Identifier reference | yes, if it is a reference to a local +A template with no substitutions | yes +A template with substitutions | yes, if the substitutions are foldable +Literal string | yes +Literal number | yes +Literal boolean | yes +Literal null | yes +Supported prefix operator | yes, if operand is foldable +Supported binary operator | yes, if both left and right are foldable +Conditional operator | yes, if condition is foldable +Parentheses | yes, if the expression is foldable + +If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for the compiler to resolve. + + +## Phase 2: code generation + +The _collector_ makes no attempt to understand the metadata that it collects and outputs to `.metadata.json`. It represents the metadata as best it can and records errors when it detects a metadata syntax violation. + +It's the compiler's job to interpret the `.metadata.json` in the code generation phase. + +The compiler understands all syntax forms that the _collector_ supports, but it may reject _syntactically_ correct metadata if the _semantics_ violate compiler rules. + +The compiler can only reference _exported symbols_. + +Decorated component class members must be public. You cannot make an `@Input()` property private or internal. + +Data bound properties must also be public. + +```TypeScript +// BAD CODE - title is private +@Component({ + selector: 'app-root', + template: '

{{title}}

' +}) +class AppComponent { + private title = 'My App'; // Bad +} +``` + +{@a supported-functions} +Most importantly, the compiler only generates code to create instances of certain classes, support certain decorators, and call certain functions from the following lists. + + +### New instances + +The compiler only allows metadata that create instances of these Angular classes. + +Class | Module +-----------------|-------------- +`OpaqueToken` | `@angular/core` +`InjectionToken` | `@angular/core` + + +### Annotations/Decorators + +The compiler only supports metadata for these Angular decorators. + +Decorator | Module +------------------|-------------- +`Attribute` | `@angular/core` +`Component` | `@angular/core` +`ContentChild` | `@angular/core` +`ContentChildren` | `@angular/core` +`Directive` | `@angular/core` +`Host` | `@angular/core` +`HostBinding` | `@angular/core` +`HostListener` | `@angular/core` +`Inject` | `@angular/core` +`Injectable` | `@angular/core` +`Input` | `@angular/core` +`NgModule` | `@angular/core` +`Optional` | `@angular/core` +`Output` | `@angular/core` +`Pipe` | `@angular/core` +`Self` | `@angular/core` +`SkipSelf` | `@angular/core` +`ViewChild` | `@angular/core` + + +### Macro-functions and macro-static methods + +The compiler also supports _macros_ in the form of functions or static +methods that return an expression. + +For example, consider the following function: + +```TypeScript +export function wrapInArray(value: T): T[] { + return [value]; +} +``` + +You can call the `wrapInArray` in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset. + +You might use `wrapInArray()` like this: + +```TypeScript +@NgModule({ + declarations: wrapInArray(TypicalComponent) +}) +class TypicalModule {} +``` + +The compiler treats this usage as if you had written: + +```TypeScript +@NgModule({ + declarations: [TypicalComponent] +}) +class TypicalModule {} +``` + +The collector is simplistic in its determination of what qualifies as a macro +function; it can only contain a single `return` statement. + +The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes. +Review the [source code](https://github.com/angular/angular/blob/master/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code") +for these methods to see how macros can simplify configuration of complex Angular modules. + +## Metadata Errors + +The following are metadata errors you may encounter, with explanations and suggested corrections. + +[Expression form not supported](#expression-form-not-supported)
+[Reference to a local (non-exported) symbol](#reference-to-a-local-symbol)
+[Only initialized variables and constants](#only-initialized-variables)
+[Reference to a non-exported class](#reference-to-a-non-exported-class)
+[Reference to a non-exported function](#reference-to-a-non-exported-function)
+[Function calls are not supported](#function-calls-not-supported)
+[Destructured variable or constant not supported](#destructured-variable-not-supported)
+[Could not resolve type](#could-not-resolve-type)
+[Name expected](#name-expected)
+[Unsupported enum member name](#unsupported-enum-member-name)
+[Tagged template expressions are not supported](#tagged-template-expressions-not-supported)
+[Symbol reference expected](#symbol-reference-expected)
+ +
+ +

Expression form not supported

+ +The compiler encountered an expression it didn't understand while evalutating Angular metadata. + +Language features outside of the compiler's [restricted expression syntax](#expression-syntax) +can produce this error, as seen in the following example: + +``` +// ERROR +export class Fooish { ... } +... +const prop = typeof Fooish; // typeof is not valid in metadata + ... + // bracket notation is not valid in metadata + { provide: 'token', useValue: { [prop]: 'value' } }; + ... +``` + +You can use `typeof` and bracket notation in normal application code. +You just can't use those features within expressions that define Angular metadata. + +Avoid this error by sticking to the compiler's [restricted expression syntax](#expression-syntax) +when writing Angular metadata +and be wary of new or unusual TypeScript features. + +
+ +{@a reference-to-a-local-symbol} +

Reference to a local (non-exported) symbol

+ +
+ +_Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol._ + +
+ +The compiler encountered a referenced to a locally defined symbol that either wasn't exported or wasn't initialized. + +Here's a `provider` example of the problem. + +``` +// ERROR +let foo: number; // neither exported nor initialized + +@Component({ + selector: 'my-component', + template: ... , + providers: [ + { provide: Foo, useValue: foo } + ] +}) +export class MyComponent {} +``` +The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. _That_ factory module can't reach back to _this_ source module to access the local (non-exported) `foo` variable. + +You could fix the problem by initializing `foo`. + +``` +let foo = 42; // initialized +``` + +The compiler will [fold](#folding) the expression into the provider as if you had written this. + +``` + providers: [ + { provide: Foo, useValue: 42 } + ] +``` + +Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value. + +``` +// CORRECTED +export let foo: number; // exported + +@Component({ + selector: 'my-component', + template: ... , + providers: [ + { provide: Foo, useValue: foo } + ] +}) +export class MyComponent {} +``` + +Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate _references_ to the exported variables in these expressions. It doesn't need the _values_ of those variables. + +Adding `export` doesn't work when the compiler needs the _actual value_ +in order to generate code. +For example, it doesn't work for the `template` property. + +``` +// ERROR +export let someTemplate: string; // exported but not initialized + +@Component({ + selector: 'my-component', + template: someTemplate +}) +export class MyComponent {} +``` + +The compiler needs the value of the `template` property _right now_ to generate the component factory. +The variable reference alone is insufficient. +Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](#only-initialized-variables)". + +
+ +{@a only-initialized-variables} +

Only initialized variables and constants

+ +
+ +_Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler._ + +
+ +The compiler found a reference to an exported variable or static field that wasn't initialized. +It needs the value of that variable to generate code. + +The following example tries to set the component's `template` property to the value of +the exported `someTemplate` variable which is declared but _unassigned_. + +``` +// ERROR +export let someTemplate: string; + +@Component({ + selector: 'my-component', + template: someTemplate +}) +export class MyComponent {} +``` + +You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there. + +``` +// ERROR - not initialized there either +import { someTemplate } from './config'; + +@Component({ + selector: 'my-component', + template: someTemplate +}) +export class MyComponent {} +``` + +The compiler cannot wait until runtime to get the template information. +It must statically derive the value of the `someTemplate` variable from the source code +so that it can generate the component factory, which includes +instructions for building the element based on the template. + +To correct this error, provide the initial value of the variable in an initializer clause _on the same line_. + +``` +// CORRECTED +export let someTemplate = '

Greetings from Angular

'; + +@Component({ + selector: 'my-component', + template: someTemplate +}) +export class MyComponent {} +``` + +
+ +

Reference to a non-exported class

+ +
+ +_Reference to a non-exported class . Consider exporting the class._ + +
+ +Metadata referenced a class that wasn't exported. + +For example, you may have defined a class and used it as an injection token in a providers array +but neglected to export that class. + +``` +// ERROR +abstract class MyStrategy { } + + ... + providers: [ + { provide: MyStrategy, useValue: ... } + ] + ... +``` + +Angular generates a class factory in a separate module and that +factory [can only access exported classes](#exported-symbols). +To correct this error, export the referenced class. + +``` +// CORRECTED +export abstract class MyStrategy { } + + ... + providers: [ + { provide: MyStrategy, useValue: ... } + ] + ... +``` +
+ +

Reference to a non-exported function

+ +Metadata referenced a function that wasn't exported. + +For example, you may have set a providers `useFactory` property to a locally defined function that you neglected to export. + +``` +// ERROR +function myStrategy() { ... } + + ... + providers: [ + { provide: MyStrategy, useFactory: myStrategy } + ] + ... +``` + +Angular generates a class factory in a separate module and that +factory [can only access exported functions](#exported-symbols). +To correct this error, export the function. + +``` +// CORRECTED +export function myStrategy() { ... } + + ... + providers: [ + { provide: MyStrategy, useFactory: myStrategy } + ] + ... +``` +
+ +{@a function-calls-not-supported} +

Function calls are not supported

+ +
+ +_Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function._ + +
+ +The compiler does not currently support [function expressions or lambda functions](#function-expression). +For example, you cannot set a provider's `useFactory` to an anonymous function or arrow function like this. + +``` +// ERROR + ... + providers: [ + { provide: MyStrategy, useFactory: function() { ... } }, + { provide: OtherStrategy, useFactory: () => { ... } } + ] + ... +``` +You also get this error if you call a function or method in a provider's `useValue`. +``` +// ERROR +import { calculateValue } from './utilities'; + + ... + providers: [ + { provide: SomeValue, useValue: calculateValue() } + ] + ... +``` + +To correct this error, export a function from the module and refer to the function in a `useFactory` provider instead. + + +// CORRECTED +import { calculateValue } from './utilities'; + +export function myStrategy() { ... } +export function otherStrategy() { ... } +export function someValueFactory() { + return calculateValue(); +} + ... + providers: [ + { provide: MyStrategy, useFactory: myStrategy }, + { provide: OtherStrategy, useFactory: otherStrategy }, + { provide: SomeValue, useFactory: someValueFactory } + ] + ... + + +
+ +{@a destructured-variable-not-supported} +

Destructured variable or constant not supported

+ +
+ +_Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring._ + +
+ +The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring). + +For example, you cannot write something like this: + + +// ERROR +import { configuration } from './configuration'; + +// destructured assignment to foo and bar +const {foo, bar} = configuration; + ... + providers: [ + {provide: Foo, useValue: foo}, + {provide: Bar, useValue: bar}, + ] + ... + + +To correct this error, refer to non-destructured values. + + +// CORRECTED +import { configuration } from './configuration'; + ... + providers: [ + {provide: Foo, useValue: configuration.foo}, + {provide: Bar, useValue: configuration.bar}, + ] + ... + + +
+ +

Could not resolve type

+ +The compiler encountered a type and can't determine which module exports that type. + +This can happen if you refer to an ambient type. +For example, the `Window` type is an ambiant type declared in the global `.d.ts` file. + +You'll get an error if you reference it in the component constructor, +which the compiler must statically analyze. + +``` +// ERROR +@Component({ }) +export class MyComponent { + constructor (private win: Window) { ... } +} +``` +TypeScript understands ambiant types so you don't import them. +The Angular compiler does not understand a type that you neglect to export or import. + +In this case, the compiler doesn't understand how to inject something with the `Window` token. + +Do not refer to ambient types in metadata expressions. + +If you must inject an instance of an ambiant type, +you can finesse the problem in four steps: + +1. Create an injection token for an instance of the ambiant type. +1. Create a factory function that returns that instance. +1. Add a `useFactory` provider with that factory function. +1. Use `@Inject` to inject the instance. + +Here's an illustrative example. + + +// CORRECTED +import { Inject } from '@angular/core'; + +export const WINDOW = new InjectionToken('Window'); +export function _window() { return window; } + +@Component({ + ... + providers: [ + { provide: WINDOW, useFactory: _window } + ] +}) +export class MyComponent { + constructor (@Inject(WINDOW) private win: Window) { ... } +} + + +The `Window` type in the constructor is no longer a problem for the compiler because it +uses the `@Inject(WINDOW)` to generate the injection code. + +Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs). + + +import { Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/platform-browser'; + +@Component({ ... }) +export class MyComponent { + constructor (@Inject(DOCUMENT) private doc: Document) { ... } +} + +
+ +

Name expected

+ +The compiler expected a name in an expression it was evaluating. +This can happen if you use a number as a property name as in the following example. + +``` +// ERROR +provider: [{ provide: Foo, useValue: { 0: 'test' } }] +``` + +Change the name of the property to something non-numeric. + +``` +// CORRECTED +provider: [{ provide: Foo, useValue: { '0': 'test' } }] +``` + +
+ +

Unsupported enum member name

+ +Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html) +that you referenced in metadata. + +The compiler can understand simple enum values but not complex values such as those derived from computed properties. + + +// ERROR +enum Colors { + Red = 1, + White, + Blue = "Blue".length // computed +} + + ... + providers: [ + { provide: BaseColor, useValue: Colors.White } // ok + { provide: DangerColor, useValue: Colors.Red } // ok + { provide: StrongColor, useValue: Colors.Blue } // bad + ] + ... + + +Avoid referring to enums with complicated initializers or computed properties. + +
+ +{@a tagged-template-expressions-not-supported} +

Tagged template expressions are not supported

+ +
+ +_Tagged template expressions are not supported in metadata._ + +
+ +The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as, +``` +// ERROR +const expression = 'funky'; +const raw = String.raw`A tagged template ${expression} string`; + ... + template: '
' + raw + '
' + ... +``` +[`String.raw()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw) +is a _tag function_ native to JavaScript ES2015. + +The AOT compiler does not support tagged template expressions; avoid them in metadata expressions. + +
+ +

Symbol reference expected

+ +The compiler expected a reference to a symbol at the location specified in the error message. + +This error can occur if you use an expression in the `extends` clause of a class. + + + +## Conclusion + +This page covered: + +* What the AOT compiler does. +* Why metadata must be written in a subset of JavaScript. +* What that subset is. +* Other restrictions on metadata definition. +* Macro-functions and macro-static methods. +* Compiler errors related to metadata. diff --git a/aio/content/guide/reactive-forms.md b/aio/content/guide/reactive-forms.md index b1affa725f..d6118201ee 100644 --- a/aio/content/guide/reactive-forms.md +++ b/aio/content/guide/reactive-forms.md @@ -1673,10 +1673,10 @@ The default form displays a nameless hero with no addresses. 默认的表单显示一个无地址的无名英雄。 You need a method to populate (or repopulate) the _secretLairs_ with actual hero addresses whenever -the parent `HeroListComponent` sets the `HeroListComponent.hero` input property to a new `Hero`. +the parent `HeroListComponent` sets the `HeroDetailComponent.hero` input property to a new `Hero`. 我们需要一个方法来用实际英雄的地址填充(或重新填充)`secretLairs`, -而不用管父组件`HeroListComponent`何时把输入属性`HeroListComponent.hero`设置为一个新的`Hero`。 +而不用管父组件`HeroListComponent`何时把输入属性`HeroDetailComponent.hero`设置为一个新的`Hero`。 The following `setAddresses` method replaces the _secretLairs_ `FormArray` with a new `FormArray`, initialized by an array of hero address `FormGroups`. diff --git a/aio/content/guide/template-syntax.md b/aio/content/guide/template-syntax.md index 0ad2c00f98..9694100948 100644 --- a/aio/content/guide/template-syntax.md +++ b/aio/content/guide/template-syntax.md @@ -2140,7 +2140,7 @@ They are usually applied to elements as if they were HTML attributes, hence the 它们通常会作为HTML属性的名称而应用在元素上。 Many details are covered in the [_Attribute Directives_](guide/attribute-directives) guide. -Many NgMdules such as the [`RouterModule`](guide/router "Routing and Navigation") +Many NgModules such as the [`RouterModule`](guide/router "Routing and Navigation") and the [`FormsModule`](guide/forms "Forms") define their own attribute directives. This section is an introduction to the most commonly used attribute directives: diff --git a/aio/content/guide/ts-to-js.md b/aio/content/guide/ts-to-js.md index 031d1fa1d0..e69de29bb2 100644 --- a/aio/content/guide/ts-to-js.md +++ b/aio/content/guide/ts-to-js.md @@ -1,982 +0,0 @@ -# TypeScript to JavaScript - -# 从 TypeScript 到 JavaScript - -## Introduction - -## 简介 - -Anything you can do with Angular in _TypeScript_, you can also do -in JavaScript. Translating from one language to the other is mostly a -matter of changing the way you organize your code and access Angular APIs. - -在 Angular 中,_TypeScript_ 可以做的任何事,也可以用 JavaScript 实现。 -将一种语言翻译成另一种语言,主要是改变了组织代码和访问 Angular API 的方式。 - -_TypeScript_ is a popular language option for Angular development. -Most code examples on the Internet as well as on this site are written in _TypeScript_. -This cookbook contains recipes for translating _TypeScript_ -code examples to _ES6_ and to _ES5_ so that JavaScript developers -can read and write Angular apps in their preferred dialect. - -`TypeScript` 在 Angular 开发中比较流行。 -互联网上和本网站中的大多数范例都是用 `TypeScript` 写的。 -这本烹饪宝典会教你如何把 `TypeScript` 代码的例子翻译成 `ES6` 和 `ES5` 以便 JavaScript 的开发者可以用自己喜欢的语言来写 Angular 应用。 - -Run and compare the live TypeScript and JavaScript -code shown in this cookbook. - -运行并比较本章显示的在线例子的 TypeScript 版和 JavaScript 版。 - - -## _TypeScript_ to _ES6_ to _ES5_ - -## 从_TypeScript_ 到 _ES6_ 到 _ES5_ - -_TypeScript_ -is a typed superset of _ES6 JavaScript_. -_ES6 JavaScript_ is a superset of _ES5 JavaScript_. _ES5_ is the kind of JavaScript that runs natively in all modern browsers. -The transformation of _TypeScript_ code all the way down to _ES5_ code can be seen as "shedding" features. - -_TypeScript_ 是 _ES6 JavaScript_ 类型化的超集。_ES6 JavaScript_ 是 _ES5 JavaScript_ 的超集。_ES5_ 是可以在所有现代浏览器中运行的 JavaScript。 - -The downgrade progression is as follows: - -降级的过程是 - -* _TypeScript_ to _ES6-with-decorators_. - - _TypeScript_ 降级到 _带装饰器的 ES6_。 - -* _ES6-with-decorators_ to _ES6-without-decorators_ ("_plain ES6_"). - - _带装饰器的 ES6_ 降级到 _没有装饰器的 ES6_ (“_普通 ES6_”)。 - -* _ES6-without-decorators_ to _ES5_. - - _没有装饰器的 ES6_ 降级到 _ES5_。 - -When translating from _TypeScript_ to _ES6-with-decorators_, remove -[class property access modifiers](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers) -such as `public` and `private`. -Remove most of the -[type declarations](https://www.typescriptlang.org/docs/handbook/basic-types.html), -such as `:string` and `:boolean` -but **keep the constructor parameter types, which are used for dependency injection**. - -_TypeScript_ 翻译到 _带装饰器的 ES6_ 时,移除了[类属性访问修饰符](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers),如`public`和`private`。 -移除了大部分的[类型声明](https://www.typescriptlang.org/docs/handbook/basic-types.html),如`:string`和`:boolean`。 -但**保留了用于依赖注入的构造函数参数类型**。 - -From _ES6-with-decorators_ to _plain ES6_, remove all -[decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) -and the remaining types. -You must declare properties in the class constructor (`this.title = '...'`) rather than in the body of the class. - -_带装饰器的 ES6_ 翻译到_普通 ES6_ 时,移除了所有的[装饰器](https://www.typescriptlang.org/docs/handbook/decorators.html)和剩下的类型。 -必须在构造函数中声明属性(`this.title = '...'`),而不是在类的代码体中。 - -Finally, from _plain ES6_ to _ES5_, the main missing features are `import` -statements and `class` declarations. - -最后,_普通 ES6_ 翻译成 _ES5_,缺少的主要特性是`import`和`class`声明。 - -For _plain ES6_ transpilation you can _start_ with a setup similar to the -[_TypeScript_ quickstart](https://github.com/angular/quickstart) and adjust the application code accordingly. -Transpile with [Babel](https://babeljs.io/) using the `es2015` preset. -To use decorators and annotations with Babel, install the -[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations) preset as well. - - 对_普通 ES6_ 的翻译,可以从类似 [_TypeScript_ 快速开始](https://github.com/angular/quickstart)的设置开始, -调整相应代码。然后用 [Babel](https://babeljs.io/) 进行转译,使用`es2015`预设值。 -要在 Babel 中使用装饰器和注释,还需安装[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations)预设值。 - - -{@a modularity} - -## Importing and Exporting - -## 导入和导出 - -### Importing Angular Code - -### 导入 Angular 代码 - -In both _TypeScript_ and _ES6_, you import Angular classes, functions, and other members with _ES6_ `import` statements. - -在 _TypeScript_ 和 _ES6_ 中,可以使用 _ES6_ `import`语句导入 Angular 类、函数和其它成员。 - -In _ES5_, you access the Angular entities of the [the Angular packages](guide/glossary#scoped-package) -through the global `ng` object. -Anything you can import from `@angular` is a nested member of this `ng` object: - -在 _ES5_ 中,通过全局`ng`对象访问 [Angular 包](guide/glossary#scoped-package)中的 Angular 实体。 -凡是可以从`@angular`导入的,都是该`ng`对象的嵌套成员。 - - - - - - - - - - - - - -### Exporting application code - -### 导出应用代码 - -Each file in a _TypeScript_ or _ES6_ Angular application constitutes an _ES6_ module. -When you want to make something available to other modules, you `export` it. - -_TypeScript_ 或 _ES6_ Angular 应用中每个文件都构成一个 _ES6_ 模块。 -当想要让某个东西对其它模块可用时,就`export`它。 - -_ES5_ lacks native support for modules. -In an Angular _ES5_ application, you load each file manually by adding a ` + + + + + \ 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 0000000000..81d2aa7b15 --- /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 0000000000..34cc0b9fb2 --- /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/integration/language_service_plugin/scripts/test.sh b/integration/language_service_plugin/scripts/test.sh index 29f5d34be2..0721da0c45 100755 --- a/integration/language_service_plugin/scripts/test.sh +++ b/integration/language_service_plugin/scripts/test.sh @@ -9,6 +9,11 @@ source scripts/env.sh HOST="node tools/typescript_host.js" VALIDATE="node tools/typescript_validator.js" +# Ensure the languages service can load correctly in node before typescript loads it. +# This verifies its dependencies and emits any exceptions, both of which are only +# emitted to the typescript logs (not the validated output). +node tools/load_test.js + for TYPESCRIPT in ${TYPESCRIPTS[@]} do SERVER="node typescripts/$TYPESCRIPT/node_modules/typescript/lib/tsserver.js" diff --git a/integration/language_service_plugin/tools/load_test.ts b/integration/language_service_plugin/tools/load_test.ts new file mode 100644 index 0000000000..96638d81d2 --- /dev/null +++ b/integration/language_service_plugin/tools/load_test.ts @@ -0,0 +1,35 @@ +const ts = require('typescript'); +const Module = require('module'); + +const existingRequire = Module.prototype.require; + +const recordedRequires: string[] = []; + +function recordingRequire(path: string) { + recordedRequires.push(path); + return existingRequire.call(this, path); +} + +Module.prototype.require = recordingRequire; + +try { + const lsf = require('@angular/language-service'); + const ls = lsf({typescript: ts}); + + // Assert that the only module that should have been required are '@angular/langauge-service', 'fs', and 'path' + + const allowedLoads = new Set(["@angular/language-service", "fs", "path"]); + + const invalidModules = recordedRequires.filter(m => !allowedLoads.has(m)); + + if (invalidModules.length > 0) { + console.error(`FAILED: Loading the language service required: ${invalidModules.join(', ')}`); + process.exit(1); + } +} catch (e) { + console.error(`FAILED: Loading the language service caused the following exception: ${e.stack || e}`); + process.exit(1); +} + +console.log('SUCCESS: Loading passed') +process.exit(0); \ No newline at end of file diff --git a/integration/language_service_plugin/tools/tsconfig.json b/integration/language_service_plugin/tools/tsconfig.json index c576365a6e..fd844f194c 100644 --- a/integration/language_service_plugin/tools/tsconfig.json +++ b/integration/language_service_plugin/tools/tsconfig.json @@ -13,6 +13,7 @@ }, "files": [ "typescript_host.ts", - "typescript_validator.ts" + "typescript_validator.ts", + "load_test.ts" ] } \ No newline at end of file diff --git a/integration/ng-cli-create.sh b/integration/ng-cli-create.sh new file mode 100755 index 0000000000..a7d1ce2a79 --- /dev/null +++ b/integration/ng-cli-create.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +if [ $# -eq 0 ] + then + echo "Angular cli integration create project" + echo + echo "./ng-cli-create.sh [project-name]" + echo +else + TEMP=`dirname $0` + INTEGRATION_DIR=`(cd $TEMP; pwd)` + PROJECT=$1 + PROJECT_DIR=$INTEGRATION_DIR/$PROJECT + NG=$INTEGRATION_DIR/.ng-cli/node_modules/.bin/ng + ( + echo "===================" + echo Creating $PROJECT... + echo "===================" + cd $INTEGRATION_DIR + rm -rf $PROJECT + $NG set --global packageManager=yarn + $NG new $PROJECT --skip-install + echo "===================" + echo $PROJECT created + echo "===================" + ) + + + # By default `ng new` creates a package.json which uses @angular/* from NPM. + # Instead we want to use them from the current build so we overwrite theme here. + ( + echo "===================" + echo Updating $PROJECT bundles + echo "===================" + cd $PROJECT_DIR + + sed -i -E 's/ng build/ng build --prod --build-optimizer/g' package.json + sed -i -E 's/ng test/ng test --single-run/g' package.json + # workaround for https://github.com/angular/angular-cli/issues/7401 + sed -i -E 's/"@angular\/cli\"\: \".*\"/"@angular\/cli": "https:\/\/github.com\/angular\/cli-builds"/g' package.json + + yarn add \ + file:../../dist/packages-dist/compiler-cli \ + file:../../dist/packages-dist/language-service \ + --save-dev --skip-integrity-check --emoji + + yarn add \ + file:../../dist/packages-dist/core \ + file:../../dist/packages-dist/common \ + file:../../dist/packages-dist/forms \ + file:../../dist/packages-dist/http \ + --save --skip-integrity-check --emoji + + # yarn bug: can not install all of them in a single command and it has to be broken into separate invocations. + yarn add \ + file:../../dist/packages-dist/animations \ + file:../../dist/packages-dist/compiler \ + file:../../dist/packages-dist/platform-browser \ + file:../../dist/packages-dist/platform-browser-dynamic \ + --save --skip-integrity-check --emoji + + yarn install --emoji + + echo "===================" + echo $PROJECT created succesfully + echo "===================" + ) +fi diff --git a/integration/run_tests.sh b/integration/run_tests.sh index 53a64ad566..5603611f52 100755 --- a/integration/run_tests.sh +++ b/integration/run_tests.sh @@ -4,6 +4,10 @@ set -e -o pipefail cd `dirname $0` +# Track payload size functions +source ../scripts/ci/payload-size.sh +source ./_payload-limits.sh + # Workaround https://github.com/yarnpkg/yarn/issues/2165 # Yarn will cache file://dist URIs and not update Angular code readonly cache=.yarn_local_cache @@ -14,6 +18,17 @@ rm_cache mkdir $cache trap rm_cache EXIT +# We need to install `ng` but don't want to do it globally so we plate it into `.ng-cli` folder. +# This check prevents constant re-installing. +if [ ! -d ".ng-cli" ]; then + ( + mkdir -p .ng-cli + cd .ng-cli + yarn add https://github.com/angular/cli-builds --cache-folder ../$cache + ) +fi +./ng-cli-create.sh cli-hello-world + for testDir in $(ls | grep -v node_modules) ; do [[ -d "$testDir" ]] || continue echo "#################################" @@ -23,7 +38,17 @@ for testDir in $(ls | grep -v node_modules) ; do cd $testDir # Workaround for https://github.com/yarnpkg/yarn/issues/2256 rm -f yarn.lock + rm -rf dist yarn install --cache-folder ../$cache yarn test || exit 1 + # Track payload size for cli-hello-world and hello_world__closure + if [[ $testDir == cli-hello-world ]] || [[ $testDir == hello_world__closure ]]; then + if [[ $testDir == cli-hello-world ]]; then + yarn build + fi + trackPayloadSize "$testDir" "dist/*.js" true false + fi ) done + +trackPayloadSize "umd" "../dist/packages-dist/*/bundles/*.umd.min.js" false false diff --git a/integration/typings_test_ts21/include-all.ts b/integration/typings_test_ts21/include-all.ts deleted file mode 100644 index eaa53d24c2..0000000000 --- a/integration/typings_test_ts21/include-all.ts +++ /dev/null @@ -1,41 +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 * as compiler from '@angular/compiler'; -import * as compilerTesting from '@angular/compiler/testing'; -import * as core from '@angular/core'; -import * as coreTesting from '@angular/core/testing'; -import * as forms from '@angular/forms'; -import * as http from '@angular/http'; -import * as httpTesting from '@angular/http/testing'; -import * as platformBrowserDynamic from '@angular/platform-browser-dynamic'; -import * as platformBrowser from '@angular/platform-browser'; -import * as platformBrowserTesting from '@angular/platform-browser/testing'; -import * as platformServer from '@angular/platform-server'; -import * as platformServerTesting from '@angular/platform-server/testing'; -import * as router from '@angular/router'; -import * as routerTesting from '@angular/router/testing'; -import * as upgrade from '@angular/upgrade'; - -export default { - compiler, - compilerTesting, - core, - coreTesting, - forms, - http, - httpTesting, - platformBrowser, - platformBrowserTesting, - platformBrowserDynamic, - platformServer, - platformServerTesting, - router, - routerTesting, - upgrade -}; diff --git a/integration/typings_test_ts21/tsconfig.json b/integration/typings_test_ts21/tsconfig.json deleted file mode 100644 index 468b20cdac..0000000000 --- a/integration/typings_test_ts21/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "commonjs", - "moduleResolution": "node", - "outDir": "../../dist/typing-test/", - "rootDir": ".", - "target": "es5", - "lib": ["es5", "dom", "es2015.collection", "es2015.iterable", "es2015.promise"], - "types": [], - "strictNullChecks": true - }, - "files": [ - "include-all.ts", - "node_modules/@types/jasmine/index.d.ts" - ] -} diff --git a/package.json b/package.json index 41e326ee97..be1d406933 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.4", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index 721f1bd75b..5e2d6763d1 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -817,6 +817,7 @@ export class TransitionAnimationEngine { const disabledElementsSet = new Set(); this.disabledNodes.forEach(node => { + disabledElementsSet.add(node); const nodesThatAreDisabled = this.driver.query(node, QUEUED_SELECTOR, true); for (let i = 0; i < nodesThatAreDisabled.length; i++) { disabledElementsSet.add(nodesThatAreDisabled[i]); @@ -960,22 +961,45 @@ export class TransitionAnimationEngine { const element = player.element; const previousPlayers = this._getPreviousPlayers(element, false, player.namespaceId, player.triggerName, null); - previousPlayers.forEach( - prevPlayer => { getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer); }); + previousPlayers.forEach(prevPlayer => { + getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer); + prevPlayer.destroy(); + }); }); - allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy())); - - // PRE STAGE: fill the ! styles - const preStylesMap = allPreStyleElements.size ? - cloakAndComputeStyles( - this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) : - new Map(); + // this is a special case for nodes that will be removed (either by) + // having their own leave animations or by being queried in a container + // that will be removed once a parent animation is complete. The idea + // here is that * styles must be identical to ! styles because of + // backwards compatibility (* is also filled in by default in many places). + // Otherwise * styles will return an empty value or auto since the element + // that is being getComputedStyle'd will not be visible (since * = destination) + const replaceNodes = allLeaveNodes.filter(node => { + return replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements); + }); // POST STAGE: fill the * styles - const postStylesMap = cloakAndComputeStyles( + const [postStylesMap, allLeaveQueriedNodes] = cloakAndComputeStyles( this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE); + allLeaveQueriedNodes.forEach(node => { + if (replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements)) { + replaceNodes.push(node); + } + }); + + // PRE STAGE: fill the ! styles + const [preStylesMap] = allPreStyleElements.size ? + cloakAndComputeStyles( + this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) : + [new Map()]; + + replaceNodes.forEach(node => { + const post = postStylesMap.get(node); + const pre = preStylesMap.get(node); + postStylesMap.set(node, { ...post, ...pre } as any); + }); + const rootPlayers: TransitionAnimationPlayer[] = []; const subPlayers: TransitionAnimationPlayer[] = []; queuedInstructions.forEach(entry => { @@ -1015,11 +1039,20 @@ export class TransitionAnimationEngine { } else { eraseStyles(element, instruction.fromStyles); player.onDestroy(() => setStyles(element, instruction.toStyles)); + // there still might be a ancestor player animating this + // element therefore we will still add it as a sub player + // even if its animation may be disabled subPlayers.push(player); + if (disabledElementsSet.has(element)) { + skippedPlayers.push(player); + } } }); + // find all of the sub players' corresponding inner animation player subPlayers.forEach(player => { + // even if any players are not found for a sub animation then it + // will still complete itself after the next tick since it's Noop const playersForElement = skippedPlayersMap.get(player.element); if (playersForElement && playersForElement.length) { const innerPlayer = optimizeGroupPlayer(playersForElement); @@ -1051,7 +1084,7 @@ export class TransitionAnimationEngine { // until that animation is over (or the parent queried animation) if (details && details.hasAnimation) continue; - let players: AnimationPlayer[] = []; + let players: TransitionAnimationPlayer[] = []; // if this element is queried or if it contains queried children // then we want for the element not to be removed from the page @@ -1070,8 +1103,10 @@ export class TransitionAnimationEngine { } } } - if (players.length) { - removeNodesAfterAnimationDone(this, element, players); + + const activePlayers = players.filter(p => !p.destroyed); + if (activePlayers.length) { + removeNodesAfterAnimationDone(this, element, activePlayers); } else { this.processLeaveNode(element); } @@ -1141,10 +1176,6 @@ export class TransitionAnimationEngine { private _beforeAnimationBuild( namespaceId: string, instruction: AnimationTransitionInstruction, allPreviousPlayersMap: Map) { - // it's important to do this step before destroying the players - // so that the onDone callback below won't fire before this - eraseStyles(instruction.element, instruction.fromStyles); - const triggerName = instruction.triggerName; const rootElement = instruction.element; @@ -1166,9 +1197,14 @@ export class TransitionAnimationEngine { if (realPlayer.beforeDestroy) { realPlayer.beforeDestroy(); } + player.destroy(); players.push(player); }); }); + + // this needs to be done so that the PRE/POST styles can be + // computed properly without interfering with the previous animation + eraseStyles(rootElement, instruction.fromStyles); } private _buildAnimation( @@ -1400,9 +1436,10 @@ function cloakElement(element: any, value?: string) { function cloakAndComputeStyles( driver: AnimationDriver, elements: any[], elementPropsMap: Map>, - defaultStyle: string): Map { + defaultStyle: string): [Map, any[]] { const cloakVals = elements.map(element => cloakElement(element)); const valuesMap = new Map(); + const failedElements: any[] = []; elementPropsMap.forEach((props: Set, element: any) => { const styles: ɵStyleData = {}; @@ -1413,13 +1450,14 @@ function cloakAndComputeStyles( // by a parent animation element being detached. if (!value || value.length == 0) { element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE; + failedElements.push(element); } }); valuesMap.set(element, styles); }); elements.forEach((element, i) => cloakElement(element, cloakVals[i])); - return valuesMap; + return [valuesMap, failedElements]; } /* @@ -1526,3 +1564,20 @@ function objEquals(a: {[key: string]: any}, b: {[key: string]: any}): boolean { } return true; } + +function replacePostStylesAsPre( + element: any, allPreStyleElements: Map>, + allPostStyleElements: Map>): boolean { + const postEntry = allPostStyleElements.get(element); + if (!postEntry) return false; + + let preEntry = allPreStyleElements.get(element); + if (preEntry) { + postEntry.forEach(data => preEntry !.add(data)); + } else { + allPreStyleElements.set(element, postEntry); + } + + allPostStyleElements.delete(element); + return true; +} diff --git a/packages/animations/browser/src/render/web_animations/web_animations_player.ts b/packages/animations/browser/src/render/web_animations/web_animations_player.ts index 580ebb5042..37b2d626eb 100644 --- a/packages/animations/browser/src/render/web_animations/web_animations_player.ts +++ b/packages/animations/browser/src/render/web_animations/web_animations_player.ts @@ -158,9 +158,9 @@ export class WebAnimationsPlayer implements AnimationPlayer { destroy(): void { if (!this._destroyed) { + this._destroyed = true; this._resetDomPlayerState(); this._onFinish(); - this._destroyed = true; this._onDestroyFns.forEach(fn => fn()); this._onDestroyFns = []; } diff --git a/packages/animations/browser/test/render/transition_animation_engine_spec.ts b/packages/animations/browser/test/render/transition_animation_engine_spec.ts index 3770fb8cd5..64bc1748db 100644 --- a/packages/animations/browser/test/render/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/transition_animation_engine_spec.ts @@ -411,8 +411,10 @@ export function main() { () => { const engine = makeEngine(); const trig = trigger('something', [ - state('x', style({opacity: 0})), state('y', style({opacity: .5})), - state('z', style({opacity: 1})), transition('* => *', animate(1000)) + state('x', style({opacity: 0})), + state('y', style({opacity: .5})), + state('z', style({opacity: 1})), + transition('* => *', animate(1000)), ]); registerTrigger(element, engine, trig); @@ -428,7 +430,7 @@ export function main() { const player2 = engine.players[0]; - expect(parseFloat(element.style.opacity)).toEqual(.5); + expect(parseFloat(element.style.opacity)).not.toEqual(.5); player2.finish(); expect(parseFloat(element.style.opacity)).toEqual(1); diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts index d0f907508b..b353198fcc 100755 --- a/packages/animations/src/animation_metadata.ts +++ b/packages/animations/src/animation_metadata.ts @@ -282,11 +282,12 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { *
...
* ``` * - * ## Disable Child Animations + * ## Disable Animations * A special animation control binding called `@.disabled` can be placed on an element which will - then disable animations for any inner animation triggers situated within the element. + then disable animations for any inner animation triggers situated within the element as well as + any animations on the element itself. * - * When true, the `@.disabled` binding will prevent inner animations from rendering. The example + * When true, the `@.disabled` binding will prevent all animations from rendering. The example below shows how to use this feature: * * ```ts @@ -312,8 +313,8 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * The `@childAnimation` trigger will not animate because `@.disabled` prevents it from happening (when true). * - * Note that `@.disbled` will only disable inner animations (any animations running on the same - element will not be disabled). + * Note that `@.disbled` will only disable all animations (this means any animations running on + * the same element will also be disabled). * * ### Disabling Animations Application-wide * When an area of the template is set to have animations disabled, **all** inner components will diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts index c4e7d3cb76..5e79a49ad1 100644 --- a/packages/common/src/common.ts +++ b/packages/common/src/common.ts @@ -14,7 +14,7 @@ export * from './location/index'; export {NgLocaleLocalization, NgLocalization} from './localization'; export {parseCookieValue as ɵparseCookieValue} from './cookie'; -export {CommonModule} from './common_module'; +export {CommonModule, DeprecatedI18NPipesModule} from './common_module'; export {NgClass, NgFor, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index'; export {DOCUMENT} from './dom_tokens'; export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index'; diff --git a/packages/common/src/common_module.ts b/packages/common/src/common_module.ts index 7c80454f93..4a75bb4f39 100644 --- a/packages/common/src/common_module.ts +++ b/packages/common/src/common_module.ts @@ -29,3 +29,19 @@ import {COMMON_PIPES} from './pipes/index'; }) export class CommonModule { } + +/** + * I18N pipes are being changed to move away from using the JS Intl API. + * + * The former pipes relying on the Intl API will be moved to this module while the `CommonModule` + * will contain the new pipes that do not rely on Intl. + * + * As a first step this module is created empty to ease the migration. + * + * see https://github.com/angular/angular/pull/18284 + * + * @deprecated from v5 + */ +@NgModule({declarations: [], exports: []}) +export class DeprecatedI18NPipesModule { +} diff --git a/packages/common/src/directives/ng_template_outlet.ts b/packages/common/src/directives/ng_template_outlet.ts index 800eed23ec..860d8dce04 100644 --- a/packages/common/src/directives/ng_template_outlet.ts +++ b/packages/common/src/directives/ng_template_outlet.ts @@ -30,7 +30,7 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange * * {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'} * - * @experimental + * @stable */ @Directive({selector: '[ngTemplateOutlet]'}) export class NgTemplateOutlet implements OnChanges { @@ -42,12 +42,6 @@ export class NgTemplateOutlet implements OnChanges { constructor(private _viewContainerRef: ViewContainerRef) {} - /** - * @deprecated v4.0.0 - Renamed to ngTemplateOutletContext. - */ - @Input() - set ngOutletContext(context: Object) { this.ngTemplateOutletContext = context; } - ngOnChanges(changes: SimpleChanges) { const recreateView = this._shouldRecreateView(changes); @@ -72,7 +66,7 @@ export class NgTemplateOutlet implements OnChanges { * - templateRef has changed * - context has changes * - * To mark context object as changed when the corresponding object + * We mark context object as changed when the corresponding object * shape changes (new properties are added or existing properties are removed). * In other words we consider context with the same properties as "the same" even * if object reference changes (see https://github.com/angular/angular/issues/13407). diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts index a4fb977011..85a997bcc1 100644 --- a/packages/common/test/pipes/date_pipe_spec.ts +++ b/packages/common/test/pipes/date_pipe_spec.ts @@ -7,8 +7,8 @@ */ import {DatePipe} from '@angular/common'; -import {JitReflector} from '@angular/compiler'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; export function main() { diff --git a/packages/common/test/pipes/i18n_plural_pipe_spec.ts b/packages/common/test/pipes/i18n_plural_pipe_spec.ts index 436657b026..0dc60f8ad5 100644 --- a/packages/common/test/pipes/i18n_plural_pipe_spec.ts +++ b/packages/common/test/pipes/i18n_plural_pipe_spec.ts @@ -7,9 +7,9 @@ */ import {I18nPluralPipe, NgLocalization} from '@angular/common'; -import {JitReflector} from '@angular/compiler'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; export function main() { describe('I18nPluralPipe', () => { diff --git a/packages/common/test/pipes/i18n_select_pipe_spec.ts b/packages/common/test/pipes/i18n_select_pipe_spec.ts index d88e4b5c64..d0b716f032 100644 --- a/packages/common/test/pipes/i18n_select_pipe_spec.ts +++ b/packages/common/test/pipes/i18n_select_pipe_spec.ts @@ -7,8 +7,8 @@ */ import {I18nSelectPipe} from '@angular/common'; -import {JitReflector} from '@angular/compiler'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; export function main() { describe('I18nSelectPipe', () => { diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 02d37f163f..3e7e682949 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -10,7 +10,6 @@ ts_library( module_name = "@angular/compiler-cli", deps = [ "//packages/compiler", - "//packages/core", "//packages/tsc-wrapped", ], tsconfig = ":tsconfig-build.json", diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts index 482b8ecba8..e7095d0698 100644 --- a/packages/compiler-cli/index.ts +++ b/packages/compiler-cli/index.ts @@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp export * from './src/transformers/api'; export * from './src/transformers/entry_points'; -export {performCompilation} from './src/perform-compile'; +export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform-compile'; // TODO(hansl): moving to Angular 4 need to update this API. export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'; 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 d6d7aedc7f..0000000000 --- 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/basic_spec.ts b/packages/compiler-cli/integrationtest/test/basic_spec.ts index e48e60074d..e9e7f0ac46 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 431cccc4dd..25b4dd7b1b 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', () => { +// TODO(tbosch): 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 972dc03ac9..0000000000 --- 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 2a1553a619..0000000000 --- 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 2928fa0a65..5c753f89ba 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 ef1ce4b573..fddcdf19f9 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -9,14 +9,14 @@ "ng-xi18n": "./src/extract_i18n.js" }, "dependencies": { - "@angular/tsc-wrapped": "5.0.0-beta.2", + "@angular/tsc-wrapped": "5.0.0-beta.4", "reflect-metadata": "^0.1.2", - "minimist": "^1.2.0" + "minimist": "^1.2.0", + "tsickle": "^0.23.4" }, "peerDependencies": { "typescript": "^2.0.2", - "@angular/compiler": "0.0.0-PLACEHOLDER", - "@angular/core": "0.0.0-PLACEHOLDER" + "@angular/compiler": "0.0.0-PLACEHOLDER" }, "repository": { "type": "git", diff --git a/packages/compiler-cli/src/codegen.ts b/packages/compiler-cli/src/codegen.ts index f861db7382..a2ac62f7d0 100644 --- a/packages/compiler-cli/src/codegen.ts +++ b/packages/compiler-cli/src/codegen.ts @@ -11,7 +11,6 @@ * Intended to be used in a build step. */ import * as compiler from '@angular/compiler'; -import {MissingTranslationStrategy} from '@angular/core'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {readFileSync} from 'fs'; import * as ts from 'typescript'; @@ -78,17 +77,17 @@ export class CodeGenerator { } transContent = readFileSync(cliOptions.i18nFile, 'utf8'); } - let missingTranslation = MissingTranslationStrategy.Warning; + let missingTranslation = compiler.core.MissingTranslationStrategy.Warning; if (cliOptions.missingTranslation) { switch (cliOptions.missingTranslation) { case 'error': - missingTranslation = MissingTranslationStrategy.Error; + missingTranslation = compiler.core.MissingTranslationStrategy.Error; break; case 'warning': - missingTranslation = MissingTranslationStrategy.Warning; + missingTranslation = compiler.core.MissingTranslationStrategy.Warning; break; case 'ignore': - missingTranslation = MissingTranslationStrategy.Ignore; + missingTranslation = compiler.core.MissingTranslationStrategy.Ignore; break; default: throw new Error( @@ -96,7 +95,7 @@ export class CodeGenerator { } } if (!transContent) { - missingTranslation = MissingTranslationStrategy.Ignore; + missingTranslation = compiler.core.MissingTranslationStrategy.Ignore; } const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, { translations: transContent, diff --git a/packages/compiler-cli/src/compiler_host.ts b/packages/compiler-cli/src/compiler_host.ts index e7c5c03954..63b7d595d4 100644 --- a/packages/compiler-cli/src/compiler_host.ts +++ b/packages/compiler-cli/src/compiler_host.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompilerHost, StaticSymbol} from '@angular/compiler'; +import {AotCompilerHost, StaticSymbol, UrlResolver, createOfflineCompileUrlResolver, syntaxError} from '@angular/compiler'; import {AngularCompilerOptions, CollectorOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; @@ -20,159 +20,33 @@ const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/; const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$|\.ngsummary\.ts$/; const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/; -export interface CompilerHostContext extends ts.ModuleResolutionHost { - readResource?(fileName: string): Promise|string; - assumeFileExists(fileName: string): void; -} - export interface MetadataProvider { getMetadata(source: ts.SourceFile): ModuleMetadata|undefined; } -export class CompilerHost implements AotCompilerHost { - private isGenDirChildOfRootDir: boolean; - protected basePath: string; - private genDir: string; +export interface BaseAotCompilerHostContext extends ts.ModuleResolutionHost { + readResource?(fileName: string): Promise|string; +} + +export abstract class BaseAotCompilerHost implements + AotCompilerHost { private resolverCache = new Map(); private flatModuleIndexCache = new Map(); private flatModuleIndexNames = new Set(); private flatModuleIndexRedirectNames = new Set(); - private moduleFileNames = new Map(); - protected resolveModuleNameHost: CompilerHostContext; constructor( protected program: ts.Program, protected options: AngularCompilerOptions, - protected context: CompilerHostContext, collectorOptions?: CollectorOptions, - protected metadataProvider: MetadataProvider = new MetadataCollector()) { - // normalize the path so that it never ends with '/'. - this.basePath = path.normalize(path.join(this.options.basePath !, '.')).replace(/\\/g, '/'); - this.genDir = path.normalize(path.join(this.options.genDir !, '.')).replace(/\\/g, '/'); + protected context: C, + protected metadataProvider: MetadataProvider = new MetadataCollector()) {} - const genPath: string = path.relative(this.basePath, this.genDir); - this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); - this.resolveModuleNameHost = Object.create(this.context); + abstract moduleNameToFileName(m: string, containingFile: string): string|null; - // When calling ts.resolveModuleName, - // additional allow checks for .d.ts files to be done based on - // checks for .ngsummary.json files, - // so that our codegen depends on fewer inputs and requires to be called - // less often. - // This is needed as we use ts.resolveModuleName in reflector_host - // and it should be able to resolve summary file names. - this.resolveModuleNameHost.fileExists = (fileName: string): boolean => { - if (this.context.fileExists(fileName)) { - return true; - } - if (DTS.test(fileName)) { - const base = fileName.substring(0, fileName.length - 5); - return this.context.fileExists(base + '.ngsummary.json'); - } - return false; - }; - } + abstract resourceNameToFileName(m: string, containingFile: string): string|null; - // We use absolute paths on disk as canonical. - getCanonicalFileName(fileName: string): string { return fileName; } + abstract fileNameToModuleName(importedFile: string, containingFile: string): string|null; - moduleNameToFileName(m: string, containingFile: string): string|null { - const key = m + ':' + (containingFile || ''); - let result: string|null = this.moduleFileNames.get(key) || null; - if (!result) { - if (!containingFile || !containingFile.length) { - if (m.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); - } - m = m.replace(EXT, ''); - const resolved = - ts.resolveModuleName( - m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) - .resolvedModule; - result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; - this.moduleFileNames.set(key, result); - } - return result; - }; + abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string; - /** - * We want a moduleId that will appear in import statements in the generated code. - * These need to be in a form that system.js can load, so absolute file paths don't work. - * - * The `containingFile` is always in the `genDir`, where as the `importedFile` can be in - * `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or - * existing file. - * - * | genDir | node_module | rootDir - * --------------+----------+-------------+---------- - * generated | relative | relative | n/a - * existing file | n/a | absolute | relative(*) - * - * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. - */ - fileNameToModuleName(importedFile: string, containingFile: string): string { - // If a file does not yet exist (because we compile it later), we still need to - // assume it exists it so that the `resolve` method works! - if (importedFile !== containingFile && !this.context.fileExists(importedFile)) { - this.context.assumeFileExists(importedFile); - } - - containingFile = this.rewriteGenDirPath(containingFile); - const containingDir = path.dirname(containingFile); - // drop extension - importedFile = importedFile.replace(EXT, ''); - - const nodeModulesIndex = importedFile.indexOf(NODE_MODULES); - const importModule = nodeModulesIndex === -1 ? - null : - importedFile.substring(nodeModulesIndex + NODE_MODULES.length); - const isGeneratedFile = IS_GENERATED.test(importedFile); - - if (isGeneratedFile) { - // rewrite to genDir path - if (importModule) { - // it is generated, therefore we do a relative path to the factory - return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule); - } else { - // assume that import is also in `genDir` - importedFile = this.rewriteGenDirPath(importedFile); - return this.dotRelative(containingDir, importedFile); - } - } else { - // user code import - if (importModule) { - return importModule; - } else { - if (!this.isGenDirChildOfRootDir) { - // assume that they are on top of each other. - importedFile = importedFile.replace(this.basePath, this.genDir); - } - if (SHALLOW_IMPORT.test(importedFile)) { - return importedFile; - } - return this.dotRelative(containingDir, importedFile); - } - } - } - - private dotRelative(from: string, to: string): string { - const rPath: string = path.relative(from, to).replace(/\\/g, '/'); - return rPath.startsWith('.') ? rPath : './' + rPath; - } - - /** - * Moves the path into `genDir` folder while preserving the `node_modules` directory. - */ - private rewriteGenDirPath(filepath: string) { - const nodeModulesIndex = filepath.indexOf(NODE_MODULES); - if (nodeModulesIndex !== -1) { - // If we are in node_modulse, transplant them into `genDir`. - return path.join(this.genDir, filepath.substring(nodeModulesIndex)); - } else { - // pretend that containing file is on top of the `genDir` to normalize the paths. - // we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later. - return filepath.replace(this.basePath, this.genDir); - } - } + abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string; protected getSourceFile(filePath: string): ts.SourceFile { const sf = this.program.getSourceFile(filePath); @@ -263,6 +137,9 @@ export class CompilerHost implements AotCompilerHost { loadResource(filePath: string): Promise|string { if (this.context.readResource) return this.context.readResource(filePath); + if (!this.context.fileExists(filePath)) { + throw syntaxError(`Error: Resource file not found: ${filePath}`); + } return this.context.readFile(filePath); } @@ -273,10 +150,6 @@ export class CompilerHost implements AotCompilerHost { return null; } - getOutputFileName(sourceFilePath: string): string { - return sourceFilePath.replace(EXT, '') + '.d.ts'; - } - isSourceFile(filePath: string): boolean { const excludeRegex = this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; @@ -294,29 +167,6 @@ export class CompilerHost implements AotCompilerHost { return true; } - calculateEmitPath(filePath: string): string { - // Write codegen in a directory structure matching the sources. - let root = this.options.basePath !; - for (const eachRootDir of this.options.rootDirs || []) { - if (this.options.trace) { - console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); - } - if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) { - root = eachRootDir; - } - } - - // transplant the codegen path to be inside the `genDir` - let relativePath: string = path.relative(root, filePath); - while (relativePath.startsWith('..' + path.sep)) { - // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything - // into `genDir`. - relativePath = relativePath.substr(3); - } - - return path.join(this.options.genDir !, relativePath); - } - private hasBundleIndex(filePath: string): boolean { const checkBundleIndex = (directory: string): boolean => { let result = this.flatModuleIndexCache.get(directory); @@ -375,6 +225,192 @@ export class CompilerHost implements AotCompilerHost { } } +export interface CompilerHostContext extends ts.ModuleResolutionHost { + readResource?(fileName: string): Promise|string; + assumeFileExists(fileName: string): void; +} + +export class CompilerHost extends BaseAotCompilerHost { + protected basePath: string; + private moduleFileNames = new Map(); + private isGenDirChildOfRootDir: boolean; + private genDir: string; + protected resolveModuleNameHost: CompilerHostContext; + private urlResolver: UrlResolver; + + constructor( + program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext, + collectorOptions?: CollectorOptions, + metadataProvider: MetadataProvider = new MetadataCollector(collectorOptions)) { + super(program, options, context, metadataProvider); + // normalize the path so that it never ends with '/'. + this.basePath = path.normalize(path.join(this.options.basePath !, '.')).replace(/\\/g, '/'); + this.genDir = path.normalize(path.join(this.options.genDir !, '.')).replace(/\\/g, '/'); + + const genPath: string = path.relative(this.basePath, this.genDir); + this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); + + this.resolveModuleNameHost = Object.create(this.context); + + // When calling ts.resolveModuleName, + // additional allow checks for .d.ts files to be done based on + // checks for .ngsummary.json files, + // so that our codegen depends on fewer inputs and requires to be called + // less often. + // This is needed as we use ts.resolveModuleName in reflector_host + // and it should be able to resolve summary file names. + this.resolveModuleNameHost.fileExists = (fileName: string): boolean => { + if (this.context.fileExists(fileName)) { + return true; + } + if (DTS.test(fileName)) { + const base = fileName.substring(0, fileName.length - 5); + return this.context.fileExists(base + '.ngsummary.json'); + } + return false; + }; + this.urlResolver = createOfflineCompileUrlResolver(); + } + + toSummaryFileName(fileName: string, referringSrcFileName: string): string { + return fileName.replace(EXT, '') + '.d.ts'; + } + + fromSummaryFileName(fileName: string, referringLibFileName: string): string { return fileName; } + + calculateEmitPath(filePath: string): string { + // Write codegen in a directory structure matching the sources. + let root = this.options.basePath !; + for (const eachRootDir of this.options.rootDirs || []) { + if (this.options.trace) { + console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); + } + if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) { + root = eachRootDir; + } + } + + // transplant the codegen path to be inside the `genDir` + let relativePath: string = path.relative(root, filePath); + while (relativePath.startsWith('..' + path.sep)) { + // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything + // into `genDir`. + relativePath = relativePath.substr(3); + } + + return path.join(this.options.genDir !, relativePath); + } + + // We use absolute paths on disk as canonical. + getCanonicalFileName(fileName: string): string { return fileName; } + + moduleNameToFileName(m: string, containingFile: string): string|null { + const key = m + ':' + (containingFile || ''); + let result: string|null = this.moduleFileNames.get(key) || null; + if (!result) { + if (!containingFile || !containingFile.length) { + if (m.indexOf('.') === 0) { + throw new Error('Resolution of relative paths requires a containing file.'); + } + // Any containing file gives the same result for absolute imports + containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts')); + } + m = m.replace(EXT, ''); + const resolved = + ts.resolveModuleName( + m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) + .resolvedModule; + result = resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; + this.moduleFileNames.set(key, result); + } + return result; + }; + + /** + * We want a moduleId that will appear in import statements in the generated code. + * These need to be in a form that system.js can load, so absolute file paths don't work. + * + * The `containingFile` is always in the `genDir`, where as the `importedFile` can be in + * `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or + * existing file. + * + * | genDir | node_module | rootDir + * --------------+----------+-------------+---------- + * generated | relative | relative | n/a + * existing file | n/a | absolute | relative(*) + * + * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. + */ + fileNameToModuleName(importedFile: string, containingFile: string): string { + // If a file does not yet exist (because we compile it later), we still need to + // assume it exists it so that the `resolve` method works! + if (importedFile !== containingFile && !this.context.fileExists(importedFile)) { + this.context.assumeFileExists(importedFile); + } + + containingFile = this.rewriteGenDirPath(containingFile); + const containingDir = path.dirname(containingFile); + // drop extension + importedFile = importedFile.replace(EXT, ''); + + const nodeModulesIndex = importedFile.indexOf(NODE_MODULES); + const importModule = nodeModulesIndex === -1 ? + null : + importedFile.substring(nodeModulesIndex + NODE_MODULES.length); + const isGeneratedFile = IS_GENERATED.test(importedFile); + + if (isGeneratedFile) { + // rewrite to genDir path + if (importModule) { + // it is generated, therefore we do a relative path to the factory + return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule); + } else { + // assume that import is also in `genDir` + importedFile = this.rewriteGenDirPath(importedFile); + return this.dotRelative(containingDir, importedFile); + } + } else { + // user code import + if (importModule) { + return importModule; + } else { + if (!this.isGenDirChildOfRootDir) { + // assume that they are on top of each other. + importedFile = importedFile.replace(this.basePath, this.genDir); + } + if (SHALLOW_IMPORT.test(importedFile)) { + return importedFile; + } + return this.dotRelative(containingDir, importedFile); + } + } + } + + resourceNameToFileName(m: string, containingFile: string): string { + return this.urlResolver.resolve(containingFile, m); + } + + /** + * Moves the path into `genDir` folder while preserving the `node_modules` directory. + */ + private rewriteGenDirPath(filepath: string) { + const nodeModulesIndex = filepath.indexOf(NODE_MODULES); + if (nodeModulesIndex !== -1) { + // If we are in node_modulse, transplant them into `genDir`. + return path.join(this.genDir, filepath.substring(nodeModulesIndex)); + } else { + // pretend that containing file is on top of the `genDir` to normalize the paths. + // we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later. + return filepath.replace(this.basePath, this.genDir); + } + } + + private dotRelative(from: string, to: string): string { + const rPath: string = path.relative(from, to).replace(/\\/g, '/'); + return rPath.startsWith('.') ? rPath : './' + rPath; + } +} + export class CompilerHostContextAdapter { protected assumedExists: {[fileName: string]: boolean} = {}; diff --git a/packages/compiler-cli/src/diagnostics/check_types.ts b/packages/compiler-cli/src/diagnostics/check_types.ts index 0029f2fe60..48584a8493 100644 --- a/packages/compiler-cli/src/diagnostics/check_types.ts +++ b/packages/compiler-cli/src/diagnostics/check_types.ts @@ -9,7 +9,7 @@ import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler'; import * as ts from 'typescript'; -import {Diagnostic, DiagnosticCategory} from '../transformers/api'; +import {Diagnostic} from '../transformers/api'; interface FactoryInfo { source: ts.SourceFile; @@ -143,7 +143,7 @@ export class TypeChecker { const diagnosticsList = diagnosticsFor(fileName); diagnosticsList.push({ message: diagnosticMessageToString(diagnostic.messageText), - category: diagnosticCategoryConverter(diagnostic.category), span + category: diagnostic.category, span }); } } @@ -166,11 +166,6 @@ function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string): return ts.flattenDiagnosticMessageText(message, '\n'); } -function diagnosticCategoryConverter(kind: ts.DiagnosticCategory) { - // The diagnostics kind matches ts.DiagnosticCategory. Review this code if this changes. - return kind as any as DiagnosticCategory; -} - function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo { const {sourceText, context} = emitter.emitStatementsAndContext(file.srcFileUrl, file.genFileUrl, file.stmts !); diff --git a/packages/compiler-cli/src/diagnostics/expression_diagnostics.ts b/packages/compiler-cli/src/diagnostics/expression_diagnostics.ts index 3a60d67709..4bc0721c60 100644 --- a/packages/compiler-cli/src/diagnostics/expression_diagnostics.ts +++ b/packages/compiler-cli/src/diagnostics/expression_diagnostics.ts @@ -56,7 +56,7 @@ function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] { name: reference.name, kind: 'reference', type: type || info.query.getBuiltinType(BuiltinType.Any), - get definition() { return getDefintionOf(info, reference); } + get definition() { return getDefinitionOf(info, reference); } }); } } @@ -77,7 +77,7 @@ function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] { return result; } -function getDefintionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined { +function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined { if (info.fileName) { const templateOffset = info.offset; return [{ @@ -124,7 +124,7 @@ function getVarDeclarations( } result.push({ name, - kind: 'variable', type, get definition() { return getDefintionOf(info, variable); } + kind: 'variable', type, get definition() { return getDefinitionOf(info, variable); } }); } } diff --git a/packages/compiler-cli/src/diagnostics/typescript_symbols.ts b/packages/compiler-cli/src/diagnostics/typescript_symbols.ts index 9d5199be2b..216c04e3b9 100644 --- a/packages/compiler-cli/src/diagnostics/typescript_symbols.ts +++ b/packages/compiler-cli/src/diagnostics/typescript_symbols.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotSummaryResolver, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, InterpolationConfig, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, analyzeNgModules, createOfflineCompileUrlResolver, extractProgramSymbols} from '@angular/compiler'; -import {ViewEncapsulation, ɵConsole as Console} from '@angular/core'; +import {AotSummaryResolver, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, InterpolationConfig, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, analyzeNgModules, extractProgramSymbols} from '@angular/compiler'; import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; diff --git a/packages/compiler-cli/src/language_services.ts b/packages/compiler-cli/src/language_services.ts new file mode 100644 index 0000000000..34fd77fd23 --- /dev/null +++ b/packages/compiler-cli/src/language_services.ts @@ -0,0 +1,24 @@ +/** + * @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 + */ + +/* + +The API from compiler-cli that language-service can see. +It is important that none the exported modules require anything other than +Angular modules and Typescript as this will indirectly add a dependency +to the language service. + +*/ + +export {AngularCompilerOptions} from '@angular/tsc-wrapped'; +export {CompilerHost, CompilerHostContext, MetadataProvider, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host'; +export {TypeChecker} from './diagnostics/check_types'; +export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics'; +export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type'; +export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols'; +export {getClassFromStaticSymbol, getClassMembers, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './diagnostics/typescript_symbols'; diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 3dc68977d9..60b989bb42 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -7,17 +7,131 @@ * found in the LICENSE file at https://angular.io/license */ - // Must be imported first, because Angular decorators throw on load. import 'reflect-metadata'; import * as ts from 'typescript'; import * as tsc from '@angular/tsc-wrapped'; -import {isSyntaxError} from '@angular/compiler'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as tsickle from 'tsickle'; +import * as api from './transformers/api'; +import * as ngc from './transformers/entry_points'; +import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile'; +import {isSyntaxError} from '@angular/compiler'; import {CodeGenerator} from './codegen'; -function codegen( +export function main( + args: string[], consoleError: (s: string) => void = console.error): Promise { + const parsedArgs = require('minimist')(args); + const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs); + if (configErrors.length) { + return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError)); + } + if (options.disableTransformerPipeline) { + return disabledTransformerPipelineNgcMain(parsedArgs, consoleError); + } + const {diagnostics: compileDiags} = + performCompilation({rootNames, options, emitCallback: createEmitCallback(options)}); + return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError)); +} + +export function mainSync( + args: string[], consoleError: (s: string) => void = console.error): number { + const parsedArgs = require('minimist')(args); + const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs); + if (configErrors.length) { + return reportErrorsAndExit(options, configErrors, consoleError); + } + const {diagnostics: compileDiags} = + performCompilation({rootNames, options, emitCallback: createEmitCallback(options)}); + return reportErrorsAndExit(options, compileDiags, consoleError); +} + +function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback { + const tsickleOptions: tsickle.TransformerOptions = { + googmodule: false, + untyped: true, + convertIndexImportShorthand: true, + transformDecorators: options.annotationsAs !== 'decorators', + transformTypesToClosure: options.annotateForClosureCompiler, + }; + + const tsickleHost: tsickle.TransformerHost = { + shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName), + pathToModuleName: (context, importPath) => '', + shouldIgnoreWarningsForPath: (filePath) => false, + fileNameToModuleId: (fileName) => fileName, + }; + + return ({ + program, + targetSourceFile, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers = {}, + host, + options + }) => + tsickle.emitWithTsickle( + program, tsickleHost, tsickleOptions, host, options, targetSourceFile, writeFile, + cancellationToken, emitOnlyDtsFiles, { + beforeTs: customTransformers.before, + afterTs: customTransformers.after, + }); +} + +function readCommandLineAndConfiguration(args: any): ParsedConfiguration { + const project = args.p || args.project || '.'; + const allDiagnostics: Diagnostics = []; + const config = readConfiguration(project); + const options = mergeCommandLineParams(args, config.options); + return {rootNames: config.rootNames, options, errors: config.errors}; +} + +function reportErrorsAndExit( + options: api.CompilerOptions, allDiagnostics: Diagnostics, + consoleError: (s: string) => void = console.error): number { + const exitCode = allDiagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0; + if (allDiagnostics.length) { + consoleError(formatDiagnostics(options, allDiagnostics)); + } + return exitCode; +} + +function mergeCommandLineParams( + cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions { + // TODO: also merge in tsc command line parameters by calling + // ts.readCommandLine. + if (cliArgs.i18nFile) options.i18nInFile = cliArgs.i18nFile; + if (cliArgs.i18nFormat) options.i18nInFormat = cliArgs.i18nFormat; + if (cliArgs.locale) options.i18nInLocale = cliArgs.locale; + const mt = cliArgs.missingTranslation; + if (mt === 'error' || mt === 'warning' || mt === 'ignore') { + options.i18nInMissingTranslations = mt; + } + return options; +} + +function disabledTransformerPipelineNgcMain( + args: any, consoleError: (s: string) => void = console.error): Promise { + const cliOptions = new tsc.NgcCliOptions(args); + const project = args.p || args.project || '.'; + return tsc.main(project, cliOptions, disabledTransformerPipelineCodegen) + .then(() => 0) + .catch(e => { + if (e instanceof tsc.UserError || isSyntaxError(e)) { + consoleError(e.message); + } else { + consoleError(e.stack); + } + return Promise.resolve(1); + }); +} + +function disabledTransformerPipelineCodegen( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program, host: ts.CompilerHost) { if (ngOptions.enableSummariesForJit === undefined) { @@ -27,25 +141,8 @@ function codegen( return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen(); } -export function main( - args: any, consoleError: (s: string) => void = console.error): Promise { - const project = args.p || args.project || '.'; - const cliOptions = new tsc.NgcCliOptions(args); - - return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => { - if (e instanceof tsc.UserError || isSyntaxError(e)) { - consoleError(e.message); - return Promise.resolve(1); - } else { - consoleError(e.stack); - consoleError('Compilation failed'); - return Promise.resolve(1); - } - }); -} - // 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); + main(args).then((exitCode: number) => process.exitCode = exitCode); } diff --git a/packages/compiler-cli/src/ngc.ts b/packages/compiler-cli/src/ngc.ts deleted file mode 100644 index 658ed31d6e..0000000000 --- a/packages/compiler-cli/src/ngc.ts +++ /dev/null @@ -1,42 +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 - */ - -// Must be imported first, because Angular decorators throw on load. -import 'reflect-metadata'; - -import {isSyntaxError, syntaxError} from '@angular/compiler'; -import * as fs from 'fs'; -import * as path from 'path'; - -import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile'; - -export function main( - args: string[], consoleError: (s: string) => void = console.error, - checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics): number { - try { - 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 {parsed, ngOptions} = readConfiguration(project, basePath, checkFunc); - return performCompilation( - basePath, parsed.fileNames, parsed.options, ngOptions, consoleError, checkFunc); - } catch (e) { - consoleError(e.stack); - consoleError('Compilation failed'); - return 2; - } -} - -// 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/ngtools_impl.ts b/packages/compiler-cli/src/ngtools_impl.ts index c14f35f09a..2e90fd6bc6 100644 --- a/packages/compiler-cli/src/ngtools_impl.ts +++ b/packages/compiler-cli/src/ngtools_impl.ts @@ -12,8 +12,8 @@ * This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by * something else. */ -import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler'; -import {NgModule} from '@angular/core'; +import {AotCompilerHost, StaticReflector, StaticSymbol, core} from '@angular/compiler'; + // We cannot depend directly to @angular/router. type Route = any; @@ -168,8 +168,10 @@ function _extractLazyRoutesFromStaticModule( /** * Get the NgModule Metadata of a symbol. */ -function _getNgModuleMetadata(staticSymbol: StaticSymbol, reflector: StaticReflector): NgModule { - const ngModules = reflector.annotations(staticSymbol).filter((s: any) => s instanceof NgModule); +function _getNgModuleMetadata( + staticSymbol: StaticSymbol, reflector: StaticReflector): core.NgModule { + const ngModules = + reflector.annotations(staticSymbol).filter((s: any) => core.createNgModule.isTypeOf(s)); if (ngModules.length === 0) { throw new Error(`${staticSymbol.name} is not an NgModule`); } diff --git a/packages/compiler-cli/src/perform-compile.ts b/packages/compiler-cli/src/perform-compile.ts index 55feddb347..2a81339840 100644 --- a/packages/compiler-cli/src/perform-compile.ts +++ b/packages/compiler-cli/src/perform-compile.ts @@ -7,33 +7,35 @@ */ import {isSyntaxError, syntaxError} from '@angular/compiler'; -import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped'; +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'; const TS_EXT = /\.ts$/; -export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[]; +export type Diagnostics = Array; -function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] { - return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText); +function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic { + return diagnostic && (diagnostic.file || diagnostic.messageText); } -function formatDiagnostics(cwd: string, diags: Diagnostics): string { +export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string { if (diags && diags.length) { - if (isTsDiagnostics(diags)) { - return ts.formatDiagnostics(diags, { - getCurrentDirectory: () => cwd, - getCanonicalFileName: fileName => fileName, - getNewLine: () => ts.sys.newLine - }); - } else { - return diags - .map(d => { - let res = api.DiagnosticCategory[d.category]; + const tsFormatHost: ts.FormatDiagnosticsHost = { + getCurrentDirectory: () => options.basePath || process.cwd(), + getCanonicalFileName: fileName => fileName, + getNewLine: () => ts.sys.newLine + }; + return diags + .map(d => { + if (isTsDiagnostic(d)) { + return ts.formatDiagnostics([d], tsFormatHost); + } else { + let res = ts.DiagnosticCategory[d.category]; if (d.span) { res += ` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`; @@ -44,149 +46,141 @@ function formatDiagnostics(cwd: string, diags: Diagnostics): string { res += `: ${d.message}\n`; } return res; - }) - .join(); - } + } + }) + .join(); } else return ''; } -/** - * Throw a syntax error exception with a message formatted for output - * if the args parameter contains diagnostics errors. - * - * @param cwd The directory to report error as relative to. - * @param args A list of potentially empty diagnostic errors. - */ -export function throwOnDiagnostics(cwd: string, ...args: Diagnostics[]) { - if (args.some(diags => !!(diags && diags[0]))) { - throw syntaxError(args.map(diags => { - if (diags && diags[0]) { - return formatDiagnostics(cwd, diags); - } - }) - .filter(message => !!message) - .join('')); - } +export interface ParsedConfiguration { + options: api.CompilerOptions; + rootNames: string[]; + errors: Diagnostics; } -function syntheticError(message: string): ts.Diagnostic { - return { - file: null as any as ts.SourceFile, - start: 0, - length: 0, - messageText: message, - category: ts.DiagnosticCategory.Error, - code: 0 - }; +export function calcProjectFileAndBasePath(project: string): + {projectFile: string, basePath: string} { + const projectIsDir = fs.lstatSync(project).isDirectory(); + const projectFile = projectIsDir ? path.join(project, 'tsconfig.json') : project; + const projectDir = projectIsDir ? project : path.dirname(project); + const basePath = path.resolve(process.cwd(), projectDir); + return {projectFile, basePath}; +} + +export function createNgCompilerOptions( + basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions { + return {...tsOptions, ...config.angularCompilerOptions, genDir: basePath, basePath}; } export function readConfiguration( - project: string, basePath: string, - checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics, - existingOptions?: ts.CompilerOptions) { - // Allow a directory containing tsconfig.json as the project value - // Note, TS@next returns an empty array, while earlier versions throw - const projectFile = - fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project; - let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); - - if (error) checkFunc(basePath, [error]); - const parseConfigHost = { - useCaseSensitiveFileNames: true, - fileExists: fs.existsSync, - readDirectory: ts.sys.readDirectory, - readFile: ts.sys.readFile - }; - const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions); - - checkFunc(basePath, parsed.errors); - - // Default codegen goes to the current directory - // Parsed options are already converted to absolute paths - const ngOptions = config.angularCompilerOptions || {}; - // Ignore the genDir option - ngOptions.genDir = basePath; - - return {parsed, ngOptions}; -} - -function getProjectDirectory(project: string): string { - let isFile: boolean; + project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration { try { - isFile = fs.lstatSync(project).isFile(); - } catch (e) { - // Project doesn't exist. Assume it is a file has an extension. This case happens - // when the project file is passed to set basePath but no tsconfig.json file exists. - // It is used in tests to ensure that the options can be passed in without there being - // an actual config file. - isFile = path.extname(project) !== ''; - } + const {projectFile, basePath} = calcProjectFileAndBasePath(project); - // If project refers to a file, the project directory is the file's parent directory - // otherwise project is the project directory. - return isFile ? path.dirname(project) : project; + let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile); + + if (error) { + return {errors: [error], rootNames: [], options: {}}; + } + const parseConfigHost = { + useCaseSensitiveFileNames: true, + fileExists: fs.existsSync, + readDirectory: ts.sys.readDirectory, + readFile: ts.sys.readFile + }; + const parsed = + ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions); + const rootNames = parsed.fileNames.map(f => path.normalize(f)); + + const options = createNgCompilerOptions(basePath, config, parsed.options); + return {rootNames, options, errors: parsed.errors}; + } catch (e) { + const errors: Diagnostics = [{ + category: ts.DiagnosticCategory.Error, + message: e.stack, + }]; + return {errors, rootNames: [], options: {}}; + } } export function performCompilation( - basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any, - consoleError: (s: string) => void = console.error, - checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics, - tsCompilerHost?: ts.CompilerHost) { - try { - ngOptions.basePath = basePath; - ngOptions.genDir = basePath; + {rootNames, options, host, oldProgram, emitCallback, customTransformers}: { + rootNames: string[], + options: api.CompilerOptions, + host?: api.CompilerHost, + oldProgram?: api.Program, + emitCallback?: api.TsEmitCallback, + customTransformers?: api.CustomTransformers + }): { + program?: api.Program, + emitResult?: ts.EmitResult, + diagnostics: Diagnostics, +} { + const [major, minor] = ts.version.split('.'); - 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 ngHostOptions = {...options, ...ngOptions}; - const ngHost = ng.createHost({tsHost: host, options: ngHostOptions}); - - const ngProgram = - ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions}); - - // Check parameter diagnostics - checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics()); - - // Check syntactic diagnostics - checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics()); - - // Check TypeScript semantic and Angular structure diagnostics - checkFunc( - basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics()); - - // Check Angular semantic diagnostics - checkFunc(basePath, ngProgram.getNgSemanticDiagnostics()); - - ngProgram.emit({ - emitFlags: api.EmitFlags.Default | - ((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) - }); - } catch (e) { - if (isSyntaxError(e)) { - console.error(e.message); - consoleError(e.message); - return 1; - } - throw e; + if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) { + throw new Error('Must use TypeScript > 2.3 to have transformer support'); } - return 0; -} + const allDiagnostics: Diagnostics = []; + + function checkDiagnostics(diags: Diagnostics | undefined) { + if (diags) { + allDiagnostics.push(...diags); + return diags.every(d => d.category !== ts.DiagnosticCategory.Error); + } + return true; + } + + let program: api.Program|undefined; + let emitResult: ts.EmitResult|undefined; + try { + if (!host) { + host = ng.createCompilerHost({options}); + } + + program = ng.createProgram({rootNames, host, options, oldProgram}); + + let shouldEmit = true; + // Check parameter diagnostics + shouldEmit = shouldEmit && checkDiagnostics([ + ...program !.getTsOptionDiagnostics(), ...program !.getNgOptionDiagnostics() + ]); + + // Check syntactic diagnostics + shouldEmit = shouldEmit && checkDiagnostics(program !.getTsSyntacticDiagnostics()); + + // Check TypeScript semantic and Angular structure diagnostics + shouldEmit = + shouldEmit && + checkDiagnostics( + [...program !.getTsSemanticDiagnostics(), ...program !.getNgStructuralDiagnostics()]); + + // Check Angular semantic diagnostics + shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics()); + + if (shouldEmit) { + emitResult = program !.emit({ + emitCallback, + customTransformers, + emitFlags: api.EmitFlags.Default | + ((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata) + }); + allDiagnostics.push(...emitResult.diagnostics); + } + } catch (e) { + let errMsg: string; + if (isSyntaxError(e)) { + // don't report the stack for syntax errors as they are well known errors. + errMsg = e.message; + } else { + errMsg = e.stack; + } + allDiagnostics.push({ + category: ts.DiagnosticCategory.Error, + message: errMsg, + }); + } + return {program, emitResult, diagnostics: allDiagnostics}; +} \ 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 de1ccc3b9d..cc8860968c 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -9,21 +9,16 @@ import {ParseSourceSpan} from '@angular/compiler'; import * as ts from 'typescript'; -export enum DiagnosticCategory { - Warning = 0, - Error = 1, - Message = 2, -} - export interface Diagnostic { message: string; span?: ParseSourceSpan; - category: DiagnosticCategory; + category: ts.DiagnosticCategory; } 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. @@ -95,36 +90,68 @@ export interface CompilerOptions extends ts.CompilerOptions { // Whether to enable lowering expressions lambdas and expressions in a reference value // position. disableExpressionLowering?: boolean; + + // The list of expected files, when provided: + // - extra files are filtered out, + // - missing files are created empty. + expectedOut?: string[]; + + // Locale of the application + i18nOutLocale?: string; + // Export format (xlf, xlf2 or xmb) + i18nOutFormat?: 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 + i18nInMissingTranslations?: 'error'|'warning'|'ignore'; + + // Whether to remove blank text nodes from compiled templates. It is `true` by default + // in Angular 5 and will be re-visited in Angular 6. + preserveWhitespaces?: boolean; } -export interface ModuleFilenameResolver { +export interface CompilerHost extends ts.CompilerHost { /** * Converts a module name that is used in an `import` to a file path. * I.e. `path/to/containingFile.ts` containing `import {...} from 'module-name'`. */ moduleNameToFileName(moduleName: string, containingFile?: string): string|null; - /** - * Converts a file path to a module name that can be used as an `import. + * Converts a file path to a module name that can be used as an `import ...` * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. - * - * See ImportResolver. */ fileNameToModuleName(importedFilePath: string, containingFilePath: string): string|null; - - getNgCanonicalFileName(fileName: string): string; - - assumeFileExists(fileName: string): void; -} - -export interface CompilerHost extends ts.CompilerHost, ModuleFilenameResolver { + /** + * Converts a file path for a resource that is used in a source file or another resource + * into a filepath. + */ + resourceNameToFileName(resourceName: string, containingFilePath: string): string|null; + /** + * Converts a file name into a representation that should be stored in a summary file. + * This has to include changing the suffix as well. + * E.g. + * `some_file.ts` -> `some_file.d.ts` + * + * @param referringSrcFileName the soure file that refers to fileName + */ + toSummaryFileName(fileName: string, referringSrcFileName: string): string; + /** + * Converts a fileName that was processed by `toSummaryFileName` back into a real fileName + * given the fileName of the library that is referrig to it. + */ + fromSummaryFileName(fileName: string, referringLibFileName: string): string; /** * Load a referenced resource either statically or asynchronously. If the host returns a * `Promise` it is assumed the user of the corresponding `Program` will call * `loadNgStructureAsync()`. Returing `Promise` outside `loadNgStructureAsync()` will * cause a diagnostics diagnostic error or an exception to be thrown. - * - * If `loadResource()` is not provided, `readFile()` will be called to load the resource. */ readResource?(fileName: string): Promise|string; } @@ -140,11 +167,23 @@ export enum EmitFlags { All = DTS | JS | Metadata | I18nBundle | Summary } -// TODO(chuckj): Support CustomTransformers once we require TypeScript 2.3+ -// export interface CustomTransformers { -// beforeTs?: ts.TransformerFactory[]; -// afterTs?: ts.TransformerFactory[]; -// } +export interface CustomTransformers { + beforeTs?: ts.TransformerFactory[]; + afterTs?: ts.TransformerFactory[]; +} + +export interface TsEmitArguments { + program: ts.Program; + host: CompilerHost; + options: CompilerOptions; + targetSourceFile?: ts.SourceFile; + writeFile?: ts.WriteFileCallback; + cancellationToken?: ts.CancellationToken; + emitOnlyDtsFiles?: boolean; + customTransformers?: ts.CustomTransformers; +} + +export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; } export interface Program { /** @@ -155,7 +194,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. */ @@ -167,7 +206,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. */ @@ -188,7 +227,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): @@ -222,10 +261,10 @@ export interface Program { * * Angular structural information is required to emit files. */ - emit({// transformers, - emitFlags, cancellationToken}: { - emitFlags: EmitFlags, - // transformers?: CustomTransformers, // See TODO above + emit({emitFlags, cancellationToken, customTransformers, emitCallback}: { + emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken, - }): void; + customTransformers?: CustomTransformers, + emitCallback?: TsEmitCallback + }): ts.EmitResult; } diff --git a/packages/compiler-cli/src/transformers/compiler_host.ts b/packages/compiler-cli/src/transformers/compiler_host.ts new file mode 100644 index 0000000000..9d07cbfc4f --- /dev/null +++ b/packages/compiler-cli/src/transformers/compiler_host.ts @@ -0,0 +1,237 @@ +/** + * @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 {syntaxError} from '@angular/compiler'; +import * as path from 'path'; +import * as ts from 'typescript'; + +import {CompilerHost, CompilerOptions} from './api'; + +const NODE_MODULES_PACKAGE_NAME = /node_modules\/((\w|-)+|(@(\w|-)+\/(\w|-)+))/; +const DTS = /\.d\.ts$/; +const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; + +export function createCompilerHost( + {options, tsHost = ts.createCompilerHost(options, true)}: + {options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost { + const mixin = new CompilerHostMixin(tsHost, options); + const host = Object.create(tsHost); + + host.moduleNameToFileName = mixin.moduleNameToFileName.bind(mixin); + host.fileNameToModuleName = mixin.fileNameToModuleName.bind(mixin); + host.toSummaryFileName = mixin.toSummaryFileName.bind(mixin); + host.fromSummaryFileName = mixin.fromSummaryFileName.bind(mixin); + host.resourceNameToFileName = mixin.resourceNameToFileName.bind(mixin); + + // Make sure we do not `host.realpath()` from TS as we do not want to resolve symlinks. + // https://github.com/Microsoft/TypeScript/issues/9552 + host.realpath = (fileName: string) => fileName; + + return host; +} + +class CompilerHostMixin { + private rootDirs: string[]; + private basePath: string; + private moduleResolutionHost: ModuleFilenameResolutionHost; + private moduleResolutionCache: ts.ModuleResolutionCache; + + constructor(private context: ts.CompilerHost, private options: CompilerOptions) { + // normalize the path so that it never ends with '/'. + this.basePath = normalizePath(this.options.basePath !); + this.rootDirs = (this.options.rootDirs || [ + this.options.basePath ! + ]).map(p => path.resolve(this.basePath, normalizePath(p))); + this.moduleResolutionHost = createModuleFilenameResolverHost(context); + this.moduleResolutionCache = ts.createModuleResolutionCache( + this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context)); + } + + moduleNameToFileName(m: string, containingFile: string): string|null { + if (!containingFile) { + if (m.indexOf('.') === 0) { + throw new Error('Resolution of relative paths requires a containing file.'); + } + // Any containing file gives the same result for absolute imports + containingFile = path.join(this.basePath, 'index.ts'); + } + const resolved = ts.resolveModuleName( + m, containingFile.replace(/\\/g, '/'), this.options, + this.moduleResolutionHost, this.moduleResolutionCache) + .resolvedModule; + if (resolved) { + if (this.options.traceResolution) { + console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName); + } + return resolved.resolvedFileName; + } + return null; + } + + /** + * We want a moduleId that will appear in import statements in the generated code + * which will be written to `containingFile`. + * + * Note that we also generate files for files in node_modules, as libraries + * only ship .metadata.json files but not the generated code. + * + * Logic: + * 1. if the importedFile and the containingFile are from the project sources + * or from the same node_modules package, use a relative path + * 2. if the importedFile is in a node_modules package, + * use a path that starts with the package name. + * 3. Error if the containingFile is in the node_modules package + * and the importedFile is in the project soures, + * as that is a violation of the principle that node_modules packages cannot + * import project sources. + */ + fileNameToModuleName(importedFile: string, containingFile: string): string { + const originalImportedFile = importedFile; + if (this.options.traceResolution) { + console.error( + 'fileNameToModuleName from containingFile', containingFile, 'to importedFile', + importedFile); + } + // If a file does not yet exist (because we compile it later), we still need to + // assume it exists it so that the `resolve` method works! + if (!this.moduleResolutionHost.fileExists(importedFile)) { + this.moduleResolutionHost.assumeFileExists(importedFile); + } + // drop extension + importedFile = importedFile.replace(EXT, ''); + const importedFilePackagName = getPackageName(importedFile); + const containingFilePackageName = getPackageName(containingFile); + + let moduleName: string; + if (importedFilePackagName === containingFilePackageName) { + const rootedContainingFile = stripRootDir(this.rootDirs, containingFile); + const rootedImportedFile = stripRootDir(this.rootDirs, importedFile); + + if (rootedContainingFile !== containingFile && rootedImportedFile !== importedFile) { + // if both files are contained in the `rootDirs`, then strip the rootDirs + containingFile = rootedContainingFile; + importedFile = rootedImportedFile; + } + moduleName = dotRelative(path.dirname(containingFile), importedFile); + } else if (importedFilePackagName) { + moduleName = stripNodeModulesPrefix(importedFile); + } else { + throw new Error( + `Trying to import a source file from a node_modules package: import ${originalImportedFile} from ${containingFile}`); + } + return moduleName; + } + + toSummaryFileName(fileName: string, referringSrcFileName: string): string { + return this.fileNameToModuleName(fileName, referringSrcFileName); + } + + fromSummaryFileName(fileName: string, referringLibFileName: string): string { + const resolved = this.moduleNameToFileName(fileName, referringLibFileName); + if (!resolved) { + throw new Error(`Could not resolve ${fileName} from ${referringLibFileName}`); + } + return resolved; + } + + resourceNameToFileName(resourceName: string, containingFile: string): string|null { + // Note: we convert package paths into relative paths to be compatible with the the + // previous implementation of UrlResolver. + if (resourceName && resourceName.charAt(0) !== '.' && !path.isAbsolute(resourceName)) { + resourceName = `./${resourceName}`; + } + const filePathWithNgResource = + this.moduleNameToFileName(addNgResourceSuffix(resourceName), containingFile); + return filePathWithNgResource ? stripNgResourceSuffix(filePathWithNgResource) : null; + } +} + +interface ModuleFilenameResolutionHost extends ts.ModuleResolutionHost { + assumeFileExists(fileName: string): void; +} + +function createModuleFilenameResolverHost(host: ts.ModuleResolutionHost): + ModuleFilenameResolutionHost { + const assumedExists = new Set(); + const resolveModuleNameHost = Object.create(host); + // When calling ts.resolveModuleName, additional allow checks for .d.ts files to be done based on + // checks for .ngsummary.json files, so that our codegen depends on fewer inputs and requires + // to be called less often. + // This is needed as we use ts.resolveModuleName in DefaultModuleFilenameResolver + // and it should be able to resolve summary file names. + resolveModuleNameHost.fileExists = (fileName: string): boolean => { + fileName = stripNgResourceSuffix(fileName); + if (assumedExists.has(fileName)) { + return true; + } + + if (host.fileExists(fileName)) { + return true; + } + + if (DTS.test(fileName)) { + const base = fileName.substring(0, fileName.length - 5); + return host.fileExists(base + '.ngsummary.json'); + } + + return false; + }; + + resolveModuleNameHost.assumeFileExists = (fileName: string) => assumedExists.add(fileName); + // Make sure we do not `host.realpath()` from TS as we do not want to resolve symlinks. + // https://github.com/Microsoft/TypeScript/issues/9552 + resolveModuleNameHost.realpath = (fileName: string) => fileName; + + return resolveModuleNameHost; +} + +function dotRelative(from: string, to: string): string { + const rPath: string = path.relative(from, to).replace(/\\/g, '/'); + return rPath.startsWith('.') ? rPath : './' + rPath; +} + +/** + * Moves the path into `genDir` folder while preserving the `node_modules` directory. + */ +function getPackageName(filePath: string): string|null { + const match = NODE_MODULES_PACKAGE_NAME.exec(filePath); + return match ? match[1] : null; +} + +function stripRootDir(rootDirs: string[], fileName: string): string { + if (!fileName) return fileName; + // NB: the rootDirs should have been sorted longest-first + for (const dir of rootDirs) { + if (fileName.indexOf(dir) === 0) { + fileName = fileName.substring(dir.length); + break; + } + } + return fileName; +} + +function stripNodeModulesPrefix(filePath: string): string { + return filePath.replace(/.*node_modules\//, ''); +} + +function getNodeModulesPrefix(filePath: string): string|null { + const match = /.*node_modules\//.exec(filePath); + return match ? match[1] : null; +} + +function normalizePath(p: string): string { + return path.normalize(path.join(p, '.')).replace(/\\/g, '/'); +} + +function stripNgResourceSuffix(fileName: string): string { + return fileName.replace(/\.\$ngresource\$.*/, ''); +} + +function addNgResourceSuffix(fileName: string): string { + return `${fileName}.$ngresource$`; +} diff --git a/packages/compiler-cli/src/transformers/entry_points.ts b/packages/compiler-cli/src/transformers/entry_points.ts index 4e4f5b1721..9a574fa586 100644 --- a/packages/compiler-cli/src/transformers/entry_points.ts +++ b/packages/compiler-cli/src/transformers/entry_points.ts @@ -9,24 +9,6 @@ import * as ts from 'typescript'; import {CompilerHost, CompilerOptions, Program} from './api'; -import {createModuleFilenameResolver} from './module_filename_resolver'; + +export {createCompilerHost} from './compiler_host'; export {createProgram} from './program'; -export {createModuleFilenameResolver}; - -export function createHost({tsHost, options}: {tsHost: ts.CompilerHost, options: CompilerOptions}): - CompilerHost { - const resolver = createModuleFilenameResolver(tsHost, options); - - const host = Object.create(tsHost); - - host.moduleNameToFileName = resolver.moduleNameToFileName.bind(resolver); - host.fileNameToModuleName = resolver.fileNameToModuleName.bind(resolver); - host.getNgCanonicalFileName = resolver.getNgCanonicalFileName.bind(resolver); - host.assumeFileExists = resolver.assumeFileExists.bind(resolver); - - // Make sure we do not `host.realpath()` from TS as we do not want to resolve symlinks. - // https://github.com/Microsoft/TypeScript/issues/9552 - host.realpath = (fileName: string) => fileName; - - return host; -} diff --git a/packages/compiler-cli/src/transformers/lower_expressions.ts b/packages/compiler-cli/src/transformers/lower_expressions.ts index 286ce3b4fa..1a2edb825e 100644 --- a/packages/compiler-cli/src/transformers/lower_expressions.ts +++ b/packages/compiler-cli/src/transformers/lower_expressions.ts @@ -32,35 +32,66 @@ function toMap(items: T[], select: (item: T) => K): Map { return new Map(items.map<[K, T]>(i => [select(i), i])); } +// We will never lower expressions in a nested lexical scope so avoid entering them. +// This also avoids a bug in TypeScript 2.3 where the lexical scopes get out of sync +// when using visitEachChild. +function isLexicalScope(node: ts.Node): boolean { + switch (node.kind) { + case ts.SyntaxKind.ArrowFunction: + case ts.SyntaxKind.FunctionExpression: + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.ClassExpression: + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.FunctionType: + case ts.SyntaxKind.TypeLiteral: + case ts.SyntaxKind.ArrayType: + return true; + } + return false; +} + function transformSourceFile( sourceFile: ts.SourceFile, requests: RequestLocationMap, context: ts.TransformationContext): ts.SourceFile { const inserts: DeclarationInsert[] = []; - // Calculate the range of intersting locations. The transform will only visit nodes in this + // Calculate the range of interesting locations. The transform will only visit nodes in this // range to improve the performance on large files. const locations = Array.from(requests.keys()); const min = Math.min(...locations); const max = Math.max(...locations); + // Visit nodes matching the request and synthetic nodes added by tsickle + function shouldVisit(pos: number, end: number): boolean { + return (pos <= max && end >= min) || pos == -1; + } + function visitSourceFile(sourceFile: ts.SourceFile): ts.SourceFile { function topLevelStatement(node: ts.Node): ts.Node { const declarations: Declaration[] = []; function visitNode(node: ts.Node): ts.Node { - const nodeRequest = requests.get(node.pos); - if (nodeRequest && nodeRequest.kind == node.kind && nodeRequest.end == node.end) { + // Get the original node before tsickle + const {pos, end, kind} = ts.getOriginalNode(node); + const nodeRequest = requests.get(pos); + if (nodeRequest && nodeRequest.kind == kind && nodeRequest.end == end) { // This node is requested to be rewritten as a reference to the exported name. // Record that the node needs to be moved to an exported variable with the given name const name = nodeRequest.name; declarations.push({name, node}); return ts.createIdentifier(name); } - if (node.pos <= max && node.end >= min) return ts.visitEachChild(node, visitNode, context); - return node; + let result = node; + + if (shouldVisit(pos, end) && !isLexicalScope(node)) { + result = ts.visitEachChild(node, visitNode, context); + } + return result; } - const result = ts.visitEachChild(node, visitNode, context); + // Get the original node before tsickle + const {pos, end} = ts.getOriginalNode(node); + const result = shouldVisit(pos, end) ? ts.visitEachChild(node, visitNode, context) : node; if (declarations.length) { inserts.push({priorTo: result, declarations}); @@ -69,6 +100,7 @@ function transformSourceFile( } const traversedSource = ts.visitEachChild(sourceFile, topLevelStatement, context); + if (inserts.length) { // Insert the declarations before the rewritten statement that references them. const insertMap = toMap(inserts, i => i.priorTo); @@ -126,6 +158,29 @@ interface MetadataAndLoweringRequests { requests: RequestLocationMap; } +function shouldLower(node: ts.Node | undefined): boolean { + if (node) { + switch (node.kind) { + case ts.SyntaxKind.SourceFile: + case ts.SyntaxKind.Decorator: + // Lower expressions that are local to the module scope or + // in a decorator. + return true; + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.EnumDeclaration: + case ts.SyntaxKind.FunctionDeclaration: + // Don't lower expressions in a declaration. + return false; + case ts.SyntaxKind.VariableDeclaration: + // Avoid lowering expressions already in an exported variable declaration + return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) == 0; + } + return shouldLower(node.parent); + } + return true; +} + export class LowerMetadataCache implements RequestsMap { private collector: MetadataCollector; private metadataCache = new Map(); @@ -162,8 +217,9 @@ export class LowerMetadataCache implements RequestsMap { }; const substituteExpression = (value: MetadataValue, node: ts.Node): MetadataValue => { - if (node.kind === ts.SyntaxKind.ArrowFunction || - node.kind === ts.SyntaxKind.FunctionExpression) { + if ((node.kind === ts.SyntaxKind.ArrowFunction || + node.kind === ts.SyntaxKind.FunctionExpression) && + shouldLower(node)) { return replaceNode(node); } return value; diff --git a/packages/compiler-cli/src/transformers/module_filename_resolver.ts b/packages/compiler-cli/src/transformers/module_filename_resolver.ts deleted file mode 100644 index d0a55da56a..0000000000 --- a/packages/compiler-cli/src/transformers/module_filename_resolver.ts +++ /dev/null @@ -1,292 +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 * as path from 'path'; -import * as ts from 'typescript'; - -import {CompilerOptions, ModuleFilenameResolver} from './api'; - -const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; -const DTS = /\.d\.ts$/; -const NODE_MODULES = '/node_modules/'; -const IS_GENERATED = /\.(ngfactory|ngstyle|ngsummary)$/; -const SHALLOW_IMPORT = /^((\w|-)+|(@(\w|-)+(\/(\w|-)+)+))$/; - -export function createModuleFilenameResolver( - tsHost: ts.ModuleResolutionHost, options: CompilerOptions): ModuleFilenameResolver { - const host = createModuleFilenameResolverHost(tsHost); - - return options.rootDirs && options.rootDirs.length > 0 ? - new MultipleRootDirModuleFilenameResolver(host, options) : - new SingleRootDirModuleFilenameResolver(host, options); -} - -class SingleRootDirModuleFilenameResolver implements ModuleFilenameResolver { - private isGenDirChildOfRootDir: boolean; - private basePath: string; - private genDir: string; - private moduleFileNames = new Map(); - - constructor(private host: ModuleFilenameResolutionHost, private options: CompilerOptions) { - // normalize the path so that it never ends with '/'. - this.basePath = path.normalize(path.join(options.basePath !, '.')).replace(/\\/g, '/'); - this.genDir = path.normalize(path.join(options.genDir !, '.')).replace(/\\/g, '/'); - - const genPath: string = path.relative(this.basePath, this.genDir); - this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); - } - - moduleNameToFileName(m: string, containingFile: string): string|null { - const key = m + ':' + (containingFile || ''); - let result: string|null = this.moduleFileNames.get(key) || null; - if (!result) { - if (!containingFile) { - if (m.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = this.getNgCanonicalFileName(path.join(this.basePath, 'index.ts')); - } - m = m.replace(EXT, ''); - const resolved = - ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.host) - .resolvedModule; - result = resolved ? this.getNgCanonicalFileName(resolved.resolvedFileName) : null; - this.moduleFileNames.set(key, result); - } - return result; - } - - /** - * We want a moduleId that will appear in import statements in the generated code. - * These need to be in a form that system.js can load, so absolute file paths don't work. - * - * The `containingFile` is always in the `genDir`, where as the `importedFile` can be in - * `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or - * existing file. - * - * | genDir | node_module | rootDir - * --------------+----------+-------------+---------- - * generated | relative | relative | n/a - * existing file | n/a | absolute | relative(*) - * - * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. - */ - fileNameToModuleName(importedFile: string, containingFile: string): string { - // If a file does not yet exist (because we compile it later), we still need to - // assume it exists it so that the `resolve` method works! - if (!this.host.fileExists(importedFile)) { - this.host.assumeFileExists(importedFile); - } - - containingFile = this.rewriteGenDirPath(containingFile); - const containingDir = path.dirname(containingFile); - // drop extension - importedFile = importedFile.replace(EXT, ''); - - const nodeModulesIndex = importedFile.indexOf(NODE_MODULES); - const importModule = nodeModulesIndex === -1 ? - null : - importedFile.substring(nodeModulesIndex + NODE_MODULES.length); - const isGeneratedFile = IS_GENERATED.test(importedFile); - - if (isGeneratedFile) { - // rewrite to genDir path - if (importModule) { - // it is generated, therefore we do a relative path to the factory - return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule); - } else { - // assume that import is also in `genDir` - importedFile = this.rewriteGenDirPath(importedFile); - return this.dotRelative(containingDir, importedFile); - } - } else { - // user code import - if (importModule) { - return importModule; - } else { - if (!this.isGenDirChildOfRootDir) { - // assume that they are on top of each other. - importedFile = importedFile.replace(this.basePath, this.genDir); - } - if (SHALLOW_IMPORT.test(importedFile)) { - return importedFile; - } - return this.dotRelative(containingDir, importedFile); - } - } - } - - // We use absolute paths on disk as canonical. - getNgCanonicalFileName(fileName: string): string { return fileName; } - - assumeFileExists(fileName: string) { this.host.assumeFileExists(fileName); } - - private dotRelative(from: string, to: string): string { - const rPath: string = path.relative(from, to).replace(/\\/g, '/'); - return rPath.startsWith('.') ? rPath : './' + rPath; - } - - /** - * Moves the path into `genDir` folder while preserving the `node_modules` directory. - */ - private rewriteGenDirPath(filepath: string) { - const nodeModulesIndex = filepath.indexOf(NODE_MODULES); - if (nodeModulesIndex !== -1) { - // If we are in node_module, transplant them into `genDir`. - return path.join(this.genDir, filepath.substring(nodeModulesIndex)); - } else { - // pretend that containing file is on top of the `genDir` to normalize the paths. - // we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later. - return filepath.replace(this.basePath, this.genDir); - } - } -} - -/** - * This version of the AotCompilerHost expects that the program will be compiled - * and executed with a "path mapped" directory structure, where generated files - * are in a parallel tree with the sources, and imported using a `./` relative - * import. This requires using TS `rootDirs` option and also teaching the module - * loader what to do. - */ -class MultipleRootDirModuleFilenameResolver implements ModuleFilenameResolver { - private basePath: string; - - constructor(private host: ModuleFilenameResolutionHost, private options: CompilerOptions) { - // normalize the path so that it never ends with '/'. - this.basePath = path.normalize(path.join(options.basePath !, '.')).replace(/\\/g, '/'); - } - - getNgCanonicalFileName(fileName: string): string { - if (!fileName) return fileName; - // NB: the rootDirs should have been sorted longest-first - for (const dir of this.options.rootDirs || []) { - if (fileName.indexOf(dir) === 0) { - fileName = fileName.substring(dir.length); - } - } - return fileName; - } - - assumeFileExists(fileName: string) { this.host.assumeFileExists(fileName); } - - moduleNameToFileName(m: string, containingFile: string): string|null { - if (!containingFile) { - if (m.indexOf('.') === 0) { - throw new Error('Resolution of relative paths requires a containing file.'); - } - // Any containing file gives the same result for absolute imports - containingFile = this.getNgCanonicalFileName(path.join(this.basePath, 'index.ts')); - } - for (const root of this.options.rootDirs || ['']) { - const rootedContainingFile = path.join(root, containingFile); - const resolved = - ts.resolveModuleName(m, rootedContainingFile, this.options, this.host).resolvedModule; - if (resolved) { - if (this.options.traceResolution) { - console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName); - } - return this.getNgCanonicalFileName(resolved.resolvedFileName); - } - } - return null; - } - - /** - * We want a moduleId that will appear in import statements in the generated code. - * These need to be in a form that system.js can load, so absolute file paths don't work. - * Relativize the paths by checking candidate prefixes of the absolute path, to see if - * they are resolvable by the moduleResolution strategy from the CompilerHost. - */ - fileNameToModuleName(importedFile: string, containingFile: string): string { - if (this.options.traceResolution) { - console.error( - 'getImportPath from containingFile', containingFile, 'to importedFile', importedFile); - } - - // If a file does not yet exist (because we compile it later), we still need to - // assume it exists so that the `resolve` method works! - if (!this.host.fileExists(importedFile)) { - if (this.options.rootDirs && this.options.rootDirs.length > 0) { - this.host.assumeFileExists(path.join(this.options.rootDirs[0], importedFile)); - } else { - this.host.assumeFileExists(importedFile); - } - } - - const resolvable = (candidate: string) => { - const resolved = this.moduleNameToFileName(candidate, importedFile); - return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, ''); - }; - - const importModuleName = importedFile.replace(EXT, ''); - const parts = importModuleName.split(path.sep).filter(p => !!p); - let foundRelativeImport: string|undefined; - - for (let index = parts.length - 1; index >= 0; index--) { - let candidate = parts.slice(index, parts.length).join(path.sep); - if (resolvable(candidate)) { - return candidate; - } - candidate = '.' + path.sep + candidate; - if (resolvable(candidate)) { - foundRelativeImport = candidate; - } - } - - if (foundRelativeImport) return foundRelativeImport; - - // Try a relative import - const candidate = path.relative(path.dirname(containingFile), importModuleName); - if (resolvable(candidate)) { - return candidate; - } - - throw new Error( - `Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`); - } -} - -interface ModuleFilenameResolutionHost extends ts.ModuleResolutionHost { - assumeFileExists(fileName: string): void; -} - -function createModuleFilenameResolverHost(host: ts.ModuleResolutionHost): - ModuleFilenameResolutionHost { - const assumedExists = new Set(); - const resolveModuleNameHost = Object.create(host); - // When calling ts.resolveModuleName, additional allow checks for .d.ts files to be done based on - // checks for .ngsummary.json files, so that our codegen depends on fewer inputs and requires - // to be called less often. - // This is needed as we use ts.resolveModuleName in reflector_host and it should be able to - // resolve summary file names. - resolveModuleNameHost.fileExists = (fileName: string): boolean => { - if (assumedExists.has(fileName)) { - return true; - } - - if (host.fileExists(fileName)) { - return true; - } - - if (DTS.test(fileName)) { - const base = fileName.substring(0, fileName.length - 5); - return host.fileExists(base + '.ngsummary.json'); - } - - return false; - }; - - resolveModuleNameHost.assumeFileExists = (fileName: string) => assumedExists.add(fileName); - // Make sure we do not `host.realpath()` from TS as we do not want to resolve symlinks. - // https://github.com/Microsoft/TypeScript/issues/9552 - resolveModuleNameHost.realpath = (fileName: string) => fileName; - - return resolveModuleNameHost; -} \ No newline at end of file diff --git a/packages/compiler-cli/src/transformers/node_emitter.ts b/packages/compiler-cli/src/transformers/node_emitter.ts index 8adc7e8f64..29cd86863c 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 b2753555fa..ee81730f98 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -6,20 +6,21 @@ * 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, AotCompilerHost, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, core, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} 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 {CompilerHost as AotCompilerHost, CompilerHostContext} from '../compiler_host'; +import {BaseAotCompilerHost} from '../compiler_host'; import {TypeChecker} from '../diagnostics/check_types'; -import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, Program} from './api'; +import {CompilerHost, CompilerOptions, CustomTransformers, Diagnostic, EmitFlags, Program, TsEmitArguments, TsEmitCallback} from './api'; import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; import {getAngularEmitterTransformFactory} from './node_emitter_transform'; const GENERATED_FILES = /\.ngfactory\.js$|\.ngstyle\.js$|\.ngsummary\.js$/; + const SUMMARY_JSON_FILES = /\.ngsummary.json$/; const emptyModules: NgAnalyzedModules = { @@ -28,9 +29,14 @@ const emptyModules: NgAnalyzedModules = { files: [] }; +const defaultEmitCallback: TsEmitCallback = + ({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, + customTransformers}) => + program.emit( + targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + + class AngularCompilerProgram implements Program { - // Initialized in the constructor - private oldTsProgram: ts.Program|undefined; private tsProgram: ts.Program; private aotCompilerHost: AotCompilerHost; private compiler: AotCompiler; @@ -47,22 +53,36 @@ class AngularCompilerProgram implements Program { private _generatedFileDiagnostics: Diagnostic[]|undefined; private _typeChecker: TypeChecker|undefined; private _semanticDiagnostics: Diagnostic[]|undefined; + private _optionsDiagnostics: Diagnostic[] = []; constructor( private rootNames: string[], private options: CompilerOptions, private host: CompilerHost, private oldProgram?: Program) { - this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined; - - this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram); - this.srcNames = this.tsProgram.getSourceFiles().map(sf => sf.fileName); - this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit); - this.aotCompilerHost = new AotCompilerHost( - this.tsProgram, options, host, /* collectorOptions */ undefined, this.metadataCache); - if (host.readResource) { - this.aotCompilerHost.loadResource = host.readResource.bind(host); + if (options.flatModuleOutFile && !options.skipMetadataEmit) { + const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host); + if (errors) { + // TODO(tbosch): once we move MetadataBundler from tsc_wrapped into compiler_cli, + // directly create ng.Diagnostic instead of using ts.Diagnostic here. + this._optionsDiagnostics.push( + ...errors.map(e => ({category: e.category, message: e.messageText as string}))); + } else { + rootNames.push(indexName !); + this.host = host = bundleHost; + } } - const {compiler} = createAotCompiler(this.aotCompilerHost, options); - this.compiler = compiler; + + const oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined; + this.tsProgram = ts.createProgram(rootNames, options, host, oldTsProgram); + this.srcNames = + this.tsProgram.getSourceFiles() + .map(sf => sf.fileName) + .filter(f => !f.match(/\.ngfactory\.[\w.]+$|\.ngstyle\.[\w.]+$|\.ngsummary\.[\w.]+$/)); + this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit); + this.aotCompilerHost = + new AotCompilerHostImpl(this.tsProgram, options, host, this.metadataCache); + + const aotOptions = getAotCompilerOptions(options); + this.compiler = createAotCompiler(this.aotCompilerHost, aotOptions).compiler; } // Program implementation @@ -73,7 +93,7 @@ class AngularCompilerProgram implements Program { } getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[] { - return getNgOptionDiagnostics(this.options); + return [...this._optionsDiagnostics, ...getNgOptionDiagnostics(this.options)]; } getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken): @@ -114,27 +134,53 @@ class AngularCompilerProgram implements Program { getLazyRoutes(cancellationToken?: ts.CancellationToken): {[route: string]: string} { return {}; } - emit({emitFlags = EmitFlags.Default, cancellationToken}: - {emitFlags?: EmitFlags, cancellationToken?: ts.CancellationToken}): ts.EmitResult { + emit({emitFlags = EmitFlags.Default, cancellationToken, customTransformers, + emitCallback = defaultEmitCallback}: { + emitFlags?: EmitFlags, + cancellationToken?: ts.CancellationToken, + customTransformers?: CustomTransformers, + emitCallback?: TsEmitCallback + }): ts.EmitResult { const emitMap = new Map(); - const result = this.programWithStubs.emit( - /* targetSourceFile */ undefined, - createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap), - cancellationToken, (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, - this.calculateTransforms()); + + const expectedOut = this.options.expectedOut ? + this.options.expectedOut.map(f => path.resolve(process.cwd(), f)) : + undefined; + + // Ensure that expected output files exist. + for (const out of expectedOut || []) { + this.host.writeFile(out, '', false); + } + + const emitResult = emitCallback({ + program: this.programWithStubs, + host: this.host, + options: this.options, + targetSourceFile: undefined, + writeFile: + createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut), + cancellationToken, + emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS, + customTransformers: this.calculateTransforms(customTransformers) + }); this.generatedFiles.forEach(file => { + // In order not to replicate the TS calculation of the out folder for files + // derive the out location for .json files from the out location of the .ts files if (file.source && file.source.length && SUMMARY_JSON_FILES.test(file.genFileUrl)) { // If we have emitted the ngsummary.ts file, ensure the ngsummary.json file is emitted to // the same location. + const emittedFile = emitMap.get(file.srcFileUrl); - const fileName = emittedFile ? - path.join(path.dirname(emittedFile), path.basename(file.genFileUrl)) : - file.genFileUrl; - this.host.writeFile(fileName, file.source, false, error => {}); + + if (emittedFile) { + const fileName = path.join(path.dirname(emittedFile), path.basename(file.genFileUrl)); + this.host.writeFile(fileName, file.source, false, error => {}); + } } }); - return result; + + return emitResult; } // Private members @@ -183,20 +229,19 @@ class AngularCompilerProgram implements Program { return this.generatedFiles && this._generatedFileDiagnostics !; } - private calculateTransforms(): ts.CustomTransformers { - const before: ts.TransformerFactory[] = []; - const after: ts.TransformerFactory[] = []; + private calculateTransforms(customTransformers?: CustomTransformers): ts.CustomTransformers { + const beforeTs: ts.TransformerFactory[] = []; if (!this.options.disableExpressionLowering) { - // TODO(chuckj): fix and re-enable + tests - see https://github.com/angular/angular/pull/18388 - // before.push(getExpressionLoweringTransformFactory(this.metadataCache)); + beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache)); } if (!this.options.skipTemplateCodegen) { - after.push(getAngularEmitterTransformFactory(this.generatedFiles)); + beforeTs.push(getAngularEmitterTransformFactory(this.generatedFiles)); } - const result: ts.CustomTransformers = {}; - if (before.length) result.before = before; - if (after.length) result.after = after; - return result; + if (customTransformers && customTransformers.beforeTs) { + beforeTs.push(...customTransformers.beforeTs); + } + const afterTs = customTransformers ? customTransformers.afterTs : undefined; + return {before: beforeTs, after: afterTs}; } private catchAnalysisError(e: any): NgAnalyzedModules { @@ -206,11 +251,11 @@ class AngularCompilerProgram implements Program { this._structuralDiagnostics = parserErrors.map(e => ({ message: e.contextualMessage(), - category: DiagnosticCategory.Error, + category: ts.DiagnosticCategory.Error, span: e.span })); } else { - this._structuralDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}]; + this._structuralDiagnostics = [{message: e.message, category: ts.DiagnosticCategory.Error}]; } this._analyzedModules = emptyModules; return emptyModules; @@ -229,8 +274,8 @@ class AngularCompilerProgram implements Program { private generateStubs() { return this.options.skipTemplateCodegen ? [] : this.options.generateCodeForLibraries === false ? - this.compiler.emitAllStubs(this.analyzedModules) : - this.compiler.emitPartialStubs(this.analyzedModules); + this.compiler.emitPartialStubs(this.analyzedModules) : + this.compiler.emitAllStubs(this.analyzedModules); } private generateFiles() { @@ -241,7 +286,8 @@ class AngularCompilerProgram implements Program { return this.options.skipTemplateCodegen ? [] : result; } catch (e) { if (isSyntaxError(e)) { - this._generatedFileDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}]; + this._generatedFileDiagnostics = + [{message: e.message, category: ts.DiagnosticCategory.Error}]; return []; } throw e; @@ -264,6 +310,28 @@ class AngularCompilerProgram implements Program { } } +class AotCompilerHostImpl extends BaseAotCompilerHost { + moduleNameToFileName(m: string, containingFile: string): string|null { + return this.context.moduleNameToFileName(m, containingFile); + } + + fileNameToModuleName(importedFile: string, containingFile: string): string|null { + return this.context.fileNameToModuleName(importedFile, containingFile); + } + + resourceNameToFileName(resourceName: string, containingFile: string): string|null { + return this.context.resourceNameToFileName(resourceName, containingFile); + } + + toSummaryFileName(fileName: string, referringSrcFileName: string): string { + return this.context.toSummaryFileName(fileName, referringSrcFileName); + } + + fromSummaryFileName(fileName: string, referringLibFileName: string): string { + return this.context.fromSummaryFileName(fileName, referringLibFileName); + } +} + export function createProgram( {rootNames, options, host, oldProgram}: {rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}): @@ -271,8 +339,44 @@ export function createProgram( return new AngularCompilerProgram(rootNames, options, host, oldProgram); } +// Compute the AotCompiler options +function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions { + let missingTranslation = core.MissingTranslationStrategy.Warning; + + switch (options.i18nInMissingTranslations) { + case 'ignore': + missingTranslation = core.MissingTranslationStrategy.Ignore; + break; + case 'error': + missingTranslation = core.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 = core.MissingTranslationStrategy.Ignore; + } + + return { + locale: options.i18nInLocale, + i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation, + enableLegacyTemplate: options.enableLegacyTemplate, + enableSummariesForJit: true, + preserveWhitespaces: options.preserveWhitespaces, + }; +} + function writeMetadata( - emitFilePath: string, sourceFile: ts.SourceFile, metadataCache: LowerMetadataCache) { + host: ts.CompilerHost, emitFilePath: string, sourceFile: ts.SourceFile, + metadataCache: LowerMetadataCache) { if (/\.js$/.test(emitFilePath)) { const path = emitFilePath.replace(/\.js$/, '.metadata.json'); @@ -288,38 +392,36 @@ function writeMetadata( const metadata = metadataCache.getMetadata(collectableFile); if (metadata) { const metadataText = JSON.stringify([metadata]); - writeFileSync(path, metadataText, {encoding: 'utf-8'}); + host.writeFile(path, metadataText, false); } } } function createWriteFileCallback( emitFlags: EmitFlags, host: ts.CompilerHost, metadataCache: LowerMetadataCache, - emitMap: Map) { - const withMetadata = - (fileName: string, data: string, writeByteOrderMark: boolean, - onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { - const generatedFile = GENERATED_FILES.test(fileName); - if (!generatedFile || data != '') { - host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); - } - if (!generatedFile && sourceFiles && sourceFiles.length == 1) { - emitMap.set(sourceFiles[0].fileName, fileName); - writeMetadata(fileName, sourceFiles[0], metadataCache); - } - }; - const withoutMetadata = - (fileName: string, data: string, writeByteOrderMark: boolean, - onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { - const generatedFile = GENERATED_FILES.test(fileName); - if (!generatedFile || data != '') { - host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); - } - if (!generatedFile && sourceFiles && sourceFiles.length == 1) { - emitMap.set(sourceFiles[0].fileName, fileName); - } - }; - return (emitFlags & EmitFlags.Metadata) != 0 ? withMetadata : withoutMetadata; + emitMap: Map, expectedOut?: string[]) { + return (fileName: string, data: string, writeByteOrderMark: boolean, + onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { + + let srcFile: ts.SourceFile|undefined; + + if (sourceFiles && sourceFiles.length == 1) { + srcFile = sourceFiles[0]; + emitMap.set(srcFile.fileName, fileName); + } + + const absFile = path.resolve(process.cwd(), fileName); + const generatedFile = GENERATED_FILES.test(fileName); + + // Don't emit unexpected files nor empty generated files + if ((!expectedOut || expectedOut.indexOf(absFile) > -1) && (!generatedFile || data)) { + host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); + + if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) { + writeMetadata(host, fileName, srcFile, metadataCache); + } + } + }; } function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] { @@ -332,7 +434,7 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] { return [{ message: 'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"', - category: DiagnosticCategory.Error + category: ts.DiagnosticCategory.Error }]; } } @@ -403,4 +505,4 @@ function createProgramWithStubsHost( fileExists = (fileName: string) => this.generatedFiles.has(fileName) || originalHost.fileExists(fileName); }; -} \ No newline at end of file +} diff --git a/packages/compiler-cli/src/version.ts b/packages/compiler-cli/src/version.ts index 19db5be5a5..dd4544fcb4 100644 --- a/packages/compiler-cli/src/version.ts +++ b/packages/compiler-cli/src/version.ts @@ -12,7 +12,7 @@ * Entry point for all public APIs of the common package. */ -import {Version} from '@angular/core'; +import {Version} from '@angular/compiler'; /** * @stable */ diff --git a/packages/compiler-cli/test/diagnostics/mocks.ts b/packages/compiler-cli/test/diagnostics/mocks.ts index c87acd9f96..979d352e27 100644 --- a/packages/compiler-cli/test/diagnostics/mocks.ts +++ b/packages/compiler-cli/test/diagnostics/mocks.ts @@ -105,7 +105,8 @@ const summaryResolver = new AotSummaryResolver( { loadSummary(filePath: string) { return null; }, isSourceFile(sourceFilePath: string) { return true; }, - getOutputFileName(sourceFilePath: string) { return sourceFilePath; } + toSummaryFileName(sourceFilePath: string) { return sourceFilePath; }, + fromSummaryFileName(filePath: string): string{return filePath;}, }, staticSymbolCache); diff --git a/packages/compiler-cli/test/main_spec.ts b/packages/compiler-cli/test/main_spec.ts index a69e50c1b7..46b109846e 100644 --- a/packages/compiler-cli/test/main_spec.ts +++ b/packages/compiler-cli/test/main_spec.ts @@ -18,16 +18,22 @@ function getNgRootDir() { return moduleFilename.substr(0, distIndex); } -describe('compiler-cli', () => { +describe('compiler-cli with disableTransformerPipeline', () => { let basePath: string; let outDir: string; let write: (fileName: string, content: string) => void; + let errorSpy: jasmine.Spy&((s: string) => void); function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') { - write('tsconfig.json', tsconfig); + const json = JSON.parse(tsconfig); + // Note: 'extends' does not work for "angularCompilerOptions" yet. + const ngOptions = json['angularCompilerOptions'] = json['angularCompilerOptions'] || {}; + ngOptions['disableTransformerPipeline'] = true; + write('tsconfig.json', JSON.stringify(json)); } beforeEach(() => { + errorSpy = jasmine.createSpy('consoleError'); basePath = makeTempDir(); write = (fileName: string, content: string) => { fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'}); @@ -58,13 +64,9 @@ describe('compiler-cli', () => { writeConfig(); write('test.ts', 'export const A = 1;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); expect(exitCode).toEqual(0); done(); }) @@ -76,16 +78,11 @@ describe('compiler-cli', () => { "extends": "./tsconfig-base.json", "files": ["test.ts"] }`); - const mockConsole = {error: (s: string) => {}}; - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error) - .toHaveBeenCalledWith( - `Error File '` + path.join(basePath, 'test.ts') + `' not found.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + `Error File '` + path.join(basePath, 'test.ts') + `' not found.`); expect(exitCode).toEqual(1); done(); }) @@ -96,16 +93,10 @@ describe('compiler-cli', () => { writeConfig(); write('test.ts', 'foo;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error) - .toHaveBeenCalledWith( - 'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`); expect(exitCode).toEqual(1); done(); }) @@ -116,17 +107,11 @@ describe('compiler-cli', () => { writeConfig(); write('test.ts', `import {MyClass} from './not-exist-deps';`); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error) - .toHaveBeenCalledWith( - 'Error at ' + path.join(basePath, 'test.ts') + - `:1:23: Cannot find module './not-exist-deps'.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + + `:1:23: Cannot find module './not-exist-deps'.`); expect(exitCode).toEqual(1); done(); }) @@ -138,17 +123,11 @@ describe('compiler-cli', () => { write('empty-deps.ts', 'export const A = 1;'); write('test.ts', `import {MyClass} from './empty-deps';`); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error) - .toHaveBeenCalledWith( - 'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` + - path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` + + path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`); expect(exitCode).toEqual(1); done(); }) @@ -163,18 +142,12 @@ describe('compiler-cli', () => { A(); `); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { - expect(mockConsole.error) - .toHaveBeenCalledWith( - 'Error at ' + path.join(basePath, 'test.ts') + - ':3:7: Cannot invoke an expression whose type lacks a call signature. ' + - 'Type \'String\' has no compatible call signatures.'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalledWith( + 'Error at ' + path.join(basePath, 'test.ts') + + ':3:7: Cannot invoke an expression whose type lacks a call signature. ' + + 'Type \'String\' has no compatible call signatures.'); expect(exitCode).toEqual(1); done(); }) @@ -184,14 +157,11 @@ describe('compiler-cli', () => { it('should print the stack trace on compiler internal errors', (done) => { write('test.ts', 'export const A = 1;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - main({p: 'not-exist'}, mockConsole.error) + main(['-p', 'not-exist'], errorSpy) .then((exitCode) => { - expect(mockConsole.error).toHaveBeenCalled(); - expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed'); + expect(errorSpy).toHaveBeenCalled(); + expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory'); + expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)'); expect(exitCode).toEqual(1); done(); }) @@ -215,7 +185,7 @@ describe('compiler-cli', () => { export class MyModule {} `); - main({p: basePath}) + main(['-p', basePath], errorSpy) .then((exitCode) => { expect(exitCode).toEqual(0); @@ -244,11 +214,7 @@ describe('compiler-cli', () => { export class MyModule {} `); - const mockConsole = {error: (s: string) => {}}; - - const errorSpy = spyOn(mockConsole, 'error'); - - main({p: basePath}, mockConsole.error) + main(['-p', basePath], errorSpy) .then((exitCode) => { expect(errorSpy).toHaveBeenCalledTimes(1); expect(errorSpy.calls.mostRecent().args[0]) @@ -280,7 +246,7 @@ describe('compiler-cli', () => { export class MyModule {} `); - main({p: basePath}) + main(['-p', basePath], errorSpy) .then((exitCode) => { expect(exitCode).toEqual(0); @@ -307,7 +273,7 @@ describe('compiler-cli', () => { export class MyModule {} `); - main({p: basePath}) + main(['-p', basePath], errorSpy) .then((exitCode) => { expect(exitCode).toEqual(0); expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false); @@ -333,7 +299,7 @@ describe('compiler-cli', () => { export class MyModule {} `); - main({p: basePath}) + main(['-p', basePath], errorSpy) .then((exitCode) => { expect(exitCode).toEqual(0); expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(true); diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 0a994cf6e8..893068872b 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -11,8 +11,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ts from 'typescript'; -import {main} from '../src/ngc'; -import {performCompilation} from '../src/perform-compile'; +import {mainSync} from '../src/main'; function getNgRootDir() { const moduleFilename = module.filename.replace(/\\/g, '/'); @@ -20,16 +19,18 @@ function getNgRootDir() { return moduleFilename.substr(0, distIndex); } -describe('ngc command-line', () => { +describe('ngc transformer command-line', () => { let basePath: string; let outDir: string; let write: (fileName: string, content: string) => void; + let errorSpy: jasmine.Spy&((s: string) => void); function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') { write('tsconfig.json', tsconfig); } beforeEach(() => { + errorSpy = jasmine.createSpy('consoleError'); basePath = makeTempDir(); write = (fileName: string, content: string) => { const dir = path.dirname(fileName); @@ -66,35 +67,9 @@ describe('ngc command-line', () => { writeConfig(); write('test.ts', 'export const A = 1;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const result = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error).not.toHaveBeenCalled(); - expect(result).toBe(0); - }); - - it('should be able to be called without a config file by passing options explicitly', () => { - write('test.ts', 'export const A = 1;'); - - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const result = performCompilation( - basePath, [path.join(basePath, 'test.ts')], { - experimentalDecorators: true, - skipLibCheck: true, - types: [], - outDir: path.join(basePath, 'built'), - declaration: true, - module: ts.ModuleKind.ES2015, - moduleResolution: ts.ModuleResolutionKind.NodeJs, - }, - {}, mockConsole.error); - expect(mockConsole.error).not.toHaveBeenCalled(); - expect(result).toBe(0); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); }); it('should not print the stack trace if user input file does not exist', () => { @@ -102,16 +77,11 @@ describe('ngc command-line', () => { "extends": "./tsconfig-base.json", "files": ["test.ts"] }`); - const mockConsole = {error: (s: string) => {}}; - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error) - .toHaveBeenCalledWith( - `error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` + + '\n'); expect(exitCode).toEqual(1); }); @@ -119,16 +89,10 @@ describe('ngc command-line', () => { writeConfig(); write('test.ts', 'foo;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error) - .toHaveBeenCalledWith( - `test.ts(1,1): error TS2304: Cannot find name 'foo'.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `test.ts(1,1): error TS2304: Cannot find name 'foo'.` + + '\n'); expect(exitCode).toEqual(1); }); @@ -136,16 +100,10 @@ describe('ngc command-line', () => { writeConfig(); write('test.ts', `import {MyClass} from './not-exist-deps';`); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error) - .toHaveBeenCalledWith( - `test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` + + '\n'); expect(exitCode).toEqual(1); }); @@ -154,17 +112,11 @@ describe('ngc command-line', () => { write('empty-deps.ts', 'export const A = 1;'); write('test.ts', `import {MyClass} from './empty-deps';`); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error) - .toHaveBeenCalledWith( - `test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') + - `"' has no exported member 'MyClass'.` + - '\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + `test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') + + `"' has no exported member 'MyClass'.` + + '\n'); expect(exitCode).toEqual(1); }); @@ -176,30 +128,21 @@ describe('ngc command-line', () => { A(); `); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); - expect(mockConsole.error) - .toHaveBeenCalledWith( - 'test.ts(3,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' + - 'Type \'String\' has no compatible call signatures.\n'); - expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed'); + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(errorSpy).toHaveBeenCalledWith( + 'test.ts(3,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' + + 'Type \'String\' has no compatible call signatures.\n'); expect(exitCode).toEqual(1); }); it('should print the stack trace on compiler internal errors', () => { write('test.ts', 'export const A = 1;'); - const mockConsole = {error: (s: string) => {}}; - - spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', 'not-exist'], mockConsole.error); - expect(mockConsole.error).toHaveBeenCalled(); - expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed'); - expect(exitCode).toEqual(2); + const exitCode = mainSync(['-p', 'not-exist'], errorSpy); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory'); + expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)'); + expect(exitCode).toEqual(1); }); describe('compile ngfactory files', () => { @@ -218,11 +161,7 @@ describe('ngc command-line', () => { export class MyModule {} `); - const mockConsole = {error: (s: string) => {}}; - - const errorSpy = spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); + const exitCode = mainSync(['-p', basePath], errorSpy); expect(errorSpy).toHaveBeenCalledTimes(1); expect(errorSpy.calls.mostRecent().args[0]) .toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html')); @@ -253,11 +192,7 @@ describe('ngc command-line', () => { export class MyModule {} `); - const mockConsole = {error: (s: string) => {}}; - - const errorSpy = spyOn(mockConsole, 'error'); - - const exitCode = main(['-p', basePath], mockConsole.error); + const exitCode = mainSync(['-p', basePath], errorSpy); expect(errorSpy).toHaveBeenCalledTimes(1); expect(errorSpy.calls.mostRecent().args[0]) .toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):')); @@ -282,7 +217,7 @@ describe('ngc command-line', () => { export class MyModule {} `); - const exitCode = main(['-p', basePath]); + const exitCode = mainSync(['-p', basePath], errorSpy); expect(exitCode).toEqual(0); expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true); @@ -292,7 +227,7 @@ describe('ngc command-line', () => { .toBe(true); }); - it('should compile with a explicit tsconfig reference', () => { + it('should compile with an explicit tsconfig reference', () => { writeConfig(`{ "extends": "./tsconfig-base.json", "files": ["mymodule.ts"] @@ -307,7 +242,7 @@ describe('ngc command-line', () => { export class MyModule {} `); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); + const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); expect(exitCode).toEqual(0); expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true); expect(fs.existsSync(path.resolve( @@ -316,7 +251,90 @@ describe('ngc command-line', () => { .toBe(true); }); - xdescribe('expression lowering', () => { + describe('closure', () => { + it('should not generate closure specific code by default', () => { + writeConfig(`{ + "extends": "./tsconfig-base.json", + "files": ["mymodule.ts"] + }`); + write('mymodule.ts', ` + import {NgModule, Component} from '@angular/core'; + + @Component({template: ''}) + export class MyComp {} + + @NgModule({declarations: [MyComp]}) + export class MyModule {} + `); + + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(exitCode).toEqual(0); + + 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.decorators = ['); + }); + + it('should add closure annotations', () => { + writeConfig(`{ + "extends": "./tsconfig-base.json", + "angularCompilerOptions": { + "annotateForClosureCompiler": true + }, + "files": ["mymodule.ts"] + }`); + write('mymodule.ts', ` + import {NgModule, Component} from '@angular/core'; + + @Component({template: ''}) + export class MyComp { + fn(p: any) {} + } + + @NgModule({declarations: [MyComp]}) + export class MyModule {} + `); + + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(exitCode).toEqual(0); + + const mymodulejs = path.resolve(outDir, 'mymodule.js'); + const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); + expect(mymoduleSource).toContain('@fileoverview added by tsickle'); + expect(mymoduleSource).toContain('@param {?} p'); + }); + + it('should add metadata as decorators', () => { + writeConfig(`{ + "extends": "./tsconfig-base.json", + "angularCompilerOptions": { + "annotationsAs": "decorators" + }, + "files": ["mymodule.ts"] + }`); + write('mymodule.ts', ` + import {NgModule, Component} from '@angular/core'; + + @Component({template: ''}) + export class MyComp { + fn(p: any) {} + } + + @NgModule({declarations: [MyComp]}) + export class MyModule {} + `); + + const exitCode = mainSync(['-p', basePath], errorSpy); + expect(exitCode).toEqual(0); + + const mymodulejs = path.resolve(outDir, 'mymodule.js'); + const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); + expect(mymoduleSource).toContain('MyComp = __decorate(['); + }); + }); + + describe('expression lowering', () => { beforeEach(() => { writeConfig(`{ "extends": "./tsconfig-base.json", @@ -325,9 +343,9 @@ describe('ngc command-line', () => { }); function compile(): number { - const errors: string[] = []; - const result = main(['-p', path.join(basePath, 'tsconfig.json')], s => errors.push(s)); - expect(errors).toEqual([]); + errorSpy.calls.reset(); + const result = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); return result; } @@ -424,13 +442,35 @@ describe('ngc command-line', () => { }) export class MyModule {} `); - expect(compile()).toEqual(0); + expect(compile()).toEqual(0, 'Compile failed'); const mymodulejs = path.resolve(outDir, 'mymodule.js'); const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }'); expect(mymoduleSource).toContain('export { ɵ0'); }); + + it('should not lower a lambda that is already exported', () => { + write('mymodule.ts', ` + import {CommonModule} from '@angular/common'; + import {NgModule} from '@angular/core'; + + export class Foo {} + + export const factory = () => new Foo(); + + @NgModule({ + imports: [CommonModule], + providers: [{provide: 'someToken', useFactory: factory}] + }) + export class MyModule {} + `); + expect(compile()).toEqual(0); + + const mymodulejs = path.resolve(outDir, 'mymodule.js'); + const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); + expect(mymoduleSource).not.toContain('ɵ0'); + }); }); const shouldExist = (fileName: string) => { @@ -499,63 +539,7 @@ describe('ngc command-line', () => { export class FlatModule { }`); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); - expect(exitCode).toEqual(0); - shouldExist('index.js'); - shouldExist('index.metadata.json'); - }); - - it('should be able to build a flat module passing explicit options', () => { - write('public-api.ts', ` - export * from './src/flat.component'; - export * from './src/flat.module';`); - write('src/flat.component.html', '
flat module component
'); - write('src/flat.component.ts', ` - import {Component} from '@angular/core'; - - @Component({ - selector: 'flat-comp', - templateUrl: 'flat.component.html', - }) - export class FlatComponent { - }`); - write('src/flat.module.ts', ` - import {NgModule} from '@angular/core'; - - import {FlatComponent} from './flat.component'; - - @NgModule({ - declarations: [ - FlatComponent, - ], - exports: [ - FlatComponent, - ] - }) - export class FlatModule { - }`); - - const exitCode = performCompilation( - basePath, [path.join(basePath, 'public-api.ts')], { - target: ts.ScriptTarget.ES5, - experimentalDecorators: true, - noImplicitAny: true, - moduleResolution: ts.ModuleResolutionKind.NodeJs, - rootDir: basePath, - declaration: true, - lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'], - baseUrl: basePath, - outDir: path.join(basePath, 'built'), - typeRoots: [path.join(basePath, 'node_modules/@types')] - }, - { - genDir: 'ng', - flatModuleId: 'flat_module', - flatModuleOutFile: 'index.js', - skipTemplateCodegen: true - }); - - + const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); expect(exitCode).toEqual(0); shouldExist('index.js'); shouldExist('index.metadata.json'); @@ -648,7 +632,7 @@ describe('ngc command-line', () => { it('should honor skip code generation', () => { // First ensure that we skip code generation when requested;. writeGenConfig(/* skipCodegen */ true); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); + const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); expect(exitCode).toEqual(0); modules.forEach(moduleName => { shouldExist(moduleName + '.js'); @@ -664,7 +648,7 @@ describe('ngc command-line', () => { it('should produce factories', () => { // First ensure that we skip code generation when requested;. writeGenConfig(/* skipCodegen */ false); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); + const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); expect(exitCode).toEqual(0); modules.forEach(moduleName => { shouldExist(moduleName + '.js'); @@ -715,7 +699,7 @@ describe('ngc command-line', () => { }); it('should compile without error', () => { - expect(main(['-p', path.join(basePath, 'tsconfig.json')])).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0); }); }); @@ -736,7 +720,7 @@ describe('ngc command-line', () => { write(path.join(dir, 'tsconfig.json'), ` { "angularCompilerOptions": { - "generateCodeForLibraries": false, + "generateCodeForLibraries": true, "enableSummariesForJit": true }, "compilerOptions": { @@ -789,7 +773,7 @@ describe('ngc command-line', () => { }); it('should be able to compile library 1', () => { - expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0); shouldExist('lib1/module.js'); shouldExist('lib1/module.ngsummary.json'); shouldExist('lib1/module.ngsummary.js'); @@ -798,9 +782,9 @@ describe('ngc command-line', () => { shouldExist('lib1/module.ngfactory.d.ts'); }); - it('should be able to compiler library 2', () => { - expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0); - expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0); + it('should be able to compile library 2', () => { + expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0); shouldExist('lib2/module.js'); shouldExist('lib2/module.ngsummary.json'); shouldExist('lib2/module.ngsummary.js'); @@ -811,12 +795,12 @@ describe('ngc command-line', () => { describe('building an application', () => { beforeEach(() => { - expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0); - expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0); }); it('should build without error', () => { - expect(main(['-p', path.join(basePath, 'app')])).toBe(0); + expect(mainSync(['-p', path.join(basePath, 'app')], errorSpy)).toBe(0); shouldExist('app/main.js'); }); }); diff --git a/packages/compiler-cli/test/transformers/compiler_host_spec.ts b/packages/compiler-cli/test/transformers/compiler_host_spec.ts new file mode 100644 index 0000000000..9d86f460c6 --- /dev/null +++ b/packages/compiler-cli/test/transformers/compiler_host_spec.ts @@ -0,0 +1,126 @@ +/** + * @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 * as ts from 'typescript'; + +import {CompilerHost, CompilerOptions} from '../../src/transformers/api'; +import {createCompilerHost} from '../../src/transformers/compiler_host'; +import {Directory, Entry, MockAotContext, MockCompilerHost} from '../mocks'; + +const dummyModule = 'export let foo: any[];'; + +describe('NgCompilerHost', () => { + function createHost( + {files = {}, options = {basePath: '/tmp'}}: {files?: Directory, + options?: CompilerOptions} = {}) { + const context = new MockAotContext('/tmp/', files); + const tsHost = new MockCompilerHost(context); + return createCompilerHost({tsHost, options}); + } + + describe('fileNameToModuleName', () => { + let ngHost: CompilerHost; + beforeEach(() => { ngHost = createHost(); }); + + it('should use a package import when accessing a package from a source file', () => { + expect(ngHost.fileNameToModuleName('/tmp/node_modules/@angular/core.d.ts', '/tmp/main.ts')) + .toBe('@angular/core'); + }); + + it('should use a package import when accessing a package from another package', () => { + expect(ngHost.fileNameToModuleName( + '/tmp/node_modules/mod1/index.d.ts', '/tmp/node_modules/mod2/index.d.ts')) + .toBe('mod1/index'); + expect(ngHost.fileNameToModuleName( + '/tmp/node_modules/@angular/core/index.d.ts', + '/tmp/node_modules/@angular/common/index.d.ts')) + .toBe('@angular/core/index'); + }); + + it('should use a relative import when accessing a file in the same package', () => { + expect(ngHost.fileNameToModuleName( + '/tmp/node_modules/mod/a/child.d.ts', '/tmp/node_modules/mod/index.d.ts')) + .toBe('./a/child'); + expect(ngHost.fileNameToModuleName( + '/tmp/node_modules/@angular/core/src/core.d.ts', + '/tmp/node_modules/@angular/core/index.d.ts')) + .toBe('./src/core'); + }); + + it('should use a relative import when accessing a source file from a source file', () => { + expect(ngHost.fileNameToModuleName('/tmp/src/a/child.ts', '/tmp/src/index.ts')) + .toBe('./a/child'); + }); + + it('should support multiple rootDirs when accessing a source file form a source file', () => { + const ngHostWithMultipleRoots = createHost({ + options: { + basePath: '/tmp/', + rootDirs: [ + 'src/a', + 'src/b', + ] + } + }); + // both files are in the rootDirs + expect(ngHostWithMultipleRoots.fileNameToModuleName('/tmp/src/b/b.ts', '/tmp/src/a/a.ts')) + .toBe('./b'); + + // one file is not in the rootDirs + expect(ngHostWithMultipleRoots.fileNameToModuleName('/tmp/src/c/c.ts', '/tmp/src/a/a.ts')) + .toBe('../c/c'); + }); + + it('should error if accessing a source file from a package', () => { + expect( + () => ngHost.fileNameToModuleName( + '/tmp/src/a/child.ts', '/tmp/node_modules/@angular/core.d.ts')) + .toThrowError( + 'Trying to import a source file from a node_modules package: import /tmp/src/a/child.ts from /tmp/node_modules/@angular/core.d.ts'); + }); + + }); + + describe('moduleNameToFileName', () => { + it('should resolve a package import without a containing file', () => { + const ngHost = createHost( + {files: {'tmp': {'node_modules': {'@angular': {'core': {'index.d.ts': dummyModule}}}}}}); + expect(ngHost.moduleNameToFileName('@angular/core')) + .toBe('/tmp/node_modules/@angular/core/index.d.ts'); + }); + + it('should resolve an import using the containing file', () => { + const ngHost = createHost({files: {'tmp': {'src': {'a': {'child.d.ts': dummyModule}}}}}); + expect(ngHost.moduleNameToFileName('./a/child', '/tmp/src/index.ts')) + .toBe('/tmp/src/a/child.d.ts'); + }); + }); + + describe('resourceNameToFileName', () => { + it('should resolve a relative import', () => { + const ngHost = createHost({files: {'tmp': {'src': {'a': {'child.html': '
'}}}}}); + expect(ngHost.resourceNameToFileName('./a/child.html', '/tmp/src/index.ts')) + .toBe('/tmp/src/a/child.html'); + + expect(ngHost.resourceNameToFileName('./a/non-existing.html', '/tmp/src/index.ts')) + .toBe(null); + }); + + it('should resolve package paths as relative paths', () => { + const ngHost = createHost({files: {'tmp': {'src': {'a': {'child.html': '
'}}}}}); + expect(ngHost.resourceNameToFileName('a/child.html', '/tmp/src/index.ts')) + .toBe('/tmp/src/a/child.html'); + }); + + it('should resolve absolute paths', () => { + const ngHost = createHost({files: {'tmp': {'src': {'a': {'child.html': '
'}}}}}); + expect(ngHost.resourceNameToFileName('/tmp/src/a/child.html', '/tmp/src/index.ts')) + .toBe('/tmp/src/a/child.html'); + }); + }); +}); diff --git a/packages/compiler-cli/test/transformers/lower_expressions_spec.ts b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts index 7588982b03..6b923ed671 100644 --- a/packages/compiler-cli/test/transformers/lower_expressions_spec.ts +++ b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts @@ -51,11 +51,9 @@ function convert(annotatedSource: string) { for (const annotation of annotations) { const node = findNode(sourceFile, annotation.start, annotation.length); - expect(node).toBeDefined(); - if (node) { - const location = node.pos; - requests.set(location, {name: annotation.name, kind: node.kind, location, end: node.end}); - } + if (!node) throw new Error('Invalid test specification. Could not find the node to substitute'); + const location = node.pos; + requests.set(location, {name: annotation.name, kind: node.kind, location, end: node.end}); } const program = ts.createProgram( diff --git a/packages/compiler-cli/test/transformers/node_emitter_spec.ts b/packages/compiler-cli/test/transformers/node_emitter_spec.ts index 2918e70535..f7b169c2a7 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 6937d14a26..14d2bb226b 100644 --- a/packages/compiler-cli/tsconfig-build.json +++ b/packages/compiler-cli/tsconfig-build.json @@ -8,12 +8,7 @@ "module": "commonjs", "outDir": "../../dist/packages/compiler-cli", "paths": { - "@angular/core": ["../../dist/packages/core"], - "@angular/common": ["../../dist/packages/common"], "@angular/compiler": ["../../dist/packages/compiler"], - "@angular/http": ["../../dist/packages/http"], - "@angular/platform-server": ["../../dist/packages/platform-server"], - "@angular/platform-browser": ["../../dist/packages/platform-browser"], "@angular/tsc-wrapped": ["../../dist/packages-dist/tsc-wrapped"] }, "rootDir": ".", @@ -32,8 +27,8 @@ "files": [ "index.ts", "src/main.ts", - "src/ngc.ts", "src/extract_i18n.ts", + "src/language_services.ts", "../../node_modules/@types/node/index.d.ts", "../../node_modules/@types/jasmine/index.d.ts", "../../node_modules/zone.js/dist/zone.js.d.ts" diff --git a/packages/compiler/BUILD.bazel b/packages/compiler/BUILD.bazel index 857fadd6e0..fde96d4c01 100644 --- a/packages/compiler/BUILD.bazel +++ b/packages/compiler/BUILD.bazel @@ -8,6 +8,6 @@ ts_library( "testing/**", ]), module_name = "@angular/compiler", - deps = ["//packages/core"], + deps = [], tsconfig = ":tsconfig-build.json", ) diff --git a/packages/compiler/package.json b/packages/compiler/package.json index a3b4bd3858..5232563993 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -11,9 +11,6 @@ "dependencies": { "tslib": "^1.7.1" }, - "peerDependencies": { - "@angular/core": "0.0.0-PLACEHOLDER" - }, "repository": { "type": "git", "url": "https://github.com/angular/angular.git" diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 03b1e34aac..76d3aca8df 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -207,10 +207,10 @@ export class AotCompiler { } private _createSummary( - srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[], + srcFileName: string, directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[], injectables: StaticSymbol[], ngFactoryCtx: OutputContext): GeneratedFile[] { - const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl) + const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileName) .map(symbol => this._symbolResolver.resolveSymbol(symbol)); const typeData: { summary: CompileTypeSummary, @@ -235,18 +235,19 @@ export class AotCompiler { metadata: this._metadataResolver.getInjectableSummary(ref) !.type })) ]; - const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileUrl, true)); + const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileName, true)); const {json, exportAs} = serializeSummaries( - forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries, typeData); + srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries, + typeData); exportAs.forEach((entry) => { ngFactoryCtx.statements.push( o.variable(entry.exportAs).set(ngFactoryCtx.importExpr(entry.symbol)).toDeclStmt(null, [ o.StmtModifier.Exported ])); }); - const summaryJson = new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json); + const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json); if (this._enableSummariesForJit) { - return [summaryJson, this._codegenSourceModule(srcFileUrl, forJitOutputCtx)]; + return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)]; }; return [summaryJson]; @@ -322,9 +323,10 @@ export class AotCompiler { const pipes = ngModule.transitiveModule.pipes.map( pipe => this._metadataResolver.getPipeSummary(pipe.reference)); + const preserveWhitespaces = compMeta !.template !.preserveWhitespaces; const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse( compMeta, compMeta.template !.template !, directives, pipes, ngModule.schemas, - templateSourceUrl(ngModule.type, compMeta, compMeta.template !)); + templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces); const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); const viewResult = this._viewCompiler.compileComponent( outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes); diff --git a/packages/compiler/src/aot/compiler_factory.ts b/packages/compiler/src/aot/compiler_factory.ts index 7d911e4c81..f8155dad33 100644 --- a/packages/compiler/src/aot/compiler_factory.ts +++ b/packages/compiler/src/aot/compiler_factory.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {MissingTranslationStrategy, ViewEncapsulation, ɵConsole as Console} from '@angular/core'; import {CompilerConfig} from '../config'; +import {MissingTranslationStrategy, ViewEncapsulation} from '../core'; import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveResolver} from '../directive_resolver'; import {Lexer} from '../expression_parser/lexer'; @@ -22,7 +22,8 @@ import {PipeResolver} from '../pipe_resolver'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {StyleCompiler} from '../style_compiler'; import {TemplateParser} from '../template_parser/template_parser'; -import {createOfflineCompileUrlResolver} from '../url_resolver'; +import {UrlResolver} from '../url_resolver'; +import {syntaxError} from '../util'; import {ViewCompiler} from '../view_compiler/view_compiler'; import {AotCompiler} from './compiler'; @@ -33,6 +34,19 @@ import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbolResolver} from './static_symbol_resolver'; import {AotSummaryResolver} from './summary_resolver'; +export function createAotUrlResolver(host: { + resourceNameToFileName(resourceName: string, containingFileName: string): string | null; +}): UrlResolver { + return { + resolve: (basePath: string, url: string) => { + const filePath = host.resourceNameToFileName(url, basePath); + if (!filePath) { + throw syntaxError(`Couldn't resolve resource ${url} from ${basePath}`); + } + return filePath; + } + }; +} /** * Creates a new AotCompiler based on options and a host. @@ -41,12 +55,11 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom {compiler: AotCompiler, reflector: StaticReflector} { let translations: string = options.translations || ''; - const urlResolver = createOfflineCompileUrlResolver(); + const urlResolver = createAotUrlResolver(compilerHost); const symbolCache = new StaticSymbolCache(); const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache); const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver); const staticReflector = new StaticReflector(summaryResolver, symbolResolver); - const console = new Console(); const htmlParser = new I18NHtmlParser( new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console); const config = new CompilerConfig({ @@ -54,6 +67,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom useJit: false, enableLegacyTemplate: options.enableLegacyTemplate !== false, missingTranslation: options.missingTranslation, + preserveWhitespaces: options.preserveWhitespaces, }); const normalizer = new DirectiveNormalizer( {get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config); diff --git a/packages/compiler/src/aot/compiler_host.ts b/packages/compiler/src/aot/compiler_host.ts index e5c2927845..00bbf1e45b 100644 --- a/packages/compiler/src/aot/compiler_host.ts +++ b/packages/compiler/src/aot/compiler_host.ts @@ -14,6 +14,11 @@ import {AotSummaryResolverHost} from './summary_resolver'; * services and from underlying file systems. */ export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost { + /** + * Converts a path that refers to a resource into an absolute filePath + * that can be later on used for loading the resource via `loadResource. + */ + resourceNameToFileName(resourceName: string, containingFileName: string): string|null; /** * Loads a resource (e.g. html / css) */ diff --git a/packages/compiler/src/aot/compiler_options.ts b/packages/compiler/src/aot/compiler_options.ts index ed260b3a6a..5528e9b5d2 100644 --- a/packages/compiler/src/aot/compiler_options.ts +++ b/packages/compiler/src/aot/compiler_options.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {MissingTranslationStrategy} from '@angular/core'; +import {MissingTranslationStrategy} from '../core'; export interface AotCompilerOptions { locale?: string; @@ -15,4 +15,5 @@ export interface AotCompilerOptions { missingTranslation?: MissingTranslationStrategy; enableLegacyTemplate?: boolean; enableSummariesForJit?: boolean; + preserveWhitespaces?: boolean; } diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts index fb76750878..0a72ad91fa 100644 --- a/packages/compiler/src/aot/static_reflector.ts +++ b/packages/compiler/src/aot/static_reflector.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; - import {CompileSummaryKind} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; +import {MetadataFactory, createAttribute, createComponent, createContentChild, createContentChildren, createDirective, createHost, createHostBinding, createHostListener, createInject, createInjectable, createInput, createNgModule, createOptional, createOutput, createPipe, createSelf, createSkipSelf, createViewChild, createViewChildren} from '../core'; import * as o from '../output/output_ast'; import {SummaryResolver} from '../summary_resolver'; import {syntaxError} from '../util'; @@ -48,8 +47,8 @@ export class StaticReflector implements CompileReflector { private opaqueToken: StaticSymbol; private ROUTES: StaticSymbol; private ANALYZE_FOR_ENTRY_COMPONENTS: StaticSymbol; - private annotationForParentClassWithSummaryKind = new Map(); - private annotationNames = new Map(); + private annotationForParentClassWithSummaryKind = + new Map[]>(); constructor( private summaryResolver: SummaryResolver, @@ -64,16 +63,12 @@ export class StaticReflector implements CompileReflector { knownMetadataFunctions.forEach( (kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn)); this.annotationForParentClassWithSummaryKind.set( - CompileSummaryKind.Directive, [Directive, Component]); - this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [Pipe]); - this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [NgModule]); + CompileSummaryKind.Directive, [createDirective, createComponent]); + this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [createPipe]); + this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [createNgModule]); this.annotationForParentClassWithSummaryKind.set( - CompileSummaryKind.Injectable, [Injectable, Pipe, Directive, Component, NgModule]); - this.annotationNames.set(Directive, 'Directive'); - this.annotationNames.set(Component, 'Component'); - this.annotationNames.set(Pipe, 'Pipe'); - this.annotationNames.set(NgModule, 'NgModule'); - this.annotationNames.set(Injectable, 'Injectable'); + CompileSummaryKind.Injectable, + [createInjectable, createPipe, createDirective, createComponent, createNgModule]); } componentModuleUrl(typeOrFunc: StaticSymbol): string { @@ -82,12 +77,11 @@ export class StaticReflector implements CompileReflector { } resolveExternalReference(ref: o.ExternalReference): StaticSymbol { - const importSymbol = this.getStaticSymbol(ref.moduleName !, ref.name !); - const rootSymbol = this.findDeclaration(ref.moduleName !, ref.name !); - if (importSymbol != rootSymbol) { - this.symbolResolver.recordImportAs(rootSymbol, importSymbol); - } - return rootSymbol; + const refSymbol = this.symbolResolver.getSymbolByModule(ref.moduleName !, ref.name !); + const declarationSymbol = this.findSymbolDeclaration(refSymbol); + this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName !); + this.symbolResolver.recordImportAs(declarationSymbol, refSymbol); + return declarationSymbol; } findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol { @@ -130,12 +124,12 @@ export class StaticReflector implements CompileReflector { const requiredAnnotationTypes = this.annotationForParentClassWithSummaryKind.get(summary.type.summaryKind !) !; const typeHasRequiredAnnotation = requiredAnnotationTypes.some( - (requiredType: any) => ownAnnotations.some(ann => ann instanceof requiredType)); + (requiredType) => ownAnnotations.some(ann => requiredType.isTypeOf(ann))); if (!typeHasRequiredAnnotation) { this.reportError( syntaxError( `Class ${type.name} in ${type.filePath} extends from a ${CompileSummaryKind[summary.type.summaryKind!]} in another compilation unit without duplicating the decorator. ` + - `Please add a ${requiredAnnotationTypes.map((type: any) => this.annotationNames.get(type)).join(' or ')} decorator to the class.`), + `Please add a ${requiredAnnotationTypes.map((type) => type.ngMetadataName).join(' or ')} decorator to the class.`), type); } } @@ -282,50 +276,48 @@ export class StaticReflector implements CompileReflector { this.ANALYZE_FOR_ENTRY_COMPONENTS = this.findDeclaration(ANGULAR_CORE, 'ANALYZE_FOR_ENTRY_COMPONENTS'); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), Host); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Injectable'), Injectable); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), Self); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), SkipSelf); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Inject'), Inject); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Optional'), Optional); + this.findDeclaration(ANGULAR_CORE, 'Injectable'), createInjectable); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Attribute'), Attribute); + this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ContentChild'), ContentChild); + this.findDeclaration(ANGULAR_CORE, 'Inject'), createInject); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ContentChildren'), ContentChildren); + this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ViewChild'), ViewChild); + this.findDeclaration(ANGULAR_CORE, 'Attribute'), createAttribute); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ViewChildren'), ViewChildren); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Input'), Input); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Output'), Output); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Pipe'), Pipe); + this.findDeclaration(ANGULAR_CORE, 'ContentChild'), createContentChild); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'HostBinding'), HostBinding); + this.findDeclaration(ANGULAR_CORE, 'ContentChildren'), createContentChildren); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'HostListener'), HostListener); + this.findDeclaration(ANGULAR_CORE, 'ViewChild'), createViewChild); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Directive'), Directive); + this.findDeclaration(ANGULAR_CORE, 'ViewChildren'), createViewChildren); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Input'), createInput); this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Component'), Component); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'NgModule'), NgModule); + this.findDeclaration(ANGULAR_CORE, 'Output'), createOutput); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Pipe'), createPipe); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'HostBinding'), createHostBinding); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'HostListener'), createHostListener); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'Directive'), createDirective); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'Component'), createComponent); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'NgModule'), createNgModule); // Note: Some metadata classes can be used directly with Provider.deps. - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), Host); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), Self); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), SkipSelf); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Optional'), Optional); - - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'trigger'), trigger); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'state'), state); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'transition'), transition); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'style'), style); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'animate'), animate); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'keyframes'), keyframes); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'sequence'), sequence); - this._registerFunction(this.findDeclaration(ANGULAR_CORE, 'group'), group); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost); + this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf); + this._registerDecoratorOrConstructor( + this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional); } /** @@ -756,4 +748,4 @@ function positionalError(message: string, fileName: string, line: number, column (result as any).line = line; (result as any).column = column; return result; -} +} \ No newline at end of file diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts index 80d3ec5be4..6a66d3ab78 100644 --- a/packages/compiler/src/aot/static_symbol_resolver.ts +++ b/packages/compiler/src/aot/static_symbol_resolver.ts @@ -172,6 +172,10 @@ export class StaticSymbolResolver { this.importAs.set(sourceSymbol, targetSymbol); } + recordModuleNameForFileName(fileName: string, moduleName: string) { + this.knownFileNameToModuleNames.set(fileName, moduleName); + } + /** * Invalidate all information derived from the given file. * diff --git a/packages/compiler/src/aot/summary_resolver.ts b/packages/compiler/src/aot/summary_resolver.ts index 52f7857629..c630675dc7 100644 --- a/packages/compiler/src/aot/summary_resolver.ts +++ b/packages/compiler/src/aot/summary_resolver.ts @@ -23,11 +23,20 @@ export interface AotSummaryResolverHost { */ isSourceFile(sourceFilePath: string): boolean; /** - * Returns the output file path of a source file. + * Converts a file name into a representation that should be stored in a summary file. + * This has to include changing the suffix as well. * E.g. * `some_file.ts` -> `some_file.d.ts` + * + * @param referringSrcFileName the soure file that refers to fileName */ - getOutputFileName(sourceFilePath: string): string; + toSummaryFileName(fileName: string, referringSrcFileName: string): string; + + /** + * Converts a fileName that was processed by `toSummaryFileName` back into a real fileName + * given the fileName of the library that is referrig to it. + */ + fromSummaryFileName(fileName: string, referringLibFileName: string): string; } export class AotSummaryResolver implements SummaryResolver { @@ -46,7 +55,13 @@ export class AotSummaryResolver implements SummaryResolver { return !this.host.isSourceFile(stripGeneratedFileSuffix(filePath)); } - getLibraryFileName(filePath: string) { return this.host.getOutputFileName(filePath); } + toSummaryFileName(filePath: string, referringSrcFileName: string) { + return this.host.toSummaryFileName(filePath, referringSrcFileName); + } + + fromSummaryFileName(fileName: string, referringLibFileName: string) { + return this.host.fromSummaryFileName(fileName, referringLibFileName); + } resolveSummary(staticSymbol: StaticSymbol): Summary { staticSymbol.assertNoMembers(); @@ -85,7 +100,8 @@ export class AotSummaryResolver implements SummaryResolver { throw e; } if (json) { - const {summaries, importAs} = deserializeSummaries(this.staticSymbolCache, json); + const {summaries, importAs} = + deserializeSummaries(this.staticSymbolCache, this, filePath, json); summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary)); importAs.forEach((importAs) => { this.importAs.set( diff --git a/packages/compiler/src/aot/summary_serializer.ts b/packages/compiler/src/aot/summary_serializer.ts index 11e420a2d1..165c803cbf 100644 --- a/packages/compiler/src/aot/summary_serializer.ts +++ b/packages/compiler/src/aot/summary_serializer.ts @@ -15,7 +15,7 @@ import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolv import {summaryForJitFileName, summaryForJitName} from './util'; export function serializeSummaries( - forJitCtx: OutputContext, summaryResolver: SummaryResolver, + srcFileName: string, forJitCtx: OutputContext, summaryResolver: SummaryResolver, symbolResolver: StaticSymbolResolver, symbols: ResolvedStaticSymbol[], types: { summary: CompileTypeSummary, metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata | @@ -76,15 +76,17 @@ export function serializeSummaries( }); } }); - const {json, exportAs} = toJsonSerializer.serialize(); + const {json, exportAs} = toJsonSerializer.serialize(srcFileName); forJitSerializer.serialize(exportAs); return {json, exportAs}; } -export function deserializeSummaries(symbolCache: StaticSymbolCache, json: string): +export function deserializeSummaries( + symbolCache: StaticSymbolCache, summaryResolver: SummaryResolver, + libraryFileName: string, json: string): {summaries: Summary[], importAs: {symbol: StaticSymbol, importAs: string}[]} { - const deserializer = new FromJsonDeserializer(symbolCache); - return deserializer.deserialize(json); + const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver); + return deserializer.deserialize(libraryFileName, json); } export function createForJitStub(outputCtx: OutputContext, reference: StaticSymbol) { @@ -151,7 +153,8 @@ class ToJsonSerializer extends ValueTransformer { } } - serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} { + serialize(srcFileName: string): + {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} { const exportAs: {symbol: StaticSymbol, exportAs: string}[] = []; const json = JSON.stringify({ summaries: this.processedSummaries, @@ -165,10 +168,7 @@ class ToJsonSerializer extends ValueTransformer { return { __symbol: index, name: symbol.name, - // We convert the source filenames tinto output filenames, - // as the generated summary file will be used when the current - // compilation unit is used as a library - filePath: this.summaryResolver.getLibraryFileName(symbol.filePath), + filePath: this.summaryResolver.toSummaryFileName(symbol.filePath, srcFileName), importAs: importAs }; }) @@ -317,15 +317,21 @@ class ForJitSerializer { class FromJsonDeserializer extends ValueTransformer { private symbols: StaticSymbol[]; - constructor(private symbolCache: StaticSymbolCache) { super(); } + constructor( + private symbolCache: StaticSymbolCache, + private summaryResolver: SummaryResolver) { + super(); + } - deserialize(json: string): + deserialize(libraryFileName: string, json: string): {summaries: Summary[], importAs: {symbol: StaticSymbol, importAs: string}[]} { const data: {summaries: any[], symbols: any[]} = JSON.parse(json); const importAs: {symbol: StaticSymbol, importAs: string}[] = []; this.symbols = []; data.symbols.forEach((serializedSymbol) => { - const symbol = this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name); + const symbol = this.symbolCache.get( + this.summaryResolver.fromSummaryFileName(serializedSymbol.filePath, libraryFileName), + serializedSymbol.name); this.symbols.push(symbol); if (serializedSymbol.importAs) { importAs.push({symbol: symbol, importAs: serializedSymbol.importAs}); diff --git a/packages/compiler/src/assertions.ts b/packages/compiler/src/assertions.ts index a3a525e202..2b3736e0e1 100644 --- a/packages/compiler/src/assertions.ts +++ b/packages/compiler/src/assertions.ts @@ -6,11 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {isDevMode} from '@angular/core'; - - export function assertArrayOfStrings(identifier: string, value: any) { - if (!isDevMode() || value == null) { + if (value == null) { return; } if (!Array.isArray(value)) { @@ -34,7 +31,7 @@ const INTERPOLATION_BLACKLIST_REGEXPS = [ export function assertInterpolationSymbols(identifier: string, value: any): void { if (value != null && !(Array.isArray(value) && value.length == 2)) { throw new Error(`Expected '${identifier}' to be an array, [start, end].`); - } else if (isDevMode() && value != null) { + } else if (value != null) { const start = value[0] as string; const end = value[1] as string; // black list checking diff --git a/packages/compiler/src/compile_metadata.ts b/packages/compiler/src/compile_metadata.ts index 8d2b5c4f1e..82829da690 100644 --- a/packages/compiler/src/compile_metadata.ts +++ b/packages/compiler/src/compile_metadata.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, Type, ViewEncapsulation, ɵstringify as stringify} from '@angular/core'; - import {StaticSymbol} from './aot/static_symbol'; +import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core'; import {LifecycleHooks} from './lifecycle_reflector'; import {CssSelector} from './selector'; -import {splitAtColon} from './util'; +import {splitAtColon, stringify} from './util'; @@ -252,8 +251,9 @@ export class CompileTemplateMetadata { animations: any[]; ngContentSelectors: string[]; interpolation: [string, string]|null; + preserveWhitespaces: boolean; constructor({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, - animations, ngContentSelectors, interpolation, isInline}: { + animations, ngContentSelectors, interpolation, isInline, preserveWhitespaces}: { encapsulation: ViewEncapsulation | null, template: string|null, templateUrl: string|null, @@ -263,7 +263,8 @@ export class CompileTemplateMetadata { ngContentSelectors: string[], animations: any[], interpolation: [string, string]|null, - isInline: boolean + isInline: boolean, + preserveWhitespaces: boolean }) { this.encapsulation = encapsulation; this.template = template; @@ -278,6 +279,7 @@ export class CompileTemplateMetadata { } this.interpolation = interpolation; this.isInline = isInline; + this.preserveWhitespaces = preserveWhitespaces; } toSummary(): CompileTemplateSummary { @@ -291,7 +293,7 @@ export class CompileTemplateMetadata { export interface CompileEntryComponentMetadata { componentType: any; - componentFactory: StaticSymbol|ComponentFactory; + componentFactory: StaticSymbol|object; } // Note: This should only use interfaces as nested data types @@ -314,8 +316,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary { changeDetection: ChangeDetectionStrategy|null; template: CompileTemplateSummary|null; componentViewType: StaticSymbol|ProxyClass|null; - rendererType: StaticSymbol|RendererType2|null; - componentFactory: StaticSymbol|ComponentFactory|null; + rendererType: StaticSymbol|object|null; + componentFactory: StaticSymbol|object|null; } /** @@ -341,8 +343,8 @@ export class CompileDirectiveMetadata { entryComponents: CompileEntryComponentMetadata[], template: CompileTemplateMetadata, componentViewType: StaticSymbol|ProxyClass|null, - rendererType: StaticSymbol|RendererType2|null, - componentFactory: StaticSymbol|ComponentFactory|null, + rendererType: StaticSymbol|object|null, + componentFactory: StaticSymbol|object|null, }): CompileDirectiveMetadata { const hostListeners: {[key: string]: string} = {}; const hostProperties: {[key: string]: string} = {}; @@ -419,8 +421,8 @@ export class CompileDirectiveMetadata { template: CompileTemplateMetadata|null; componentViewType: StaticSymbol|ProxyClass|null; - rendererType: StaticSymbol|RendererType2|null; - componentFactory: StaticSymbol|ComponentFactory|null; + rendererType: StaticSymbol|object|null; + componentFactory: StaticSymbol|object|null; constructor({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners, hostProperties, @@ -444,8 +446,8 @@ export class CompileDirectiveMetadata { entryComponents: CompileEntryComponentMetadata[], template: CompileTemplateMetadata|null, componentViewType: StaticSymbol|ProxyClass|null, - rendererType: StaticSymbol|RendererType2|null, - componentFactory: StaticSymbol|ComponentFactory|null, + rendererType: StaticSymbol|object|null, + componentFactory: StaticSymbol|object|null, }) { this.isHost = !!isHost; this.type = type; @@ -516,7 +518,8 @@ export function createHostComponentMeta( animations: [], isInline: true, externalStylesheets: [], - interpolation: null + interpolation: null, + preserveWhitespaces: false, }), exportAs: null, changeDetection: ChangeDetectionStrategy.Default, @@ -530,7 +533,8 @@ export function createHostComponentMeta( queries: [], viewQueries: [], componentViewType: hostViewType, - rendererType: {id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}}, + rendererType: + {id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as object, entryComponents: [], componentFactory: null }); @@ -716,7 +720,7 @@ function _normalizeArray(obj: any[] | undefined | null): any[] { export class ProviderMeta { token: any; - useClass: Type|null; + useClass: Type|null; useValue: any; useExisting: any; useFactory: Function|null; @@ -724,7 +728,7 @@ export class ProviderMeta { multi: boolean; constructor(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}: { - useClass?: Type, + useClass?: Type, useValue?: any, useExisting?: any, useFactory?: Function|null, diff --git a/packages/compiler/src/compile_reflector.ts b/packages/compiler/src/compile_reflector.ts index 67eec55e54..96cc30c8c9 100644 --- a/packages/compiler/src/compile_reflector.ts +++ b/packages/compiler/src/compile_reflector.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component} from './core'; import * as o from './output/output_ast'; /** diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 8a88d720b9..8b42c8efc1 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -21,10 +21,14 @@ *

*
*/ -export {VERSION} from './version'; + +import * as core from './core'; + +export {core}; + +export * from './version'; export * from './template_parser/template_ast'; -export {TEMPLATE_TRANSFORMS} from './template_parser/template_parser'; -export {CompilerConfig} from './config'; +export {CompilerConfig, preserveWhitespacesDefault} from './config'; export * from './compile_metadata'; export * from './aot/compiler_factory'; export * from './aot/compiler'; @@ -37,9 +41,8 @@ export * from './aot/static_symbol_resolver'; export * from './aot/summary_resolver'; export * from './ast_path'; export * from './summary_resolver'; +export {Identifiers} from './identifiers'; export {JitCompiler} from './jit/compiler'; -export * from './jit/compiler_factory'; -export * from './jit/jit_reflector'; export * from './compile_reflector'; export * from './url_resolver'; export * from './resource_loader'; @@ -69,5 +72,5 @@ export * from './selector'; export * from './style_compiler'; export * from './template_parser/template_parser'; export {ViewCompiler} from './view_compiler/view_compiler'; -export {getParseErrors, isSyntaxError, syntaxError} from './util'; +export {getParseErrors, isSyntaxError, syntaxError, Version} from './util'; // This file only reexports content of the `src` folder. Keep it that way. diff --git a/packages/compiler/src/config.ts b/packages/compiler/src/config.ts index 75be193731..12bab10a0b 100644 --- a/packages/compiler/src/config.ts +++ b/packages/compiler/src/config.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken, MissingTranslationStrategy, ViewEncapsulation, isDevMode} from '@angular/core'; - import {CompileIdentifierMetadata} from './compile_metadata'; +import {MissingTranslationStrategy, ViewEncapsulation} from './core'; import {Identifiers} from './identifiers'; - +import * as o from './output/output_ast'; +import {noUndefined} from './util'; export class CompilerConfig { public defaultEncapsulation: ViewEncapsulation|null; @@ -18,19 +18,30 @@ export class CompilerConfig { // templates. They have been deprecated in 4.x, `` should be used instead. public enableLegacyTemplate: boolean; public useJit: boolean; + public jitDevMode: boolean; public missingTranslation: MissingTranslationStrategy|null; + public preserveWhitespaces: boolean; constructor( - {defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, missingTranslation, - enableLegacyTemplate}: { + {defaultEncapsulation = ViewEncapsulation.Emulated, useJit = true, jitDevMode = false, + missingTranslation, enableLegacyTemplate, preserveWhitespaces}: { defaultEncapsulation?: ViewEncapsulation, useJit?: boolean, + jitDevMode?: boolean, missingTranslation?: MissingTranslationStrategy, enableLegacyTemplate?: boolean, + preserveWhitespaces?: boolean } = {}) { this.defaultEncapsulation = defaultEncapsulation; this.useJit = !!useJit; + this.jitDevMode = !!jitDevMode; this.missingTranslation = missingTranslation || null; this.enableLegacyTemplate = enableLegacyTemplate !== false; + this.preserveWhitespaces = preserveWhitespacesDefault(noUndefined(preserveWhitespaces)); } } + +export function preserveWhitespacesDefault( + preserveWhitespacesOption: boolean | null, defaultSetting = true): boolean { + return preserveWhitespacesOption === null ? defaultSetting : preserveWhitespacesOption; +} diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts new file mode 100644 index 0000000000..71af0afaab --- /dev/null +++ b/packages/compiler/src/core.ts @@ -0,0 +1,262 @@ +/** + * @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 + */ + +// Attention: +// This file duplicates types and values from @angular/core +// so that we are able to make @angular/compiler independent of @angular/core. +// This is important to prevent a build cycle, as @angular/core needs to +// be compiled with the compiler. + +export interface Inject { token: any; } +export const createInject = makeMetadataFactory('Inject', (token: any) => ({token})); +export const createInjectionToken = + makeMetadataFactory('InjectionToken', (desc: string) => ({_desc: desc})); + +export interface Attribute { attributeName?: string; } +export const createAttribute = + makeMetadataFactory('Attribute', (attributeName?: string) => ({attributeName})); + +export interface Query { + descendants: boolean; + first: boolean; + read: any; + isViewQuery: boolean; + selector: any; +} + +export const createContentChildren = makeMetadataFactory( + 'ContentChildren', + (selector?: any, data: any = {}) => + ({selector, first: false, isViewQuery: false, descendants: false, ...data})); +export const createContentChild = makeMetadataFactory( + 'ContentChild', (selector?: any, data: any = {}) => + ({selector, first: true, isViewQuery: false, descendants: true, ...data})); +export const createViewChildren = makeMetadataFactory( + 'ViewChildren', (selector?: any, data: any = {}) => + ({selector, first: false, isViewQuery: true, descendants: true, ...data})); +export const createViewChild = makeMetadataFactory( + 'ViewChild', (selector: any, data: any) => + ({selector, first: true, isViewQuery: true, descendants: true, ...data})); + +export interface Directive { + selector?: string; + inputs?: string[]; + outputs?: string[]; + host?: {[key: string]: string}; + providers?: Provider[]; + exportAs?: string; + queries?: {[key: string]: any}; +} +export const createDirective = + makeMetadataFactory('Directive', (dir: Directive = {}) => dir); + +export interface Component extends Directive { + changeDetection?: ChangeDetectionStrategy; + viewProviders?: Provider[]; + moduleId?: string; + templateUrl?: string; + template?: string; + styleUrls?: string[]; + styles?: string[]; + animations?: any[]; + encapsulation?: ViewEncapsulation; + interpolation?: [string, string]; + entryComponents?: Array; + preserveWhitespaces?: boolean; +} +export enum ViewEncapsulation { + Emulated = 0, + Native = 1, + None = 2 +} + +export enum ChangeDetectionStrategy { + OnPush = 0, + Default = 1 +} + +export const createComponent = makeMetadataFactory( + 'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c})); + +export interface Pipe { + name: string; + pure?: boolean; +} +export const createPipe = makeMetadataFactory('Pipe', (p: Pipe) => ({pure: true, ...p})); + +export interface Input { bindingPropertyName?: string; } +export const createInput = + makeMetadataFactory('Input', (bindingPropertyName?: string) => ({bindingPropertyName})); + +export interface Output { bindingPropertyName?: string; } +export const createOutput = makeMetadataFactory( + 'Output', (bindingPropertyName?: string) => ({bindingPropertyName})); + +export interface HostBinding { hostPropertyName?: string; } +export const createHostBinding = makeMetadataFactory( + 'HostBinding', (hostPropertyName?: string) => ({hostPropertyName})); + +export interface HostListener { + eventName?: string; + args?: string[]; +} +export const createHostListener = makeMetadataFactory( + 'HostListener', (eventName?: string, args?: string[]) => ({eventName, args})); + +export interface NgModule { + providers?: Provider[]; + declarations?: Array; + imports?: Array; + exports?: Array; + entryComponents?: Array; + bootstrap?: Array; + schemas?: Array; + id?: string; +} +export const createNgModule = + makeMetadataFactory('NgModule', (ngModule: NgModule) => ngModule); + +export interface ModuleWithProviders { + ngModule: Type; + providers?: Provider[]; +} + +export interface SchemaMetadata { name: string; } + +export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { + name: 'custom-elements' +}; + +export const NO_ERRORS_SCHEMA: SchemaMetadata = { + name: 'no-errors-schema' +}; + +export const createOptional = makeMetadataFactory('Optional'); +export const createInjectable = makeMetadataFactory('Injectable'); +export const createSelf = makeMetadataFactory('Self'); +export const createSkipSelf = makeMetadataFactory('SkipSelf'); +export const createHost = makeMetadataFactory('Host'); + +export interface Type extends Function { new (...args: any[]): any; } +export const Type = Function; + +export enum SecurityContext { + NONE = 0, + HTML = 1, + STYLE = 2, + SCRIPT = 3, + URL = 4, + RESOURCE_URL = 5, +} + +export type Provider = any; + +export const enum NodeFlags { + None = 0, + TypeElement = 1 << 0, + TypeText = 1 << 1, + ProjectedTemplate = 1 << 2, + CatRenderNode = TypeElement | TypeText, + TypeNgContent = 1 << 3, + TypePipe = 1 << 4, + TypePureArray = 1 << 5, + TypePureObject = 1 << 6, + TypePurePipe = 1 << 7, + CatPureExpression = TypePureArray | TypePureObject | TypePurePipe, + TypeValueProvider = 1 << 8, + TypeClassProvider = 1 << 9, + TypeFactoryProvider = 1 << 10, + TypeUseExistingProvider = 1 << 11, + LazyProvider = 1 << 12, + PrivateProvider = 1 << 13, + TypeDirective = 1 << 14, + Component = 1 << 15, + CatProviderNoDirective = + TypeValueProvider | TypeClassProvider | TypeFactoryProvider | TypeUseExistingProvider, + CatProvider = CatProviderNoDirective | TypeDirective, + OnInit = 1 << 16, + OnDestroy = 1 << 17, + DoCheck = 1 << 18, + OnChanges = 1 << 19, + AfterContentInit = 1 << 20, + AfterContentChecked = 1 << 21, + AfterViewInit = 1 << 22, + AfterViewChecked = 1 << 23, + EmbeddedViews = 1 << 24, + ComponentView = 1 << 25, + TypeContentQuery = 1 << 26, + TypeViewQuery = 1 << 27, + StaticQuery = 1 << 28, + DynamicQuery = 1 << 29, + CatQuery = TypeContentQuery | TypeViewQuery, + + // mutually exclusive values... + Types = CatRenderNode | TypeNgContent | TypePipe | CatPureExpression | CatProvider | CatQuery +} + +export const enum DepFlags { + None = 0, + SkipSelf = 1 << 0, + Optional = 1 << 1, + Value = 2 << 2, +} + +export const enum ArgumentType {Inline = 0, Dynamic = 1} + +export const enum BindingFlags { + TypeElementAttribute = 1 << 0, + TypeElementClass = 1 << 1, + TypeElementStyle = 1 << 2, + TypeProperty = 1 << 3, + SyntheticProperty = 1 << 4, + SyntheticHostProperty = 1 << 5, + CatSyntheticProperty = SyntheticProperty | SyntheticHostProperty, + + // mutually exclusive values... + Types = TypeElementAttribute | TypeElementClass | TypeElementStyle | TypeProperty +} + +export const enum QueryBindingType {First = 0, All = 1} + +export const enum QueryValueType { + ElementRef = 0, + RenderElement = 1, + TemplateRef = 2, + ViewContainerRef = 3, + Provider = 4 +} + +export const enum ViewFlags { + None = 0, + OnPush = 1 << 1, +} + +export enum MissingTranslationStrategy { + Error = 0, + Warning = 1, + Ignore = 2, +} + +export interface MetadataFactory { + (...args: any[]): T; + isTypeOf(obj: any): obj is T; + ngMetadataName: string; +} + +function makeMetadataFactory(name: string, props?: (...args: any[]) => T): MetadataFactory { + const factory: any = (...args: any[]) => { + const values = props ? props(...args) : {}; + return { + ngMetadataName: name, + ...values, + }; + }; + factory.isTypeOf = (obj: any) => obj && obj.ngMetadataName === name; + factory.ngMetadataName = name; + return factory; +} diff --git a/packages/compiler/src/directive_normalizer.ts b/packages/compiler/src/directive_normalizer.ts index e9484a34ae..2ac66f6f76 100644 --- a/packages/compiler/src/directive_normalizer.ts +++ b/packages/compiler/src/directive_normalizer.ts @@ -6,11 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ViewEncapsulation, ɵstringify as stringify} from '@angular/core'; - import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata'; -import {CompilerConfig} from './config'; -import {CompilerInjectable} from './injectable'; +import {CompilerConfig, preserveWhitespacesDefault} from './config'; +import {ViewEncapsulation} from './core'; import * as html from './ml_parser/ast'; import {HtmlParser} from './ml_parser/html_parser'; import {InterpolationConfig} from './ml_parser/interpolation_config'; @@ -18,7 +16,7 @@ import {ResourceLoader} from './resource_loader'; import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; import {PreparsedElementType, preparseElement} from './template_parser/template_preparser'; import {UrlResolver} from './url_resolver'; -import {SyncAsync, isDefined, syntaxError} from './util'; +import {SyncAsync, isDefined, stringify, syntaxError} from './util'; export interface PrenormalizedTemplateMetadata { ngModuleType: any; @@ -31,9 +29,9 @@ export interface PrenormalizedTemplateMetadata { interpolation: [string, string]|null; encapsulation: ViewEncapsulation|null; animations: CompileAnimationEntryMetadata[]; + preserveWhitespaces: boolean|null; } -@CompilerInjectable() export class DirectiveNormalizer { private _resourceLoaderCache = new Map>(); @@ -82,6 +80,13 @@ export class DirectiveNormalizer { throw syntaxError( `No template specified for component ${stringify(prenormData.componentType)}`); } + + if (isDefined(prenormData.preserveWhitespaces) && + typeof prenormData.preserveWhitespaces !== 'boolean') { + throw syntaxError( + `The preserveWhitespaces option for component ${stringify(prenormData.componentType)} must be a boolean`); + } + return SyncAsync.then( this.normalizeTemplateOnly(prenormData), (result: CompileTemplateMetadata) => this.normalizeExternalStylesheets(result)); @@ -149,7 +154,9 @@ export class DirectiveNormalizer { ngContentSelectors: visitor.ngContentSelectors, animations: prenormData.animations, interpolation: prenormData.interpolation, isInline, - externalStylesheets: [] + externalStylesheets: [], + preserveWhitespaces: preserveWhitespacesDefault( + prenormData.preserveWhitespaces, this._config.preserveWhitespaces), }); } @@ -168,6 +175,7 @@ export class DirectiveNormalizer { animations: templateMeta.animations, interpolation: templateMeta.interpolation, isInline: templateMeta.isInline, + preserveWhitespaces: templateMeta.preserveWhitespaces, })); } diff --git a/packages/compiler/src/directive_resolver.ts b/packages/compiler/src/directive_resolver.ts index 2b1790305f..c32f328d1a 100644 --- a/packages/compiler/src/directive_resolver.ts +++ b/packages/compiler/src/directive_resolver.ts @@ -6,13 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef, ɵstringify as stringify} from '@angular/core'; - import {CompileReflector} from './compile_reflector'; -import {CompilerInjectable} from './injectable'; -import {splitAtColon} from './util'; - +import {Component, Directive, Type, createComponent, createContentChild, createContentChildren, createDirective, createHostBinding, createHostListener, createInput, createOutput, createViewChild, createViewChildren} from './core'; +import {resolveForwardRef, splitAtColon, stringify} from './util'; +const QUERY_METADATA_IDENTIFIERS = [ + createViewChild, + createViewChildren, + createContentChild, + createContentChildren, +]; /* * Resolve a `Type` for {@link Directive}. @@ -21,11 +24,10 @@ import {splitAtColon} from './util'; * * See {@link Compiler} */ -@CompilerInjectable() export class DirectiveResolver { constructor(private _reflector: CompileReflector) {} - isDirective(type: Type) { + isDirective(type: Type) { const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); return typeMetadata && typeMetadata.some(isDirectiveMetadata); } @@ -33,10 +35,10 @@ export class DirectiveResolver { /** * Return {@link Directive} for a given `Type`. */ - resolve(type: Type): Directive; - resolve(type: Type, throwIfNotFound: true): Directive; - resolve(type: Type, throwIfNotFound: boolean): Directive|null; - resolve(type: Type, throwIfNotFound = true): Directive|null { + resolve(type: Type): Directive; + resolve(type: Type, throwIfNotFound: true): Directive; + resolve(type: Type, throwIfNotFound: boolean): Directive|null; + resolve(type: Type, throwIfNotFound = true): Directive|null { const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); if (typeMetadata) { const metadata = findLast(typeMetadata, isDirectiveMetadata); @@ -54,15 +56,14 @@ export class DirectiveResolver { } private _mergeWithPropertyMetadata( - dm: Directive, propertyMetadata: {[key: string]: any[]}, - directiveType: Type): Directive { + dm: Directive, propertyMetadata: {[key: string]: any[]}, directiveType: Type): Directive { const inputs: string[] = []; const outputs: string[] = []; const host: {[key: string]: string} = {}; const queries: {[key: string]: any} = {}; Object.keys(propertyMetadata).forEach((propName: string) => { - const input = findLast(propertyMetadata[propName], (a) => a instanceof Input); + const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a)); if (input) { if (input.bindingPropertyName) { inputs.push(`${propName}: ${input.bindingPropertyName}`); @@ -70,7 +71,7 @@ export class DirectiveResolver { inputs.push(propName); } } - const output = findLast(propertyMetadata[propName], (a) => a instanceof Output); + const output = findLast(propertyMetadata[propName], (a) => createOutput.isTypeOf(a)); if (output) { if (output.bindingPropertyName) { outputs.push(`${propName}: ${output.bindingPropertyName}`); @@ -78,7 +79,7 @@ export class DirectiveResolver { outputs.push(propName); } } - const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding); + const hostBindings = propertyMetadata[propName].filter(a => createHostBinding.isTypeOf(a)); hostBindings.forEach(hostBinding => { if (hostBinding.hostPropertyName) { const startWith = hostBinding.hostPropertyName[0]; @@ -93,12 +94,13 @@ export class DirectiveResolver { host[`[${propName}]`] = propName; } }); - const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener); + const hostListeners = propertyMetadata[propName].filter(a => createHostListener.isTypeOf(a)); hostListeners.forEach(hostListener => { const args = hostListener.args || []; host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`; }); - const query = findLast(propertyMetadata[propName], (a) => a instanceof Query); + const query = findLast( + propertyMetadata[propName], (a) => QUERY_METADATA_IDENTIFIERS.some(i => i.isTypeOf(a))); if (query) { queries[propName] = query; } @@ -125,7 +127,7 @@ export class DirectiveResolver { private _merge( directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string}, - queries: {[key: string]: any}, directiveType: Type): Directive { + queries: {[key: string]: any}, directiveType: Type): Directive { const mergedInputs = this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs); const mergedOutputs = @@ -133,29 +135,31 @@ export class DirectiveResolver { const mergedHost = directive.host ? {...directive.host, ...host} : host; const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries; - if (directive instanceof Component) { - return new Component({ - selector: directive.selector, + if (createComponent.isTypeOf(directive)) { + const comp = directive as Component; + return createComponent({ + selector: comp.selector, inputs: mergedInputs, outputs: mergedOutputs, host: mergedHost, - exportAs: directive.exportAs, - moduleId: directive.moduleId, + exportAs: comp.exportAs, + moduleId: comp.moduleId, queries: mergedQueries, - changeDetection: directive.changeDetection, - providers: directive.providers, - viewProviders: directive.viewProviders, - entryComponents: directive.entryComponents, - template: directive.template, - templateUrl: directive.templateUrl, - styles: directive.styles, - styleUrls: directive.styleUrls, - encapsulation: directive.encapsulation, - animations: directive.animations, - interpolation: directive.interpolation + changeDetection: comp.changeDetection, + providers: comp.providers, + viewProviders: comp.viewProviders, + entryComponents: comp.entryComponents, + template: comp.template, + templateUrl: comp.templateUrl, + styles: comp.styles, + styleUrls: comp.styleUrls, + encapsulation: comp.encapsulation, + animations: comp.animations, + interpolation: comp.interpolation, + preserveWhitespaces: directive.preserveWhitespaces, }); } else { - return new Directive({ + return createDirective({ selector: directive.selector, inputs: mergedInputs, outputs: mergedOutputs, @@ -169,7 +173,7 @@ export class DirectiveResolver { } function isDirectiveMetadata(type: any): type is Directive { - return type instanceof Directive; + return createDirective.isTypeOf(type) || createComponent.isTypeOf(type); } export function findLast(arr: T[], condition: (value: T) => boolean): T|null { diff --git a/packages/compiler/src/expression_parser/lexer.ts b/packages/compiler/src/expression_parser/lexer.ts index 17b59c4284..cc485ea791 100644 --- a/packages/compiler/src/expression_parser/lexer.ts +++ b/packages/compiler/src/expression_parser/lexer.ts @@ -7,7 +7,6 @@ */ import * as chars from '../chars'; -import {CompilerInjectable} from '../injectable'; export enum TokenType { Character, @@ -21,7 +20,6 @@ export enum TokenType { const KEYWORDS = ['var', 'let', 'as', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this']; -@CompilerInjectable() export class Lexer { tokenize(text: string): Token[] { const scanner = new _Scanner(text); diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index 14dacda156..93d2362a43 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -7,7 +7,6 @@ */ import * as chars from '../chars'; -import {CompilerInjectable} from '../injectable'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; import {escapeRegExp} from '../util'; @@ -29,7 +28,6 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp { return new RegExp(pattern, 'g'); } -@CompilerInjectable() export class Parser { private errors: ParserError[] = []; diff --git a/packages/compiler/src/i18n/extractor.ts b/packages/compiler/src/i18n/extractor.ts index 5d6cbb5d1f..3b573e7f47 100644 --- a/packages/compiler/src/i18n/extractor.ts +++ b/packages/compiler/src/i18n/extractor.ts @@ -10,15 +10,15 @@ /** * Extract i18n messages from source code */ -import {ViewEncapsulation, ɵConsole as Console} from '@angular/core'; - import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler'; +import {createAotUrlResolver} from '../aot/compiler_factory'; import {StaticReflector} from '../aot/static_reflector'; import {StaticSymbolCache} from '../aot/static_symbol'; import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver'; import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver'; import {CompileDirectiveMetadata} from '../compile_metadata'; import {CompilerConfig} from '../config'; +import {ViewEncapsulation} from '../core'; import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveResolver} from '../directive_resolver'; import {CompileMetadataResolver} from '../metadata_resolver'; @@ -28,14 +28,22 @@ import {NgModuleResolver} from '../ng_module_resolver'; import {ParseError} from '../parse_util'; import {PipeResolver} from '../pipe_resolver'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; -import {createOfflineCompileUrlResolver} from '../url_resolver'; +import {syntaxError} from '../util'; + import {MessageBundle} from './message_bundle'; + + /** * The host of the Extractor disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost { + /** + * Converts a path that refers to a resource into an absolute filePath + * that can be lateron used for loading the resource via `loadResource. + */ + resourceNameToFileName(path: string, containingFile: string): string|null; /** * Loads a resource (e.g. html / css) */ @@ -87,7 +95,7 @@ export class Extractor { {extractor: Extractor, staticReflector: StaticReflector} { const htmlParser = new HtmlParser(); - const urlResolver = createOfflineCompileUrlResolver(); + const urlResolver = createAotUrlResolver(host); const symbolCache = new StaticSymbolCache(); const summaryResolver = new AotSummaryResolver(host, symbolCache); const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver); @@ -102,7 +110,7 @@ export class Extractor { const resolver = new CompileMetadataResolver( config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, - new Console(), symbolCache, staticReflector); + console, symbolCache, staticReflector); // TODO(vicb): implicit tags & attributes const messageBundle = new MessageBundle(htmlParser, [], {}, locale); diff --git a/packages/compiler/src/i18n/i18n_html_parser.ts b/packages/compiler/src/i18n/i18n_html_parser.ts index aab0aa0b95..b0c3397c03 100644 --- a/packages/compiler/src/i18n/i18n_html_parser.ts +++ b/packages/compiler/src/i18n/i18n_html_parser.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {MissingTranslationStrategy, ɵConsole as Console} from '@angular/core'; - +import {MissingTranslationStrategy} from '../core'; import {HtmlParser} from '../ml_parser/html_parser'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; import {ParseTreeResult} from '../ml_parser/parser'; +import {Console} from '../util'; import {digest} from './digest'; import {mergeTranslations} from './extractor_merger'; diff --git a/packages/compiler/src/i18n/translation_bundle.ts b/packages/compiler/src/i18n/translation_bundle.ts index 90c334833c..c1c5daf68c 100644 --- a/packages/compiler/src/i18n/translation_bundle.ts +++ b/packages/compiler/src/i18n/translation_bundle.ts @@ -6,13 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {MissingTranslationStrategy, ɵConsole as Console} from '@angular/core'; +import {MissingTranslationStrategy} from '../core'; import * as html from '../ml_parser/ast'; import {HtmlParser} from '../ml_parser/html_parser'; +import {Console} from '../util'; + import * as i18n from './i18n_ast'; import {I18nError} from './parse_util'; import {PlaceholderMapper, Serializer} from './serializers/serializer'; + /** * A container for translated messages */ diff --git a/packages/compiler/src/identifiers.ts b/packages/compiler/src/identifiers.ts index b2c1d179eb..598aff3454 100644 --- a/packages/compiler/src/identifiers.ts +++ b/packages/compiler/src/identifiers.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, NgModuleRef, QueryList, Renderer, SecurityContext, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation, ɵCodegenComponentFactoryResolver, ɵEMPTY_ARRAY, ɵEMPTY_MAP, ɵand, ɵccf, ɵcmf, ɵcrt, ɵdid, ɵeld, ɵinlineInterpolate, ɵinterpolate, ɵmod, ɵmpd, ɵncd, ɵnov, ɵpad, ɵpid, ɵpod, ɵppd, ɵprd, ɵqud, ɵregisterModuleFactory, ɵted, ɵunv, ɵvid} from '@angular/core'; - import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileReflector} from './compile_reflector'; +import {Attribute, Component, Directive, HostBinding, HostListener, Inject, Input, NgModule, Output, Pipe, Query} from './core'; import * as o from './output/output_ast'; const CORE = '@angular/core'; @@ -18,121 +17,109 @@ export class Identifiers { static ANALYZE_FOR_ENTRY_COMPONENTS: o.ExternalReference = { name: 'ANALYZE_FOR_ENTRY_COMPONENTS', moduleName: CORE, - runtime: ANALYZE_FOR_ENTRY_COMPONENTS + }; - static ElementRef: - o.ExternalReference = {name: 'ElementRef', moduleName: CORE, runtime: ElementRef}; - static NgModuleRef: - o.ExternalReference = {name: 'NgModuleRef', moduleName: CORE, runtime: NgModuleRef}; - static ViewContainerRef: - o.ExternalReference = {name: 'ViewContainerRef', moduleName: CORE, runtime: ViewContainerRef}; + static ElementRef: o.ExternalReference = {name: 'ElementRef', moduleName: CORE}; + static NgModuleRef: o.ExternalReference = {name: 'NgModuleRef', moduleName: CORE}; + static ViewContainerRef: o.ExternalReference = {name: 'ViewContainerRef', moduleName: CORE}; static ChangeDetectorRef: o.ExternalReference = { name: 'ChangeDetectorRef', moduleName: CORE, - runtime: ChangeDetectorRef + }; - static QueryList: o.ExternalReference = {name: 'QueryList', moduleName: CORE, runtime: QueryList}; - static TemplateRef: - o.ExternalReference = {name: 'TemplateRef', moduleName: CORE, runtime: TemplateRef}; + static QueryList: o.ExternalReference = {name: 'QueryList', moduleName: CORE}; + static TemplateRef: o.ExternalReference = {name: 'TemplateRef', moduleName: CORE}; static CodegenComponentFactoryResolver: o.ExternalReference = { name: 'ɵCodegenComponentFactoryResolver', moduleName: CORE, - runtime: ɵCodegenComponentFactoryResolver + }; static ComponentFactoryResolver: o.ExternalReference = { name: 'ComponentFactoryResolver', moduleName: CORE, - runtime: ComponentFactoryResolver + }; - static ComponentFactory: - o.ExternalReference = {name: 'ComponentFactory', moduleName: CORE, runtime: ComponentFactory}; - static ComponentRef: - o.ExternalReference = {name: 'ComponentRef', moduleName: CORE, runtime: ComponentRef}; - static NgModuleFactory: - o.ExternalReference = {name: 'NgModuleFactory', moduleName: CORE, runtime: NgModuleFactory}; + static ComponentFactory: o.ExternalReference = {name: 'ComponentFactory', moduleName: CORE}; + static ComponentRef: o.ExternalReference = {name: 'ComponentRef', moduleName: CORE}; + static NgModuleFactory: o.ExternalReference = {name: 'NgModuleFactory', moduleName: CORE}; static createModuleFactory: o.ExternalReference = { name: 'ɵcmf', moduleName: CORE, - runtime: ɵcmf, + }; static moduleDef: o.ExternalReference = { name: 'ɵmod', moduleName: CORE, - runtime: ɵmod, + }; static moduleProviderDef: o.ExternalReference = { name: 'ɵmpd', moduleName: CORE, - runtime: ɵmpd, + }; static RegisterModuleFactoryFn: o.ExternalReference = { name: 'ɵregisterModuleFactory', moduleName: CORE, - runtime: ɵregisterModuleFactory, + }; - static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE, runtime: Injector}; + static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE}; static ViewEncapsulation: o.ExternalReference = { name: 'ViewEncapsulation', moduleName: CORE, - runtime: ViewEncapsulation + }; static ChangeDetectionStrategy: o.ExternalReference = { name: 'ChangeDetectionStrategy', moduleName: CORE, - runtime: ChangeDetectionStrategy + }; static SecurityContext: o.ExternalReference = { name: 'SecurityContext', moduleName: CORE, - runtime: SecurityContext, + }; - static LOCALE_ID: o.ExternalReference = {name: 'LOCALE_ID', moduleName: CORE, runtime: LOCALE_ID}; + static LOCALE_ID: o.ExternalReference = {name: 'LOCALE_ID', moduleName: CORE}; static TRANSLATIONS_FORMAT: o.ExternalReference = { name: 'TRANSLATIONS_FORMAT', moduleName: CORE, - runtime: TRANSLATIONS_FORMAT + }; static inlineInterpolate: o.ExternalReference = { name: 'ɵinlineInterpolate', moduleName: CORE, - runtime: ɵinlineInterpolate + }; - static interpolate: - o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE, runtime: ɵinterpolate}; - static EMPTY_ARRAY: - o.ExternalReference = {name: 'ɵEMPTY_ARRAY', moduleName: CORE, runtime: ɵEMPTY_ARRAY}; - static EMPTY_MAP: - o.ExternalReference = {name: 'ɵEMPTY_MAP', moduleName: CORE, runtime: ɵEMPTY_MAP}; - static Renderer: o.ExternalReference = {name: 'Renderer', moduleName: CORE, runtime: Renderer}; - static viewDef: o.ExternalReference = {name: 'ɵvid', moduleName: CORE, runtime: ɵvid}; - static elementDef: o.ExternalReference = {name: 'ɵeld', moduleName: CORE, runtime: ɵeld}; - static anchorDef: o.ExternalReference = {name: 'ɵand', moduleName: CORE, runtime: ɵand}; - static textDef: o.ExternalReference = {name: 'ɵted', moduleName: CORE, runtime: ɵted}; - static directiveDef: o.ExternalReference = {name: 'ɵdid', moduleName: CORE, runtime: ɵdid}; - static providerDef: o.ExternalReference = {name: 'ɵprd', moduleName: CORE, runtime: ɵprd}; - static queryDef: o.ExternalReference = {name: 'ɵqud', moduleName: CORE, runtime: ɵqud}; - static pureArrayDef: o.ExternalReference = {name: 'ɵpad', moduleName: CORE, runtime: ɵpad}; - static pureObjectDef: o.ExternalReference = {name: 'ɵpod', moduleName: CORE, runtime: ɵpod}; - static purePipeDef: o.ExternalReference = {name: 'ɵppd', moduleName: CORE, runtime: ɵppd}; - static pipeDef: o.ExternalReference = {name: 'ɵpid', moduleName: CORE, runtime: ɵpid}; - static nodeValue: o.ExternalReference = {name: 'ɵnov', moduleName: CORE, runtime: ɵnov}; - static ngContentDef: o.ExternalReference = {name: 'ɵncd', moduleName: CORE, runtime: ɵncd}; - static unwrapValue: o.ExternalReference = {name: 'ɵunv', moduleName: CORE, runtime: ɵunv}; - static createRendererType2: o.ExternalReference = {name: 'ɵcrt', moduleName: CORE, runtime: ɵcrt}; + static interpolate: o.ExternalReference = {name: 'ɵinterpolate', moduleName: CORE}; + static EMPTY_ARRAY: o.ExternalReference = {name: 'ɵEMPTY_ARRAY', moduleName: CORE}; + static EMPTY_MAP: o.ExternalReference = {name: 'ɵEMPTY_MAP', moduleName: CORE}; + static Renderer: o.ExternalReference = {name: 'Renderer', moduleName: CORE}; + static viewDef: o.ExternalReference = {name: 'ɵvid', moduleName: CORE}; + static elementDef: o.ExternalReference = {name: 'ɵeld', moduleName: CORE}; + static anchorDef: o.ExternalReference = {name: 'ɵand', moduleName: CORE}; + static textDef: o.ExternalReference = {name: 'ɵted', moduleName: CORE}; + static directiveDef: o.ExternalReference = {name: 'ɵdid', moduleName: CORE}; + static providerDef: o.ExternalReference = {name: 'ɵprd', moduleName: CORE}; + static queryDef: o.ExternalReference = {name: 'ɵqud', moduleName: CORE}; + static pureArrayDef: o.ExternalReference = {name: 'ɵpad', moduleName: CORE}; + static pureObjectDef: o.ExternalReference = {name: 'ɵpod', moduleName: CORE}; + static purePipeDef: o.ExternalReference = {name: 'ɵppd', moduleName: CORE}; + static pipeDef: o.ExternalReference = {name: 'ɵpid', moduleName: CORE}; + static nodeValue: o.ExternalReference = {name: 'ɵnov', moduleName: CORE}; + static ngContentDef: o.ExternalReference = {name: 'ɵncd', moduleName: CORE}; + static unwrapValue: o.ExternalReference = {name: 'ɵunv', moduleName: CORE}; + static createRendererType2: o.ExternalReference = {name: 'ɵcrt', moduleName: CORE}; + // type only static RendererType2: o.ExternalReference = { name: 'RendererType2', moduleName: CORE, - // type only - runtime: null + }; + // type only static ViewDefinition: o.ExternalReference = { name: 'ɵViewDefinition', moduleName: CORE, - // type only - runtime: null }; - static createComponentFactory: - o.ExternalReference = {name: 'ɵccf', moduleName: CORE, runtime: ɵccf}; + static createComponentFactory: o.ExternalReference = {name: 'ɵccf', moduleName: CORE}; } export function createTokenForReference(reference: any): CompileTokenMetadata { diff --git a/packages/compiler/src/injectable.ts b/packages/compiler/src/injectable.ts deleted file mode 100644 index b5dcb4ec2e..0000000000 --- a/packages/compiler/src/injectable.ts +++ /dev/null @@ -1,17 +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 - */ - -/** - * A replacement for @Injectable to be used in the compiler, so that - * we don't try to evaluate the metadata in the compiler during AoT. - * This decorator is enough to make the compiler work with the ReflectiveInjector though. - * @Annotation - */ -export function CompilerInjectable(): (data: any) => any { - return (x) => x; -} diff --git a/packages/compiler/src/jit/compiler.ts b/packages/compiler/src/jit/compiler.ts index 7cf3a447b3..dfc508a4aa 100644 --- a/packages/compiler/src/jit/compiler.ts +++ b/packages/compiler/src/jit/compiler.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵConsole as Console, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core'; - -import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata'; +import {CompileReflector} from '../compile_reflector'; import {CompilerConfig} from '../config'; -import {CompilerInjectable} from '../injectable'; +import {Type} from '../core'; import {CompileMetadataResolver} from '../metadata_resolver'; import {NgModuleCompiler} from '../ng_module_compiler'; import * as ir from '../output/output_ast'; @@ -19,10 +18,13 @@ import {jitStatements} from '../output/output_jit'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {SummaryResolver} from '../summary_resolver'; import {TemplateParser} from '../template_parser/template_parser'; -import {OutputContext, SyncAsync} from '../util'; +import {Console, OutputContext, SyncAsync, stringify} from '../util'; import {ViewCompiler} from '../view_compiler/view_compiler'; - +export interface ModuleWithComponentFactories { + ngModuleFactory: object; + componentFactories: object[]; +} /** * An internal module of the Angular compiler that begins with component types, @@ -33,41 +35,38 @@ import {ViewCompiler} from '../view_compiler/view_compiler'; * from a trusted source. Attacker-controlled data introduced by a template could expose your * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). */ -@CompilerInjectable() -export class JitCompiler implements Compiler { - private _compiledTemplateCache = new Map, CompiledTemplate>(); - private _compiledHostTemplateCache = new Map, CompiledTemplate>(); - private _compiledDirectiveWrapperCache = new Map, Type>(); - private _compiledNgModuleCache = new Map, NgModuleFactory>(); +export class JitCompiler { + private _compiledTemplateCache = new Map(); + private _compiledHostTemplateCache = new Map(); + private _compiledDirectiveWrapperCache = new Map(); + private _compiledNgModuleCache = new Map(); private _sharedStylesheetCount = 0; constructor( - private _injector: Injector, private _metadataResolver: CompileMetadataResolver, - private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, - private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler, - private _summaryResolver: SummaryResolver>, private _compilerConfig: CompilerConfig, - private _console: Console) {} + private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, + private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, + private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver, + private _reflector: CompileReflector, private _compilerConfig: CompilerConfig, + private _console: Console, + private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {} - get injector(): Injector { return this._injector; } - - compileModuleSync(moduleType: Type): NgModuleFactory { + compileModuleSync(moduleType: Type): object { return SyncAsync.assertSync(this._compileModuleAndComponents(moduleType, true)); } - compileModuleAsync(moduleType: Type): Promise> { + compileModuleAsync(moduleType: Type): Promise { return Promise.resolve(this._compileModuleAndComponents(moduleType, false)); } - compileModuleAndAllComponentsSync(moduleType: Type): ModuleWithComponentFactories { + compileModuleAndAllComponentsSync(moduleType: Type): ModuleWithComponentFactories { return SyncAsync.assertSync(this._compileModuleAndAllComponents(moduleType, true)); } - compileModuleAndAllComponentsAsync(moduleType: Type): - Promise> { + compileModuleAndAllComponentsAsync(moduleType: Type): Promise { return Promise.resolve(this._compileModuleAndAllComponents(moduleType, false)); } - getNgContentSelectors(component: Type): string[] { + getNgContentSelectors(component: Type): string[] { this._console.warn( 'Compiler.getNgContentSelectors is deprecated. Use ComponentFactory.ngContentSelectors instead!'); const template = this._compiledTemplateCache.get(component); @@ -77,9 +76,9 @@ export class JitCompiler implements Compiler { return template.compMeta.template !.ngContentSelectors; } - getComponentFactory(component: Type): ComponentFactory { + getComponentFactory(component: Type): object { const summary = this._metadataResolver.getDirectiveSummary(component); - return >summary.componentFactory; + return summary.componentFactory as object; } loadAotSummaries(summaries: () => any[]) { @@ -90,26 +89,28 @@ export class JitCompiler implements Compiler { }); } - hasAotSummary(ref: Type) { return !!this._summaryResolver.resolveSummary(ref); } + hasAotSummary(ref: Type) { return !!this._summaryResolver.resolveSummary(ref); } private _filterJitIdentifiers(ids: CompileIdentifierMetadata[]): any[] { return ids.map(mod => mod.reference).filter((ref) => !this.hasAotSummary(ref)); } - private _compileModuleAndComponents(moduleType: Type, isSync: boolean): - SyncAsync> { + private _compileModuleAndComponents(moduleType: Type, isSync: boolean): SyncAsync { return SyncAsync.then(this._loadModules(moduleType, isSync), () => { this._compileComponents(moduleType, null); return this._compileModule(moduleType); }); } - private _compileModuleAndAllComponents(moduleType: Type, isSync: boolean): - SyncAsync> { + private _compileModuleAndAllComponents(moduleType: Type, isSync: boolean): + SyncAsync { return SyncAsync.then(this._loadModules(moduleType, isSync), () => { - const componentFactories: ComponentFactory[] = []; + const componentFactories: object[] = []; this._compileComponents(moduleType, componentFactories); - return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories); + return { + ngModuleFactory: this._compileModule(moduleType), + componentFactories: componentFactories + }; }); } @@ -134,22 +135,16 @@ export class JitCompiler implements Compiler { return SyncAsync.all(loading); } - private _compileModule(moduleType: Type): NgModuleFactory { + private _compileModule(moduleType: Type): object { let ngModuleFactory = this._compiledNgModuleCache.get(moduleType) !; if (!ngModuleFactory) { const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType) !; // Always provide a bound Compiler - const extraProviders = [this._metadataResolver.getProviderMetadata(new ProviderMeta( - Compiler, {useFactory: () => new ModuleBoundCompiler(this, moduleMeta.type.reference)}))]; + const extraProviders = this.getExtraNgModuleProviders(moduleMeta.type.reference); const outputCtx = createOutputContext(); const compileResult = this._ngModuleCompiler.compile(outputCtx, moduleMeta, extraProviders); - if (!this._compilerConfig.useJit) { - ngModuleFactory = - interpretStatements(outputCtx.statements)[compileResult.ngModuleFactoryVar]; - } else { - ngModuleFactory = jitStatements( - ngModuleJitUrl(moduleMeta), outputCtx.statements, )[compileResult.ngModuleFactoryVar]; - } + ngModuleFactory = this._interpretOrJit( + ngModuleJitUrl(moduleMeta), outputCtx.statements)[compileResult.ngModuleFactoryVar]; this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory); } return ngModuleFactory; @@ -158,7 +153,7 @@ export class JitCompiler implements Compiler { /** * @internal */ - _compileComponents(mainModule: Type, allComponentFactories: ComponentFactory[]|null) { + _compileComponents(mainModule: Type, allComponentFactories: object[]|null) { const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule) !; const moduleByJitDirective = new Map(); const templates = new Set(); @@ -175,7 +170,7 @@ export class JitCompiler implements Compiler { const template = this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta); templates.add(template); - allComponentFactories.push(>dirMeta.componentFactory); + allComponentFactories.push(dirMeta.componentFactory as object); } } }); @@ -203,7 +198,7 @@ export class JitCompiler implements Compiler { templates.forEach((template) => this._compileTemplate(template)); } - clearCacheFor(type: Type) { + clearCacheFor(type: Type) { this._compiledNgModuleCache.delete(type); this._metadataResolver.clearCacheFor(type); this._compiledHostTemplateCache.delete(type); @@ -220,7 +215,7 @@ export class JitCompiler implements Compiler { this._compiledNgModuleCache.clear(); } - private _createCompiledHostTemplate(compType: Type, ngModule: CompileNgModuleMetadata): + private _createCompiledHostTemplate(compType: Type, ngModule: CompileNgModuleMetadata): CompiledTemplate { if (!ngModule) { throw new Error( @@ -231,10 +226,9 @@ export class JitCompiler implements Compiler { const compMeta = this._metadataResolver.getDirectiveMetadata(compType); assertComponent(compMeta); - const componentFactory = >compMeta.componentFactory; const hostClass = this._metadataResolver.getHostComponentType(compType); const hostMeta = createHostComponentMeta( - hostClass, compMeta, getComponentViewDefinitionFactory(componentFactory)); + hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory); compiledTemplate = new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]); this._compiledHostTemplateCache.set(compType, compiledTemplate); @@ -262,6 +256,7 @@ export class JitCompiler implements Compiler { const externalStylesheetsByModuleUrl = new Map(); const outputContext = createOutputContext(); const componentStylesheet = this._styleCompiler.compileComponent(outputContext, compMeta); + const preserveWhitespaces = compMeta !.template !.preserveWhitespaces; compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => { const compiledStylesheet = this._styleCompiler.compileStyles(createOutputContext(), compMeta, stylesheetMeta); @@ -274,17 +269,13 @@ export class JitCompiler implements Compiler { pipe => this._metadataResolver.getPipeSummary(pipe.reference)); const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse( compMeta, compMeta.template !.template !, directives, pipes, template.ngModule.schemas, - templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template !)); + templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template !), + preserveWhitespaces); const compileResult = this._viewCompiler.compileComponent( outputContext, compMeta, parsedTemplate, ir.variable(componentStylesheet.stylesVar), usedPipes); - let evalResult: any; - if (!this._compilerConfig.useJit) { - evalResult = interpretStatements(outputContext.statements); - } else { - evalResult = jitStatements( - templateJitUrl(template.ngModule.type, template.compMeta), outputContext.statements); - } + const evalResult = this._interpretOrJit( + templateJitUrl(template.ngModule.type, template.compMeta), outputContext.statements); const viewClass = evalResult[compileResult.viewClassVar]; const rendererType = evalResult[compileResult.rendererTypeVar]; template.compiled(viewClass, rendererType); @@ -304,12 +295,16 @@ export class JitCompiler implements Compiler { result: CompiledStylesheet, externalStylesheetsByModuleUrl: Map): string[] { this._resolveStylesCompileResult(result, externalStylesheetsByModuleUrl); + return this._interpretOrJit( + sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), + result.outputCtx.statements)[result.stylesVar]; + } + + private _interpretOrJit(sourceUrl: string, statements: ir.Statement[]): any { if (!this._compilerConfig.useJit) { - return interpretStatements(result.outputCtx.statements)[result.stylesVar]; + return interpretStatements(statements, this._reflector); } else { - return jitStatements( - sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), - result.outputCtx.statements)[result.stylesVar]; + return jitStatements(sourceUrl, statements, this._reflector, this._compilerConfig.jitDevMode); } } } @@ -340,46 +335,6 @@ function assertComponent(meta: CompileDirectiveMetadata) { } } -/** - * Implements `Compiler` by delegating to the JitCompiler using a known module. - */ -class ModuleBoundCompiler implements Compiler { - constructor(private _delegate: JitCompiler, private _ngModule: Type) {} - - get _injector(): Injector { return this._delegate.injector; } - - compileModuleSync(moduleType: Type): NgModuleFactory { - return this._delegate.compileModuleSync(moduleType); - } - - compileModuleAsync(moduleType: Type): Promise> { - return this._delegate.compileModuleAsync(moduleType); - } - compileModuleAndAllComponentsSync(moduleType: Type): ModuleWithComponentFactories { - return this._delegate.compileModuleAndAllComponentsSync(moduleType); - } - - compileModuleAndAllComponentsAsync(moduleType: Type): - Promise> { - return this._delegate.compileModuleAndAllComponentsAsync(moduleType); - } - - getNgContentSelectors(component: Type): string[] { - return this._delegate.getNgContentSelectors(component); - } - - /** - * Clears all caches - */ - clearCache(): void { this._delegate.clearCache(); } - - /** - * Clears the cache for the given component/ngModule. - */ - clearCacheFor(type: Type) { this._delegate.clearCacheFor(type); } -} - - function flattenSummaries(fn: () => any[], out: CompileTypeSummary[] = []): CompileTypeSummary[] { fn().forEach((entry) => { if (typeof entry === 'function') { diff --git a/packages/compiler/src/jit/jit_reflector.ts b/packages/compiler/src/jit/jit_reflector.ts deleted file mode 100644 index 2fd8a31c01..0000000000 --- a/packages/compiler/src/jit/jit_reflector.ts +++ /dev/null @@ -1,46 +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 {Component, ɵReflectionCapabilities as ReflectionCapabilities, ɵstringify as stringify} from '@angular/core'; - -import {CompileReflector} from '../compile_reflector'; -import * as o from '../output/output_ast'; -import {getUrlScheme} from '../url_resolver'; -import {MODULE_SUFFIX, ValueTransformer, noUndefined, syntaxError, visitValue} from '../util'; - -export class JitReflector implements CompileReflector { - private reflectionCapabilities: ReflectionCapabilities; - constructor() { this.reflectionCapabilities = new ReflectionCapabilities(); } - componentModuleUrl(type: any, cmpMetadata: Component): string { - const moduleId = cmpMetadata.moduleId; - - if (typeof moduleId === 'string') { - const scheme = getUrlScheme(moduleId); - return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`; - } else if (moduleId !== null && moduleId !== void 0) { - throw syntaxError( - `moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` + - `If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`); - } - - return `./${stringify(type)}`; - } - parameters(typeOrFunc: /*Type*/ any): any[][] { - return this.reflectionCapabilities.parameters(typeOrFunc); - } - annotations(typeOrFunc: /*Type*/ any): any[] { - return this.reflectionCapabilities.annotations(typeOrFunc); - } - propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]} { - return this.reflectionCapabilities.propMetadata(typeOrFunc); - } - hasLifecycleHook(type: any, lcProperty: string): boolean { - return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty); - } - resolveExternalReference(ref: o.ExternalReference): any { return ref.runtime; } -} \ No newline at end of file diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts index 5efad1d1d5..3392966529 100644 --- a/packages/compiler/src/metadata_resolver.ts +++ b/packages/compiler/src/metadata_resolver.ts @@ -6,27 +6,26 @@ * found in the LICENSE file at https://angular.io/license */ -import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵisPromise as isPromise, ɵstringify as stringify} from '@angular/core'; - import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol'; import {ngfactoryFilePath} from './aot/util'; import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions'; import * as cpl from './compile_metadata'; import {CompileReflector} from './compile_reflector'; import {CompilerConfig} from './config'; +import {ChangeDetectionStrategy, Component, Directive, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createOptional, createSelf, createSkipSelf} from './core'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveResolver} from './directive_resolver'; import {Identifiers} from './identifiers'; -import {CompilerInjectable} from './injectable'; import {getAllLifecycleHooks} from './lifecycle_reflector'; import {NgModuleResolver} from './ng_module_resolver'; import {PipeResolver} from './pipe_resolver'; import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {SummaryResolver} from './summary_resolver'; -import {SyncAsync, ValueTransformer, noUndefined, syntaxError, visitValue} from './util'; +import {Console, SyncAsync, ValueTransformer, isPromise, noUndefined, resolveForwardRef, stringify, syntaxError, visitValue} from './util'; export type ErrorCollector = (error: any, type?: any) => void; -export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector'); + +export const ERROR_COMPONENT_TYPE = 'ngComponentType'; // Design notes: // - don't lazily create metadata: @@ -35,15 +34,14 @@ export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector'); // But we want to report errors even when the async work is // not required to check that the user would have been able // to wait correctly. -@CompilerInjectable() export class CompileMetadataResolver { private _nonNormalizedDirectiveCache = - new Map, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>(); - private _directiveCache = new Map, cpl.CompileDirectiveMetadata>(); - private _summaryCache = new Map, cpl.CompileTypeSummary|null>(); - private _pipeCache = new Map, cpl.CompilePipeMetadata>(); - private _ngModuleCache = new Map, cpl.CompileNgModuleMetadata>(); - private _ngModuleOfTypes = new Map, Type>(); + new Map(); + private _directiveCache = new Map(); + private _summaryCache = new Map(); + private _pipeCache = new Map(); + private _ngModuleCache = new Map(); + private _ngModuleOfTypes = new Map(); constructor( private _config: CompilerConfig, private _ngModuleResolver: NgModuleResolver, @@ -51,13 +49,12 @@ export class CompileMetadataResolver { private _summaryResolver: SummaryResolver, private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _console: Console, - @Optional() private _staticSymbolCache: StaticSymbolCache, - private _reflector: CompileReflector, - @Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {} + private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector, + private _errorCollector?: ErrorCollector) {} getReflector(): CompileReflector { return this._reflector; } - clearCacheFor(type: Type) { + clearCacheFor(type: Type) { const dirMeta = this._directiveCache.get(type); this._directiveCache.delete(type); this._nonNormalizedDirectiveCache.delete(type); @@ -115,7 +112,7 @@ export class CompileMetadataResolver { return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType)); } - getHostComponentType(dirType: any): StaticSymbol|Type { + getHostComponentType(dirType: any): StaticSymbol|Type { const name = `${cpl.identifierName({reference: dirType})}_Host`; if (dirType instanceof StaticSymbol) { return this._staticSymbolCache.get(dirType.filePath, name); @@ -127,7 +124,7 @@ export class CompileMetadataResolver { } } - private getRendererType(dirType: any): StaticSymbol|RendererType2 { + private getRendererType(dirType: any): StaticSymbol|object { if (dirType instanceof StaticSymbol) { return this._staticSymbolCache.get( ngfactoryFilePath(dirType.filePath), cpl.rendererTypeName(dirType)); @@ -140,7 +137,7 @@ export class CompileMetadataResolver { private getComponentFactory( selector: string, dirType: any, inputs: {[key: string]: string}|null, - outputs: {[key: string]: string}): StaticSymbol|ComponentFactory { + outputs: {[key: string]: string}): StaticSymbol|object { if (dirType instanceof StaticSymbol) { return this._staticSymbolCache.get( ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); @@ -148,14 +145,15 @@ export class CompileMetadataResolver { const hostView = this.getHostComponentViewClass(dirType); // Note: ngContentSelectors will be filled later once the template is // loaded. + const createComponentFactory = + this._reflector.resolveExternalReference(Identifiers.createComponentFactory); return createComponentFactory(selector, dirType, hostView, inputs, outputs, []); } } - private initComponentFactory( - factory: StaticSymbol|ComponentFactory, ngContentSelectors: string[]) { + private initComponentFactory(factory: StaticSymbol|object, ngContentSelectors: string[]) { if (!(factory instanceof StaticSymbol)) { - factory.ngContentSelectors.push(...ngContentSelectors); + (factory as any).ngContentSelectors.push(...ngContentSelectors); } } @@ -219,7 +217,8 @@ export class CompileMetadataResolver { styles: template.styles, styleUrls: template.styleUrls, animations: template.animations, - interpolation: template.interpolation + interpolation: template.interpolation, + preserveWhitespaces: template.preserveWhitespaces }); if (isPromise(templateMeta) && isSync) { this._reportError(componentStillLoadingError(directiveType), directiveType); @@ -249,25 +248,27 @@ export class CompileMetadataResolver { } let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata = undefined !; - if (dirMeta instanceof Component) { + if (createComponent.isTypeOf(dirMeta)) { // component - assertArrayOfStrings('styles', dirMeta.styles); - assertArrayOfStrings('styleUrls', dirMeta.styleUrls); - assertInterpolationSymbols('interpolation', dirMeta.interpolation); + const compMeta = dirMeta as Component; + assertArrayOfStrings('styles', compMeta.styles); + assertArrayOfStrings('styleUrls', compMeta.styleUrls); + assertInterpolationSymbols('interpolation', compMeta.interpolation); - const animations = dirMeta.animations; + const animations = compMeta.animations; nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({ - encapsulation: noUndefined(dirMeta.encapsulation), - template: noUndefined(dirMeta.template), - templateUrl: noUndefined(dirMeta.templateUrl), - styles: dirMeta.styles || [], - styleUrls: dirMeta.styleUrls || [], + encapsulation: noUndefined(compMeta.encapsulation), + template: noUndefined(compMeta.template), + templateUrl: noUndefined(compMeta.templateUrl), + styles: compMeta.styles || [], + styleUrls: compMeta.styleUrls || [], animations: animations || [], - interpolation: noUndefined(dirMeta.interpolation), - isInline: !!dirMeta.template, + interpolation: noUndefined(compMeta.interpolation), + isInline: !!compMeta.template, externalStylesheets: [], - ngContentSelectors: [] + ngContentSelectors: [], + preserveWhitespaces: noUndefined(dirMeta.preserveWhitespaces), }); } @@ -276,16 +277,17 @@ export class CompileMetadataResolver { let entryComponentMetadata: cpl.CompileEntryComponentMetadata[] = []; let selector = dirMeta.selector; - if (dirMeta instanceof Component) { + if (createComponent.isTypeOf(dirMeta)) { // Component - changeDetectionStrategy = dirMeta.changeDetection !; - if (dirMeta.viewProviders) { + const compMeta = dirMeta as Component; + changeDetectionStrategy = compMeta.changeDetection !; + if (compMeta.viewProviders) { viewProviders = this._getProvidersMetadata( - dirMeta.viewProviders, entryComponentMetadata, + compMeta.viewProviders, entryComponentMetadata, `viewProviders for "${stringifyType(directiveType)}"`, [], directiveType); } - if (dirMeta.entryComponents) { - entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents) + if (compMeta.entryComponents) { + entryComponentMetadata = flattenAndDedupeArray(compMeta.entryComponents) .map((type) => this._getEntryComponentMetadata(type) !) .concat(entryComponentMetadata); } @@ -442,7 +444,7 @@ export class CompileMetadataResolver { if (meta.imports) { flattenAndDedupeArray(meta.imports).forEach((importedType) => { - let importedModuleType: Type = undefined !; + let importedModuleType: Type = undefined !; if (isValidType(importedType)) { importedModuleType = importedType; } else if (importedType && importedType.ngModule) { @@ -601,7 +603,7 @@ export class CompileMetadataResolver { return compileMeta; } - private _checkSelfImport(moduleType: Type, importedModuleType: Type): boolean { + private _checkSelfImport(moduleType: Type, importedModuleType: Type): boolean { if (moduleType === importedModuleType) { this._reportError( syntaxError(`'${stringifyType(moduleType)}' module can't import itself`), moduleType); @@ -610,7 +612,7 @@ export class CompileMetadataResolver { return false; } - private _getTypeDescriptor(type: Type): string { + private _getTypeDescriptor(type: Type): string { if (this.isDirective(type)) { return 'directive'; } @@ -631,7 +633,7 @@ export class CompileMetadataResolver { } - private _addTypeToModule(type: Type, moduleType: Type) { + private _addTypeToModule(type: Type, moduleType: Type) { const oldModule = this._ngModuleOfTypes.get(type); if (oldModule && oldModule !== moduleType) { this._reportError( @@ -683,16 +685,14 @@ export class CompileMetadataResolver { return result; } - private _getIdentifierMetadata(type: Type): cpl.CompileIdentifierMetadata { + private _getIdentifierMetadata(type: Type): cpl.CompileIdentifierMetadata { type = resolveForwardRef(type); return {reference: type}; } isInjectable(type: any): boolean { const annotations = this._reflector.annotations(type); - // Note: We need an exact check here as @Component / @Directive / ... inherit - // from @CompilerInjectable! - return annotations.some(ann => ann.constructor === Injectable); + return annotations.some(ann => createInjectable.isTypeOf(ann)); } getInjectableSummary(type: any): cpl.CompileTypeSummary { @@ -702,7 +702,7 @@ export class CompileMetadataResolver { }; } - private _getInjectableMetadata(type: Type, dependencies: any[]|null = null): + private _getInjectableMetadata(type: Type, dependencies: any[]|null = null): cpl.CompileTypeMetadata { const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable); if (typeSummary) { @@ -711,9 +711,8 @@ export class CompileMetadataResolver { return this._getTypeMetadata(type, dependencies); } - private _getTypeMetadata( - type: Type, dependencies: any[]|null = null, - throwOnUnknownDeps = true): cpl.CompileTypeMetadata { + private _getTypeMetadata(type: Type, dependencies: any[]|null = null, throwOnUnknownDeps = true): + cpl.CompileTypeMetadata { const identifier = this._getIdentifierMetadata(type); return { reference: identifier.reference, @@ -778,7 +777,7 @@ export class CompileMetadataResolver { } private _getDependenciesMetadata( - typeOrFunc: Type|Function, dependencies: any[]|null, + typeOrFunc: Type|Function, dependencies: any[]|null, throwOnUnknownDeps = true): cpl.CompileDiDependencyMetadata[] { let hasUnknownDeps = false; const params = dependencies || this._reflector.parameters(typeOrFunc) || []; @@ -792,20 +791,21 @@ export class CompileMetadataResolver { let token: any = null; if (Array.isArray(param)) { param.forEach((paramEntry) => { - if (paramEntry instanceof Host) { + if (createHost.isTypeOf(paramEntry)) { isHost = true; - } else if (paramEntry instanceof Self) { + } else if (createSelf.isTypeOf(paramEntry)) { isSelf = true; - } else if (paramEntry instanceof SkipSelf) { + } else if (createSkipSelf.isTypeOf(paramEntry)) { isSkipSelf = true; - } else if (paramEntry instanceof Optional) { + } else if (createOptional.isTypeOf(paramEntry)) { isOptional = true; - } else if (paramEntry instanceof Attribute) { + } else if (createAttribute.isTypeOf(paramEntry)) { isAttribute = true; token = paramEntry.attributeName; - } else if (paramEntry instanceof Inject) { + } else if (createInject.isTypeOf(paramEntry)) { token = paramEntry.token; - } else if (paramEntry instanceof InjectionToken) { + } else if ( + createInjectionToken.isTypeOf(paramEntry) || paramEntry instanceof StaticSymbol) { token = paramEntry; } else if (isValidType(paramEntry) && token == null) { token = paramEntry; @@ -993,7 +993,7 @@ export class CompileMetadataResolver { private _getQueriesMetadata( queries: {[key: string]: Query}, isViewQuery: boolean, - directiveType: Type): cpl.CompileQueryMetadata[] { + directiveType: Type): cpl.CompileQueryMetadata[] { const res: cpl.CompileQueryMetadata[] = []; Object.keys(queries).forEach((propertyName: string) => { @@ -1008,7 +1008,7 @@ export class CompileMetadataResolver { private _queryVarBindings(selector: any): string[] { return selector.split(/\s*,\s*/); } - private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type|Function): + private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type|Function): cpl.CompileQueryMetadata { let selectors: cpl.CompileTokenMetadata[]; if (typeof q.selector === 'string') { @@ -1096,9 +1096,9 @@ function stringifyType(type: any): string { /** * Indicates that a component is still being loaded in a synchronous compile. */ -function componentStillLoadingError(compType: Type) { +function componentStillLoadingError(compType: Type) { const error = Error(`Can't compile synchronously as ${stringify(compType)} is still being loaded!`); - (error as any)[ɵERROR_COMPONENT_TYPE] = compType; + (error as any)[ERROR_COMPONENT_TYPE] = compType; return error; } diff --git a/packages/compiler/src/ml_parser/html_parser.ts b/packages/compiler/src/ml_parser/html_parser.ts index ad69f6c2d9..60702f25b0 100644 --- a/packages/compiler/src/ml_parser/html_parser.ts +++ b/packages/compiler/src/ml_parser/html_parser.ts @@ -6,15 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompilerInjectable} from '../injectable'; - import {getHtmlTagDefinition} from './html_tags'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config'; import {ParseTreeResult, Parser} from './parser'; export {ParseTreeResult, TreeError} from './parser'; -@CompilerInjectable() export class HtmlParser extends Parser { constructor() { super(getHtmlTagDefinition); } diff --git a/packages/compiler/src/ml_parser/html_whitespaces.ts b/packages/compiler/src/ml_parser/html_whitespaces.ts new file mode 100644 index 0000000000..73e5b48665 --- /dev/null +++ b/packages/compiler/src/ml_parser/html_whitespaces.ts @@ -0,0 +1,86 @@ +/** + * @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 * as html from './ast'; +import {ParseTreeResult} from './parser'; +import {NGSP_UNICODE} from './tags'; + +export const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces'; + +const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']); + +function hasPreserveWhitespacesAttr(attrs: html.Attribute[]): boolean { + return attrs.some((attr: html.Attribute) => attr.name === PRESERVE_WS_ATTR_NAME); +} + +/** + * Angular Dart introduced &ngsp; as a placeholder for non-removable space, see: + * https://github.com/dart-lang/angular/blob/0bb611387d29d65b5af7f9d2515ab571fd3fbee4/_tests/test/compiler/preserve_whitespace_test.dart#L25-L32 + * In Angular Dart &ngsp; is converted to the 0xE500 PUA (Private Use Areas) unicode character + * and later on replaced by a space. We are re-implementing the same idea here. + */ +export function replaceNgsp(value: string): string { + // lexer is replacing the &ngsp; pseudo-entity with NGSP_UNICODE + return value.replace(new RegExp(NGSP_UNICODE, 'g'), ' '); +} + +/** + * This visitor can walk HTML parse tree and remove / trim text nodes using the following rules: + * - consider spaces, tabs and new lines as whitespace characters; + * - drop text nodes consisting of whitespace characters only; + * - for all other text nodes replace consecutive whitespace characters with one space; + * - convert &ngsp; pseudo-entity to a single space; + * + * Removal and trimming of whitespaces have positive performance impact (less code to generate + * while compiling templates, faster view creation). At the same time it can be "destructive" + * in some cases (whitespaces can influence layout). Because of the potential of breaking layout + * this visitor is not activated by default in Angular 5 and people need to explicitly opt-in for + * whitespace removal. The default option for whitespace removal will be revisited in Angular 6 + * and might be changed to "on" by default. + */ +class WhitespaceVisitor implements html.Visitor { + visitElement(element: html.Element, context: any): any { + if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) { + // don't descent into elements where we need to preserve whitespaces + // but still visit all attributes to eliminate one used as a market to preserve WS + return new html.Element( + element.name, html.visitAll(this, element.attrs), element.children, element.sourceSpan, + element.startSourceSpan, element.endSourceSpan); + } + + return new html.Element( + element.name, element.attrs, html.visitAll(this, element.children), element.sourceSpan, + element.startSourceSpan, element.endSourceSpan); + } + + visitAttribute(attribute: html.Attribute, context: any): any { + return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null; + } + + visitText(text: html.Text, context: any): any { + const isBlank = text.value.trim().length === 0; + + if (!isBlank) { + return new html.Text(replaceNgsp(text.value).replace(/\s\s+/g, ' '), text.sourceSpan); + } + + return null; + } + + visitComment(comment: html.Comment, context: any): any { return comment; } + + visitExpansion(expansion: html.Expansion, context: any): any { return expansion; } + + visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; } +} + +export function removeWhitespaces(htmlAstWithErrors: ParseTreeResult): ParseTreeResult { + return new ParseTreeResult( + html.visitAll(new WhitespaceVisitor(), htmlAstWithErrors.rootNodes), + htmlAstWithErrors.errors); +} diff --git a/packages/compiler/src/ml_parser/tags.ts b/packages/compiler/src/ml_parser/tags.ts index 5f06c3fd25..8fb6580d7c 100644 --- a/packages/compiler/src/ml_parser/tags.ts +++ b/packages/compiler/src/ml_parser/tags.ts @@ -71,6 +71,7 @@ export function mergeNsAndName(prefix: string, localName: string): string { // This list is not exhaustive to keep the compiler footprint low. // The `{` / `ƫ` syntax should be used when the named character reference does not // exist. + export const NAMED_ENTITIES: {[k: string]: string} = { 'Aacute': '\u00C1', 'aacute': '\u00E1', @@ -325,3 +326,9 @@ export const NAMED_ENTITIES: {[k: string]: string} = { 'zwj': '\u200D', 'zwnj': '\u200C', }; + +// The &ngsp; pseudo-entity is denoting a space. see: +// https://github.com/dart-lang/angular/blob/0bb611387d29d65b5af7f9d2515ab571fd3fbee4/_tests/test/compiler/preserve_whitespace_test.dart +export const NGSP_UNICODE = '\uE500'; + +NAMED_ENTITIES['ngsp'] = NGSP_UNICODE; diff --git a/packages/compiler/src/ng_module_compiler.ts b/packages/compiler/src/ng_module_compiler.ts index 9b576f1a25..70ba3f6f52 100644 --- a/packages/compiler/src/ng_module_compiler.ts +++ b/packages/compiler/src/ng_module_compiler.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵNodeFlags as NodeFlags} from '@angular/core'; - import {CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata'; import {CompileReflector} from './compile_reflector'; +import {NodeFlags} from './core'; import {Identifiers} from './identifiers'; -import {CompilerInjectable} from './injectable'; import * as o from './output/output_ast'; import {typeSourceSpan} from './parse_util'; import {NgModuleProviderAnalyzer} from './provider_analyzer'; @@ -24,7 +22,6 @@ export class NgModuleCompileResult { const LOG_VAR = o.variable('_l'); -@CompilerInjectable() export class NgModuleCompiler { constructor(private reflector: CompileReflector) {} compile( diff --git a/packages/compiler/src/ng_module_resolver.ts b/packages/compiler/src/ng_module_resolver.ts index 59472eeaf7..6996f376a1 100644 --- a/packages/compiler/src/ng_module_resolver.ts +++ b/packages/compiler/src/ng_module_resolver.ts @@ -6,27 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {NgModule, Type, ɵstringify as stringify} from '@angular/core'; - import {CompileReflector} from './compile_reflector'; +import {NgModule, Type, createNgModule} from './core'; import {findLast} from './directive_resolver'; -import {CompilerInjectable} from './injectable'; +import {stringify} from './util'; -function _isNgModuleMetadata(obj: any): obj is NgModule { - return obj instanceof NgModule; -} /** * Resolves types to {@link NgModule}. */ -@CompilerInjectable() export class NgModuleResolver { constructor(private _reflector: CompileReflector) {} - isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); } + isNgModule(type: any) { return this._reflector.annotations(type).some(createNgModule.isTypeOf); } - resolve(type: Type, throwIfNotFound = true): NgModule|null { - const ngModuleMeta: NgModule = findLast(this._reflector.annotations(type), _isNgModuleMetadata); + resolve(type: Type, throwIfNotFound = true): NgModule|null { + const ngModuleMeta: NgModule = + findLast(this._reflector.annotations(type), createNgModule.isTypeOf); if (ngModuleMeta) { return ngModuleMeta; diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index d98f67f4d1..4f36042f0d 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -353,7 +353,8 @@ export class ExternalExpr extends Expression { } export class ExternalReference { - constructor(public moduleName: string|null, public name: string|null, public runtime: any|null) {} + constructor(public moduleName: string|null, public name: string|null, public runtime?: any|null) { + } } export class ConditionalExpr extends Expression { diff --git a/packages/compiler/src/output/output_interpreter.ts b/packages/compiler/src/output/output_interpreter.ts index 4339c78062..3f2b134a84 100644 --- a/packages/compiler/src/output/output_interpreter.ts +++ b/packages/compiler/src/output/output_interpreter.ts @@ -8,12 +8,15 @@ +import {CompileReflector} from '../compile_reflector'; + import * as o from './output_ast'; import {debugOutputAstAsTypeScript} from './ts_emitter'; -export function interpretStatements(statements: o.Statement[]): {[key: string]: any} { +export function interpretStatements( + statements: o.Statement[], reflector: CompileReflector): {[key: string]: any} { const ctx = new _ExecutionContext(null, null, null, new Map()); - const visitor = new StatementInterpreter(); + const visitor = new StatementInterpreter(reflector); visitor.visitAllStatements(statements, ctx); const result: {[key: string]: any} = {}; ctx.exports.forEach((exportName) => { result[exportName] = ctx.vars.get(exportName); }); @@ -88,6 +91,7 @@ function createDynamicClass( } class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { + constructor(private reflector: CompileReflector) {} debugAst(ast: o.Expression|o.Statement|o.Type): string { return debugOutputAstAsTypeScript(ast); } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: _ExecutionContext): any { @@ -227,7 +231,9 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { return new clazz(...args); } visitLiteralExpr(ast: o.LiteralExpr, ctx: _ExecutionContext): any { return ast.value; } - visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { return ast.value.runtime; } + visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { + return this.reflector.resolveExternalReference(ast.value); + } visitConditionalExpr(ast: o.ConditionalExpr, ctx: _ExecutionContext): any { if (ast.condition.visitExpression(this, ctx)) { return ast.trueCase.visitExpression(this, ctx); diff --git a/packages/compiler/src/output/output_jit.ts b/packages/compiler/src/output/output_jit.ts index 0135be2096..1239c9280c 100644 --- a/packages/compiler/src/output/output_jit.ts +++ b/packages/compiler/src/output/output_jit.ts @@ -6,15 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {isDevMode} from '@angular/core'; import {identifierName} from '../compile_metadata'; +import {CompileReflector} from '../compile_reflector'; import {EmitterVisitorContext} from './abstract_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import * as o from './output_ast'; function evalExpression( - sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}): any { + sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any}, + createSourceMap: boolean): any { let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; const fnArgNames: string[] = []; const fnArgValues: any[] = []; @@ -22,7 +23,7 @@ function evalExpression( fnArgNames.push(argName); fnArgValues.push(vars[argName]); } - if (isDevMode()) { + if (createSourceMap) { // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise // E.g. ``` // function anonymous(a,b,c @@ -35,12 +36,14 @@ function evalExpression( return new Function(...fnArgNames.concat(fnBody))(...fnArgValues); } -export function jitStatements(sourceUrl: string, statements: o.Statement[]): {[key: string]: any} { - const converter = new JitEmitterVisitor(); +export function jitStatements( + sourceUrl: string, statements: o.Statement[], reflector: CompileReflector, + createSourceMaps: boolean): {[key: string]: any} { + const converter = new JitEmitterVisitor(reflector); const ctx = EmitterVisitorContext.createRoot(); converter.visitAllStatements(statements, ctx); converter.createReturnStmt(ctx); - return evalExpression(sourceUrl, ctx, converter.getArgs()); + return evalExpression(sourceUrl, ctx, converter.getArgs(), createSourceMaps); } export class JitEmitterVisitor extends AbstractJsEmitterVisitor { @@ -48,6 +51,8 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { private _evalArgValues: any[] = []; private _evalExportedVars: string[] = []; + constructor(private reflector: CompileReflector) { super(); } + createReturnStmt(ctx: EmitterVisitorContext) { const stmt = new o.ReturnStatement(new o.LiteralMapExpr(this._evalExportedVars.map( resultVar => new o.LiteralMapEntry(resultVar, o.variable(resultVar), false)))); @@ -63,12 +68,12 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { } visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { - const value = ast.value.runtime; + const value = this.reflector.resolveExternalReference(ast.value); let id = this._evalArgValues.indexOf(value); if (id === -1) { id = this._evalArgValues.length; this._evalArgValues.push(value); - const name = identifierName({reference: ast.value.runtime}) || 'val'; + const name = identifierName({reference: value}) || 'val'; this._evalArgNames.push(`jit_${name}_${id}`); } ctx.print(ast, this._evalArgNames[id]); diff --git a/packages/compiler/src/pipe_resolver.ts b/packages/compiler/src/pipe_resolver.ts index a7f36ee129..4f15e9a3ee 100644 --- a/packages/compiler/src/pipe_resolver.ts +++ b/packages/compiler/src/pipe_resolver.ts @@ -6,15 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Pipe, Type, resolveForwardRef, ɵstringify as stringify} from '@angular/core'; - import {CompileReflector} from './compile_reflector'; +import {Pipe, Type, createPipe} from './core'; import {findLast} from './directive_resolver'; -import {CompilerInjectable} from './injectable'; - -function _isPipeMetadata(type: any): boolean { - return type instanceof Pipe; -} +import {resolveForwardRef, stringify} from './util'; /** * Resolve a `Type` for {@link Pipe}. @@ -23,22 +18,21 @@ function _isPipeMetadata(type: any): boolean { * * See {@link Compiler} */ -@CompilerInjectable() export class PipeResolver { constructor(private _reflector: CompileReflector) {} - isPipe(type: Type) { + isPipe(type: Type) { const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); - return typeMetadata && typeMetadata.some(_isPipeMetadata); + return typeMetadata && typeMetadata.some(createPipe.isTypeOf); } /** * Return {@link Pipe} for a given `Type`. */ - resolve(type: Type, throwIfNotFound = true): Pipe|null { + resolve(type: Type, throwIfNotFound = true): Pipe|null { const metas = this._reflector.annotations(resolveForwardRef(type)); if (metas) { - const annotation = findLast(metas, _isPipeMetadata); + const annotation = findLast(metas, createPipe.isTypeOf); if (annotation) { return annotation; } diff --git a/packages/compiler/src/schema/dom_element_schema_registry.ts b/packages/compiler/src/schema/dom_element_schema_registry.ts index f5961ebd82..7382670a94 100644 --- a/packages/compiler/src/schema/dom_element_schema_registry.ts +++ b/packages/compiler/src/schema/dom_element_schema_registry.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; +import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '../core'; -import {CompilerInjectable} from '../injectable'; import {isNgContainer, isNgContent} from '../ml_parser/tags'; import {dashCaseToCamelCase} from '../util'; @@ -241,7 +240,6 @@ const _ATTR_TO_PROP: {[name: string]: string} = { 'tabindex': 'tabIndex', }; -@CompilerInjectable() export class DomElementSchemaRegistry extends ElementSchemaRegistry { private _schema: {[element: string]: {[property: string]: string}} = {}; diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index 81c3ed888c..0843a95840 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {SecurityContext} from '@angular/core'; +import {SecurityContext} from '../core'; // ================================================================================================= // ================================================================================================= diff --git a/packages/compiler/src/schema/element_schema_registry.ts b/packages/compiler/src/schema/element_schema_registry.ts index 3b8727b0b7..ce856f2ece 100644 --- a/packages/compiler/src/schema/element_schema_registry.ts +++ b/packages/compiler/src/schema/element_schema_registry.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {SchemaMetadata, SecurityContext} from '@angular/core'; +import {SchemaMetadata, SecurityContext} from '../core'; export abstract class ElementSchemaRegistry { abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean; diff --git a/packages/compiler/src/style_compiler.ts b/packages/compiler/src/style_compiler.ts index 71c84d25dc..59010f393e 100644 --- a/packages/compiler/src/style_compiler.ts +++ b/packages/compiler/src/style_compiler.ts @@ -6,10 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {ViewEncapsulation} from '@angular/core'; - import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; -import {CompilerInjectable} from './injectable'; +import {ViewEncapsulation} from './core'; import * as o from './output/output_ast'; import {ShadowCss} from './shadow_css'; import {UrlResolver} from './url_resolver'; @@ -31,7 +29,6 @@ export class CompiledStylesheet { public meta: CompileStylesheetMetadata) {} } -@CompilerInjectable() export class StyleCompiler { private _shadowCss: ShadowCss = new ShadowCss(); diff --git a/packages/compiler/src/style_url_resolver.ts b/packages/compiler/src/style_url_resolver.ts index 1a45ff0ddc..844871ee0a 100644 --- a/packages/compiler/src/style_url_resolver.ts +++ b/packages/compiler/src/style_url_resolver.ts @@ -29,19 +29,20 @@ export function extractStyleUrls( resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports { const foundUrls: string[] = []; - const modifiedCssText = - cssText.replace(CSS_COMMENT_REGEXP, '').replace(CSS_IMPORT_REGEXP, (...m: string[]) => { - const url = m[1] || m[2]; - if (!isStyleUrlResolvable(url)) { - // Do not attempt to resolve non-package absolute URLs with URI scheme - return m[0]; - } - foundUrls.push(resolver.resolve(baseUrl, url)); - return ''; - }); + const modifiedCssText = cssText.replace(CSS_STRIPPABLE_COMMENT_REGEXP, '') + .replace(CSS_IMPORT_REGEXP, (...m: string[]) => { + const url = m[1] || m[2]; + if (!isStyleUrlResolvable(url)) { + // Do not attempt to resolve non-package absolute URLs with URI + // scheme + return m[0]; + } + foundUrls.push(resolver.resolve(baseUrl, url)); + return ''; + }); return new StyleWithImports(modifiedCssText, foundUrls); } const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g; -const CSS_COMMENT_REGEXP = /\/\*[\s\S]+?\*\//g; +const CSS_STRIPPABLE_COMMENT_REGEXP = /\/\*(?!#\s*(?:sourceURL|sourceMappingURL)=)[\s\S]+?\*\//g; const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/; diff --git a/packages/compiler/src/summary_resolver.ts b/packages/compiler/src/summary_resolver.ts index d6246ac9ac..1c7cd7a166 100644 --- a/packages/compiler/src/summary_resolver.ts +++ b/packages/compiler/src/summary_resolver.ts @@ -5,9 +5,8 @@ * 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 {Type} from '@angular/core'; import {CompileTypeSummary} from './compile_metadata'; -import {CompilerInjectable} from './injectable'; +import {Type} from './core'; export interface Summary { symbol: T; @@ -17,23 +16,24 @@ export interface Summary { export abstract class SummaryResolver { abstract isLibraryFile(fileName: string): boolean; - abstract getLibraryFileName(fileName: string): string|null; + abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string; + abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string; abstract resolveSummary(reference: T): Summary|null; abstract getSymbolsOf(filePath: string): T[]; abstract getImportAs(reference: T): T; abstract addSummary(summary: Summary): void; } -@CompilerInjectable() -export class JitSummaryResolver implements SummaryResolver> { - private _summaries = new Map, Summary>>(); +export class JitSummaryResolver implements SummaryResolver { + private _summaries = new Map>(); - isLibraryFile(fileName: string): boolean { return false; }; - getLibraryFileName(fileName: string): string|null { return null; } - resolveSummary(reference: Type): Summary>|null { + isLibraryFile(): boolean { return false; }; + toSummaryFileName(fileName: string): string { return fileName; } + fromSummaryFileName(fileName: string): string { return fileName; } + resolveSummary(reference: Type): Summary|null { return this._summaries.get(reference) || null; }; - getSymbolsOf(filePath: string): Type[] { return []; } - getImportAs(reference: Type): Type { return reference; } - addSummary(summary: Summary>) { this._summaries.set(summary.symbol, summary); }; + getSymbolsOf(): Type[] { return []; } + getImportAs(reference: Type): Type { return reference; } + addSummary(summary: Summary) { this._summaries.set(summary.symbol, summary); }; } diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index b44bcbf8bf..ba1dc77cb1 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {SecurityContext} from '@angular/core'; - import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; +import {SecurityContext} from '../core'; import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; import {InterpolationConfig} from '../ml_parser/interpolation_config'; @@ -219,7 +218,7 @@ export class BindingParser { // This will occur when a @trigger is not paired with an expression. // For animations it is valid to not have an expression since */void // states will be applied by angular when the element is attached/detached - const ast = this._parseBinding(expression || 'null', false, sourceSpan); + const ast = this._parseBinding(expression || 'undefined', false, sourceSpan); targetMatchableAttrs.push([name, ast.source !]); targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan)); } diff --git a/packages/compiler/src/template_parser/template_ast.ts b/packages/compiler/src/template_parser/template_ast.ts index f0710f7a13..391caf6b81 100644 --- a/packages/compiler/src/template_parser/template_ast.ts +++ b/packages/compiler/src/template_parser/template_ast.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {SecurityContext} from '@angular/core'; - import {AstPath} from '../ast_path'; import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata'; +import {SecurityContext} from '../core'; import {AST} from '../expression_parser/ast'; import {LifecycleHooks} from '../lifecycle_reflector'; import {ParseSourceSpan} from '../parse_util'; diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts index 9d342c2dda..c3b53737e8 100644 --- a/packages/compiler/src/template_parser/template_parser.ts +++ b/packages/compiler/src/template_parser/template_parser.ts @@ -6,18 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, InjectionToken, Optional, SchemaMetadata, ɵConsole as Console} from '@angular/core'; - import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; import {CompilerConfig} from '../config'; +import {SchemaMetadata} from '../core'; import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; import {I18NHtmlParser} from '../i18n/i18n_html_parser'; import {Identifiers, createTokenForExternalReference, createTokenForReference} from '../identifiers'; -import {CompilerInjectable} from '../injectable'; import * as html from '../ml_parser/ast'; import {ParseTreeResult} from '../ml_parser/html_parser'; +import {removeWhitespaces, replaceNgsp} from '../ml_parser/html_whitespaces'; import {expandNodes} from '../ml_parser/icu_ast_expander'; import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {isNgTemplate, splitNsName} from '../ml_parser/tags'; @@ -26,7 +25,7 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer' import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector, SelectorMatcher} from '../selector'; import {isStyleUrlResolvable} from '../style_url_resolver'; -import {syntaxError} from '../util'; +import {Console, syntaxError} from '../util'; import {BindingParser, BoundProperty} from './binding_parser'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast'; @@ -82,15 +81,6 @@ function warnOnlyOnce(warnings: string[]): (warning: ParseError) => boolean { }; } -/** - * Provides an array of {@link TemplateAstVisitor}s which will be used to transform - * parsed templates before compilation is invoked, allowing custom expression syntax - * and other advanced transformations. - * - * This is currently an internal-only feature and not meant for general use. - */ -export const TEMPLATE_TRANSFORMS = new InjectionToken('TemplateTransforms'); - export class TemplateParseError extends ParseError { constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) { super(span, message, level); @@ -103,19 +93,19 @@ export class TemplateParseResult { public errors?: ParseError[]) {} } -@CompilerInjectable() export class TemplateParser { constructor( private _config: CompilerConfig, private _reflector: CompileReflector, private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, private _htmlParser: I18NHtmlParser, private _console: Console, - @Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {} + public transforms: TemplateAstVisitor[]) {} parse( component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[], - pipes: CompilePipeSummary[], schemas: SchemaMetadata[], - templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} { - const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl); + pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string, + preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} { + const result = this.tryParse( + component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces); const warnings = result.errors !.filter(error => error.level === ParseErrorLevel.WARNING) .filter(warnOnlyOnce( @@ -137,12 +127,17 @@ export class TemplateParser { tryParse( component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[], - pipes: CompilePipeSummary[], schemas: SchemaMetadata[], - templateUrl: string): TemplateParseResult { + pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string, + preserveWhitespaces: boolean): TemplateParseResult { + let htmlParseResult = this._htmlParser !.parse( + template, templateUrl, true, this.getInterpolationConfig(component)); + + if (!preserveWhitespaces) { + htmlParseResult = removeWhitespaces(htmlParseResult); + } + return this.tryParseHtml( - this.expandHtml(this._htmlParser !.parse( - template, templateUrl, true, this.getInterpolationConfig(component))), - component, directives, pipes, schemas); + this.expandHtml(htmlParseResult), component, directives, pipes, schemas); } tryParseHtml( @@ -253,9 +248,10 @@ class TemplateParseVisitor implements html.Visitor { visitText(text: html.Text, parent: ElementContext): any { const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !; - const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan !); + const valueNoNgsp = replaceNgsp(text.value); + const expr = this._bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan !); return expr ? new BoundTextAst(expr, ngContentIndex, text.sourceSpan !) : - new TextAst(text.value, ngContentIndex, text.sourceSpan !); + new TextAst(valueNoNgsp, ngContentIndex, text.sourceSpan !); } visitAttribute(attribute: html.Attribute, context: any): any { @@ -573,7 +569,7 @@ class TemplateParseVisitor implements html.Visitor { directive.inputs, props, directiveProperties, targetBoundDirectivePropNames); elementOrDirectiveRefs.forEach((elOrDirRef) => { if ((elOrDirRef.value.length === 0 && directive.isComponent) || - (directive.exportAs == elOrDirRef.value)) { + (elOrDirRef.isReferenceToDirective(directive))) { targetReferences.push(new ReferenceAst( elOrDirRef.name, createTokenForReference(directive.type.reference), elOrDirRef.sourceSpan)); @@ -798,8 +794,25 @@ class NonBindableVisitor implements html.Visitor { visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; } } +/** + * A reference to an element or directive in a template. E.g., the reference in this template: + * + *
+ * + * would be {name: 'myMenu', value: 'coolMenu', sourceSpan: ...} + */ class ElementOrDirectiveRef { constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} + + /** Gets whether this is a reference to the given directive. */ + isReferenceToDirective(directive: CompileDirectiveSummary) { + return splitExportAs(directive.exportAs).indexOf(this.value) !== -1; + } +} + +/** Splits a raw, potentially comma-delimted `exportAs` value into an array of names. */ +function splitExportAs(exportAs: string | null): string[] { + return exportAs ? exportAs.split(',').map(e => e.trim()) : []; } export function splitClasses(classAttrValue: string): string[] { diff --git a/packages/compiler/src/url_resolver.ts b/packages/compiler/src/url_resolver.ts index b72fb3a2c9..3f819309c0 100644 --- a/packages/compiler/src/url_resolver.ts +++ b/packages/compiler/src/url_resolver.ts @@ -6,12 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, InjectionToken, PACKAGE_ROOT_URL} from '@angular/core'; - -import {CompilerInjectable} from './injectable'; - - - /** * Create a {@link UrlResolver} with no package prefix. */ @@ -23,14 +17,6 @@ export function createOfflineCompileUrlResolver(): UrlResolver { return new UrlResolver('.'); } -/** - * A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'. - */ -export const DEFAULT_PACKAGE_URL_PROVIDER = { - provide: PACKAGE_ROOT_URL, - useValue: '/' -}; - /** * Used by the {@link Compiler} when resolving HTML and CSS template URLs. * @@ -47,9 +33,12 @@ export const DEFAULT_PACKAGE_URL_PROVIDER = { * Attacker-controlled data introduced by a template could expose your * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). */ -@CompilerInjectable() -export class UrlResolver { - constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string|null = null) {} +export interface UrlResolver { resolve(baseUrl: string, url: string): string; } + +export interface UrlResolverCtor { new (packagePrefix?: string|null): UrlResolver; } + +export const UrlResolver: UrlResolverCtor = class UrlResolverImpl { + constructor(private _packagePrefix: string|null = null) {} /** * Resolves the `url` given the `baseUrl`: @@ -75,7 +64,7 @@ export class UrlResolver { } return resolvedUrl; } -} +}; /** * Extract the scheme of a URL. diff --git a/packages/compiler/src/util.ts b/packages/compiler/src/util.ts index 8692eda3a3..8ca94c62f8 100644 --- a/packages/compiler/src/util.ts +++ b/packages/compiler/src/util.ts @@ -6,13 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵisPromise as isPromise} from '@angular/core'; - import * as o from './output/output_ast'; import {ParseError} from './parse_util'; -export const MODULE_SUFFIX = ''; - const CAMEL_CASE_REGEXP = /([A-Z])/g; const DASH_CASE_REGEXP = /-+([a-z0-9])/g; @@ -163,3 +159,69 @@ export interface OutputContext { statements: o.Statement[]; importExpr(reference: any, typeParams?: o.Type[]|null): o.Expression; } + +export function stringify(token: any): string { + if (typeof token === 'string') { + return token; + } + + if (token instanceof Array) { + return '[' + token.map(stringify).join(', ') + ']'; + } + + if (token == null) { + return '' + token; + } + + if (token.overriddenName) { + return `${token.overriddenName}`; + } + + if (token.name) { + return `${token.name}`; + } + + const res = token.toString(); + + if (res == null) { + return '' + res; + } + + const newLineIndex = res.indexOf('\n'); + return newLineIndex === -1 ? res : res.substring(0, newLineIndex); +} + +/** + * Lazily retrieves the reference value from a forwardRef. + */ +export function resolveForwardRef(type: any): any { + if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__')) { + return type(); + } else { + return type; + } +} + +/** + * Determine if the argument is shaped like a Promise + */ +export function isPromise(obj: any): obj is Promise { + // allow any Promise/A+ compliant thenable. + // It's up to the caller to ensure that obj.then conforms to the spec + return !!obj && typeof obj.then === 'function'; +} + +export class Version { + constructor(public full: string) {} + + get major(): string { return this.full.split('.')[0]; } + + get minor(): string { return this.full.split('.')[1]; } + + get patch(): string { return this.full.split('.').slice(2).join('.'); } +} + +export interface Console { + log(message: string): void; + warn(message: string): void; +} diff --git a/packages/compiler/src/version.ts b/packages/compiler/src/version.ts index 19db5be5a5..e7ed88caf3 100644 --- a/packages/compiler/src/version.ts +++ b/packages/compiler/src/version.ts @@ -12,7 +12,7 @@ * Entry point for all public APIs of the common package. */ -import {Version} from '@angular/core'; +import {Version} from './util'; /** * @stable */ diff --git a/packages/compiler/src/view_compiler/provider_compiler.ts b/packages/compiler/src/view_compiler/provider_compiler.ts index e99578b712..0babaf57bc 100644 --- a/packages/compiler/src/view_compiler/provider_compiler.ts +++ b/packages/compiler/src/view_compiler/provider_compiler.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags} from '@angular/core'; - import {CompileDiDependencyMetadata, CompileEntryComponentMetadata, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; +import {DepFlags, NodeFlags} from '../core'; import {Identifiers, createTokenForExternalReference} from '../identifiers'; import {LifecycleHooks} from '../lifecycle_reflector'; import * as o from '../output/output_ast'; diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts index f0dca508b5..fbf3d80710 100644 --- a/packages/compiler/src/view_compiler/view_compiler.ts +++ b/packages/compiler/src/view_compiler/view_compiler.ts @@ -6,15 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ɵArgumentType as ArgumentType, ɵBindingFlags as BindingFlags, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵQueryBindingType as QueryBindingType, ɵQueryValueType as QueryValueType, ɵViewFlags as ViewFlags, ɵelementEventFullName as elementEventFullName} from '@angular/core'; - import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, CompileTypeMetadata, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {CompilerConfig} from '../config'; +import {ArgumentType, BindingFlags, ChangeDetectionStrategy, DepFlags, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core'; import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {Identifiers} from '../identifiers'; -import {CompilerInjectable} from '../injectable'; import {LifecycleHooks} from '../lifecycle_reflector'; import {isNgContainer} from '../ml_parser/tags'; import * as o from '../output/output_ast'; @@ -35,7 +33,6 @@ export class ViewCompileResult { constructor(public viewClassVar: string, public rendererTypeVar: string) {} } -@CompilerInjectable() export class ViewCompiler { constructor( private _config: CompilerConfig, private _reflector: CompileReflector, @@ -1072,3 +1069,7 @@ function calcStaticDynamicQueryFlags( } return flags; } + +export function elementEventFullName(target: string | null, name: string): string { + return target ? `${target}:${name}` : name; +} diff --git a/packages/compiler/test/aot/compiler_spec.ts b/packages/compiler/test/aot/compiler_spec.ts index a515126e51..fe081e3f0a 100644 --- a/packages/compiler/test/aot/compiler_spec.ts +++ b/packages/compiler/test/aot/compiler_spec.ts @@ -459,6 +459,11 @@ describe('compiler (unbundled Angular)', () => { }); describe('inheritance with summaries', () => { + let angularSummaryFiles: MockDirectory; + beforeEach(() => { + angularSummaryFiles = compile(angularFiles, {useSummaries: false, emit: true}).outDir; + }); + function compileParentAndChild( {parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: { parentClassDecorator: string, @@ -494,8 +499,8 @@ describe('compiler (unbundled Angular)', () => { } }; - const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true}); - const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true}); + const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); + const {genFiles} = compile([libOutDir, appInput, angularSummaryFiles], {useSummaries: true}); return genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); } @@ -529,8 +534,8 @@ describe('compiler (unbundled Angular)', () => { } }; - const {outDir: libOutDir} = compile([libInput, angularFiles], {useSummaries: true}); - const {genFiles} = compile([libOutDir, appInput, angularFiles], {useSummaries: true}); + const {outDir: libOutDir} = compile([libInput, angularSummaryFiles], {useSummaries: true}); + const {genFiles} = compile([libOutDir, appInput, angularSummaryFiles], {useSummaries: true}); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; expect(toTypeScript(mainNgFactory)) @@ -578,10 +583,12 @@ describe('compiler (unbundled Angular)', () => { ` } }; - const {outDir: lib1OutDir} = compile([lib1Input, angularFiles], {useSummaries: true}); + const {outDir: lib1OutDir} = + compile([lib1Input, angularSummaryFiles], {useSummaries: true}); const {outDir: lib2OutDir} = - compile([lib1OutDir, lib2Input, angularFiles], {useSummaries: true}); - const {genFiles} = compile([lib2OutDir, appInput, angularFiles], {useSummaries: true}); + compile([lib1OutDir, lib2Input, angularSummaryFiles], {useSummaries: true}); + const {genFiles} = + compile([lib2OutDir, appInput, angularSummaryFiles], {useSummaries: true}); const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'); const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy; expect(toTypeScript(mainNgFactory)) diff --git a/packages/compiler/test/aot/jit_summaries_spec.ts b/packages/compiler/test/aot/jit_summaries_spec.ts index 0bf7643a61..dd63c3c411 100644 --- a/packages/compiler/test/aot/jit_summaries_spec.ts +++ b/packages/compiler/test/aot/jit_summaries_spec.ts @@ -12,10 +12,17 @@ import {MockDirectory, compile, setup} from './test_util'; describe('aot summaries for jit', () => { let angularFiles = setup(); + let angularSummaryFiles: MockDirectory; + + beforeEach(() => { + angularSummaryFiles = compile(angularFiles, {useSummaries: false, emit: true}).outDir; + }); function compileApp(rootDir: MockDirectory, options: {useSummaries?: boolean} = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} { - return compile([rootDir, angularFiles], {...options, enableSummariesForJit: true}); + return compile( + [rootDir, options.useSummaries ? angularSummaryFiles : angularFiles], + {...options, enableSummariesForJit: true}); } it('should create @Injectable summaries', () => { diff --git a/packages/compiler/test/aot/static_reflector_spec.ts b/packages/compiler/test/aot/static_reflector_spec.ts index c594c15a30..8a1824ad6e 100644 --- a/packages/compiler/test/aot/static_reflector_spec.ts +++ b/packages/compiler/test/aot/static_reflector_spec.ts @@ -6,8 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; -import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; +import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, core as compilerCore} from '@angular/compiler'; import {CollectorOptions} from '@angular/tsc-wrapped'; import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec'; @@ -66,14 +65,6 @@ describe('StaticReflector', () => { expect(annotations.length).toEqual(1); const annotation = annotations[0]; expect(annotation.selector).toEqual('my-hero-detail'); - expect(annotation.animations).toEqual([trigger('myAnimation', [ - state('state1', style({'background': 'white'})), - transition( - '* => *', - sequence([group([animate( - '1s 0.5s', - keyframes([style({'background': 'blue'}), style({'background': 'red'})]))])])) - ])]); }); it('should get and empty annotation list for an unknown class', () => { @@ -97,7 +88,8 @@ describe('StaticReflector', () => { reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); const props = reflector.propMetadata(HeroDetailComponent); expect(props['hero']).toBeTruthy(); - expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); + expect(props['onMouseOver']).toEqual([compilerCore.createHostListener( + 'mouseover', ['$event'])]); }); it('should get an empty object from propMetadata for an unknown class', () => { @@ -401,7 +393,7 @@ describe('StaticReflector', () => { const src = '/tmp/src/forward-ref.ts'; const dep = reflector.getStaticSymbol(src, 'Dep'); const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward')); - expect(props).toEqual([[dep, new Inject(dep)]]); + expect(props).toEqual([[dep, compilerCore.createInject(dep)]]); }); it('should report an error for invalid function calls', () => { @@ -600,7 +592,7 @@ describe('StaticReflector', () => { const someClass = reflector.getStaticSymbol(file, 'SomeClass'); const parameters = reflector.parameters(someClass); - expect(parameters.toString()).toEqual('@Inject'); + expect(compilerCore.createInject.isTypeOf(parameters[0][0])).toBe(true); }); it('should reject a ctor parameter without a @Inject and a type exprssion', () => { @@ -889,431 +881,237 @@ describe('StaticReflector', () => { }); const DEFAULT_TEST_DATA: {[key: string]: any} = { - '/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{ - '__symbolic': 'module', - 'version': 3, - 'metadata': { - 'FORM_DIRECTIVES': [ - { - '__symbolic': 'reference', - 'name': 'NgFor', - 'module': '@angular/common/src/directives/ng_for' - } - ] - } - }], - '/tmp/@angular/common/src/directives/ng_for.d.ts': { - '__symbolic': 'module', - 'version': 3, - 'metadata': { - 'NgFor': { - '__symbolic': 'class', - 'decorators': [ - { - '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'Directive', - 'module': '@angular/core' - }, - 'arguments': [ - { - 'selector': '[ngFor][ngForOf]', - 'inputs': ['ngForTrackBy', 'ngForOf', 'ngForTemplate'] - } - ] + '/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{ + '__symbolic': 'module', + 'version': 3, + 'metadata': { + 'FORM_DIRECTIVES': [{ + '__symbolic': 'reference', + 'name': 'NgFor', + 'module': '@angular/common/src/directives/ng_for' + }] + } + }], + '/tmp/@angular/common/src/directives/ng_for.d.ts': { + '__symbolic': 'module', + 'version': 3, + 'metadata': { + 'NgFor': { + '__symbolic': 'class', + 'decorators': [{ + '__symbolic': 'call', + 'expression': {'__symbolic': 'reference', 'name': 'Directive', 'module': '@angular/core'}, + 'arguments': [{ + 'selector': '[ngFor][ngForOf]', + 'inputs': ['ngForTrackBy', 'ngForOf', 'ngForTemplate'] + }] + }], + 'members': { + '__ctor__': [{ + '__symbolic': 'constructor', + 'parameters': [ + {'__symbolic': 'reference', 'module': '@angular/core', 'name': 'ViewContainerRef'}, + {'__symbolic': 'reference', 'module': '@angular/core', 'name': 'TemplateRef'}, + {'__symbolic': 'reference', 'module': '@angular/core', 'name': 'IterableDiffers'}, { + '__symbolic': 'reference', + 'module': '@angular/core', + 'name': 'ChangeDetectorRef' } - ], - 'members': { - '__ctor__': [ - { - '__symbolic': 'constructor', - 'parameters': [ - { - '__symbolic': 'reference', - 'module': '@angular/core', - 'name': 'ViewContainerRef' - }, - { - '__symbolic': 'reference', - 'module': '@angular/core', - 'name': 'TemplateRef' - }, - { - '__symbolic': 'reference', - 'module': '@angular/core', - 'name': 'IterableDiffers' - }, - { - '__symbolic': 'reference', - 'module': '@angular/core', - 'name': 'ChangeDetectorRef' - } - ] - } - ] - } - } - } - }, - '/tmp/@angular/core/src/linker/view_container_ref.d.ts': - {version: 3, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}}, - '/tmp/@angular/core/src/linker/template_ref.d.ts': - {version: 3, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}}, - '/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts': - {version: 3, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}}, - '/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts': - {version: 3, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}}, - '/tmp/src/app/hero-detail.component.d.ts': { - '__symbolic': 'module', - 'version': 3, - 'metadata': { - 'HeroDetailComponent': { - '__symbolic': 'class', - 'decorators': [ - { - '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'Component', - 'module': '@angular/core' - }, - 'arguments': [ - { - 'selector': 'my-hero-detail', - 'template': - '\n
\n

{{hero.name}} details!

\n
{{hero.id}}
\n
\n \n \n
\n
\n', - 'animations': [{ - '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'trigger', - 'module': '@angular/core' - }, - 'arguments': [ - 'myAnimation', - [{ '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'state', - 'module': '@angular/core' - }, - 'arguments': [ - 'state1', - { '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'style', - 'module': '@angular/core' - }, - 'arguments': [ - { 'background':'white' } - ] - } - ] - }, { - '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'transition', - 'module': '@angular/core' - }, - 'arguments': [ - '* => *', - { - '__symbolic':'call', - 'expression':{ - '__symbolic':'reference', - 'name':'sequence', - 'module': '@angular/core' - }, - 'arguments':[[{ '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'group', - 'module': '@angular/core' - }, - 'arguments':[[{ - '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'animate', - 'module': '@angular/core' - }, - 'arguments':[ - '1s 0.5s', - { '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'keyframes', - 'module': '@angular/core' - }, - 'arguments':[[{ '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'style', - 'module': '@angular/core' - }, - 'arguments':[ { 'background': 'blue'} ] - }, { - '__symbolic': 'call', - 'expression': { - '__symbolic':'reference', - 'name':'style', - 'module': '@angular/core' - }, - 'arguments':[ { 'background': 'red'} ] - }]] - } - ] - }]] - }]] - } - ] - } - ] - ] - }] - }] - }], - 'members': { - 'hero': [ - { - '__symbolic': 'property', - 'decorators': [ - { - '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'name': 'Input', - 'module': '@angular/core' - } - } - ] - } - ], - 'onMouseOver': [ - { - '__symbolic': 'method', - 'decorators': [ - { - '__symbolic': 'call', - 'expression': { - '__symbolic': 'reference', - 'module': '@angular/core', - 'name': 'HostListener' - }, - 'arguments': [ - 'mouseover', - [ - '$event' - ] - ] - } - ] - } - ] - } - } - } - }, - '/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}}, - '/tmp/src/error-reporting.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - SomeClass: { - __symbolic: 'class', - decorators: [ - { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - name: 'Component', - module: '@angular/core' - }, - arguments: [ - { - entryComponents: [ - { - __symbolic: 'reference', - module: 'src/error-references', - name: 'Link1', - } - ] - } - ] - } - ], - } - } - }, - '/tmp/src/error-references.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - Link1: { - __symbolic: 'reference', - module: 'src/error-references', - name: 'Link2' - }, - Link2: { - __symbolic: 'reference', - module: 'src/error-references', - name: 'ErrorSym' - }, - ErrorSym: { - __symbolic: 'error', - message: 'A reasonable error message', - line: 12, - character: 33 - } - } - }, - '/tmp/src/function-declaration.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - one: { - __symbolic: 'function', - parameters: ['a'], - value: [ - {__symbolic: 'reference', name: 'a'} ] - }, - add: { - __symbolic: 'function', - parameters: ['a','b'], - value: { - __symbolic: 'binop', - operator: '+', - left: {__symbolic: 'reference', name: 'a'}, - right: { - __symbolic: 'binop', - operator: '+', - left: {__symbolic: 'reference', name: 'b'}, - right: {__symbolic: 'reference', name: 'oneLiteral'} - } - } - }, - oneLiteral: 1 + }] } + } + } + }, + '/tmp/@angular/core/src/linker/view_container_ref.d.ts': + {version: 3, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}}, + '/tmp/@angular/core/src/linker/template_ref.d.ts': { + version: 3, + 'module': './template_ref', + 'metadata': {'TemplateRef': {'__symbolic': 'class'}} + }, + '/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts': + {version: 3, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}}, + '/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts': + {version: 3, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}}, + '/tmp/src/app/hero-detail.component.d.ts': { + '__symbolic': 'module', + 'version': 3, + 'metadata': { + 'HeroDetailComponent': { + '__symbolic': 'class', + 'decorators': [{ + '__symbolic': 'call', + 'expression': {'__symbolic': 'reference', 'name': 'Component', 'module': '@angular/core'}, + 'arguments': [{ + 'selector': 'my-hero-detail', + 'template': + '\n
\n

{{hero.name}} details!

\n
{{hero.id}}
\n
\n \n \n
\n
\n', + }] + }], + 'members': { + 'hero': [{ + '__symbolic': 'property', + 'decorators': [{ + '__symbolic': 'call', + 'expression': + {'__symbolic': 'reference', 'name': 'Input', 'module': '@angular/core'} + }] + }], + 'onMouseOver': [{ + '__symbolic': 'method', + 'decorators': [{ + '__symbolic': 'call', + 'expression': + {'__symbolic': 'reference', 'module': '@angular/core', 'name': 'HostListener'}, + 'arguments': ['mouseover', ['$event']] + }] + }] + } + } + } + }, + '/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}}, + '/tmp/src/error-reporting.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + SomeClass: { + __symbolic: 'class', + decorators: [{ + __symbolic: 'call', + expression: {__symbolic: 'reference', name: 'Component', module: '@angular/core'}, + arguments: [{ + entryComponents: [{ + __symbolic: 'reference', + module: 'src/error-references', + name: 'Link1', + }] + }] + }], + } + } + }, + '/tmp/src/error-references.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + Link1: {__symbolic: 'reference', module: 'src/error-references', name: 'Link2'}, + Link2: {__symbolic: 'reference', module: 'src/error-references', name: 'ErrorSym'}, + ErrorSym: + {__symbolic: 'error', message: 'A reasonable error message', line: 12, character: 33} + } + }, + '/tmp/src/function-declaration.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + one: { + __symbolic: 'function', + parameters: ['a'], + value: [{__symbolic: 'reference', name: 'a'}] }, - '/tmp/src/function-reference.ts': { - __symbolic: 'module', - version: 3, - metadata: { - one: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-declaration', - name: 'one' - }, - arguments: ['some-value'] - }, - three: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-declaration', - name: 'add' - }, - arguments: [1, 1] - }, - recursion: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-recursive', - name: 'recursive' - }, - arguments: [1] - }, - indirectRecursion: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-recursive', - name: 'indirectRecursion1' - }, - arguments: [1] + add: { + __symbolic: 'function', + parameters: ['a', 'b'], + value: { + __symbolic: 'binop', + operator: '+', + left: {__symbolic: 'reference', name: 'a'}, + right: { + __symbolic: 'binop', + operator: '+', + left: {__symbolic: 'reference', name: 'b'}, + right: {__symbolic: 'reference', name: 'oneLiteral'} } } }, - '/tmp/src/function-recursive.d.ts': { - __symbolic: 'modules', - version: 3, - metadata: { - recursive: { - __symbolic: 'function', - parameters: ['a'], - value: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-recursive', - name: 'recursive', - }, - arguments: [ - { - __symbolic: 'reference', - name: 'a' - } - ] - } - }, - indirectRecursion1: { - __symbolic: 'function', - parameters: ['a'], - value: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-recursive', - name: 'indirectRecursion2', - }, - arguments: [ - { - __symbolic: 'reference', - name: 'a' - } - ] - } - }, - indirectRecursion2: { - __symbolic: 'function', - parameters: ['a'], - value: { - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './function-recursive', - name: 'indirectRecursion1', - }, - arguments: [ - { - __symbolic: 'reference', - name: 'a' - } - ] - } - } - }, + oneLiteral: 1 + } + }, + '/tmp/src/function-reference.ts': { + __symbolic: 'module', + version: 3, + metadata: { + one: { + __symbolic: 'call', + expression: {__symbolic: 'reference', module: './function-declaration', name: 'one'}, + arguments: ['some-value'] }, - '/tmp/src/spread.ts': { - __symbolic: 'module', - version: 3, - metadata: { - spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5] + three: { + __symbolic: 'call', + expression: {__symbolic: 'reference', module: './function-declaration', name: 'add'}, + arguments: [1, 1] + }, + recursion: { + __symbolic: 'call', + expression: {__symbolic: 'reference', module: './function-recursive', name: 'recursive'}, + arguments: [1] + }, + indirectRecursion: { + __symbolic: 'call', + expression: + {__symbolic: 'reference', module: './function-recursive', name: 'indirectRecursion1'}, + arguments: [1] + } + } + }, + '/tmp/src/function-recursive.d.ts': { + __symbolic: 'modules', + version: 3, + metadata: { + recursive: { + __symbolic: 'function', + parameters: ['a'], + value: { + __symbolic: 'call', + expression: { + __symbolic: 'reference', + module: './function-recursive', + name: 'recursive', + }, + arguments: [{__symbolic: 'reference', name: 'a'}] } }, - '/tmp/src/custom-decorator.ts': ` + indirectRecursion1: { + __symbolic: 'function', + parameters: ['a'], + value: { + __symbolic: 'call', + expression: { + __symbolic: 'reference', + module: './function-recursive', + name: 'indirectRecursion2', + }, + arguments: [{__symbolic: 'reference', name: 'a'}] + } + }, + indirectRecursion2: { + __symbolic: 'function', + parameters: ['a'], + value: { + __symbolic: 'call', + expression: { + __symbolic: 'reference', + module: './function-recursive', + name: 'indirectRecursion1', + }, + arguments: [{__symbolic: 'reference', name: 'a'}] + } + } + }, + }, + '/tmp/src/spread.ts': { + __symbolic: 'module', + version: 3, + metadata: {spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]} + }, + '/tmp/src/custom-decorator.ts': ` export function CustomDecorator(): any { return () => {}; } `, - '/tmp/src/custom-decorator-reference.ts': ` + '/tmp/src/custom-decorator-reference.ts': ` import {CustomDecorator} from './custom-decorator'; @CustomDecorator() @@ -1321,7 +1119,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { @CustomDecorator() get foo(): string { return ''; } } `, - '/tmp/src/invalid-calll-definitions.ts': ` + '/tmp/src/invalid-calll-definitions.ts': ` export function someFunction(a: any) { if (Array.isArray(a)) { return a; @@ -1329,7 +1127,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { return undefined; } `, - '/tmp/src/invalid-calls.ts': ` + '/tmp/src/invalid-calls.ts': ` import {someFunction} from './nvalid-calll-definitions.ts'; import {Component} from '@angular/core'; import {NgIf} from '@angular/common'; @@ -1347,7 +1145,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { }) export class MyOtherComponent { } `, - '/tmp/src/static-method.ts': ` + '/tmp/src/static-method.ts': ` import {Component} from '@angular/core/src/metadata'; @Component({ @@ -1374,7 +1172,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { } } `, - '/tmp/src/static-method-call.ts': ` + '/tmp/src/static-method-call.ts': ` import {Component} from '@angular/core'; import {MyModule} from './static-method'; @@ -1398,7 +1196,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { }) export class MyFactoryComponent { } `, - '/tmp/src/static-field.ts': ` + '/tmp/src/static-field.ts': ` import {Injectable} from '@angular/core'; @Injectable() @@ -1406,12 +1204,12 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { static VALUE = 'Some string'; } `, - '/tmp/src/macro-function.ts': ` + '/tmp/src/macro-function.ts': ` export function v(value: any) { return { provide: 'a', useValue: value }; } `, - '/tmp/src/call-macro-function.ts': ` + '/tmp/src/call-macro-function.ts': ` import {Component} from '@angular/core'; import {v} from './macro-function'; @@ -1425,7 +1223,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { }) export class MyComponentNested { } `, - '/tmp/src/static-field-reference.ts': ` + '/tmp/src/static-field-reference.ts': ` import {Component} from '@angular/core'; import {MyModule} from './static-field'; @@ -1434,12 +1232,12 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { }) export class Foo { } `, - '/tmp/src/static-method-def.ts': ` + '/tmp/src/static-method-def.ts': ` export class ClassWithStatics { static staticMethod() {} } `, - '/tmp/src/static-method-ref.ts': ` + '/tmp/src/static-method-ref.ts': ` import {Component} from '@angular/core'; import {ClassWithStatics} from './static-method-def'; @@ -1450,7 +1248,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { } `, - '/tmp/src/invalid-metadata.ts': ` + '/tmp/src/invalid-metadata.ts': ` import {Component} from '@angular/core'; @Component({ @@ -1458,7 +1256,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { }) export class InvalidMetadata {} `, - '/tmp/src/forward-ref.ts': ` + '/tmp/src/forward-ref.ts': ` import {forwardRef} from '@angular/core'; import {Component} from '@angular/core'; import {Inject} from '@angular/core'; @@ -1470,4 +1268,4 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { @Input f: Forward; } ` - }; +}; diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts index 4ee26ff77c..728de9264f 100644 --- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts +++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts @@ -386,7 +386,8 @@ export class MockSummaryResolver implements SummaryResolver { } isLibraryFile(filePath: string): boolean { return filePath.endsWith('.d.ts'); } - getLibraryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); } + toSummaryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); } + fromSummaryFileName(filePath: string): string { return filePath; } } export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost { diff --git a/packages/compiler/test/aot/summary_resolver_spec.ts b/packages/compiler/test/aot/summary_resolver_spec.ts index 60681bb609..efff637a50 100644 --- a/packages/compiler/test/aot/summary_resolver_spec.ts +++ b/packages/compiler/test/aot/summary_resolver_spec.ts @@ -35,7 +35,8 @@ export function main() { const symbolResolver = new StaticSymbolResolver( new MockStaticSymbolResolverHost({}), symbolCache, mockSummaryResolver); return serializeSummaries( - createMockOutputContext(), mockSummaryResolver, symbolResolver, symbols, []) + 'someFile.ts', createMockOutputContext(), mockSummaryResolver, symbolResolver, + symbols, []) .json; } @@ -105,10 +106,12 @@ export class MockAotSummaryResolverHost implements AotSummaryResolverHost { return './' + path.basename(fileName).replace(EXT, ''); } - getOutputFileName(sourceFileName: string): string { + toSummaryFileName(sourceFileName: string): string { return sourceFileName.replace(EXT, '') + '.d.ts'; } + fromSummaryFileName(filePath: string): string { return filePath; } + isSourceFile(filePath: string) { return !filePath.endsWith('.d.ts'); } loadSummary(filePath: string): string { return this.summaries[filePath]; } diff --git a/packages/compiler/test/aot/summary_serializer_spec.ts b/packages/compiler/test/aot/summary_serializer_spec.ts index 6a177df793..90e8c36d06 100644 --- a/packages/compiler/test/aot/summary_serializer_spec.ts +++ b/packages/compiler/test/aot/summary_serializer_spec.ts @@ -43,7 +43,7 @@ export function main() { it('should serialize various data correctly', () => { init(); const serializedData = serializeSummaries( - createMockOutputContext(), summaryResolver, symbolResolver, + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/some_values.ts', 'Values'), @@ -77,7 +77,9 @@ export function main() { }]); - const summaries = deserializeSummaries(symbolCache, serializedData.json).summaries; + const summaries = + deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serializedData.json) + .summaries; expect(summaries.length).toBe(2); // Note: change from .ts to .d.ts is expected @@ -105,8 +107,8 @@ export function main() { it('should automatically add exported directives / pipes of NgModules that are not source files', () => { init(); - const externalSerialized = - serializeSummaries(createMockOutputContext(), summaryResolver, symbolResolver, [], [ + const externalSerialized = serializeSummaries( + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [], [ { summary: { summaryKind: CompileSummaryKind.Pipe, @@ -133,7 +135,7 @@ export function main() { }); const serialized = serializeSummaries( - createMockOutputContext(), summaryResolver, symbolResolver, [], [{ + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [], [{ summary: { summaryKind: CompileSummaryKind.NgModule, type: {reference: symbolCache.get('/tmp/some_module.ts', 'SomeModule')}, @@ -151,7 +153,9 @@ export function main() { metadata: null as any }]); - const summaries = deserializeSummaries(symbolCache, serialized.json).summaries; + const summaries = + deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) + .summaries; expect(summaries.length).toBe(3); expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule')); expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')); @@ -163,7 +167,7 @@ export function main() { () => { init(); const externalSerialized = serializeSummaries( - createMockOutputContext(), summaryResolver, symbolResolver, + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [ { symbol: symbolCache.get('/tmp/external.ts', 'PROVIDERS'), @@ -195,7 +199,7 @@ export function main() { {__symbolic: 'module', version: 3, metadata: {'external': 'b'}} }); const serialized = serializeSummaries( - createMockOutputContext(), summaryResolver, symbolResolver, [{ + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: { local: symbolCache.get('/tmp/local.ts', 'local'), @@ -205,7 +209,9 @@ export function main() { }], []); - const summaries = deserializeSummaries(symbolCache, serialized.json).summaries; + const summaries = + deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json) + .summaries; // Note: local should not show up! expect(summaries.length).toBe(4); expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'main')); @@ -230,7 +236,7 @@ export function main() { it('should create "importAs" names for non source symbols', () => { init(); const serialized = serializeSummaries( - createMockOutputContext(), summaryResolver, symbolResolver, [{ + 'someFile.ts', createMockOutputContext(), summaryResolver, symbolResolver, [{ symbol: symbolCache.get('/tmp/test.ts', 'main'), metadata: [ symbolCache.get('/tmp/external.d.ts', 'lib'), @@ -243,7 +249,8 @@ export function main() { {symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), exportAs: 'lib_1'} ]); - const deserialized = deserializeSummaries(symbolCache, serialized.json); + const deserialized = + deserializeSummaries(symbolCache, summaryResolver, 'someFile.d.ts', serialized.json); // Note: no entry for the symbol with members! expect(deserialized.importAs).toEqual([ {symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), importAs: 'lib_1'} diff --git a/packages/compiler/test/aot/test_util.ts b/packages/compiler/test/aot/test_util.ts index 7ea8633c78..f38b2ed897 100644 --- a/packages/compiler/test/aot/test_util.ts +++ b/packages/compiler/test/aot/test_util.ts @@ -330,8 +330,15 @@ export class MockAotCompilerHost implements AotCompilerHost { private metadataCollector = new MetadataCollector(); private metadataVisible: boolean = true; private dtsAreSource: boolean = true; + private resolveModuleNameHost: ts.ModuleResolutionHost; - constructor(private tsHost: MockCompilerHost) {} + constructor(private tsHost: MockCompilerHost) { + this.resolveModuleNameHost = Object.create(tsHost); + this.resolveModuleNameHost.fileExists = (fileName) => { + fileName = stripNgResourceSuffix(fileName); + return tsHost.fileExists(fileName); + }; + } hideMetadata() { this.metadataVisible = false; } @@ -369,11 +376,22 @@ export class MockAotCompilerHost implements AotCompilerHost { moduleName = moduleName.replace(EXT, ''); const resolved = ts.resolveModuleName( moduleName, containingFile.replace(/\\/g, '/'), - {baseDir: '/', genDir: '/'}, this.tsHost) + {baseDir: '/', genDir: '/'}, this.resolveModuleNameHost) .resolvedModule; return resolved ? resolved.resolvedFileName : null; } + resourceNameToFileName(resourceName: string, containingFile: string) { + // Note: we convert package paths into relative paths to be compatible with the the + // previous implementation of UrlResolver. + if (resourceName && resourceName.charAt(0) !== '.' && !path.isAbsolute(resourceName)) { + resourceName = `./${resourceName}`; + } + const filePathWithNgResource = + this.moduleNameToFileName(addNgResourceSuffix(resourceName), containingFile); + return filePathWithNgResource ? stripNgResourceSuffix(filePathWithNgResource) : null; + } + // AotSummaryResolverHost loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); } @@ -382,9 +400,9 @@ export class MockAotCompilerHost implements AotCompilerHost { (this.dtsAreSource || !DTS.test(sourceFilePath)); } - getOutputFileName(sourceFilePath: string): string { - return sourceFilePath.replace(EXT, '') + '.d.ts'; - } + toSummaryFileName(filePath: string): string { return filePath.replace(EXT, '') + '.d.ts'; } + + fromSummaryFileName(filePath: string): string { return filePath; } // AotCompilerHost fileNameToModuleName(importedFile: string, containingFile: string): string|null { @@ -593,7 +611,15 @@ export function expectNoDiagnostics(program: ts.Program) { } export function isSource(fileName: string): boolean { - return !/\.d\.ts$/.test(fileName) && /\.ts$/.test(fileName); + return !isDts(fileName) && /\.ts$/.test(fileName); +} + +function isDts(fileName: string): boolean { + return /\.d.ts$/.test(fileName); +} + +function isSourceOrDts(fileName: string): boolean { + return /\.ts$/.test(fileName); } export function compile( @@ -610,7 +636,8 @@ export function compile( const preCompile = options.preCompile || (() => {}); const postCompile = options.postCompile || expectNoDiagnostics; const rootDirArr = toMockFileArray(rootDirs); - const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource); + const scriptNames = rootDirArr.map(entry => entry.fileName) + .filter(options.useSummaries ? isSource : isSourceOrDts); const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr)); const aotHost = new MockAotCompilerHost(host); @@ -641,9 +668,19 @@ export function compile( } let outDir: MockDirectory = {}; if (emit) { - outDir = arrayToMockDir(toMockFileArray([ - host.writtenFiles, host.overrides - ]).filter((entry) => !isSource(entry.fileName))); + const dtsFilesWithGenFiles = new Set(genFiles.map(gf => gf.srcFileUrl).filter(isDts)); + outDir = + arrayToMockDir(toMockFileArray([host.writtenFiles, host.overrides]) + .filter((entry) => !isSource(entry.fileName)) + .concat(rootDirArr.filter(e => dtsFilesWithGenFiles.has(e.fileName)))); } return {genFiles, outDir}; } + +function stripNgResourceSuffix(fileName: string): string { + return fileName.replace(/\.\$ngresource\$.*/, ''); +} + +function addNgResourceSuffix(fileName: string): string { + return `${fileName}.$ngresource$`; +} diff --git a/packages/compiler/test/core_spec.ts b/packages/compiler/test/core_spec.ts new file mode 100644 index 0000000000..315cf073df --- /dev/null +++ b/packages/compiler/test/core_spec.ts @@ -0,0 +1,196 @@ +/** + * @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 {core as compilerCore} from '@angular/compiler'; +import * as core from '@angular/core'; + +export function main() { + describe('compiler core', () => { + it('Attribute should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape(new core.Attribute('someName'), compilerCore.createAttribute('someName')); + }); + + it('Inject should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape(new core.Inject('someName'), compilerCore.createInject('someName')); + }); + + it('Query should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape( + new core.ContentChild('someSelector'), compilerCore.createContentChild('someSelector')); + compareRuntimeShape( + new core.ContentChild('someSelector', {read: 'someRead'}), + compilerCore.createContentChild('someSelector', {read: 'someRead'})); + compareRuntimeShape( + new core.ContentChildren('someSelector'), + compilerCore.createContentChildren('someSelector')); + compareRuntimeShape( + new core.ContentChildren('someSelector', {read: 'someRead', descendants: false}), + compilerCore.createContentChildren( + 'someSelector', {read: 'someRead', descendants: false})); + compareRuntimeShape( + new core.ViewChild('someSelector'), compilerCore.createViewChild('someSelector')); + compareRuntimeShape( + new core.ViewChild('someSelector', {read: 'someRead'}), + compilerCore.createViewChild('someSelector', {read: 'someRead'})); + compareRuntimeShape( + new core.ViewChildren('someSelector'), compilerCore.createViewChildren('someSelector')); + compareRuntimeShape( + new core.ViewChildren('someSelector', {read: 'someRead'}), + compilerCore.createViewChildren('someSelector', {read: 'someRead'})); + }); + + it('Directive should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape(new core.Directive({}), compilerCore.createDirective({})); + }); + + it('Component should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape(new core.Component({}), compilerCore.createComponent({})); + }); + + it('Pipe should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape( + new core.Pipe({name: 'someName'}), compilerCore.createPipe({name: 'someName'})); + }); + + it('NgModule should be equal', () => { + typeExtends(); + typeExtends(); + compareRuntimeShape(new core.NgModule({}), compilerCore.createNgModule({})); + }); + + it('marker metadata should be equal', () => { + compareRuntimeShape(new core.Injectable(), compilerCore.createInjectable()); + compareRuntimeShape(new core.Optional(), compilerCore.createOptional()); + compareRuntimeShape(new core.Self(), compilerCore.createSelf()); + compareRuntimeShape(new core.SkipSelf(), compilerCore.createSkipSelf()); + compareRuntimeShape(new core.Host(), compilerCore.createHost()); + }); + + it('InjectionToken should be equal', () => { + compareRuntimeShape( + new core.InjectionToken('someName'), compilerCore.createInjectionToken('someName')); + }); + + it('non const enums should be equal', () => { + typeExtends(); + typeExtends(); + + typeExtends(); + typeExtends(); + + typeExtends(); + typeExtends(); + + typeExtends(); + typeExtends(); + }); + + it('const enums should be equal', () => { + expect(compilerCore.NodeFlags.None).toBe(core.ɵNodeFlags.None); + expect(compilerCore.NodeFlags.TypeElement).toBe(core.ɵNodeFlags.TypeElement); + expect(compilerCore.NodeFlags.TypeText).toBe(core.ɵNodeFlags.TypeText); + expect(compilerCore.NodeFlags.ProjectedTemplate).toBe(core.ɵNodeFlags.ProjectedTemplate); + expect(compilerCore.NodeFlags.CatRenderNode).toBe(core.ɵNodeFlags.CatRenderNode); + expect(compilerCore.NodeFlags.TypeNgContent).toBe(core.ɵNodeFlags.TypeNgContent); + expect(compilerCore.NodeFlags.TypePipe).toBe(core.ɵNodeFlags.TypePipe); + expect(compilerCore.NodeFlags.TypePureArray).toBe(core.ɵNodeFlags.TypePureArray); + expect(compilerCore.NodeFlags.TypePureObject).toBe(core.ɵNodeFlags.TypePureObject); + expect(compilerCore.NodeFlags.TypePurePipe).toBe(core.ɵNodeFlags.TypePurePipe); + expect(compilerCore.NodeFlags.CatPureExpression).toBe(core.ɵNodeFlags.CatPureExpression); + expect(compilerCore.NodeFlags.TypeValueProvider).toBe(core.ɵNodeFlags.TypeValueProvider); + expect(compilerCore.NodeFlags.TypeClassProvider).toBe(core.ɵNodeFlags.TypeClassProvider); + expect(compilerCore.NodeFlags.TypeFactoryProvider).toBe(core.ɵNodeFlags.TypeFactoryProvider); + expect(compilerCore.NodeFlags.TypeUseExistingProvider) + .toBe(core.ɵNodeFlags.TypeUseExistingProvider); + expect(compilerCore.NodeFlags.LazyProvider).toBe(core.ɵNodeFlags.LazyProvider); + expect(compilerCore.NodeFlags.PrivateProvider).toBe(core.ɵNodeFlags.PrivateProvider); + expect(compilerCore.NodeFlags.TypeDirective).toBe(core.ɵNodeFlags.TypeDirective); + expect(compilerCore.NodeFlags.Component).toBe(core.ɵNodeFlags.Component); + expect(compilerCore.NodeFlags.CatProviderNoDirective) + .toBe(core.ɵNodeFlags.CatProviderNoDirective); + expect(compilerCore.NodeFlags.CatProvider).toBe(core.ɵNodeFlags.CatProvider); + expect(compilerCore.NodeFlags.OnInit).toBe(core.ɵNodeFlags.OnInit); + expect(compilerCore.NodeFlags.OnDestroy).toBe(core.ɵNodeFlags.OnDestroy); + expect(compilerCore.NodeFlags.DoCheck).toBe(core.ɵNodeFlags.DoCheck); + expect(compilerCore.NodeFlags.OnChanges).toBe(core.ɵNodeFlags.OnChanges); + expect(compilerCore.NodeFlags.AfterContentInit).toBe(core.ɵNodeFlags.AfterContentInit); + expect(compilerCore.NodeFlags.AfterContentChecked).toBe(core.ɵNodeFlags.AfterContentChecked); + expect(compilerCore.NodeFlags.AfterViewInit).toBe(core.ɵNodeFlags.AfterViewInit); + expect(compilerCore.NodeFlags.AfterViewChecked).toBe(core.ɵNodeFlags.AfterViewChecked); + expect(compilerCore.NodeFlags.EmbeddedViews).toBe(core.ɵNodeFlags.EmbeddedViews); + expect(compilerCore.NodeFlags.ComponentView).toBe(core.ɵNodeFlags.ComponentView); + expect(compilerCore.NodeFlags.TypeContentQuery).toBe(core.ɵNodeFlags.TypeContentQuery); + expect(compilerCore.NodeFlags.TypeViewQuery).toBe(core.ɵNodeFlags.TypeViewQuery); + expect(compilerCore.NodeFlags.StaticQuery).toBe(core.ɵNodeFlags.StaticQuery); + expect(compilerCore.NodeFlags.DynamicQuery).toBe(core.ɵNodeFlags.DynamicQuery); + expect(compilerCore.NodeFlags.CatQuery).toBe(core.ɵNodeFlags.CatQuery); + expect(compilerCore.NodeFlags.Types).toBe(core.ɵNodeFlags.Types); + + expect(compilerCore.DepFlags.None).toBe(core.ɵDepFlags.None); + expect(compilerCore.DepFlags.SkipSelf).toBe(core.ɵDepFlags.SkipSelf); + expect(compilerCore.DepFlags.Optional).toBe(core.ɵDepFlags.Optional); + expect(compilerCore.DepFlags.Value).toBe(core.ɵDepFlags.Value); + + expect(compilerCore.ArgumentType.Inline).toBe(core.ɵArgumentType.Inline); + expect(compilerCore.ArgumentType.Dynamic).toBe(core.ɵArgumentType.Dynamic); + + expect(compilerCore.BindingFlags.TypeElementAttribute) + .toBe(core.ɵBindingFlags.TypeElementAttribute); + expect(compilerCore.BindingFlags.TypeElementClass).toBe(core.ɵBindingFlags.TypeElementClass); + expect(compilerCore.BindingFlags.TypeElementStyle).toBe(core.ɵBindingFlags.TypeElementStyle); + expect(compilerCore.BindingFlags.TypeProperty).toBe(core.ɵBindingFlags.TypeProperty); + expect(compilerCore.BindingFlags.SyntheticProperty) + .toBe(core.ɵBindingFlags.SyntheticProperty); + expect(compilerCore.BindingFlags.SyntheticHostProperty) + .toBe(core.ɵBindingFlags.SyntheticHostProperty); + expect(compilerCore.BindingFlags.CatSyntheticProperty) + .toBe(core.ɵBindingFlags.CatSyntheticProperty); + expect(compilerCore.BindingFlags.Types).toBe(core.ɵBindingFlags.Types); + + expect(compilerCore.QueryBindingType.First).toBe(core.ɵQueryBindingType.First); + expect(compilerCore.QueryBindingType.All).toBe(core.ɵQueryBindingType.All); + + expect(compilerCore.QueryValueType.ElementRef).toBe(core.ɵQueryValueType.ElementRef); + expect(compilerCore.QueryValueType.RenderElement).toBe(core.ɵQueryValueType.RenderElement); + expect(compilerCore.QueryValueType.TemplateRef).toBe(core.ɵQueryValueType.TemplateRef); + expect(compilerCore.QueryValueType.ViewContainerRef) + .toBe(core.ɵQueryValueType.ViewContainerRef); + expect(compilerCore.QueryValueType.Provider).toBe(core.ɵQueryValueType.Provider); + + expect(compilerCore.ViewFlags.None).toBe(core.ɵViewFlags.None); + expect(compilerCore.ViewFlags.OnPush).toBe(core.ɵViewFlags.OnPush); + }); + }); +} + +function compareRuntimeShape(a: any, b: any) { + const keys = metadataKeys(a); + expect(keys).toEqual(metadataKeys(b)); + keys.forEach(key => { expect(a[key]).toBe(b[key]); }); + // Need to check 'ngMetadataName' separately, as this is + // on the prototype in @angular/core, but a regular property in @angular/compiler. + expect(a.ngMetadataName).toBe(b.ngMetadataName); +} + +function metadataKeys(a: any): string[] { + return Object.keys(a).filter(prop => prop !== 'ngMetadataName' && !prop.startsWith('_')).sort(); +} + +function typeExtends() {} diff --git a/packages/compiler/test/directive_lifecycle_spec.ts b/packages/compiler/test/directive_lifecycle_spec.ts index a4b35f4bf3..5d1193ccda 100644 --- a/packages/compiler/test/directive_lifecycle_spec.ts +++ b/packages/compiler/test/directive_lifecycle_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; import {LifecycleHooks as Hooks, hasLifecycleHook as hasLifecycleHookImpl} from '@angular/compiler/src/lifecycle_reflector'; import {SimpleChanges} from '@angular/core'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; function hasLifecycleHook(hook: Hooks, directive: any): boolean { return hasLifecycleHookImpl(new JitReflector(), hook, directive); diff --git a/packages/compiler/test/directive_normalizer_spec.ts b/packages/compiler/test/directive_normalizer_spec.ts index bd68115eb1..0ed2fa5b77 100644 --- a/packages/compiler/test/directive_normalizer_spec.ts +++ b/packages/compiler/test/directive_normalizer_spec.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ import {CompileAnimationEntryMetadata} from '@angular/compiler'; -import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata'; -import {CompilerConfig} from '@angular/compiler/src/config'; +import {CompileStylesheetMetadata, CompileTemplateMetadata} from '@angular/compiler/src/compile_metadata'; +import {CompilerConfig, preserveWhitespacesDefault} from '@angular/compiler/src/config'; import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; import {ResourceLoader} from '@angular/compiler/src/resource_loader'; import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock'; -import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/src/test_bindings'; import {ViewEncapsulation} from '@angular/core/src/metadata/view'; import {TestBed} from '@angular/core/testing'; import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal'; @@ -19,6 +18,7 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang import {noUndefined} from '../src/util'; import {SpyResourceLoader} from './spies'; +import {TEST_COMPILER_PROVIDERS} from './test_bindings'; const SOME_MODULE_URL = 'package:some/module/a.js'; const SOME_HTTP_MODULE_URL = 'http://some/module/a.js'; @@ -31,6 +31,7 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: { interpolation?: [string, string] | null; encapsulation?: ViewEncapsulation | null; animations?: CompileAnimationEntryMetadata[]; + preserveWhitespaces?: boolean | null; }) { return normalizer.normalizeTemplate({ ngModuleType: noUndefined(o.ngModuleType), @@ -42,7 +43,8 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: { styleUrls: noUndefined(o.styleUrls), interpolation: noUndefined(o.interpolation), encapsulation: noUndefined(o.encapsulation), - animations: noUndefined(o.animations) + animations: noUndefined(o.animations), + preserveWhitespaces: noUndefined(o.preserveWhitespaces), }); } @@ -54,6 +56,7 @@ function normalizeTemplateOnly(normalizer: DirectiveNormalizer, o: { interpolation?: [string, string] | null; encapsulation?: ViewEncapsulation | null; animations?: CompileAnimationEntryMetadata[]; + preserveWhitespaces?: boolean | null; }) { return normalizer.normalizeTemplateOnly({ ngModuleType: noUndefined(o.ngModuleType), @@ -65,13 +68,14 @@ function normalizeTemplateOnly(normalizer: DirectiveNormalizer, o: { styleUrls: noUndefined(o.styleUrls), interpolation: noUndefined(o.interpolation), encapsulation: noUndefined(o.encapsulation), - animations: noUndefined(o.animations) + animations: noUndefined(o.animations), + preserveWhitespaces: noUndefined(o.preserveWhitespaces), }); } function compileTemplateMetadata({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, - interpolation, isInline}: { + interpolation, isInline, preserveWhitespaces}: { encapsulation?: ViewEncapsulation | null, template?: string | null, templateUrl?: string | null, @@ -81,7 +85,8 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles, ngContentSelectors?: string[], animations?: any[], interpolation?: [string, string] | null, - isInline?: boolean + isInline?: boolean, + preserveWhitespaces?: boolean | null }): CompileTemplateMetadata { return new CompileTemplateMetadata({ encapsulation: encapsulation || null, @@ -94,6 +99,7 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles, animations: animations || [], interpolation: interpolation || null, isInline: !!isInline, + preserveWhitespaces: preserveWhitespacesDefault(noUndefined(preserveWhitespaces)), }); } @@ -106,6 +112,7 @@ function normalizeLoadedTemplate( interpolation?: [string, string] | null; encapsulation?: ViewEncapsulation | null; animations?: CompileAnimationEntryMetadata[]; + preserveWhitespaces?: boolean; }, template: string, templateAbsUrl: string) { return normalizer.normalizeLoadedTemplate( @@ -120,6 +127,7 @@ function normalizeLoadedTemplate( interpolation: o.interpolation || null, encapsulation: o.encapsulation || null, animations: o.animations || [], + preserveWhitespaces: noUndefined(o.preserveWhitespaces), }, template, templateAbsUrl); } @@ -169,6 +177,18 @@ export function main() { })) .toThrowError(`'SomeComp' component cannot define both template and templateUrl`); })); + it('should throw if preserveWhitespaces is not a boolean', + inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { + expect(() => normalizeTemplate(normalizer, { + ngModuleType: null, + componentType: SomeComp, + moduleUrl: SOME_MODULE_URL, + template: '', + preserveWhitespaces: 'WRONG', + })) + .toThrowError( + 'The preserveWhitespaces option for component SomeComp must be a boolean'); + })); }); describe('normalizeTemplateOnly sync', () => { @@ -431,6 +451,28 @@ export function main() { expect(template.encapsulation).toBe(viewEncapsulation); })); + it('should use preserveWhitespaces setting from compiler config if none provided', + inject( + [DirectiveNormalizer, CompilerConfig], + (normalizer: DirectiveNormalizer, config: CompilerConfig) => { + const template = normalizeLoadedTemplate(normalizer, {}, '', ''); + expect(template.preserveWhitespaces).toBe(config.preserveWhitespaces); + })); + + it('should store the preserveWhitespaces=false in the result', + inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { + const template = + normalizeLoadedTemplate(normalizer, {preserveWhitespaces: false}, '', ''); + expect(template.preserveWhitespaces).toBe(false); + })); + + it('should store the preserveWhitespaces=true in the result', + inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { + const template = + normalizeLoadedTemplate(normalizer, {preserveWhitespaces: true}, '', ''); + expect(template.preserveWhitespaces).toBe(true); + })); + it('should keep the template as html', inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => { const template = normalizeLoadedTemplate( diff --git a/packages/compiler/test/directive_resolver_mock_spec.ts b/packages/compiler/test/directive_resolver_mock_spec.ts index 926bbc018c..48be217725 100644 --- a/packages/compiler/test/directive_resolver_mock_spec.ts +++ b/packages/compiler/test/directive_resolver_mock_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; -import {Component, Directive, Injector, ɵViewMetadata as ViewMetadata} from '@angular/core'; +import {Component, Directive, Injector} from '@angular/core'; import {TestBed, inject} from '@angular/core/testing'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; import {MockDirectiveResolver} from '../testing'; @@ -22,7 +22,7 @@ export function main() { }); beforeEach(inject([Injector], (injector: Injector) => { - dirResolver = new MockDirectiveResolver(injector, new JitReflector()); + dirResolver = new MockDirectiveResolver(new JitReflector()); })); describe('Directive overriding', () => { @@ -38,48 +38,6 @@ export function main() { expect(metadata.selector).toEqual('someOtherSelector'); }); }); - - describe('View overriding', () => { - it('should fallback to the default ViewResolver when templates are not overridden', () => { - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('template'); - }); - - it('should allow overriding the @View', () => { - dirResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'})); - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('overridden template'); - }); - - it('should allow overriding a view after it has been resolved', () => { - dirResolver.resolve(SomeComponent); - dirResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'})); - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('overridden template'); - }); - }); - - describe('inline template definition overriding', () => { - it('should allow overriding the default template', () => { - dirResolver.setInlineTemplate(SomeComponent, 'overridden template'); - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('overridden template'); - }); - - it('should allow overriding an overridden @View', () => { - dirResolver.setView(SomeComponent, new ViewMetadata({template: 'overridden template'})); - dirResolver.setInlineTemplate(SomeComponent, 'overridden template x 2'); - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('overridden template x 2'); - }); - - it('should allow overriding a view after it has been resolved', () => { - dirResolver.resolve(SomeComponent); - dirResolver.setInlineTemplate(SomeComponent, 'overridden template'); - const view = dirResolver.resolve(SomeComponent); - expect(view.template).toEqual('overridden template'); - }); - }); }); } diff --git a/packages/compiler/test/directive_resolver_spec.ts b/packages/compiler/test/directive_resolver_spec.ts index 7322277350..3e4db96754 100644 --- a/packages/compiler/test/directive_resolver_spec.ts +++ b/packages/compiler/test/directive_resolver_spec.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; +import {core} from '@angular/compiler'; import {DirectiveResolver} from '@angular/compiler/src/directive_resolver'; import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Input, Output, ViewChild, ViewChildren} from '@angular/core/src/metadata'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; @Directive({selector: 'someDirective'}) class SomeDirective { @@ -79,7 +80,12 @@ class SomeDirectiveWithViewChild { c: any; } -@Component({selector: 'sample', template: 'some template', styles: ['some styles']}) +@Component({ + selector: 'sample', + template: 'some template', + styles: ['some styles'], + preserveWhitespaces: true +}) class ComponentWithTemplate { } @@ -114,7 +120,7 @@ export function main() { it('should read out the Directive metadata', () => { const directiveMetadata = resolver.resolve(SomeDirective); - expect(directiveMetadata).toEqual(new Directive({ + expect(directiveMetadata).toEqual(core.createDirective({ selector: 'someDirective', inputs: [], outputs: [], @@ -142,7 +148,7 @@ export function main() { class ChildWithDecorator extends Parent { } - expect(resolver.resolve(ChildNoDecorator)).toEqual(new Directive({ + expect(resolver.resolve(ChildNoDecorator)).toEqual(core.createDirective({ selector: 'p', inputs: [], outputs: [], @@ -152,7 +158,7 @@ export function main() { providers: undefined })); - expect(resolver.resolve(ChildWithDecorator)).toEqual(new Directive({ + expect(resolver.resolve(ChildWithDecorator)).toEqual(core.createDirective({ selector: 'c', inputs: [], outputs: [], @@ -439,6 +445,7 @@ export function main() { const compMetadata: Component = resolver.resolve(ComponentWithTemplate); expect(compMetadata.template).toEqual('some template'); expect(compMetadata.styles).toEqual(['some styles']); + expect(compMetadata.preserveWhitespaces).toBe(true); }); }); }); diff --git a/packages/compiler/test/i18n/extractor_merger_spec.ts b/packages/compiler/test/i18n/extractor_merger_spec.ts index 0618e0408f..1cca886b3e 100644 --- a/packages/compiler/test/i18n/extractor_merger_spec.ts +++ b/packages/compiler/test/i18n/extractor_merger_spec.ts @@ -253,7 +253,7 @@ export function main() { }); it('should extract from attributes in translatable ICUs', () => { - expect(extract(`{count, plural, =0 {

{count, plural, =0 {

}}`)) .toEqual([ [['msg'], 'm', 'd', 'i'], diff --git a/packages/compiler/test/integration_spec.ts b/packages/compiler/test/integration_spec.ts index c8c4e15e4a..321b37c0d9 100644 --- a/packages/compiler/test/integration_spec.ts +++ b/packages/compiler/test/integration_spec.ts @@ -9,7 +9,6 @@ import {Component, Directive, Input} from '@angular/core'; import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; -import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {browserDetection} from '@angular/platform-browser/testing/src/browser_util'; import {expect} from '@angular/platform-browser/testing/src/matchers'; diff --git a/packages/compiler/test/metadata_resolver_spec.ts b/packages/compiler/test/metadata_resolver_spec.ts index 9c16f77099..df9e3b1c3a 100644 --- a/packages/compiler/test/metadata_resolver_spec.ts +++ b/packages/compiler/test/metadata_resolver_spec.ts @@ -7,7 +7,6 @@ */ import {LIFECYCLE_HOOKS_VALUES, LifecycleHooks} from '@angular/compiler/src/lifecycle_reflector'; -import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/src/test_bindings'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, Directive, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation, ɵstringify as stringify} from '@angular/core'; import {TestBed, async, inject} from '@angular/core/testing'; @@ -17,6 +16,7 @@ import {ResourceLoader} from '../src/resource_loader'; import {MockResourceLoader} from '../testing/src/resource_loader_mock'; import {MalformedStylesComponent} from './metadata_resolver_fixture'; +import {TEST_COMPILER_PROVIDERS} from './test_bindings'; export function main() { describe('CompileMetadataResolver', () => { diff --git a/packages/compiler/test/ml_parser/ast_serializer_spec.ts b/packages/compiler/test/ml_parser/ast_serializer_spec.ts index 9452c3c9fb..5e33a302d5 100644 --- a/packages/compiler/test/ml_parser/ast_serializer_spec.ts +++ b/packages/compiler/test/ml_parser/ast_serializer_spec.ts @@ -97,4 +97,4 @@ const serializerVisitor = new _SerializerVisitor(); export function serializeNodes(nodes: html.Node[]): string[] { return nodes.map(node => node.visit(serializerVisitor, null)); -} \ No newline at end of file +} diff --git a/packages/compiler/test/ml_parser/html_whitespaces_spec.ts b/packages/compiler/test/ml_parser/html_whitespaces_spec.ts new file mode 100644 index 0000000000..7f9f6fa898 --- /dev/null +++ b/packages/compiler/test/ml_parser/html_whitespaces_spec.ts @@ -0,0 +1,118 @@ +/** + * @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 * as html from '../../src/ml_parser/ast'; +import {HtmlParser} from '../../src/ml_parser/html_parser'; +import {PRESERVE_WS_ATTR_NAME, removeWhitespaces} from '../../src/ml_parser/html_whitespaces'; + +import {humanizeDom} from './ast_spec_utils'; + +export function main() { + describe('removeWhitespaces', () => { + + function parseAndRemoveWS(template: string): any[] { + return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp'))); + } + + it('should remove blank text nodes', () => { + expect(parseAndRemoveWS(' ')).toEqual([]); + expect(parseAndRemoveWS('\n')).toEqual([]); + expect(parseAndRemoveWS('\t')).toEqual([]); + expect(parseAndRemoveWS(' \t \n ')).toEqual([]); + }); + + it('should remove whitespaces (space, tab, new line) between elements', () => { + expect(parseAndRemoveWS('

\t
\n
')).toEqual([ + [html.Element, 'br', 0], + [html.Element, 'br', 0], + [html.Element, 'br', 0], + [html.Element, 'br', 0], + ]); + }); + + it('should remove whitespaces from child text nodes', () => { + expect(parseAndRemoveWS('
')).toEqual([ + [html.Element, 'div', 0], + [html.Element, 'span', 1], + ]); + }); + + it('should remove whitespaces from the beginning and end of a template', () => { + expect(parseAndRemoveWS(`
\t`)).toEqual([ + [html.Element, 'br', 0], + ]); + }); + + it('should convert &ngsp; to a space and preserve it', () => { + expect(parseAndRemoveWS('
foo&ngsp;bar
')).toEqual([ + [html.Element, 'div', 0], + [html.Element, 'span', 1], + [html.Text, 'foo', 2], + [html.Text, ' ', 1], + [html.Element, 'span', 1], + [html.Text, 'bar', 2], + ]); + }); + + it('should replace multiple whitespaces with one space', () => { + expect(parseAndRemoveWS('\n\n\nfoo\t\t\t')).toEqual([[html.Text, ' foo ', 0]]); + expect(parseAndRemoveWS(' \n foo \t ')).toEqual([[html.Text, ' foo ', 0]]); + }); + + it('should not replace single tab and newline with spaces', () => { + expect(parseAndRemoveWS('\nfoo')).toEqual([[html.Text, '\nfoo', 0]]); + expect(parseAndRemoveWS('\tfoo')).toEqual([[html.Text, '\tfoo', 0]]); + }); + + it('should preserve single whitespaces between interpolations', () => { + expect(parseAndRemoveWS(`{{fooExp}} {{barExp}}`)).toEqual([ + [html.Text, '{{fooExp}} {{barExp}}', 0], + ]); + expect(parseAndRemoveWS(`{{fooExp}}\t{{barExp}}`)).toEqual([ + [html.Text, '{{fooExp}}\t{{barExp}}', 0], + ]); + expect(parseAndRemoveWS(`{{fooExp}}\n{{barExp}}`)).toEqual([ + [html.Text, '{{fooExp}}\n{{barExp}}', 0], + ]); + }); + + it('should preserve whitespaces around interpolations', () => { + expect(parseAndRemoveWS(` {{exp}} `)).toEqual([ + [html.Text, ' {{exp}} ', 0], + ]); + }); + + it('should preserve whitespaces inside
 elements', () => {
+      expect(parseAndRemoveWS(`
foo\nbar
`)).toEqual([ + [html.Element, 'pre', 0], + [html.Element, 'strong', 1], + [html.Text, 'foo', 2], + [html.Text, '\n', 1], + [html.Element, 'strong', 1], + [html.Text, 'bar', 2], + ]); + }); + + it('should skip whitespace trimming in `)).toEqual([ + [html.Element, 'textarea', 0], + [html.Text, 'foo\n\n bar', 1], + ]); + }); + + it(`should preserve whitespaces inside elements annotated with ${PRESERVE_WS_ATTR_NAME}`, + () => { + expect(parseAndRemoveWS(`
`)).toEqual([ + [html.Element, 'div', 0], + [html.Element, 'img', 1], + [html.Text, ' ', 1], + [html.Element, 'img', 1], + ]); + }); + }); +} diff --git a/packages/compiler/test/ng_module_resolver_mock_spec.ts b/packages/compiler/test/ng_module_resolver_mock_spec.ts index 81ebf312c2..adf792edde 100644 --- a/packages/compiler/test/ng_module_resolver_mock_spec.ts +++ b/packages/compiler/test/ng_module_resolver_mock_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; import {Injector, NgModule} from '@angular/core'; import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; import {MockNgModuleResolver} from '../testing'; @@ -17,7 +17,7 @@ export function main() { let ngModuleResolver: MockNgModuleResolver; beforeEach(inject([Injector], (injector: Injector) => { - ngModuleResolver = new MockNgModuleResolver(injector, new JitReflector()); + ngModuleResolver = new MockNgModuleResolver(new JitReflector()); })); describe('NgModule overriding', () => { diff --git a/packages/compiler/test/ng_module_resolver_spec.ts b/packages/compiler/test/ng_module_resolver_spec.ts index 91d6a466cd..c56dfc1ec2 100644 --- a/packages/compiler/test/ng_module_resolver_spec.ts +++ b/packages/compiler/test/ng_module_resolver_spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; import {NgModuleResolver} from '@angular/compiler/src/ng_module_resolver'; import {ɵstringify as stringify} from '@angular/core'; import {NgModule} from '@angular/core/src/metadata'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; class SomeClass1 {} class SomeClass2 {} diff --git a/packages/compiler/test/output/output_jit_spec.ts b/packages/compiler/test/output/output_jit_spec.ts index b9aad8dadf..5587d4109a 100644 --- a/packages/compiler/test/output/output_jit_spec.ts +++ b/packages/compiler/test/output/output_jit_spec.ts @@ -9,6 +9,7 @@ import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter'; import * as o from '@angular/compiler/src/output/output_ast'; import {JitEmitterVisitor} from '@angular/compiler/src/output/output_jit'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; const anotherModuleUrl = 'somePackage/someOtherPath'; @@ -23,7 +24,7 @@ export function main() { (_, index) => new o.ExternalReference( anotherModuleUrl, `id_${index}_1`, {name: `id_${index}_1`})); const ctx = EmitterVisitorContext.createRoot(); - const converter = new JitEmitterVisitor(); + const converter = new JitEmitterVisitor(new JitReflector()); converter.visitAllStatements( [o.literalArr([...externalIds1, ...externalIds].map(id => o.importExpr(id))).toStmt()], ctx); diff --git a/packages/compiler/test/pipe_resolver_mock_spec.ts b/packages/compiler/test/pipe_resolver_mock_spec.ts index d52b1b494b..4db3e71a6b 100644 --- a/packages/compiler/test/pipe_resolver_mock_spec.ts +++ b/packages/compiler/test/pipe_resolver_mock_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; import {Injector, Pipe} from '@angular/core'; import {inject} from '@angular/core/testing'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; import {MockPipeResolver} from '../testing'; @@ -17,7 +17,7 @@ export function main() { let pipeResolver: MockPipeResolver; beforeEach(inject([Injector], (injector: Injector) => { - pipeResolver = new MockPipeResolver(injector, new JitReflector()); + pipeResolver = new MockPipeResolver(new JitReflector()); })); describe('Pipe overriding', () => { diff --git a/packages/compiler/test/pipe_resolver_spec.ts b/packages/compiler/test/pipe_resolver_spec.ts index 2fd5cf7c23..2e5285c823 100644 --- a/packages/compiler/test/pipe_resolver_spec.ts +++ b/packages/compiler/test/pipe_resolver_spec.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {JitReflector} from '@angular/compiler'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {ɵstringify as stringify} from '@angular/core'; import {Pipe} from '@angular/core/src/metadata'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; @Pipe({name: 'somePipe', pure: true}) class SomePipe { diff --git a/packages/compiler/test/runtime_compiler_spec.ts b/packages/compiler/test/runtime_compiler_spec.ts index 199e9e2827..73bedc3d05 100644 --- a/packages/compiler/test/runtime_compiler_spec.ts +++ b/packages/compiler/test/runtime_compiler_spec.ts @@ -7,7 +7,7 @@ */ import {DirectiveResolver, ResourceLoader} from '@angular/compiler'; -import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵViewMetadata as ViewMetadata, ɵstringify as stringify} from '@angular/core'; +import {Compiler, Component, Injector, NgModule, NgModuleFactory, ɵstringify as stringify} from '@angular/core'; import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {MockDirectiveResolver} from '../testing'; @@ -137,8 +137,9 @@ export function main() { } resourceLoader.spy('get').and.callFake(() => Promise.resolve('')); - dirResolver.setView(SomeComp, new ViewMetadata({template: ''})); - dirResolver.setView(ChildComp, new ViewMetadata({templateUrl: '/someTpl.html'})); + dirResolver.setDirective(SomeComp, new Component({selector: 'some-cmp', template: ''})); + dirResolver.setDirective( + ChildComp, new Component({selector: 'child-cmp', templateUrl: '/someTpl.html'})); expect(() => compiler.compileModuleSync(SomeModule)) .toThrowError( `Can't compile synchronously as ${stringify(ChildComp)} is still being loaded!`); diff --git a/packages/compiler/test/style_url_resolver_spec.ts b/packages/compiler/test/style_url_resolver_spec.ts index 3b135e9925..251380768a 100644 --- a/packages/compiler/test/style_url_resolver_spec.ts +++ b/packages/compiler/test/style_url_resolver_spec.ts @@ -51,6 +51,14 @@ export function main() { expect(styleWithImports.styleUrls).not.toContain('http://ng.io/3.css'); }); + it('should keep /*# sourceURL... */ and /*# sourceMappingURL... */ comments', () => { + const css = + `/*regular comment*/\n/*# sourceURL=.... */\n/*# sourceMappingURL=... *//*#sourceMappingURL=... */`; + const styleWithSourceMaps = extractStyleUrls(urlResolver, 'http://ng.io', css); + expect(styleWithSourceMaps.style.trim()) + .toEqual('/*# sourceURL=.... */\n/*# sourceMappingURL=... *//*#sourceMappingURL=... */'); + }); + it('should extract "@import url()" urls', () => { const css = ` @import url('3.css'); diff --git a/packages/compiler/test/template_parser/template_parser_spec.ts b/packages/compiler/test/template_parser/template_parser_spec.ts index 8204b2f67c..f5142322e5 100644 --- a/packages/compiler/test/template_parser/template_parser_spec.ts +++ b/packages/compiler/test/template_parser/template_parser_spec.ts @@ -5,16 +5,16 @@ * 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 {CompileQueryMetadata, CompilerConfig, JitReflector, ProxyClass, StaticSymbol} from '@angular/compiler'; +import {CompileQueryMetadata, CompilerConfig, ProxyClass, StaticSymbol, preserveWhitespacesDefault} from '@angular/compiler'; import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAstType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '@angular/compiler/src/template_parser/template_ast'; -import {TEMPLATE_TRANSFORMS, TemplateParser, splitClasses} from '@angular/compiler/src/template_parser/template_parser'; -import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/src/test_bindings'; +import {TemplateParser, splitClasses} from '@angular/compiler/src/template_parser/template_parser'; import {ChangeDetectionStrategy, ComponentFactory, RendererType2, SchemaMetadata, SecurityContext, ViewEncapsulation} from '@angular/core'; import {Console} from '@angular/core/src/console'; import {TestBed, inject} from '@angular/core/testing'; +import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_reflector'; import {CompileEntryComponentMetadata, CompileStylesheetMetadata} from '../../src/compile_metadata'; import {Identifiers, createTokenForExternalReference, createTokenForReference} from '../../src/identifiers'; @@ -22,6 +22,7 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_pa import {noUndefined} from '../../src/util'; import {MockSchemaRegistry} from '../../testing'; import {unparse} from '../expression_parser/unparser'; +import {TEST_COMPILER_PROVIDERS} from '../test_bindings'; const someModuleUrl = 'package:someModule'; @@ -37,29 +38,28 @@ function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}): return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []}; } -function compileDirectiveMetadataCreate( - {isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, - providers, viewProviders, queries, viewQueries, entryComponents, template, componentViewType, - rendererType, componentFactory}: { - isHost?: boolean, - type?: CompileTypeMetadata, - isComponent?: boolean, - selector?: string | null, - exportAs?: string | null, - changeDetection?: ChangeDetectionStrategy | null, - inputs?: string[], - outputs?: string[], - host?: {[key: string]: string}, - providers?: CompileProviderMetadata[] | null, - viewProviders?: CompileProviderMetadata[] | null, - queries?: CompileQueryMetadata[] | null, - viewQueries?: CompileQueryMetadata[], - entryComponents?: CompileEntryComponentMetadata[], - template?: CompileTemplateMetadata, - componentViewType?: StaticSymbol | ProxyClass | null, - rendererType?: StaticSymbol | RendererType2 | null, - componentFactory?: StaticSymbol | ComponentFactory - }) { +function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, exportAs, + changeDetection, inputs, outputs, host, providers, + viewProviders, queries, viewQueries, entryComponents, + template, componentViewType, rendererType}: { + isHost?: boolean, + type?: CompileTypeMetadata, + isComponent?: boolean, + selector?: string | null, + exportAs?: string | null, + changeDetection?: ChangeDetectionStrategy | null, + inputs?: string[], + outputs?: string[], + host?: {[key: string]: string}, + providers?: CompileProviderMetadata[] | null, + viewProviders?: CompileProviderMetadata[] | null, + queries?: CompileQueryMetadata[] | null, + viewQueries?: CompileQueryMetadata[], + entryComponents?: CompileEntryComponentMetadata[], + template?: CompileTemplateMetadata, + componentViewType?: StaticSymbol | ProxyClass | null, + rendererType?: StaticSymbol | RendererType2 | null, +}) { return CompileDirectiveMetadata.create({ isHost: !!isHost, type: noUndefined(type) !, @@ -78,13 +78,13 @@ function compileDirectiveMetadataCreate( template: noUndefined(template) !, componentViewType: noUndefined(componentViewType), rendererType: noUndefined(rendererType), - componentFactory: noUndefined(componentFactory), + componentFactory: null, }); } function compileTemplateMetadata({encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations, ngContentSelectors, - interpolation, isInline}: { + interpolation, isInline, preserveWhitespaces}: { encapsulation?: ViewEncapsulation | null, template?: string | null, templateUrl?: string | null, @@ -94,7 +94,8 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles, ngContentSelectors?: string[], animations?: any[], interpolation?: [string, string] | null, - isInline?: boolean + isInline?: boolean, + preserveWhitespaces?: boolean | null, }): CompileTemplateMetadata { return new CompileTemplateMetadata({ encapsulation: noUndefined(encapsulation), @@ -106,7 +107,8 @@ function compileTemplateMetadata({encapsulation, template, templateUrl, styles, animations: animations || [], ngContentSelectors: ngContentSelectors || [], interpolation: noUndefined(interpolation), - isInline: !!isInline + isInline: !!isInline, + preserveWhitespaces: preserveWhitespacesDefault(noUndefined(preserveWhitespaces)), }); } @@ -116,7 +118,7 @@ export function main() { let ngIf: CompileDirectiveSummary; let parse: ( template: string, directives: CompileDirectiveSummary[], pipes?: CompilePipeSummary[], - schemas?: SchemaMetadata[]) => TemplateAst[]; + schemas?: SchemaMetadata[], preserveWhitespaces?: boolean) => TemplateAst[]; let console: ArrayConsole; function commonBeforeEach() { @@ -148,12 +150,15 @@ export function main() { parse = (template: string, directives: CompileDirectiveSummary[], - pipes: CompilePipeSummary[] | null = null, - schemas: SchemaMetadata[] = []): TemplateAst[] => { + pipes: CompilePipeSummary[] | null = null, schemas: SchemaMetadata[] = [], + preserveWhitespaces = true): TemplateAst[] => { if (pipes === null) { pipes = []; } - return parser.parse(component, template, directives, pipes, schemas, 'TestComp') + return parser + .parse( + component, template, directives, pipes, schemas, 'TestComp', + preserveWhitespaces) .template; }; })); @@ -269,38 +274,6 @@ export function main() { }); }); - describe('TemplateParser template transform', () => { - beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); }); - - beforeEach(() => { - TestBed.configureCompiler({ - providers: - [{provide: TEMPLATE_TRANSFORMS, useValue: new FooAstTransformer(), multi: true}] - }); - }); - - describe('single', () => { - commonBeforeEach(); - it('should transform TemplateAST', () => { - expect(humanizeTplAst(parse('
', []))).toEqual([[ElementAst, 'foo']]); - }); - }); - - describe('multiple', () => { - beforeEach(() => { - TestBed.configureCompiler({ - providers: - [{provide: TEMPLATE_TRANSFORMS, useValue: new BarAstTransformer(), multi: true}] - }); - }); - - commonBeforeEach(); - it('should compose transformers', () => { - expect(humanizeTplAst(parse('
', []))).toEqual([[ElementAst, 'bar']]); - }); - }); - }); - describe('TemplateParser Security', () => { // Semi-integration test to make sure TemplateParser properly sets the security context. // Uses the actual DomElementSchemaRegistry. @@ -398,7 +371,8 @@ export function main() { externalStylesheets: [], styleUrls: [], styles: [], - encapsulation: null + encapsulation: null, + preserveWhitespaces: preserveWhitespacesDefault(null), }), isHost: false, exportAs: null, @@ -417,7 +391,7 @@ export function main() { }); expect(humanizeTplAst( - parser.parse(component, '{%a%}', [], [], [], 'TestComp').template, + parser.parse(component, '{%a%}', [], [], [], 'TestComp', true).template, {start: '{%', end: '%}'})) .toEqual([[BoundTextAst, '{% a %}']]); })); @@ -1203,6 +1177,24 @@ Binding to attribute 'onEvent' is disallowed for security reasons (" { + const pizzaTestDirective = + compileDirectiveMetadataCreate({ + selector: 'pizza-test', + type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}), + exportAs: 'pizza, cheeseSauceBread' + }).toSummary(); + + const template = ''; + + expect(humanizeTplAst(parse(template, [pizzaTestDirective]))).toEqual([ + [ElementAst, 'pizza-test'], + [ReferenceAst, 'food', createTokenForReference(pizzaTestDirective.type.reference)], + [ReferenceAst, 'yum', createTokenForReference(pizzaTestDirective.type.reference)], + [DirectiveAst, pizzaTestDirective], + ]); + }); + it('should report references with values that dont match a directive as errors', () => { expect(() => parse('
', [])).toThrowError(`Template parse errors: There is no directive with "exportAs" set to "dirA" ("
]#a="dirA">
"): TestComp@0:5`); @@ -1225,6 +1217,31 @@ Reference "#a" is defined several times ("
]#a>
}); + it('should report duplicate reference names when using mutliple exportAs names', () => { + const pizzaDirective = + compileDirectiveMetadataCreate({ + selector: '[dessert-pizza]', + type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Pizza'}}), + exportAs: 'dessertPizza, chocolate' + }).toSummary(); + + const chocolateDirective = + compileDirectiveMetadataCreate({ + selector: '[chocolate]', + type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'Chocolate'}}), + exportAs: 'chocolate' + }).toSummary(); + + const template = '
'; + const compileTemplate = () => parse(template, [pizzaDirective, chocolateDirective]); + const duplicateReferenceError = 'Template parse errors:\n' + + 'Reference "#snack" is defined several times ' + + '("
]#snack="chocolate">
")' + + ': TestComp@0:29'; + + expect(compileTemplate).toThrowError(duplicateReferenceError); + }); + it('should not throw error when there is same reference name in different templates', () => { expect(() => parse('
', [])) @@ -2052,6 +2069,66 @@ The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`); }); }); + describe('whitespaces removal', () => { + + beforeEach(() => { + TestBed.configureCompiler({providers: [TEST_COMPILER_PROVIDERS, MOCK_SCHEMA_REGISTRY]}); + }); + + commonBeforeEach(); + + it('should not remove whitespaces by default', () => { + expect(humanizeTplAst(parse('

\t
\n
', []))).toEqual([ + [TextAst, ' '], + [ElementAst, 'br'], + [TextAst, ' '], + [ElementAst, 'br'], + [TextAst, '\t'], + [ElementAst, 'br'], + [TextAst, '\n'], + [ElementAst, 'br'], + [TextAst, ' '], + ]); + }); + + it('should replace each &ngsp; with a space when preserveWhitespaces is true', () => { + expect(humanizeTplAst(parse('foo&ngsp;&ngsp;&ngsp;bar', [], [], [], true))).toEqual([ + [TextAst, 'foo bar'], + ]); + }); + + it('should replace every &ngsp; with a single space when preserveWhitespaces is false', () => { + expect(humanizeTplAst(parse('foo&ngsp;&ngsp;&ngsp;bar', [], [], [], false))).toEqual([ + [TextAst, 'foo bar'], + ]); + }); + + it('should remove whitespaces when explicitly requested', () => { + expect(humanizeTplAst(parse('

\t
\n
', [], [], [], false))).toEqual([ + [ElementAst, 'br'], + [ElementAst, 'br'], + [ElementAst, 'br'], + [ElementAst, 'br'], + ]); + }); + + it('should remove whitespace between ICU expansions when not preserving whitespaces', () => { + const shortForm = '{ count, plural, =0 {small} many {big} }'; + const expandedForm = '' + + 'small' + + 'big' + + ''; + const humanizedExpandedForm = humanizeTplAst(parse(expandedForm, [])); + + // ICU expansions are converted to `` tags and all blank text nodes are reomved + // so any whitespace between ICU exansions are removed as well + expect(humanizeTplAst(parse(`${shortForm} ${shortForm}`, [], [], [], false))).toEqual([ + ...humanizedExpandedForm, ...humanizedExpandedForm + ]); + }); + + }); + describe('Template Parser - opt-out `