From 5cb36ed706c909f1470ae1bdb988ab486be1197c Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Fri, 4 May 2018 15:08:17 -0700 Subject: [PATCH 001/582] test: fix firebase deployment script test When I fixed the project id in 2c4850dc582287b7c34d4d26066fe4993638cbf0, I didn't realize we had a test that verified the wrong behavior. --- aio/scripts/deploy-to-firebase.test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/scripts/deploy-to-firebase.test.sh b/aio/scripts/deploy-to-firebase.test.sh index 6093a3ab0e..0ffea8e128 100755 --- a/aio/scripts/deploy-to-firebase.test.sh +++ b/aio/scripts/deploy-to-firebase.test.sh @@ -94,7 +94,7 @@ Deployment URL : https://angular.io/" ) expected="Git branch : 2.4.x Build/deploy mode : archive -Firebase project : angular-io-2 +Firebase project : v2-angular-io Deployment URL : https://v2.angular.io/" check "$actual" "$expected" ) From 14d8a980014900254c48928a7f7e01639f209f68 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 25 Apr 2018 13:40:49 +0300 Subject: [PATCH 002/582] refactor(aio): include print styles last to overwrite other styles (#23538) PR Close #23538 --- aio/src/styles/1-layouts/_layouts-dir.scss | 1 - .../styles/{1-layouts/_print-layout.scss => _print.scss} | 8 ++++---- aio/src/styles/main.scss | 3 +++ 3 files changed, 7 insertions(+), 5 deletions(-) rename aio/src/styles/{1-layouts/_print-layout.scss => _print.scss} (97%) diff --git a/aio/src/styles/1-layouts/_layouts-dir.scss b/aio/src/styles/1-layouts/_layouts-dir.scss index c6f80728b4..93311dc79d 100644 --- a/aio/src/styles/1-layouts/_layouts-dir.scss +++ b/aio/src/styles/1-layouts/_layouts-dir.scss @@ -12,4 +12,3 @@ @import 'sidenav'; @import 'table-of-contents'; @import 'top-menu'; -@import 'print-layout'; \ No newline at end of file diff --git a/aio/src/styles/1-layouts/_print-layout.scss b/aio/src/styles/_print.scss similarity index 97% rename from aio/src/styles/1-layouts/_print-layout.scss rename to aio/src/styles/_print.scss index da26469d5a..38044f59b9 100644 --- a/aio/src/styles/1-layouts/_print-layout.scss +++ b/aio/src/styles/_print.scss @@ -16,7 +16,7 @@ ul, ol, img, code-example, table, tr, .alert, .l-subsection, .feature { page-break-inside: avoid; - } + } table tbody tr:last-child td { border-bottom: 1px solid $lightgray !important; @@ -80,7 +80,7 @@ } } - .content code { + .content code { border: 0.5px solid $lightgray; } @@ -96,7 +96,7 @@ } } - .api-header label { + .api-header label { color: $darkgray !important; font-weight: bold !important; margin: 2px !important; @@ -107,4 +107,4 @@ .feature-section img { max-width: 70px !important; } -} \ No newline at end of file +} diff --git a/aio/src/styles/main.scss b/aio/src/styles/main.scss index 2809816ed8..8ca81bc8f8 100755 --- a/aio/src/styles/main.scss +++ b/aio/src/styles/main.scss @@ -12,3 +12,6 @@ @import './0-base/base-dir'; @import './1-layouts/layouts-dir'; @import './2-modules/modules-dir'; + +// import print styles +@import './print'; From 3f20a5c7c8f9125755063ff90ee38faa830a1c3d Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 25 Apr 2018 13:47:14 +0300 Subject: [PATCH 003/582] refactor(aio): use the same selectors for screen and print styles (#23538) PR Close #23538 --- aio/src/styles/2-modules/_code.scss | 2 +- aio/src/styles/_print.scss | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index 28396360e2..b2806bfa25 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -179,7 +179,7 @@ ol.linenums { /* SHELL / TERMINAL CODE BLOCKS */ code-example.code-shell, code-example[language=sh], code-example[language=bash] { - & .pnk, .blk,.pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec { + .pnk, .blk, .pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec { color: $codegreen; } } diff --git a/aio/src/styles/_print.scss b/aio/src/styles/_print.scss index 38044f59b9..f15de7d455 100644 --- a/aio/src/styles/_print.scss +++ b/aio/src/styles/_print.scss @@ -66,12 +66,10 @@ } code-example { - pre.lang-bash code span { - color: $mediumgray !important; - } - - pre.lang-sh code span { - color: $darkgray !important; + &.code-shell, &[language=sh], &[language=bash] { + .pnk, .blk, .pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec { + color: $darkgray; + } } header { From 57cf5509e68ec6b3bdcc667d12356013ba6c74b1 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 25 Apr 2018 13:48:44 +0300 Subject: [PATCH 004/582] fix(aio): fix `code-example` print styles when printing backgrounds (#23538) Fixes #23431 PR Close #23538 --- aio/src/styles/_print.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aio/src/styles/_print.scss b/aio/src/styles/_print.scss index f15de7d455..07aa7c20f3 100644 --- a/aio/src/styles/_print.scss +++ b/aio/src/styles/_print.scss @@ -67,12 +67,15 @@ code-example { &.code-shell, &[language=sh], &[language=bash] { + background: none; + .pnk, .blk, .pln, .otl, .kwd, .typ, .tag, .str, .atv, .atn, .com, .lit, .pun, .dec { color: $darkgray; } } header { + background: none; border: 0.5px solid $lightgray; color: $darkgray; } From 3b067c85795c258f6583e4d3425137a0bdadd857 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 25 Apr 2018 13:50:31 +0300 Subject: [PATCH 005/582] fix(aio): remove main background color when printing (#23538) PR Close #23538 --- aio/src/styles/_print.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aio/src/styles/_print.scss b/aio/src/styles/_print.scss index 07aa7c20f3..6364dcf795 100644 --- a/aio/src/styles/_print.scss +++ b/aio/src/styles/_print.scss @@ -5,6 +5,10 @@ box-shadow: none !important; } + body, mat-sidenav-container { + background: none !important; + } + h1 { height: 40px !important; color: $darkgray !important; From b25e15c31720d241a98d589af577ac7d3e7231d7 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Thu, 3 May 2018 15:55:38 -0700 Subject: [PATCH 006/582] feat(aio): add v6 release notification (#23690) PR Close #23690 --- aio/src/app/app.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index 3519891b06..1e7ad72a53 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -8,13 +8,13 @@ - Help Angular by taking a 1 minute survey! + Version 6 of Angular Now Available! From e3e15773eeec6a08f1333a87ebc6d10927a48a59 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Mon, 30 Apr 2018 12:48:24 -0700 Subject: [PATCH 007/582] build: update bazel to 0.13 (#23623) PR Close #23623 --- .circleci/config.yml | 4 ++-- WORKSPACE | 12 ++++++------ tools/ngcontainer/Dockerfile | 2 +- tools/ngcontainer/README.md | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f374b332c6..236e4d1bc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,8 +12,8 @@ ## IMPORTANT # If you change the `docker_image` version, also change the `cache_key` suffix and the version of # `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file. -var_1: &docker_image angular/ngcontainer:0.2.0 -var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.2.0 +var_1: &docker_image angular/ngcontainer:0.3.0 +var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.0 # Define common ENV vars var_3: &define_env_vars diff --git a/WORKSPACE b/WORKSPACE index 75bf37f86b..66e4591dd7 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -9,7 +9,7 @@ http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") -check_bazel_version("0.11.1") +check_bazel_version("0.13.0") node_repositories(package_json = ["//:package.json"]) yarn_install( @@ -50,13 +50,13 @@ local_repository( # This commit matches the version of buildifier in angular/ngcontainer # If you change this, also check if it matches the version in the angular/ngcontainer # version in /.circleci/config.yml -BAZEL_BUILDTOOLS_VERSION = "70bc7843bb9950fece2bc014ed16de03419e36e2" +BAZEL_BUILDTOOLS_VERSION = "fd9878fd5de921e0bbab3dcdcb932c2627812ee1" http_archive( name = "com_github_bazelbuild_buildtools", url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION, strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION, - sha256 = "367c23a5fe7fc2a7cb57863d3718b4149f0e57426c48c8ad54c45348a0b53cc1", + sha256 = "27bb461ade23fd44ba98723ad98f84ee9c83cd3540b773b186a1bc5037f3d862", ) http_archive( @@ -74,9 +74,9 @@ go_register_toolchains() # Fetching the Bazel source code allows us to compile the Skylark linter http_archive( name = "io_bazel", - url = "https://github.com/bazelbuild/bazel/archive/5a35e72f9e97c06540c479f8c31512fb4656202f.zip", - strip_prefix = "bazel-5a35e72f9e97c06540c479f8c31512fb4656202f", - sha256 = "ed33a52874c14e3b487fb50f390c541fab9c81a33d986d38fb01766a66dbcd21", + url = "https://github.com/bazelbuild/bazel/archive/968f87900dce45a7af749a965b72dbac51b176b3.zip", + strip_prefix = "bazel-968f87900dce45a7af749a965b72dbac51b176b3", + sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f", ) # We have a source dependency on the Devkit repository, because it's built with diff --git a/tools/ngcontainer/Dockerfile b/tools/ngcontainer/Dockerfile index 69828df569..0c3f41421f 100644 --- a/tools/ngcontainer/Dockerfile +++ b/tools/ngcontainer/Dockerfile @@ -19,7 +19,7 @@ RUN JAVA_DEBIAN_VERSION="8u131-b11-1~bpo8+1" \ ### # Bazel install # See https://bazel.build/versions/master/docs/install-ubuntu.html#using-bazel-custom-apt-repository-recommended -RUN BAZEL_VERSION="0.11.1" \ +RUN BAZEL_VERSION="0.13.0" \ && wget -q -O - https://bazel.build/bazel-release.pub.gpg | apt-key add - \ && echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" > /etc/apt/sources.list.d/bazel.list \ && apt-get update \ diff --git a/tools/ngcontainer/README.md b/tools/ngcontainer/README.md index 9037feadf2..0a9e0e9d9e 100644 --- a/tools/ngcontainer/README.md +++ b/tools/ngcontainer/README.md @@ -6,7 +6,7 @@ This docker container provides everything needed to build and test Angular appli - npm 5.5.1 - yarn 1.3.2 - Java 8 (for Closure Compiler and Bazel) -- Bazel build tool v0.11.1 - http://bazel.build +- Bazel build tool v0.13.0 - http://bazel.build - Google Chrome 63.0.3239.84 - Mozilla Firefox 47.0.1 - xvfb (virtual framebuffer) for headless testing From 7e9649bdf1483885d61142172be3f709cde051e8 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Thu, 3 May 2018 11:55:56 -0700 Subject: [PATCH 008/582] build: update to latest nodejs bazel rules (#23683) PR Close #23683 --- WORKSPACE | 6 +++--- integration/bazel/WORKSPACE | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 66e4591dd7..9328952214 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,9 +2,9 @@ workspace(name = "angular") http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/1931156c232a08356dfda02e9c8b0275c2e63c00.zip", - strip_prefix = "rules_nodejs-1931156c232a08356dfda02e9c8b0275c2e63c00", - sha256 = "9cfe33276a6ac0076ee9ee159c4a2576f9851c0f437435b5ac19b2e592493078", + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip", + strip_prefix = "rules_nodejs-0.8.0", + sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953", ) load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index a441cfcd15..e91d724fa3 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -2,9 +2,9 @@ workspace(name = "bazel_integration_test") http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/cd368bd71a4b04fae0eafb5c5e2c906a93772584.zip", - strip_prefix = "rules_nodejs-cd368bd71a4b04fae0eafb5c5e2c906a93772584", - sha256 = "db74c61dd8bf73cc50aed56e78b7a8ad383f5869206901506cf8d3ee27f9277f", + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip", + strip_prefix = "rules_nodejs-0.8.0", + sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953", ) load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories") From 005dc8f68b9d09f2a4db46bdbfae3e05d3604818 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 3 May 2018 10:30:58 +0100 Subject: [PATCH 009/582] docs(animations): fix content errors (#23668) PR Close #23668 --- packages/animations/src/animation_metadata.ts | 120 ++++++++++-------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts index cd0e463c06..a139972f21 100755 --- a/packages/animations/src/animation_metadata.ts +++ b/packages/animations/src/animation_metadata.ts @@ -236,8 +236,8 @@ export declare interface AnimationQueryOptions extends AnimationOptions { * Metadata representing the entry of animations. Instances of this interface are provided via the * animation DSL when the {@link stagger stagger animation function} is called. * -* @experimental Animation support is experimental. -*/ + * @experimental Animation support is experimental. + */ export interface AnimationStaggerMetadata extends AnimationMetadata { timings: string|number; animation: AnimationMetadata|AnimationMetadata[]; @@ -256,15 +256,14 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * Triggers are registered within the component annotation data under the * {@link Component#animations animations section}. An animation trigger can be placed on an element * within a template by referencing the name of the trigger followed by the expression value that - the - * trigger is bound to (in the form of `[@triggerName]="expression"`. + * the trigger is bound to (in the form of `[@triggerName]="expression"`. * * Animation trigger bindings strigify values and then match the previous and current values against * any linked transitions. If a boolean value is provided into the trigger binding then it will both * be represented as `1` or `true` and `0` or `false` for a true and false boolean values * respectively. * - * ### Usage + * **Usage** * * `trigger` will create an animation trigger reference based on the provided `name` value. The * provided `animation` value is expected to be an array consisting of {@link state state} and @@ -289,22 +288,27 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * ``` * * The template associated with this component will make use of the `myAnimationTrigger` animation - trigger by binding to an element within its template code. + * trigger by binding to an element within its template code. * * ```html * *
...
* ``` * - * ### Using an inline function + * **Using an inline function** + * * The `transition` animation method also supports reading an inline function which can decide * if its associated animation should be run. * * ``` * // this method will be run each time the `myAnimationTrigger` * // trigger value changes... - * function myInlineMatcherFn(fromState: string, toState: string, element: any, params: {[key: - string]: any}): boolean { + * function myInlineMatcherFn( + * fromState: string, + * toState: string, + * element: any, + * params: {[key: string]: any} + * ): boolean { * // notice that `element` and `params` are also available here * return toState == 'yes-please-animate'; * } @@ -328,13 +332,14 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * The inline method will be run each time the trigger * value changes * - * ## Disable 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 as well as - any animations on the element itself. + * 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 all animations from rendering. The example - below shows how to use this feature: + * below shows how to use this feature: * * ```ts * @Component({ @@ -357,16 +362,17 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * ``` * * The `@childAnimation` trigger will not animate because `@.disabled` prevents it from happening - (when true). + * (when true). * * Note that `@.disabled` will only disable all animations (this means any animations running on * the same element will also be disabled). * - * ### Disabling Animations Application-wide + * **Disabling Animations Application-wide** + * * When an area of the template is set to have animations disabled, **all** inner components will - also have their animations disabled as well. This means that all animations for an angular - application can be disabled by placing a host binding set on `@.disabled` on the topmost Angular - component. + * also have their animations disabled as well. This means that all animations for an angular + * application can be disabled by placing a host binding set on `@.disabled` on the topmost Angular + * component. * * ```ts * import {Component, HostBinding} from '@angular/core'; @@ -381,19 +387,20 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * } * ``` * - * ### What about animations that us `query()` and `animateChild()`? - * Despite inner animations being disabled, a parent animation can {@link query query} for inner - elements located in disabled areas of the template and still animate them as it sees fit. This is - also the case for when a sub animation is queried by a parent and then later animated using {@link - animateChild animateChild}. - - * ### Detecting when an animation is disabled + * **What about animations that use `query()` and `animateChild()`?** + * + * Despite inner animations being disabled, a parent animation can `query` for inner + * elements located in disabled areas of the template and still animate them as it sees fit. This is + * also the case for when a sub animation is queried by a parent and then later animated using + * animateChild`. + * + * **Detecting when an animation is disabled** + * * If a region of the DOM (or the entire application) has its animations disabled, then animation * trigger callbacks will still fire just as normal (only for zero seconds). * * When a trigger callback fires it will provide an instance of an {@link AnimationEvent}. If - animations - * are disabled then the `.disabled` flag on the event will be true. + * animations are disabled then the `.disabled` flag on the event will be true. * * @experimental Animation support is experimental. */ @@ -403,8 +410,8 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati /** * `animate` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the {@link - * Component#animations component animations metadata page} to gain a better understanding of + * animation DSL language. If this information is new, please navigate to the + * {@link Component#animations component animations metadata page} to gain a better understanding of * how animations in Angular are used. * * `animate` specifies an animation step that will apply the provided `styles` data for a given @@ -412,7 +419,8 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati * to be used within {@link sequence an animation sequence}, {@link group group}, or {@link * transition transition}. * - * ### Usage + * {@a usage} + * **Usage** * * The `animate` function accepts two input parameters: `timing` and `styles`: * @@ -465,7 +473,7 @@ export function animate( * transition} and it will only continue to the next instruction once all of the inner animation * steps have completed. * - * ### Usage + * **Usage** * * The `steps` data that is passed into the `group` animation function can either consist of {@link * style style} or {@link animate animate} function calls. Each call to `style()` or `animate()` @@ -504,7 +512,7 @@ export function group( * To perform animation styling in parallel with other animation steps then have a look at the * {@link group group} animation function. * - * ### Usage + * **Usage** * * The `steps` data that is passed into the `sequence` animation function can either consist of * {@link style style} or {@link animate animate} function calls. A call to `style()` will apply the @@ -529,15 +537,15 @@ export function sequence(steps: AnimationMetadata[], options: AnimationOptions | /** * `style` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the {@link - * Component#animations component animations metadata page} to gain a better understanding of + * animation DSL language. If this information is new, please navigate to the + * {@link Component#animations component animations metadata page} to gain a better understanding of * how animations in Angular are used. * * `style` declares a key/value object containing CSS properties/styles that can then be used for * {@link state animation states}, within an {@link sequence animation sequence}, or as styling data * for both {@link animate animate} and {@link keyframes keyframes}. * - * ### Usage + * **Usage** * * `style` takes in a key/value string map as data and expects one or more CSS property/value pairs * to be defined. @@ -550,7 +558,7 @@ export function sequence(steps: AnimationMetadata[], options: AnimationOptions | * style({ width: 100, height: 0 }) * ``` * - * #### Auto-styles (using `*`) + * **Auto-styles (using `*`)** * * When an asterix (`*`) character is used as a value then it will be detected from the element * being animated and applied as animation data when the animation starts. @@ -589,18 +597,18 @@ export function style( * function. To register states to an animation trigger please have a look at the {@link trigger * trigger} function. * - * #### The `void` state + * **The `void` state** * * The `void` state value is a reserved word that angular uses to determine when the element is not * apart of the application anymore (e.g. when an `ngIf` evaluates to false then the state of the * associated element is void). * - * #### The `*` (default) state + * **The `*` (default) state** * * The `*` state (when styled) is a fallback state that will be used if the state that is being * animated is not declared within the trigger. * - * ### Usage + * **Usage** * * `state` will declare an animation state with its associated styles * within the given trigger. @@ -631,14 +639,14 @@ export function state( /** * `keyframes` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the {@link - * Component#animations component animations metadata page} to gain a better understanding of - * how animations in Angular are used. + * animation DSL language. If this information is new, please navigate to the + * {@link Component#animations component animations metadata page} to gain a better understanding + * of how animations in Angular are used. * * `keyframes` specifies a collection of {@link style style} entries each optionally characterized * by an `offset` value. * - * ### Usage + * **Usage** * * The `keyframes` animation function is designed to be used alongside the {@link animate animate} * animation function. Instead of applying animations from where they are currently to their @@ -697,7 +705,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * to animate to a state value and persist its styles then one or more {@link state animation * states} is expected to be defined. * - * ### Usage + * **Usage** * * An animation transition is kicked off the `stateChangeExpr` predicate evaluates to true based on * what the previous state is and what the current state has become. In other words, if a transition @@ -748,7 +756,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe *
...
* ``` * - * #### The final `animate` call + * {@a the-final-animate-call} + * **The final `animate` call** * * If the final step within the transition steps is a call to `animate()` that **only** uses a * timing value with **no style data** then it will be automatically used as the final animation arc @@ -765,7 +774,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * ### Using :enter and :leave + * **Using :enter and :leave** * * Given that enter (insertion) and leave (removal) animations are so common, the `transition` * function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `* @@ -781,7 +790,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * ### Boolean values + * **Boolean values** + * * if a trigger binding value is a boolean value then it can be matched using a transition * expression that compares `true` and `false` or `1` and `0`. * @@ -797,7 +807,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * ### Using :increment and :decrement + * **Using :increment and :decrement** + * * In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases * can be used to kick off a transition when a numeric value has increased or decreased in value. * @@ -1006,7 +1017,8 @@ export function animation( * * Now each of the sub animations start off with respect to the `100ms` staggering step. * - * ## The first frame of child animations + * **The first frame of child animations** + * * When sub animations are executed using `animateChild` the animation engine will always apply the * first frame of every sub animation immediately at the start of the animation sequence. This way * the parent animation does not need to set any initial styling data on the sub elements before the @@ -1050,7 +1062,7 @@ export function useAnimation( * to the queried element (by default, an array is provided, then this will be * treated as an animation sequence). * - * ### Usage + * **Usage** * * query() is designed to collect multiple elements and works internally by using * `element.querySelectorAll`. An additional options object can be provided which @@ -1073,7 +1085,8 @@ export function useAnimation( * ], { optional: true }) * ``` * - * ### Special Selector Values + * **Special Selector Values** + * * * The selector value within a query can collect elements that contain angular-specific * characteristics @@ -1095,7 +1108,8 @@ export function useAnimation( * query(':self, .record:enter, .record:leave, @subTrigger', [...]) * ``` * - * ### Demo + * **Demo** + * * * ``` * @Component({ @@ -1144,7 +1158,7 @@ export function query( * animation DSL language. It is designed to be used inside of an animation {@link query query()} * and works by issuing a timing gap between after each queried item is animated. * - * ### Usage + * **Usage** * * In the example below there is a container element that wraps a list of items stamped out * by an ngFor. The container element contains an animation trigger that will later be set From e3518967ad705b133ee21eedcf9546b5cb2e817e Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 3 May 2018 10:31:30 +0100 Subject: [PATCH 010/582] style(animations): fix short param names (#23668) PR Close #23668 --- packages/animations/src/players/animation_player.ts | 4 ++-- tools/public_api_guard/animations/animations.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/animations/src/players/animation_player.ts b/packages/animations/src/players/animation_player.ts index de3dfb4ffa..d88f733b19 100644 --- a/packages/animations/src/players/animation_player.ts +++ b/packages/animations/src/players/animation_player.ts @@ -26,7 +26,7 @@ export interface AnimationPlayer { finish(): void; destroy(): void; reset(): void; - setPosition(p: any /** TODO #9100 */): void; + setPosition(position: number): void; getPosition(): number; parentPlayer: AnimationPlayer|null; readonly totalTime: number; @@ -93,7 +93,7 @@ export class NoopAnimationPlayer implements AnimationPlayer { } } reset(): void {} - setPosition(p: number): void {} + setPosition(position: number): void {} getPosition(): number { return 0; } /* @internal */ diff --git a/tools/public_api_guard/animations/animations.d.ts b/tools/public_api_guard/animations/animations.d.ts index 07f51a54f8..b0f5a005b0 100644 --- a/tools/public_api_guard/animations/animations.d.ts +++ b/tools/public_api_guard/animations/animations.d.ts @@ -115,7 +115,7 @@ export interface AnimationPlayer { play(): void; reset(): void; restart(): void; - setPosition(p: any): void; + setPosition(position: number): void; } /** @experimental */ @@ -216,7 +216,7 @@ export declare class NoopAnimationPlayer implements AnimationPlayer { play(): void; reset(): void; restart(): void; - setPosition(p: number): void; + setPosition(position: number): void; } /** @experimental */ From 44095d95c97f7b89de98ad0eec21650324b88319 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 5 May 2018 08:34:25 -0700 Subject: [PATCH 011/582] Revert "docs(animations): fix content errors (#23668)" This reverts commit 005dc8f68b9d09f2a4db46bdbfae3e05d3604818. The PR accidently introduced a breaking change https://github.com/angular/angular/pull/23668#discussion_r186265055 --- packages/animations/src/animation_metadata.ts | 120 ++++++++---------- 1 file changed, 53 insertions(+), 67 deletions(-) diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts index a139972f21..cd0e463c06 100755 --- a/packages/animations/src/animation_metadata.ts +++ b/packages/animations/src/animation_metadata.ts @@ -236,8 +236,8 @@ export declare interface AnimationQueryOptions extends AnimationOptions { * Metadata representing the entry of animations. Instances of this interface are provided via the * animation DSL when the {@link stagger stagger animation function} is called. * - * @experimental Animation support is experimental. - */ +* @experimental Animation support is experimental. +*/ export interface AnimationStaggerMetadata extends AnimationMetadata { timings: string|number; animation: AnimationMetadata|AnimationMetadata[]; @@ -256,14 +256,15 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * Triggers are registered within the component annotation data under the * {@link Component#animations animations section}. An animation trigger can be placed on an element * within a template by referencing the name of the trigger followed by the expression value that - * the trigger is bound to (in the form of `[@triggerName]="expression"`. + the + * trigger is bound to (in the form of `[@triggerName]="expression"`. * * Animation trigger bindings strigify values and then match the previous and current values against * any linked transitions. If a boolean value is provided into the trigger binding then it will both * be represented as `1` or `true` and `0` or `false` for a true and false boolean values * respectively. * - * **Usage** + * ### Usage * * `trigger` will create an animation trigger reference based on the provided `name` value. The * provided `animation` value is expected to be an array consisting of {@link state state} and @@ -288,27 +289,22 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * ``` * * The template associated with this component will make use of the `myAnimationTrigger` animation - * trigger by binding to an element within its template code. + trigger by binding to an element within its template code. * * ```html * *
...
* ``` * - * **Using an inline function** - * + * ### Using an inline function * The `transition` animation method also supports reading an inline function which can decide * if its associated animation should be run. * * ``` * // this method will be run each time the `myAnimationTrigger` * // trigger value changes... - * function myInlineMatcherFn( - * fromState: string, - * toState: string, - * element: any, - * params: {[key: string]: any} - * ): boolean { + * function myInlineMatcherFn(fromState: string, toState: string, element: any, params: {[key: + string]: any}): boolean { * // notice that `element` and `params` are also available here * return toState == 'yes-please-animate'; * } @@ -332,14 +328,13 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * The inline method will be run each time the trigger * value changes * - * **Disable 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 as well as - * any animations on the element itself. + 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 all animations from rendering. The example - * below shows how to use this feature: + below shows how to use this feature: * * ```ts * @Component({ @@ -362,17 +357,16 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * ``` * * The `@childAnimation` trigger will not animate because `@.disabled` prevents it from happening - * (when true). + (when true). * * Note that `@.disabled` will only disable all animations (this means any animations running on * the same element will also be disabled). * - * **Disabling Animations Application-wide** - * + * ### Disabling Animations Application-wide * When an area of the template is set to have animations disabled, **all** inner components will - * also have their animations disabled as well. This means that all animations for an angular - * application can be disabled by placing a host binding set on `@.disabled` on the topmost Angular - * component. + also have their animations disabled as well. This means that all animations for an angular + application can be disabled by placing a host binding set on `@.disabled` on the topmost Angular + component. * * ```ts * import {Component, HostBinding} from '@angular/core'; @@ -387,20 +381,19 @@ export interface AnimationStaggerMetadata extends AnimationMetadata { * } * ``` * - * **What about animations that use `query()` and `animateChild()`?** - * - * Despite inner animations being disabled, a parent animation can `query` for inner - * elements located in disabled areas of the template and still animate them as it sees fit. This is - * also the case for when a sub animation is queried by a parent and then later animated using - * animateChild`. - * - * **Detecting when an animation is disabled** - * + * ### What about animations that us `query()` and `animateChild()`? + * Despite inner animations being disabled, a parent animation can {@link query query} for inner + elements located in disabled areas of the template and still animate them as it sees fit. This is + also the case for when a sub animation is queried by a parent and then later animated using {@link + animateChild animateChild}. + + * ### Detecting when an animation is disabled * If a region of the DOM (or the entire application) has its animations disabled, then animation * trigger callbacks will still fire just as normal (only for zero seconds). * * When a trigger callback fires it will provide an instance of an {@link AnimationEvent}. If - * animations are disabled then the `.disabled` flag on the event will be true. + animations + * are disabled then the `.disabled` flag on the event will be true. * * @experimental Animation support is experimental. */ @@ -410,8 +403,8 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati /** * `animate` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the - * {@link Component#animations component animations metadata page} to gain a better understanding of + * animation DSL language. If this information is new, please navigate to the {@link + * Component#animations component animations metadata page} to gain a better understanding of * how animations in Angular are used. * * `animate` specifies an animation step that will apply the provided `styles` data for a given @@ -419,8 +412,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati * to be used within {@link sequence an animation sequence}, {@link group group}, or {@link * transition transition}. * - * {@a usage} - * **Usage** + * ### Usage * * The `animate` function accepts two input parameters: `timing` and `styles`: * @@ -473,7 +465,7 @@ export function animate( * transition} and it will only continue to the next instruction once all of the inner animation * steps have completed. * - * **Usage** + * ### Usage * * The `steps` data that is passed into the `group` animation function can either consist of {@link * style style} or {@link animate animate} function calls. Each call to `style()` or `animate()` @@ -512,7 +504,7 @@ export function group( * To perform animation styling in parallel with other animation steps then have a look at the * {@link group group} animation function. * - * **Usage** + * ### Usage * * The `steps` data that is passed into the `sequence` animation function can either consist of * {@link style style} or {@link animate animate} function calls. A call to `style()` will apply the @@ -537,15 +529,15 @@ export function sequence(steps: AnimationMetadata[], options: AnimationOptions | /** * `style` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the - * {@link Component#animations component animations metadata page} to gain a better understanding of + * animation DSL language. If this information is new, please navigate to the {@link + * Component#animations component animations metadata page} to gain a better understanding of * how animations in Angular are used. * * `style` declares a key/value object containing CSS properties/styles that can then be used for * {@link state animation states}, within an {@link sequence animation sequence}, or as styling data * for both {@link animate animate} and {@link keyframes keyframes}. * - * **Usage** + * ### Usage * * `style` takes in a key/value string map as data and expects one or more CSS property/value pairs * to be defined. @@ -558,7 +550,7 @@ export function sequence(steps: AnimationMetadata[], options: AnimationOptions | * style({ width: 100, height: 0 }) * ``` * - * **Auto-styles (using `*`)** + * #### Auto-styles (using `*`) * * When an asterix (`*`) character is used as a value then it will be detected from the element * being animated and applied as animation data when the animation starts. @@ -597,18 +589,18 @@ export function style( * function. To register states to an animation trigger please have a look at the {@link trigger * trigger} function. * - * **The `void` state** + * #### The `void` state * * The `void` state value is a reserved word that angular uses to determine when the element is not * apart of the application anymore (e.g. when an `ngIf` evaluates to false then the state of the * associated element is void). * - * **The `*` (default) state** + * #### The `*` (default) state * * The `*` state (when styled) is a fallback state that will be used if the state that is being * animated is not declared within the trigger. * - * **Usage** + * ### Usage * * `state` will declare an animation state with its associated styles * within the given trigger. @@ -639,14 +631,14 @@ export function state( /** * `keyframes` is an animation-specific function that is designed to be used inside of Angular's - * animation DSL language. If this information is new, please navigate to the - * {@link Component#animations component animations metadata page} to gain a better understanding - * of how animations in Angular are used. + * animation DSL language. If this information is new, please navigate to the {@link + * Component#animations component animations metadata page} to gain a better understanding of + * how animations in Angular are used. * * `keyframes` specifies a collection of {@link style style} entries each optionally characterized * by an `offset` value. * - * **Usage** + * ### Usage * * The `keyframes` animation function is designed to be used alongside the {@link animate animate} * animation function. Instead of applying animations from where they are currently to their @@ -705,7 +697,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * to animate to a state value and persist its styles then one or more {@link state animation * states} is expected to be defined. * - * **Usage** + * ### Usage * * An animation transition is kicked off the `stateChangeExpr` predicate evaluates to true based on * what the previous state is and what the current state has become. In other words, if a transition @@ -756,8 +748,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe *
...
* ``` * - * {@a the-final-animate-call} - * **The final `animate` call** + * #### The final `animate` call * * If the final step within the transition steps is a call to `animate()` that **only** uses a * timing value with **no style data** then it will be automatically used as the final animation arc @@ -774,7 +765,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * **Using :enter and :leave** + * ### Using :enter and :leave * * Given that enter (insertion) and leave (removal) animations are so common, the `transition` * function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `* @@ -790,8 +781,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * **Boolean values** - * + * ### Boolean values * if a trigger binding value is a boolean value then it can be matched using a transition * expression that compares `true` and `false` or `1` and `0`. * @@ -807,8 +797,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe * ]) * ``` * - * **Using :increment and :decrement** - * + * ### Using :increment and :decrement * In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases * can be used to kick off a transition when a numeric value has increased or decreased in value. * @@ -1017,8 +1006,7 @@ export function animation( * * Now each of the sub animations start off with respect to the `100ms` staggering step. * - * **The first frame of child animations** - * + * ## The first frame of child animations * When sub animations are executed using `animateChild` the animation engine will always apply the * first frame of every sub animation immediately at the start of the animation sequence. This way * the parent animation does not need to set any initial styling data on the sub elements before the @@ -1062,7 +1050,7 @@ export function useAnimation( * to the queried element (by default, an array is provided, then this will be * treated as an animation sequence). * - * **Usage** + * ### Usage * * query() is designed to collect multiple elements and works internally by using * `element.querySelectorAll`. An additional options object can be provided which @@ -1085,8 +1073,7 @@ export function useAnimation( * ], { optional: true }) * ``` * - * **Special Selector Values** - * + * ### Special Selector Values * * The selector value within a query can collect elements that contain angular-specific * characteristics @@ -1108,8 +1095,7 @@ export function useAnimation( * query(':self, .record:enter, .record:leave, @subTrigger', [...]) * ``` * - * **Demo** - * + * ### Demo * * ``` * @Component({ @@ -1158,7 +1144,7 @@ export function query( * animation DSL language. It is designed to be used inside of an animation {@link query query()} * and works by issuing a timing gap between after each queried item is animated. * - * **Usage** + * ### Usage * * In the example below there is a container element that wraps a list of items stamped out * by an ngFor. The container element contains an animation trigger that will later be set From 77ff72f93b86e57ba3fdc67a60a5b00c69c9a824 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 5 May 2018 08:36:49 -0700 Subject: [PATCH 012/582] Revert "style(animations): fix short param names (#23668)" This reverts commit e3518967ad705b133ee21eedcf9546b5cb2e817e. This PR accidentaly introduces a breaking change: https://github.com/angular/angular/pull/23668#discussion_r186265055 --- packages/animations/src/players/animation_player.ts | 4 ++-- tools/public_api_guard/animations/animations.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/animations/src/players/animation_player.ts b/packages/animations/src/players/animation_player.ts index d88f733b19..de3dfb4ffa 100644 --- a/packages/animations/src/players/animation_player.ts +++ b/packages/animations/src/players/animation_player.ts @@ -26,7 +26,7 @@ export interface AnimationPlayer { finish(): void; destroy(): void; reset(): void; - setPosition(position: number): void; + setPosition(p: any /** TODO #9100 */): void; getPosition(): number; parentPlayer: AnimationPlayer|null; readonly totalTime: number; @@ -93,7 +93,7 @@ export class NoopAnimationPlayer implements AnimationPlayer { } } reset(): void {} - setPosition(position: number): void {} + setPosition(p: number): void {} getPosition(): number { return 0; } /* @internal */ diff --git a/tools/public_api_guard/animations/animations.d.ts b/tools/public_api_guard/animations/animations.d.ts index b0f5a005b0..07f51a54f8 100644 --- a/tools/public_api_guard/animations/animations.d.ts +++ b/tools/public_api_guard/animations/animations.d.ts @@ -115,7 +115,7 @@ export interface AnimationPlayer { play(): void; reset(): void; restart(): void; - setPosition(position: number): void; + setPosition(p: any): void; } /** @experimental */ @@ -216,7 +216,7 @@ export declare class NoopAnimationPlayer implements AnimationPlayer { play(): void; reset(): void; restart(): void; - setPosition(position: number): void; + setPosition(p: number): void; } /** @experimental */ From fc034270ced8f17cf17a82d3f8382dcef435b9a6 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 7 May 2018 10:47:59 -0700 Subject: [PATCH 013/582] fix(core): call ngOnDestroy on all services that have it (#23755) Previously, ngOnDestroy was only called on services which were statically determined to have ngOnDestroy methods. In some cases, such as with services instantiated via factory functions, it's not statically known that the service has an ngOnDestroy method. This commit changes the runtime to look for ngOnDestroy when instantiating all DI tokens, and to call the method if it's present. Fixes #22466 Fixes #22240 Fixes #14818 PR Close #23755 --- packages/core/src/view/ng_module.ts | 16 +++++- packages/core/test/view/ng_module_spec.ts | 70 +++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/core/src/view/ng_module.ts b/packages/core/src/view/ng_module.ts index 4a7d6a19ad..9e6bd20305 100644 --- a/packages/core/src/view/ng_module.ts +++ b/packages/core/src/view/ng_module.ts @@ -150,6 +150,15 @@ function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModulePr injectable = providerDef.value; break; } + + // The read of `ngOnDestroy` here is slightly expensive as it's megamorphic, so it should be + // avoided if possible. The sequence of checks here determines whether ngOnDestroy needs to be + // checked. It might not if the `injectable` isn't an object or if NodeFlags.OnDestroy is already + // set (ngOnDestroy was detected statically). + if (injectable !== UNDEFINED_VALUE && injectable != null && typeof injectable === 'object' && + !(providerDef.flags & NodeFlags.OnDestroy) && typeof injectable.ngOnDestroy === 'function') { + providerDef.flags |= NodeFlags.OnDestroy; + } return injectable === undefined ? UNDEFINED_VALUE : injectable; } @@ -199,12 +208,17 @@ function _callFactory(ngModule: NgModuleData, factory: any, deps: DepDef[]): any export function callNgModuleLifecycle(ngModule: NgModuleData, lifecycles: NodeFlags) { const def = ngModule._def; + const destroyed = new Set(); for (let i = 0; i < def.providers.length; i++) { const provDef = def.providers[i]; if (provDef.flags & NodeFlags.OnDestroy) { const instance = ngModule._providers[i]; if (instance && instance !== UNDEFINED_VALUE) { - instance.ngOnDestroy(); + const onDestroy: Function|undefined = instance.ngOnDestroy; + if (typeof onDestroy === 'function' && !destroyed.has(instance)) { + onDestroy.apply(instance); + destroyed.add(instance); + } } } } diff --git a/packages/core/test/view/ng_module_spec.ts b/packages/core/test/view/ng_module_spec.ts index 4e89b95ae0..6e3c1fa9a1 100644 --- a/packages/core/test/view/ng_module_spec.ts +++ b/packages/core/test/view/ng_module_spec.ts @@ -101,6 +101,22 @@ function makeProviders(classes: any[], modules: any[]): NgModuleDefinition { flags: NodeFlags.TypeClassProvider | NodeFlags.LazyProvider, token, value: token, })); + return makeModule(modules, providers); +} + +function makeFactoryProviders( + factories: {token: any, factory: Function}[], modules: any[]): NgModuleDefinition { + const providers = factories.map((factory, index) => ({ + index, + deps: [], + flags: NodeFlags.TypeFactoryProvider | NodeFlags.LazyProvider, + token: factory.token, + value: factory.factory, + })); + return makeModule(modules, providers); +} + +function makeModule(modules: any[], providers: NgModuleProviderDef[]): NgModuleDefinition { const providersByKey: {[key: string]: NgModuleProviderDef} = {}; providers.forEach(provider => providersByKey[tokenKey(provider.token)] = provider); return {factory: null, providers, providersByKey, modules, isRoot: true}; @@ -155,4 +171,58 @@ describe('NgModuleRef_ injector', () => { it('injects with the current injector always set', () => { expect(() => ref.injector.get(UsesInject)).not.toThrow(); }); + + it('calls ngOnDestroy on services created via factory', () => { + class Module {} + + class Service { + static destroyed = 0; + ngOnDestroy(): void { Service.destroyed++; } + } + + const ref = createNgModuleRef( + Module, Injector.NULL, [], makeFactoryProviders( + [{ + token: Service, + factory: () => new Service(), + }], + [Module])); + + expect(ref.injector.get(Service)).toBeDefined(); + expect(Service.destroyed).toBe(0); + ref.destroy(); + expect(Service.destroyed).toBe(1); + }); + + it('only calls ngOnDestroy once per instance', () => { + class Module {} + + class Service { + static destroyed = 0; + ngOnDestroy(): void { Service.destroyed++; } + } + + class OtherToken {} + + const instance = new Service(); + const ref = createNgModuleRef( + Module, Injector.NULL, [], makeFactoryProviders( + [ + { + token: Service, + factory: () => instance, + }, + { + token: OtherToken, + factory: () => instance, + } + ], + [Module])); + + expect(ref.injector.get(Service)).toBe(instance); + expect(ref.injector.get(OtherToken)).toBe(instance); + expect(Service.destroyed).toBe(0); + ref.destroy(); + expect(Service.destroyed).toBe(1); + }); }); From 9e2d87f5b89e7cce27e316bddf94d6e0accb674b Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 2 May 2018 23:16:03 -0500 Subject: [PATCH 014/582] docs(aio): Update i18n example to Angular V6 (#23660) PR Close #23660 --- aio/content/examples/.DS_Store | Bin 6148 -> 0 bytes aio/content/guide/i18n.md | 103 ++++++++--- .../customizer/package-json/i18n.json | 11 +- aio/tools/examples/example-boilerplate.js | 3 +- .../examples/example-boilerplate.spec.js | 2 +- .../shared/boilerplate/i18n/angular.json | 172 ++++++++++++++++++ .../shared/boilerplate/i18n/package.json | 72 ++++---- .../boilerplate/testing/.angular-cli.json | 61 ------- .../shared/boilerplate/testing/angular.json | 142 +++++++++++++++ 9 files changed, 438 insertions(+), 128 deletions(-) delete mode 100644 aio/content/examples/.DS_Store create mode 100644 aio/tools/examples/shared/boilerplate/i18n/angular.json delete mode 100644 aio/tools/examples/shared/boilerplate/testing/.angular-cli.json create mode 100644 aio/tools/examples/shared/boilerplate/testing/angular.json diff --git a/aio/content/examples/.DS_Store b/aio/content/examples/.DS_Store deleted file mode 100644 index 55dd692a1cad00d879837390dddaea4656b88731..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}(Pm5VlL%twBOri33O+EOFq{L;2aQxJBjXgajgp9{6b!C8Cj{q)JnIs47MK z0Q&&Tm7n(j9)cI)&W!CMsvh^*lz7m89*ZW&XJi1^uE<&}gJ1jY`g!dr>|~Y76E92^r@wtKi5Vfp zoDgSDE9p6zn{_)iH*Hu;PUr`oo7O`=YaIpgP83`Q@qW7JWG#4 z4QqZYi8D8hgH+YiHBRec>?gfi(vAI&iZ{iTec)y-s3Vr`X0Et|2WnUg{r=j|jk4UV zR0n0*uU0E%xv~ChFc6}&xUzeA-u?aO@749q?cM!@UI7k&RwX|S4&f1uS28Z0G>($^ z0(`^xVH_bbKnxHA6V8Cy)=IMz{vP!B!~iky{~5sjL4qRM26K&S>wpG7A91{hhypg= zB@m@S+hDE{MnJeu1=OkBTrs##2fs9Nw!vJZPG?-r40X)R+}u#OnjQR7g)?qzq?Q;U z21Xf}(p?MB|DAu||3{OkM+^`HUy1=<+;+BW@Ji-v9eX)EYb9tC6b0jQjjt(SsG}HS e@hDyZRRVsA2B2*)*9aaE`VdewP(uuSC<8C+ePIaz diff --git a/aio/content/guide/i18n.md b/aio/content/guide/i18n.md index 2915bf727a..4a481d8131 100644 --- a/aio/content/guide/i18n.md +++ b/aio/content/guide/i18n.md @@ -42,11 +42,10 @@ locale id to find the correct corresponding locale data. By default, Angular uses the locale `en-US`, which is English as spoken in the United States of America. -To set your app's locale to another value, use the CLI parameter `--locale` with the value -of the locale id that you want to use: +To set your app's locale to another value, use the CLI parameter `--configuration` with the value of the locale id that you want to use: - ng serve --aot --locale fr + ng serve --configuration=fr If you use JIT, you also need to define the `LOCALE_ID` provider in your main module: @@ -86,7 +85,7 @@ and `PercentPipe` use locale data to format data based on the `LOCALE_ID`. By default, Angular only contains locale data for `en-US`. If you set the value of `LOCALE_ID` to another locale, you must import locale data for that new locale. -The CLI imports the locale data for you when you use the parameter `--locale` with `ng serve` and +The CLI imports the locale data for you when you use the parameter `--configuration` with `ng serve` and `ng build`. If you want to import locale data for other languages, you can do it manually: @@ -424,9 +423,9 @@ You can specify the translation format explicitly with the `--i18nFormat` flag a these example commands: -ng xi18n --i18nFormat=xlf -ng xi18n --i18nFormat=xlf2 -ng xi18n --i18nFormat=xmb +ng xi18n --i18n-format=xlf +ng xi18n --i18n-format=xlf2 +ng xi18n --i18n-format=xmb The sample in this guide uses the default XLIFF 1.2 format. @@ -442,11 +441,11 @@ The sample in this guide uses the default XLIFF 1.2 format. ### Other options You can specify the output path used by the CLI to extract your translation source file with -the parameter `--outputPath`: +the parameter `--output-path`: - ng xi18n --outputPath src/locale + ng xi18n --output-path locale @@ -455,15 +454,15 @@ the parameter `--outFile`: - ng xi18n --outFile source.xlf + ng xi18n --out-file source.xlf -You can specify the base locale of your app with the parameter `--locale`: +You can specify the base locale of your app with the parameter `--i18n-locale`: - ng xi18n --locale fr + ng xi18n --i18n-locale fr @@ -663,7 +662,7 @@ format that Angular understands, such as `.xtb`. How you provide this information depends upon whether you compile with the JIT compiler or the AOT compiler. - * With [AOT](guide/i18n#merge-aot), you pass the information as a CLI parameter. + * With [AOT](guide/i18n#merge-aot), you pass the information as a configuration * With [JIT](guide/i18n#merge-jit), you provide the information at bootstrap time. @@ -677,18 +676,67 @@ When you internationalize with the AOT compiler, you must pre-build a separate a package for each language and serve the appropriate package based on either server-side language detection or url parameters. -You also need to instruct the AOT compiler to use your translation file. To do so, you use three -options with the `ng serve` or `ng build` commands: +You also need to instruct the AOT compiler to use your translation configuration. To do so, you configure the translation with three options in your `angular.json` file. -* `--i18nFile`: the path to the translation file. -* `--i18nFormat`: the format of the translation file. -* `--locale`: the locale id. +* `i18nFile`: the path to the translation file. +* `i18nFormat`: the format of the translation file. +* `i18nLocale`: the locale id. -The example below shows how to serve the French language file created in previous sections of this -guide: + +"configurations": { + ... + "fr": { + "aot": true, + "outputPath": "dist/my-project-fr/", + "i18nFile": "src/locale/messages.fr.xlf", + "i18nFormat": "xlf", + "i18nLocale": "fr", + ... + } +} + + +You then pass the configuration with the `ng serve` or `ng build` commands. The example below shows how to serve the French language file created in previous sections of this guide: - ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr + ng serve --configuration=fr + + +For production builds, you define a separate `production-fr` build configuration in your `angular.json`. + + +"configurations": { + ... + "production-fr": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "outputPath": "dist/my-project-fr/", + "i18nFile": "src/locale/messages.fr.xlf", + "i18nFormat": "xlf", + "i18nLocale": "fr", + "i18nMissingTranslation": "error" + }, + ... +} + + +The same configuration options can also be provided through the CLI with your existing `production` configuration. + + + ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr {@a merge-jit} @@ -731,11 +779,16 @@ compilation, the app will fail to load. * Warning (default): show a 'Missing translation' warning in the console or shell. * Ignore: do nothing. -If you use the AOT compiler, specify the warning level by using the CLI parameter -`--missingTranslation`. The example below shows how to set the warning level to error: +You specify the warning level in the `configurations` section your Angular CLI build configuration. The example below shows how to set the warning level to error: - - ng serve --aot --missingTranslation=error + +"configurations": { + ... + "fr": { + ... + "i18nMissingTranslation": "error" + } +} If you use the JIT compiler, specify the warning level in the compiler config at bootstrap by adding diff --git a/aio/tools/example-zipper/customizer/package-json/i18n.json b/aio/tools/example-zipper/customizer/package-json/i18n.json index deb97a37b6..d257e96c1d 100644 --- a/aio/tools/example-zipper/customizer/package-json/i18n.json +++ b/aio/tools/example-zipper/customizer/package-json/i18n.json @@ -1,17 +1,18 @@ { "scripts": [ - { "name": "start", "command": "ng serve --aot" }, - { "name": "start:fr", "command": "ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, + { "name": "start", "command": "ng serve" }, + { "name": "start:fr", "command": "ng serve --configuration=fr" }, { "name": "build", "command": "ng build --prod" }, - { "name": "build:fr", "command": "ng build --prod --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, + { "name": "build:fr", "command": "ng build --configuration=production-fr" }, { "name": "test", "command": "ng test" }, { "name": "lint", "command": "ng lint" }, - { "name": "e2e", "command": "ng e2e --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr" }, - { "name": "extract", "command": "ng xi18n --outputPath=src/locale" } + { "name": "e2e", "command": "ng e2e" }, + { "name": "extract", "command": "ng xi18n --output-path=locale" } ], "dependencies": [], "devDependencies": [ "@angular/cli", + "@angular-devkit/build-angular", "@types/jasminewd2", "jasmine-spec-reporter", "karma-coverage-istanbul-reporter", diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index 1d5026196a..4f4c7bedf3 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -52,6 +52,7 @@ const cliRelativePath = BOILERPLATE_PATHS.cli.map(file => `../cli/${file}`); BOILERPLATE_PATHS.i18n = [ ...cliRelativePath, + 'angular.json', 'package.json' ]; @@ -63,7 +64,7 @@ BOILERPLATE_PATHS.universal = [ BOILERPLATE_PATHS.testing = [ ...cliRelativePath, - '.angular-cli.json' + 'angular.json' ]; const EXAMPLE_CONFIG_FILENAME = 'example-config.json'; diff --git a/aio/tools/examples/example-boilerplate.spec.js b/aio/tools/examples/example-boilerplate.spec.js index dbf9820724..91cfd6b044 100644 --- a/aio/tools/examples/example-boilerplate.spec.js +++ b/aio/tools/examples/example-boilerplate.spec.js @@ -11,7 +11,7 @@ describe('example-boilerplate tool', () => { const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules'); const BPFiles = { cli: 19, - i18n: 1, + i18n: 2, universal: 2, systemjs: 7, common: 1 diff --git a/aio/tools/examples/shared/boilerplate/i18n/angular.json b/aio/tools/examples/shared/boilerplate/i18n/angular.json new file mode 100644 index 0000000000..00ec0bccd1 --- /dev/null +++ b/aio/tools/examples/shared/boilerplate/i18n/angular.json @@ -0,0 +1,172 @@ +{ + "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "angular.io-example": { + "root": "", + "projectType": "application", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/angular.io-example", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + { + "glob": "favicon.ico", + "input": "src", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + } + ], + "styles": [ + { + "input": "src/styles.css" + } + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + }, + "production-fr": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "outputPath": "dist/my-project-fr/", + "i18nFile": "src/locale/messages.fr.xlf", + "i18nFormat": "xlf", + "i18nLocale": "fr", + "i18nMissingTranslation": "error" + }, + "fr": { + "aot": true, + "outputPath": "dist/my-project-fr/", + "i18nFile": "src/locale/messages.fr.xlf", + "i18nFormat": "xlf", + "i18nLocale": "fr", + "i18nMissingTranslation": "error" + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "angular.io-example:build" + }, + "configurations": { + "production": { + "browserTarget": "angular.io-example:build:production" + }, + "fr": { + "browserTarget": "angular.io-example:build:fr" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "angular.io-example:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + { + "input": "styles.css" + } + ], + "scripts": [], + "assets": [ + { + "glob": "favicon.ico", + "input": "src/", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + } + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "angular.io-example-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "angular.io-example:serve:fr" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/aio/tools/examples/shared/boilerplate/i18n/package.json b/aio/tools/examples/shared/boilerplate/i18n/package.json index 74ab803af8..7870cc9daa 100644 --- a/aio/tools/examples/shared/boilerplate/i18n/package.json +++ b/aio/tools/examples/shared/boilerplate/i18n/package.json @@ -4,49 +4,51 @@ "license": "MIT", "scripts": { "ng": "ng", - "start": "ng serve --aot", - "start:fr": "ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr", + "start": "ng serve", + "start:fr": "ng serve --configuration=fr", "build": "ng build --prod", - "build:fr": "ng build --prod --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr", + "build:fr": "ng build --configuration=production-fr", "test": "ng test", "lint": "ng lint", - "e2e": "ng e2e --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr", - "extract": "ng xi18n --outputPath=src/locale" + "e2e": "ng e2e", + "extract": "ng xi18n --output-path=locale" }, "private": true, "dependencies": { - "@angular/animations": "^5.0.0", - "@angular/common": "^5.0.0", - "@angular/compiler": "^5.0.0", - "@angular/core": "^5.0.0", - "@angular/forms": "^5.0.0", - "@angular/http": "^5.0.0", - "@angular/platform-browser": "^5.0.0", - "@angular/platform-browser-dynamic": "^5.0.0", - "@angular/router": "^5.0.0", - "core-js": "^2.4.1", - "rxjs": "^5.5.2", - "zone.js": "^0.8.14" + "@angular/animations": "^6.0.0-rc.5", + "@angular/common": "^6.0.0-rc.5", + "@angular/compiler": "^6.0.0-rc.5", + "@angular/core": "^6.0.0-rc.5", + "@angular/forms": "^6.0.0-rc.5", + "@angular/http": "^6.0.0-rc.5", + "@angular/platform-browser": "^6.0.0-rc.5", + "@angular/platform-browser-dynamic": "^6.0.0-rc.5", + "@angular/router": "^6.0.0-rc.5", + "core-js": "^2.5.4", + "rxjs": "6.0.0-uncanny-rc.7", + "zone.js": "^0.8.24" }, "devDependencies": { - "@angular/cli": "1.5.0", - "@angular/compiler-cli": "^5.0.0", - "@angular/language-service": "^5.0.0", - "@types/jasmine": "~2.8.0", - "@types/jasminewd2": "~2.0.2", - "@types/node": "~6.0.60", - "codelyzer": "~3.2.0", - "jasmine-core": "~2.8.0", - "jasmine-spec-reporter": "~4.1.0", - "karma": "~1.7.0", - "karma-chrome-launcher": "~2.1.1", - "karma-cli": "~1.0.1", - "karma-coverage-istanbul-reporter": "^1.2.1", - "karma-jasmine": "~1.1.0", + "@angular/compiler-cli": "^6.0.0-rc.5", + "@angular-devkit/build-angular": "~0.5.0", + "@angular/cli": "~6.0.0-rc.4", + "@angular/language-service": "^6.0.0-rc.5", + "@angular/platform-server": "^6.0.0-rc.5", + "@types/jasmine": "~2.8.6", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "codelyzer": "~4.2.1", + "jasmine-core": "~2.99.1", + "jasmine-marbles": "^0.3.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~2.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~1.4.2", + "karma-jasmine": "~1.1.1", "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", - "ts-node": "~3.2.0", - "tslint": "~5.7.0", - "typescript": "~2.4.2" + "protractor": "~5.3.0", + "ts-node": "~5.0.1", + "tslint": "~5.9.1", + "typescript": "~2.7.2" } } diff --git a/aio/tools/examples/shared/boilerplate/testing/.angular-cli.json b/aio/tools/examples/shared/boilerplate/testing/.angular-cli.json deleted file mode 100644 index 8e9fd66a57..0000000000 --- a/aio/tools/examples/shared/boilerplate/testing/.angular-cli.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "project": { - "name": "angular.io-example" - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": [ - "assets", - "favicon.ico" - ], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "app", - "styles": [ - "test.css", - "styles.css" - ], - "scripts": [], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "lint": [ - { - "project": "src/tsconfig.app.json", - "exclude": "**/node_modules/**" - }, - { - "project": "src/tsconfig.spec.json", - "exclude": "**/node_modules/**" - }, - { - "project": "e2e/tsconfig.e2e.json", - "exclude": "**/node_modules/**" - } - ], - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "css", - "component": {} - } -} diff --git a/aio/tools/examples/shared/boilerplate/testing/angular.json b/aio/tools/examples/shared/boilerplate/testing/angular.json new file mode 100644 index 0000000000..9aaab56b56 --- /dev/null +++ b/aio/tools/examples/shared/boilerplate/testing/angular.json @@ -0,0 +1,142 @@ +{ + "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "angular.io-example": { + "root": "", + "projectType": "application", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/angular.io-example", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + { + "glob": "favicon.ico", + "input": "src", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + } + ], + "styles": [ + { + "input": "src/styles.css" + }, + { + "input": "src/test.css" + } + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "angular.io-example:build" + }, + "configurations": { + "production": { + "browserTarget": "angular.io-example:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "angular.io-example:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + { + "input": "styles.css" + } + ], + "scripts": [], + "assets": [ + { + "glob": "favicon.ico", + "input": "src/", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + } + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "angular.io-example-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "angular.io-example:serve" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + } +} \ No newline at end of file From d01ec03f54e2782a912869e9171268fccc92a270 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Thu, 3 May 2018 22:01:25 -0500 Subject: [PATCH 015/582] docs(aio): Upgrade example dependencies to Angular V6 (#23660) PR Close #23660 --- aio/content/guide/i18n.md | 19 +- .../shared/boilerplate/cli/package.json | 28 +- .../shared/boilerplate/i18n/package.json | 30 +- .../shared/boilerplate/universal/package.json | 36 +- aio/tools/examples/shared/package.json | 38 +- aio/tools/examples/shared/yarn.lock | 451 ++++++++++-------- 6 files changed, 333 insertions(+), 269 deletions(-) diff --git a/aio/content/guide/i18n.md b/aio/content/guide/i18n.md index 4a481d8131..4bcb42a408 100644 --- a/aio/content/guide/i18n.md +++ b/aio/content/guide/i18n.md @@ -682,7 +682,7 @@ You also need to instruct the AOT compiler to use your translation configuration * `i18nFormat`: the format of the translation file. * `i18nLocale`: the locale id. - +``` "configurations": { ... "fr": { @@ -694,17 +694,20 @@ You also need to instruct the AOT compiler to use your translation configuration ... } } - +``` -You then pass the configuration with the `ng serve` or `ng build` commands. The example below shows how to serve the French language file created in previous sections of this guide: +You then pass the configuration with the `ng serve` or `ng build` commands. +The example below shows how to serve the French language file created in previous +sections of this guide: ng serve --configuration=fr -For production builds, you define a separate `production-fr` build configuration in your `angular.json`. +For production builds, you define a separate `production-fr` build configuration in +your `angular.json`. - +``` "configurations": { ... "production-fr": { @@ -731,7 +734,7 @@ For production builds, you define a separate `production-fr` build configuration }, ... } - +``` The same configuration options can also be provided through the CLI with your existing `production` configuration. @@ -781,7 +784,7 @@ compilation, the app will fail to load. You specify the warning level in the `configurations` section your Angular CLI build configuration. The example below shows how to set the warning level to error: - +``` "configurations": { ... "fr": { @@ -789,7 +792,7 @@ You specify the warning level in the `configurations` section your Angular CLI b "i18nMissingTranslation": "error" } } - +``` If you use the JIT compiler, specify the warning level in the compiler config at bootstrap by adding the 'MissingTranslationStrategy' property. The example below shows how to set the warning level to diff --git a/aio/tools/examples/shared/boilerplate/cli/package.json b/aio/tools/examples/shared/boilerplate/cli/package.json index 5f906801e7..1ef3da3ca3 100644 --- a/aio/tools/examples/shared/boilerplate/cli/package.json +++ b/aio/tools/examples/shared/boilerplate/cli/package.json @@ -12,27 +12,27 @@ }, "private": true, "dependencies": { - "@angular/animations": "^6.0.0-rc.5", - "@angular/common": "^6.0.0-rc.5", - "@angular/compiler": "^6.0.0-rc.5", - "@angular/core": "^6.0.0-rc.5", - "@angular/forms": "^6.0.0-rc.5", - "@angular/http": "^6.0.0-rc.5", - "@angular/platform-browser": "^6.0.0-rc.5", - "@angular/platform-browser-dynamic": "^6.0.0-rc.5", - "@angular/router": "^6.0.0-rc.5", + "@angular/animations": "^6.0.0", + "@angular/common": "^6.0.0", + "@angular/compiler": "^6.0.0", + "@angular/core": "^6.0.0", + "@angular/forms": "^6.0.0", + "@angular/http": "^6.0.0", + "@angular/platform-browser": "^6.0.0", + "@angular/platform-browser-dynamic": "^6.0.0", + "@angular/router": "^6.0.0", "angular-in-memory-web-api": "^0.6.0", "core-js": "^2.5.4", - "rxjs": "6.0.0-uncanny-rc.7", + "rxjs": "^6.0.0", "web-animations-js": "^2.3.1", "zone.js": "^0.8.24" }, "devDependencies": { - "@angular/compiler-cli": "^6.0.0-rc.5", - "@angular-devkit/build-angular": "~0.5.0", + "@angular/compiler-cli": "^6.0.0", + "@angular-devkit/build-angular": "~0.6.0", "typescript": "~2.7.2", - "@angular/cli": "~6.0.0-rc.4", - "@angular/language-service": "^6.0.0-rc.5", + "@angular/cli": "^6.0.0", + "@angular/language-service": "^6.0.0", "@types/jasmine": "~2.8.6", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", diff --git a/aio/tools/examples/shared/boilerplate/i18n/package.json b/aio/tools/examples/shared/boilerplate/i18n/package.json index 7870cc9daa..82de963571 100644 --- a/aio/tools/examples/shared/boilerplate/i18n/package.json +++ b/aio/tools/examples/shared/boilerplate/i18n/package.json @@ -15,25 +15,25 @@ }, "private": true, "dependencies": { - "@angular/animations": "^6.0.0-rc.5", - "@angular/common": "^6.0.0-rc.5", - "@angular/compiler": "^6.0.0-rc.5", - "@angular/core": "^6.0.0-rc.5", - "@angular/forms": "^6.0.0-rc.5", - "@angular/http": "^6.0.0-rc.5", - "@angular/platform-browser": "^6.0.0-rc.5", - "@angular/platform-browser-dynamic": "^6.0.0-rc.5", - "@angular/router": "^6.0.0-rc.5", + "@angular/animations": "^6.0.0", + "@angular/common": "^6.0.0", + "@angular/compiler": "^6.0.0", + "@angular/core": "^6.0.0", + "@angular/forms": "^6.0.0", + "@angular/http": "^6.0.0", + "@angular/platform-browser": "^6.0.0", + "@angular/platform-browser-dynamic": "^6.0.0", + "@angular/router": "^6.0.0", "core-js": "^2.5.4", - "rxjs": "6.0.0-uncanny-rc.7", + "rxjs": "^6.0.0", "zone.js": "^0.8.24" }, "devDependencies": { - "@angular/compiler-cli": "^6.0.0-rc.5", - "@angular-devkit/build-angular": "~0.5.0", - "@angular/cli": "~6.0.0-rc.4", - "@angular/language-service": "^6.0.0-rc.5", - "@angular/platform-server": "^6.0.0-rc.5", + "@angular/compiler-cli": "^6.0.0", + "@angular-devkit/build-angular": "~0.6.0", + "@angular/cli": "^6.0.0", + "@angular/language-service": "^6.0.0", + "@angular/platform-server": "^6.0.0", "@types/jasmine": "~2.8.6", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", diff --git a/aio/tools/examples/shared/boilerplate/universal/package.json b/aio/tools/examples/shared/boilerplate/universal/package.json index 215e37ba93..5039c4bfc3 100644 --- a/aio/tools/examples/shared/boilerplate/universal/package.json +++ b/aio/tools/examples/shared/boilerplate/universal/package.json @@ -16,30 +16,30 @@ }, "private": true, "dependencies": { - "@angular/animations": "^6.0.0-rc.5", - "@angular/common": "^6.0.0-rc.5", - "@angular/compiler": "^6.0.0-rc.5", - "@angular/core": "^6.0.0-rc.5", - "@angular/forms": "^6.0.0-rc.5", - "@angular/http": "^6.0.0-rc.5", - "@angular/platform-browser": "^6.0.0-rc.5", - "@angular/platform-browser-dynamic": "^6.0.0-rc.5", - "@angular/router": "^6.0.0-rc.5", + "@angular/animations": "^6.0.0", + "@angular/common": "^6.0.0", + "@angular/compiler": "^6.0.0", + "@angular/core": "^6.0.0", + "@angular/forms": "^6.0.0", + "@angular/http": "^6.0.0", + "@angular/platform-browser": "^6.0.0", + "@angular/platform-browser-dynamic": "^6.0.0", + "@angular/router": "^6.0.0", "angular-in-memory-web-api": "^0.6.0", - "@nguniversal/common": "6.0.0-rc.2", - "@nguniversal/express-engine": "6.0.0-rc.2", - "@nguniversal/module-map-ngfactory-loader": "6.0.0-rc.2", + "@nguniversal/common": "^6.0.0", + "@nguniversal/express-engine": "^6.0.0", + "@nguniversal/module-map-ngfactory-loader": "^6.0.0", "core-js": "^2.5.4", - "rxjs": "6.0.0-uncanny-rc.7", + "rxjs": "^6.0.0", "web-animations-js": "^2.3.1", "zone.js": "^0.8.24" }, "devDependencies": { - "@angular/cli": "~6.0.0-rc.4", - "@angular/compiler-cli": "^6.0.0-rc.5", - "@angular/language-service": "^6.0.0-rc.5", - "@angular/platform-server": "^6.0.0-rc.5", - "@angular-devkit/build-angular": "~0.5.0", + "@angular/cli": "^6.0.0", + "@angular/compiler-cli": "^6.0.0", + "@angular/language-service": "^6.0.0", + "@angular/platform-server": "^6.0.0", + "@angular-devkit/build-angular": "~0.6.0", "@types/jasmine": "~2.8.6", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", diff --git a/aio/tools/examples/shared/package.json b/aio/tools/examples/shared/package.json index 44a36e924d..90e2ff8e32 100644 --- a/aio/tools/examples/shared/package.json +++ b/aio/tools/examples/shared/package.json @@ -18,33 +18,33 @@ "author": "", "license": "MIT", "dependencies": { - "@angular/animations": "^6.0.0-rc.5", - "@angular/common": "^6.0.0-rc.5", - "@angular/compiler": "^6.0.0-rc.5", - "@angular/core": "^6.0.0-rc.5", - "@angular/forms": "^6.0.0-rc.5", - "@angular/http": "^6.0.0-rc.5", - "@angular/platform-browser": "^6.0.0-rc.5", - "@angular/platform-browser-dynamic": "^6.0.0-rc.5", - "@angular/router": "^6.0.0-rc.5", - "@angular/service-worker": "^6.0.0-rc.5", - "@angular/upgrade": "^6.0.0-rc.5", - "@nguniversal/express-engine": "6.0.0-rc.2", - "@nguniversal/module-map-ngfactory-loader": "6.0.0-rc.2", + "@angular/animations": "^6.0.0", + "@angular/common": "^6.0.0", + "@angular/compiler": "^6.0.0", + "@angular/core": "^6.0.0", + "@angular/forms": "^6.0.0", + "@angular/http": "^6.0.0", + "@angular/platform-browser": "^6.0.0", + "@angular/platform-browser-dynamic": "^6.0.0", + "@angular/router": "^6.0.0", + "@angular/service-worker": "^6.0.0", + "@angular/upgrade": "^6.0.0", + "@nguniversal/express-engine": "^6.0.0", + "@nguniversal/module-map-ngfactory-loader": "^6.0.0", "angular-in-memory-web-api": "^0.6.0", "core-js": "^2.5.4", "express": "^4.14.1", - "rxjs": "6.0.0-uncanny-rc.7", + "rxjs": "^6.0.0", "systemjs": "0.19.39", "web-animations-js": "^2.3.1", "zone.js": "^0.8.24" }, "devDependencies": { - "@angular/cli": "6.0.0-rc.4", - "@angular/compiler-cli": "^6.0.0-rc.5", - "@angular/language-service": "^6.0.0-rc.5", - "@angular/platform-server": "^6.0.0-rc.5", - "@angular-devkit/build-angular": "~0.5.0", + "@angular/cli": "^6.0.0", + "@angular/compiler-cli": "^6.0.0", + "@angular/language-service": "^6.0.0", + "@angular/platform-server": "^6.0.0", + "@angular-devkit/build-angular": "~0.6.0", "@types/angular": "^1.5.16", "@types/angular-animate": "^1.5.5", "@types/angular-cookies": "^1.4.2", diff --git a/aio/tools/examples/shared/yarn.lock b/aio/tools/examples/shared/yarn.lock index 890cb20c69..fcde5b4085 100644 --- a/aio/tools/examples/shared/yarn.lock +++ b/aio/tools/examples/shared/yarn.lock @@ -2,200 +2,195 @@ # yarn lockfile v1 -"@angular-devkit/architect@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.5.6.tgz#758d1a725793641812279569edce3380f118da9e" +"@angular-devkit/architect@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.6.0.tgz#622a933337c946ef85d646545cc4244272ccb402" dependencies: - "@angular-devkit/core" "0.5.6" - rxjs "^6.0.0-beta.3" + "@angular-devkit/core" "0.6.0" + rxjs "^6.0.0" -"@angular-devkit/build-angular@~0.5.0": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.5.6.tgz#14c53c32653d153886c00911af6d62fceb7419de" +"@angular-devkit/build-angular@~0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.6.0.tgz#f5757f80fc402458e6b5eae1578bbc2a1af44ebe" dependencies: - "@angular-devkit/architect" "0.5.6" - "@angular-devkit/build-optimizer" "0.5.6" - "@angular-devkit/core" "0.5.6" - "@ngtools/webpack" "6.0.0-rc.4" - ajv "^6.0.0" + "@angular-devkit/architect" "0.6.0" + "@angular-devkit/build-optimizer" "0.6.0" + "@angular-devkit/core" "0.6.0" + "@ngtools/webpack" "6.0.0" + ajv "~6.4.0" autoprefixer "^8.1.0" cache-loader "^1.2.2" chalk "~2.2.2" - circular-dependency-plugin "^5.0.0" + circular-dependency-plugin "^5.0.2" clean-css "^4.1.11" - copy-webpack-plugin "^4.5.0" + copy-webpack-plugin "^4.5.1" file-loader "^1.1.11" glob "^7.0.3" html-webpack-plugin "^3.0.6" istanbul "^0.4.5" istanbul-instrumenter-loader "^3.0.1" karma-source-map-support "^1.2.0" - less "^3.0.1" + less "^3.0.2" less-loader "^4.1.0" - license-webpack-plugin "^1.2.3" + license-webpack-plugin "^1.3.1" lodash "^4.17.4" memory-fs "^0.4.1" - mini-css-extract-plugin "~0.3.0" + mini-css-extract-plugin "~0.4.0" minimatch "^3.0.4" - node-sass "^4.7.2" + node-sass "^4.8.3" opn "^5.1.0" parse5 "^4.0.0" portfinder "^1.0.13" postcss "^6.0.19" postcss-import "^11.1.0" - postcss-loader "^2.1.1" + postcss-loader "^2.1.4" postcss-url "^7.3.1" raw-loader "^0.5.1" - request "^2.83.0" resolve "^1.5.0" - rxjs "^6.0.0-beta.3" - sass-loader "^6.0.7" + rxjs "^6.0.0" + sass-loader "^7.0.1" silent-error "^1.1.0" source-map-support "^0.5.0" stats-webpack-plugin "^0.6.2" - style-loader "^0.20.2" + style-loader "^0.21.0" stylus "^0.54.5" stylus-loader "^3.0.2" tree-kill "^1.2.0" - uglifyjs-webpack-plugin "^1.2.2" + uglifyjs-webpack-plugin "^1.2.5" url-loader "^1.0.1" - webpack "~4.5.0" - webpack-dev-middleware "^3.1.0" - webpack-dev-server "^3.1.1" + webpack "~4.6.0" + webpack-dev-middleware "^3.1.3" + webpack-dev-server "^3.1.4" webpack-merge "^4.1.2" webpack-sources "^1.1.0" webpack-subresource-integrity "^1.1.0-rc.4" -"@angular-devkit/build-optimizer@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.5.6.tgz#c581489ccf5757800ac23d44052934cbcc6bafed" +"@angular-devkit/build-optimizer@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.6.0.tgz#150a76155b473dea17327a176d18245a2da1c13e" dependencies: loader-utils "^1.1.0" source-map "^0.5.6" typescript "~2.7.2" webpack-sources "^1.1.0" -"@angular-devkit/core@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.5.6.tgz#f7c9d550c86e924f2d75fe728e17e280be63d5ad" +"@angular-devkit/core@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.6.0.tgz#d1a7275ff0f93de5cf007c4a549d1ebd00776fd0" dependencies: - ajv "~5.5.1" - chokidar "^1.7.0" - rxjs "^6.0.0-beta.3" + ajv "~6.4.0" + chokidar "^2.0.3" + rxjs "^6.0.0" source-map "^0.5.6" -"@angular-devkit/schematics@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.5.6.tgz#25ecacc619579a9e54be854e28ae302b116f47d0" +"@angular-devkit/schematics@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.6.0.tgz#0117dc7d5905b053df4f2918e2e073efd1091f5c" dependencies: - "@angular-devkit/core" "0.5.6" - "@ngtools/json-schema" "^1.1.0" - rxjs "^6.0.0-beta.3" + "@angular-devkit/core" "0.6.0" + rxjs "^6.0.0" -"@angular/animations@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-6.0.0-rc.5.tgz#4103f620f52023c0b26c741158276bdcc9c1a451" +"@angular/animations@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-6.0.0.tgz#cfc825dbfdf33bf3bf75962d1e12495aed5e3c32" dependencies: tslib "^1.9.0" -"@angular/cli@6.0.0-rc.4": - version "6.0.0-rc.4" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.0.0-rc.4.tgz#f3cffcfdb84f7a187eb2d10bdd5831e397c5a00c" +"@angular/cli@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.0.0.tgz#346b356775ddf8cdb8a9a5095b0663878eca3486" dependencies: - "@angular-devkit/architect" "0.5.6" - "@angular-devkit/core" "0.5.6" - "@angular-devkit/schematics" "0.5.6" - "@schematics/angular" "0.5.6" - "@schematics/update" "0.5.6" - chalk "~2.2.0" - fs-extra "^4.0.0" - node-modules-path "^1.0.0" + "@angular-devkit/architect" "0.6.0" + "@angular-devkit/core" "0.6.0" + "@angular-devkit/schematics" "0.6.0" + "@schematics/angular" "0.6.0" + "@schematics/update" "0.6.0" opn "~5.1.0" resolve "^1.1.7" - rxjs "^6.0.0-turbo-rc.4" + rxjs "^6.0.0" semver "^5.1.0" silent-error "^1.0.0" symbol-observable "^1.2.0" - yargs-parser "^9.0.2" + yargs-parser "^10.0.0" -"@angular/common@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-6.0.0-rc.5.tgz#eb31379f187b60ea9724595bc29dff7439c0efcc" +"@angular/common@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-6.0.0.tgz#ca3b6b6b96837fe048861da897c31991aa04954f" dependencies: tslib "^1.9.0" -"@angular/compiler-cli@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-6.0.0-rc.5.tgz#1ecb3702532def6e15051253ecd7e98467cf5842" +"@angular/compiler-cli@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-6.0.0.tgz#be50277faaa5ac08f3002c2c8cb8c39d220c76d5" dependencies: chokidar "^1.4.2" minimist "^1.2.0" reflect-metadata "^0.1.2" tsickle "^0.27.2" -"@angular/compiler@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-6.0.0-rc.5.tgz#6d163ff459c2aa3134efd3d2f187cff08a7aeabf" +"@angular/compiler@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-6.0.0.tgz#9092a0f02f33dd1108276ab93cc48142e36a1e95" dependencies: tslib "^1.9.0" -"@angular/core@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-6.0.0-rc.5.tgz#33b1a6b4d0daaf1ec034fe2404a6009607f00cbd" +"@angular/core@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-6.0.0.tgz#785cc8a37b7fb784a6b7dcbd0984abb4f10e5dfe" dependencies: tslib "^1.9.0" -"@angular/forms@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-6.0.0-rc.5.tgz#9594f2c2423e46aa345000097ce1f8e6e6569fc5" +"@angular/forms@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-6.0.0.tgz#436e2df39dc57db124da5a5c02bc63909fdf7046" dependencies: tslib "^1.9.0" -"@angular/http@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-6.0.0-rc.5.tgz#40dad79b77d463bd325263020e79e9512dc085eb" +"@angular/http@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-6.0.0.tgz#f409e35cd2f4990b43a37beab915ffdcd9c7c992" dependencies: tslib "^1.9.0" -"@angular/language-service@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-6.0.0-rc.5.tgz#19c24928aba82c6175e833d5fbeb6f82c58d72c0" +"@angular/language-service@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-6.0.0.tgz#85bf577fd7f45eff13128d4f5f0125078d610aec" -"@angular/platform-browser-dynamic@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.0.0-rc.5.tgz#2f7fb42b10f2fd769d209dfed6b50b89590851d8" +"@angular/platform-browser-dynamic@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.0.0.tgz#66a34b65136446cb3ec39362fd6d2dbb5482ba70" dependencies: tslib "^1.9.0" -"@angular/platform-browser@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-6.0.0-rc.5.tgz#492bc951339ff170eb057871626476b34e65227f" +"@angular/platform-browser@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-6.0.0.tgz#848b687ea46786483fddcdbbbd17b29c7adcc768" dependencies: tslib "^1.9.0" -"@angular/platform-server@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-6.0.0-rc.5.tgz#b8910231cc4c9891a9e2ce3ba35ebb7db84c9969" +"@angular/platform-server@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-6.0.0.tgz#482878cc538a80caa3962e9376e4225b20c2a4bd" dependencies: domino "^2.0.1" tslib "^1.9.0" xhr2 "^0.1.4" -"@angular/router@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-6.0.0-rc.5.tgz#b37f93cd81738b7a64697d1804c87c880d7bb636" +"@angular/router@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-6.0.0.tgz#09a5c6f6220084c3575df81e8b36cbe9fff10d1f" dependencies: tslib "^1.9.0" -"@angular/service-worker@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-6.0.0-rc.5.tgz#f55f89a3012ae6e0bca21463a8fd538ae3425eac" +"@angular/service-worker@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-6.0.0.tgz#35a187554d33e05911544080fafc281ff1b322e0" dependencies: tslib "^1.9.0" -"@angular/upgrade@^6.0.0-rc.5": - version "6.0.0-rc.5" - resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-6.0.0-rc.5.tgz#f4b35f8bcad8c77a8d3eb83af9418054e3a7b740" +"@angular/upgrade@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-6.0.0.tgz#932e39709b17a455018c3dfe63a5b8e794027f05" dependencies: tslib "^1.9.0" @@ -206,41 +201,38 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@ngtools/json-schema@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922" - -"@ngtools/webpack@6.0.0-rc.4": - version "6.0.0-rc.4" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-6.0.0-rc.4.tgz#683596658ee4592f19ad6075a9166f1afdb05cd9" +"@ngtools/webpack@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-6.0.0.tgz#e160cccd85823e9b01ee7bc5156a02510a323a34" dependencies: - "@angular-devkit/core" "0.5.6" + "@angular-devkit/core" "0.6.0" tree-kill "^1.0.0" webpack-sources "^1.1.0" -"@nguniversal/express-engine@6.0.0-rc.2": - version "6.0.0-rc.2" - resolved "https://registry.yarnpkg.com/@nguniversal/express-engine/-/express-engine-6.0.0-rc.2.tgz#ca4a576d62614e04aea8a03f603b7e1235dff033" +"@nguniversal/express-engine@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@nguniversal/express-engine/-/express-engine-6.0.0.tgz#f8bc7b5e940afb1ffdbdcb1bb22665d440ea0b0b" -"@nguniversal/module-map-ngfactory-loader@6.0.0-rc.2": - version "6.0.0-rc.2" - resolved "https://registry.yarnpkg.com/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-6.0.0-rc.2.tgz#c3cdb60a7a3da180fc90e1ca39f22367427fb4d8" +"@nguniversal/module-map-ngfactory-loader@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-6.0.0.tgz#0dd26c3f1c26d17bb21b8dfc0da53d82b7f11028" -"@schematics/angular@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.5.6.tgz#c2b26037855c9a338d20bb8ace50743cb7a5af24" +"@schematics/angular@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.6.0.tgz#d7589c50f80ef089f7fba526ed9becefb187b6a2" dependencies: - "@angular-devkit/core" "0.5.6" - "@angular-devkit/schematics" "0.5.6" + "@angular-devkit/core" "0.6.0" + "@angular-devkit/schematics" "0.6.0" typescript ">=2.6.2 <2.8" -"@schematics/update@0.5.6": - version "0.5.6" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.5.6.tgz#ffba0507eaccd78bd892480edf11945734d59547" +"@schematics/update@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.6.0.tgz#1a5f75a5a02de85cc4b4bd4fa68dd53ddc95ba30" dependencies: - "@angular-devkit/core" "0.5.6" - "@angular-devkit/schematics" "0.5.6" - rxjs "^6.0.0-beta.3" + "@angular-devkit/core" "0.6.0" + "@angular-devkit/schematics" "0.6.0" + npm-registry-client "^8.5.1" + rxjs "^6.0.0" semver "^5.3.0" semver-intersect "^1.1.2" @@ -429,7 +421,7 @@ ajv@^5.0.0, ajv@^5.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.0.0, ajv@^6.1.0: +ajv@^6.1.0, ajv@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.4.0.tgz#d3aff78e9277549771daf0164cff48482b754fc6" dependencies: @@ -438,15 +430,6 @@ ajv@^6.0.0, ajv@^6.1.0: json-schema-traverse "^0.3.0" uri-js "^3.0.2" -ajv@~5.5.1: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1677,6 +1660,10 @@ btoa@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.2.tgz#3e40b81663f81d2dd6596a4cb714a8dc16cfabe0" +buffer-from@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1701,6 +1688,10 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -1884,7 +1875,7 @@ chalk@~0.4.0: has-color "~0.1.0" strip-ansi "~0.1.0" -chalk@~2.2.0, chalk@~2.2.2: +chalk@~2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.2.2.tgz#4403f5cf18f35c05f51fbdf152bf588f956cf7cb" dependencies: @@ -1896,7 +1887,7 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" -chokidar@1.7.0, chokidar@^1.4.1, chokidar@^1.4.2, chokidar@^1.7.0: +chokidar@1.7.0, chokidar@^1.4.1, chokidar@^1.4.2: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1928,7 +1919,7 @@ chokidar@^2.0.0: optionalDependencies: fsevents "^1.0.0" -chokidar@^2.0.2: +chokidar@^2.0.2, chokidar@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" dependencies: @@ -1961,7 +1952,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-dependency-plugin@^5.0.0: +circular-dependency-plugin@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz#da168c0b37e7b43563fb9f912c1c007c213389ef" @@ -2239,6 +2230,15 @@ concat-stream@1.6.0, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concurrently@^3.0.0: version "3.5.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.0.tgz#8cf1b7707a6916a78a4ff5b77bb04dec54b379b2" @@ -2354,7 +2354,7 @@ copy-webpack-plugin@^4.0.1: minimatch "^3.0.4" node-dir "^0.1.10" -copy-webpack-plugin@^4.5.0: +copy-webpack-plugin@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz#fc4f68f4add837cc5e13d111b20715793225d29c" dependencies: @@ -3676,7 +3676,7 @@ fs-extra@3.0.1: jsonfile "^3.0.0" universalify "^0.1.0" -fs-extra@^4.0.0, fs-extra@^4.0.2: +fs-extra@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" dependencies: @@ -4224,6 +4224,10 @@ hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" +hosted-git-info@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" + hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -5303,9 +5307,9 @@ less-loader@^4.1.0: loader-utils "^1.1.0" pify "^3.0.0" -less@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/less/-/less-3.0.1.tgz#ba2fea24a5632ccb8c84230d6043c0bf91855e37" +less@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/less/-/less-3.0.2.tgz#1bcb9813bb6090c884ac142f02c633bd42931844" optionalDependencies: errno "^0.1.1" graceful-fs "^4.1.2" @@ -5313,7 +5317,7 @@ less@^3.0.1: mime "^1.4.1" mkdirp "^0.5.0" promise "^7.1.1" - request "2.81.0" + request "^2.83.0" source-map "^0.5.3" levn@~0.3.0: @@ -5323,7 +5327,7 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -license-webpack-plugin@^1.2.3: +license-webpack-plugin@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.3.1.tgz#688b76472188ef597918b7cae3eec7dc2fa5a0e8" dependencies: @@ -5797,9 +5801,9 @@ mimic-response@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" -mini-css-extract-plugin@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.3.0.tgz#f8dc03abb3a8663f1a431143e232fb47fb4d9318" +mini-css-extract-plugin@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.0.tgz#ff3bf08bee96e618e177c16ca6131bfecef707f9" dependencies: loader-utils "^1.1.0" webpack-sources "^1.1.0" @@ -5938,7 +5942,11 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@^2.3.0, nan@^2.3.2: +nan@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" + +nan@^2.3.0: version "2.7.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" @@ -6059,10 +6067,6 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" -node-modules-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/node-modules-path/-/node-modules-path-1.0.1.tgz#40096b08ce7ad0ea14680863af449c7c75a5d1c8" - node-pre-gyp@^0.6.36: version "0.6.38" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" @@ -6094,9 +6098,9 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" -node-sass@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" +node-sass@^4.8.3: + version "4.9.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -6110,7 +6114,7 @@ node-sass@^4.7.2: lodash.mergewith "^4.6.0" meow "^3.7.0" mkdirp "^0.5.1" - nan "^2.3.2" + nan "^2.10.0" node-gyp "^3.3.1" npmlog "^4.0.0" request "~2.79.0" @@ -6138,7 +6142,7 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, "normalize-package-data@~1.0.1 || ^2.0.0": version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: @@ -6174,13 +6178,40 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" + dependencies: + hosted-git-info "^2.6.0" + osenv "^0.1.5" + semver "^5.5.0" + validate-npm-package-name "^3.0.0" + +npm-registry-client@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.5.1.tgz#8115809c0a4b40938b8a109b8ea74d26c6f5d7f1" + dependencies: + concat-stream "^1.5.2" + graceful-fs "^4.1.6" + normalize-package-data "~1.0.1 || ^2.0.0" + npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + once "^1.3.3" + request "^2.74.0" + retry "^0.10.0" + safe-buffer "^5.1.1" + semver "2 >=2.2.1 || 3.x || 4 || 5" + slide "^1.1.3" + ssri "^5.2.4" + optionalDependencies: + npmlog "2 || ^3.1.0 || ^4.0.0" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" dependencies: path-key "^2.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0, npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: @@ -6390,6 +6421,13 @@ osenv@0, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +osenv@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + p-cancelable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" @@ -6775,9 +6813,9 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" -postcss-loader@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.3.tgz#eb210da734e475a244f76ccd61f9860f5bb3ee09" +postcss-loader@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.4.tgz#f44a6390e03c84108b2b2063182d1a1011b2ce76" dependencies: loader-utils "^1.1.0" postcss "^6.0.0" @@ -7561,7 +7599,7 @@ request@2.81.0, request@~2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.83.0: +request@^2.74.0, request@^2.83.0: version "2.85.0" resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" dependencies: @@ -7697,6 +7735,10 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" +retry@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -7787,21 +7829,15 @@ rx@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" -rxjs@6.0.0-uncanny-rc.7: - version "6.0.0-uncanny-rc.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0-uncanny-rc.7.tgz#e5d6ebe2c538c583bf4b3600b60112d64a7a6991" - dependencies: - tslib "^1.9.0" - rxjs@^5.4.2, rxjs@^5.5.2: version "5.5.10" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045" dependencies: symbol-observable "1.0.1" -rxjs@^6.0.0-beta.3, rxjs@^6.0.0-turbo-rc.4: - version "6.0.0-turbo-rc.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0-turbo-rc.4.tgz#5995dab91914f03ee4a68d923678333ae626d2ec" +rxjs@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.1.0.tgz#833447de4e4f6427b9cec3e5eb9f56415cd28315" dependencies: tslib "^1.9.0" @@ -7828,9 +7864,9 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-loader@^6.0.7: - version "6.0.7" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.7.tgz#dd2fdb3e7eeff4a53f35ba6ac408715488353d00" +sass-loader@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.0.1.tgz#fd937259ccba3a9cfe0d5f8a98746d48adfcc261" dependencies: clone-deep "^2.0.1" loader-utils "^1.0.1" @@ -7862,7 +7898,7 @@ schema-utils@^0.3.0: dependencies: ajv "^5.0.0" -schema-utils@^0.4.0, schema-utils@^0.4.3, schema-utils@^0.4.5: +schema-utils@^0.4.0, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" dependencies: @@ -7922,14 +7958,14 @@ semver-intersect@^1.1.2: dependencies: semver "^5.0.0" +"semver@2 >=2.2.1 || 3.x || 4 || 5", semver@^5.0.0, semver@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + "semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.1.0, semver@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -semver@^5.0.0, semver@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - semver@~4.3.3: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" @@ -8127,7 +8163,7 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -slide@^1.1.5: +slide@^1.1.3, slide@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -8580,9 +8616,9 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -style-loader@^0.20.2: - version "0.20.3" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.3.tgz#ebef06b89dec491bcb1fdb3452e913a6fd1c10c4" +style-loader@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852" dependencies: loader-utils "^1.1.0" schema-utils "^0.4.5" @@ -8965,7 +9001,7 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" -uglifyjs-webpack-plugin@^1.2.2, uglifyjs-webpack-plugin@^1.2.4: +uglifyjs-webpack-plugin@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz#5eec941b2e9b8538be0a20fc6eda25b14c7c1043" dependencies: @@ -8978,6 +9014,19 @@ uglifyjs-webpack-plugin@^1.2.2, uglifyjs-webpack-plugin@^1.2.4: webpack-sources "^1.1.0" worker-farm "^1.5.2" +uglifyjs-webpack-plugin@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz#2ef8387c8f1a903ec5e44fa36f9f3cbdcea67641" + dependencies: + cacache "^10.0.4" + find-cache-dir "^1.0.0" + schema-utils "^0.4.5" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + uglify-es "^3.3.4" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -9196,6 +9245,12 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + dependencies: + builtins "^1.0.3" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -9341,9 +9396,9 @@ webpack-core@^0.6.8: source-list-map "~0.1.7" source-map "~0.4.1" -webpack-dev-middleware@3.1.2, webpack-dev-middleware@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.1.2.tgz#be4d0c36a4fa7d69d6904093418514caa9df3a40" +webpack-dev-middleware@3.1.3, webpack-dev-middleware@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz#8b32aa43da9ae79368c1bf1183f2b6cf5e1f39ed" dependencies: loud-rejection "^1.6.0" memory-fs "~0.4.1" @@ -9353,9 +9408,9 @@ webpack-dev-middleware@3.1.2, webpack-dev-middleware@^3.1.0: url-join "^4.0.0" webpack-log "^1.0.1" -webpack-dev-server@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.3.tgz#5cecfd8a9d60c4638284813f1cf9562f04e5c1c5" +webpack-dev-server@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.4.tgz#9a08d13c4addd1e3b6d8ace116e86715094ad5b4" dependencies: ansi-html "0.0.7" array-includes "^3.0.3" @@ -9382,7 +9437,7 @@ webpack-dev-server@^3.1.1: spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^5.1.0" - webpack-dev-middleware "3.1.2" + webpack-dev-middleware "3.1.3" webpack-log "^1.1.2" yargs "11.0.0" @@ -9428,9 +9483,9 @@ webpack-subresource-integrity@^1.1.0-rc.4: dependencies: webpack-core "^0.6.8" -webpack@~4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.5.0.tgz#1e6f71e148ead02be265ff2879c9cd6bb30b8848" +webpack@~4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.6.0.tgz#363eafa733710eb0ed28c512b2b9b9f5fb01e69b" dependencies: acorn "^5.0.0" acorn-dynamic-import "^3.0.0" @@ -9446,7 +9501,7 @@ webpack@~4.5.0: mkdirp "~0.5.0" neo-async "^2.5.0" node-libs-browser "^2.0.0" - schema-utils "^0.4.2" + schema-utils "^0.4.4" tapable "^1.0.0" uglifyjs-webpack-plugin "^1.2.4" watchpack "^1.5.0" @@ -9636,6 +9691,12 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yargs-parser@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.0.0.tgz#c737c93de2567657750cb1f2c00be639fd19c994" + dependencies: + camelcase "^4.1.0" + yargs-parser@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" From b0eca85e5187fbfe2f2edd02327ce2fc6f0e196b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 24 Apr 2018 11:34:11 -0700 Subject: [PATCH 016/582] refactor(compiler): compile{Component,Directive} take only local information (#23545) Previously, the compileComponent() and compileDirective() APIs still required the output of global analysis, even though they only read local information from that output. With this refactor, compileComponent() and compileDirective() now define their inputs explicitly, with the new interfaces R3ComponentMetadata and R3DirectiveMetadata. compileComponentGlobal() and compileDirectiveGlobal() are introduced and convert from global analysis output into the new metadata format. This refactor also splits out the view compiler into separate files as r3_view_compiler_local.ts was getting unwieldy. Finally, this refactor also splits out generation of DI factory functions into a separate r3_factory utility as the logic is utilized between different compilers. PR Close #23545 --- .../src/ngtsc/transform/src/injectable.ts | 3 +- packages/compiler-cli/test/ngc_spec.ts | 1 + packages/compiler/src/aot/compiler.ts | 2 +- packages/compiler/src/core.ts | 17 + .../compiler/src/injectable_compiler_2.ts | 1 + packages/compiler/src/render3/r3_factory.ts | 288 ++++++ .../compiler/src/render3/r3_identifiers.ts | 10 + .../compiler/src/render3/r3_pipe_compiler.ts | 12 +- packages/compiler/src/render3/view/api.ts | 178 ++++ .../compiler/src/render3/view/compiler.ts | 444 ++++++++++ .../template.ts} | 832 ++++-------------- packages/compiler/src/render3/view/util.ts | 122 +++ .../src/template_parser/binding_parser.ts | 2 - .../compiler/test/render3/mock_compile.ts | 6 +- .../render3/r3_compiler_compliance_spec.ts | 2 +- .../r3_view_compiler_input_outputs_spec.ts | 2 + 16 files changed, 1262 insertions(+), 660 deletions(-) create mode 100644 packages/compiler/src/render3/r3_factory.ts create mode 100644 packages/compiler/src/render3/view/api.ts create mode 100644 packages/compiler/src/render3/view/compiler.ts rename packages/compiler/src/render3/{r3_view_compiler_local.ts => view/template.ts} (55%) create mode 100644 packages/compiler/src/render3/view/util.ts diff --git a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts index 62bebcfd19..a97ffe38a1 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts @@ -131,7 +131,7 @@ function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInj } }); const token = new WrappedNodeExpr(tokenExpr); - useType.push({token, optional, self, skipSelf}); + useType.push({token, optional, self, skipSelf, attribute: false}); }); return useType; } @@ -142,6 +142,7 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep { optional: false, self: false, skipSelf: false, + attribute: false, }; function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void { diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts index 14845f2c07..c4cbf8a1dd 100644 --- a/packages/compiler-cli/test/ngc_spec.ts +++ b/packages/compiler-cli/test/ngc_spec.ts @@ -2021,6 +2021,7 @@ describe('ngc transformer command-line', () => { const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]); expect(exitCode).toBe(0, 'Compile failed'); expect(emittedFile('hello-world.js')).toContain('ngComponentDef'); + expect(emittedFile('hello-world.js')).toContain('HelloWorldComponent_Factory'); }); it('should emit an injection of a string token', () => { diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 775b8d5d8b..829691e6e8 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -25,7 +25,7 @@ import {ParseError} from '../parse_util'; import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler'; import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler'; import {HtmlToTemplateTransform} from '../render3/r3_template_transform'; -import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler_local'; +import {compileComponentFromRender2 as compileIvyComponent, compileDirectiveFromRender2 as compileIvyDirective} from '../render3/view/compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {SummaryResolver} from '../summary_resolver'; diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 6f6580fd10..1c704baf3f 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -361,3 +361,20 @@ export function parseSelectorToR3Selector(selector: string): R3CssSelectorList { const selectors = CssSelector.parse(selector); return selectors.map(parserSelectorToR3Selector); } + +// Pasted from render3/interfaces/definition since it cannot be referenced directly +/** + * Flags passed into template functions to determine which blocks (i.e. creation, update) + * should be executed. + * + * Typically, a template runs both the creation block and the update block on initialization and + * subsequent runs only execute the update block. However, dynamically created views require that + * the creation block be executed separately from the update block (for backwards compat). + */ +export const enum RenderFlags { + /* Whether to run the creation block (e.g. create elements and directives) */ + Create = 0b01, + + /* Whether to run the update block (e.g. refresh bindings) */ + Update = 0b10 +} diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index a29d18f404..0f01f881b0 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -30,6 +30,7 @@ export interface IvyInjectableDep { optional: boolean; self: boolean; skipSelf: boolean; + attribute: boolean; } export interface IvyInjectableMetadata { diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts new file mode 100644 index 0000000000..78d7e3c75c --- /dev/null +++ b/packages/compiler/src/render3/r3_factory.ts @@ -0,0 +1,288 @@ +/** + * @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 {StaticSymbol} from '../aot/static_symbol'; +import {CompileTypeMetadata, tokenReference} from '../compile_metadata'; +import {CompileReflector} from '../compile_reflector'; +import {InjectFlags} from '../core'; +import {Identifiers} from '../identifiers'; +import * as o from '../output/output_ast'; +import {Identifiers as R3} from '../render3/r3_identifiers'; +import {OutputContext} from '../util'; + +import {unsupported} from './view/util'; + +/** + * Metadata required by the factory generator to generate a `factory` function for a type. + */ +export interface R3FactoryMetadata { + /** + * String name of the type being generated (used to name the factory function). + */ + name: string; + + /** + * An expression representing the function (or constructor) which will instantiate the requested + * type. + * + * This could be a reference to a constructor type, or to a user-defined factory function. The + * `useNew` property determines whether it will be called as a constructor or not. + */ + fnOrClass: o.Expression; + + /** + * Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it + * may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata` + * for those parameters. + */ + deps: R3DependencyMetadata[]; + + /** + * Whether to interpret `fnOrClass` as a constructor function (`useNew: true`) or as a factory + * (`useNew: false`). + */ + useNew: boolean; + + + /** + * An expression for the function which will be used to inject dependencies. The API of this + * function could be different, and other options control how it will be invoked. + */ + injectFn: o.ExternalReference; + + /** + * Whether the `injectFn` given above accepts a 2nd parameter indicating the default value to + * be used to resolve missing @Optional dependencies. + * + * If the optional parameter is used, injectFn for an optional dependency will be invoked as: + * `injectFn(token, null, flags)`. + * + * If it's not used, injectFn for an optional dependency will be invoked as: + * `injectFn(token, flags)`. The Optional flag will indicate that injectFn should select a default + * value if it cannot satisfy the injection request for the token. + */ + useOptionalParam: boolean; + + /** + * If present, the return of the factory function will be an array with the injected value in the + * 0th position and the extra results included in subsequent positions. + * + * Occasionally APIs want to construct additional values when the factory function is called. The + * paradigm there is to have the factory function return an array, with the DI-created value as + * well as other values. Specifying `extraResults` enables this functionality. + */ + extraResults?: o.Expression[]; +} + +/** + * Resolved type of a dependency. + * + * Occasionally, dependencies will have special significance which is known statically. In that + * case the `R3ResolvedDependencyType` informs the factory generator that a particular dependency + * should be generated specially (usually by calling a special injection function instead of the + * standard one). + */ +export enum R3ResolvedDependencyType { + /** + * A normal token dependency. + */ + Token = 0, + + /** + * The dependency is for an attribute. + * + * The token expression is a string representing the attribute name. + */ + Attribute = 1, + + /** + * The dependency is for the `Injector` type itself. + */ + Injector = 2, + + /** + * The dependency is for `ElementRef`. + */ + ElementRef = 3, + + /** + * The dependency is for `TemplateRef`. + */ + TemplateRef = 4, + + /** + * The dependency is for `ViewContainerRef`. + */ + ViewContainerRef = 5, +} + +/** + * Metadata representing a single dependency to be injected into a constructor or function call. + */ +export interface R3DependencyMetadata { + /** + * An expression representing the token or value to be injected. + */ + token: o.Expression; + + /** + * An enum indicating whether this dependency has special meaning to Angular and needs to be + * injected specially. + */ + resolved: R3ResolvedDependencyType; + + /** + * Whether the dependency has an @Host qualifier. + */ + host: boolean; + + /** + * Whether the dependency has an @Optional qualifier. + */ + optional: boolean; + + /** + * Whether the dependency has an @Self qualifier. + */ + self: boolean; + + /** + * Whether the dependency has an @SkipSelf qualifier. + */ + skipSelf: boolean; +} + +/** + * Construct a factory function expression for the given `R3FactoryMetadata`. + */ +export function compileFactoryFunction(meta: R3FactoryMetadata): o.Expression { + // Each dependency becomes an invocation of an inject*() function. + const args = + meta.deps.map(dep => compileInjectDependency(dep, meta.injectFn, meta.useOptionalParam)); + + // The overall result depends on whether this is construction or function invocation. + const expr = meta.useNew ? new o.InstantiateExpr(meta.fnOrClass, args) : + new o.InvokeFunctionExpr(meta.fnOrClass, args); + + // If `extraResults` is specified, then the result is an array consisting of the instantiated + // value plus any extra results. + const retExpr = + meta.extraResults === undefined ? expr : o.literalArr([expr, ...meta.extraResults]); + return o.fn( + [], [new o.ReturnStatement(retExpr)], o.INFERRED_TYPE, undefined, `${meta.name}_Factory`); +} + +function compileInjectDependency( + dep: R3DependencyMetadata, injectFn: o.ExternalReference, + useOptionalParam: boolean): o.Expression { + // Interpret the dependency according to its resolved type. + switch (dep.resolved) { + case R3ResolvedDependencyType.Token: + case R3ResolvedDependencyType.Injector: { + // Build up the injection flags according to the metadata. + const flags = InjectFlags.Default | (dep.self ? InjectFlags.Self : 0) | + (dep.skipSelf ? InjectFlags.SkipSelf : 0) | (dep.host ? InjectFlags.Host : 0) | + (dep.optional ? InjectFlags.Optional : 0); + // Determine the token used for injection. In almost all cases this is the given token, but + // if the dependency is resolved to the `Injector` then the special `INJECTOR` token is used + // instead. + let token: o.Expression = dep.token; + if (dep.resolved === R3ResolvedDependencyType.Injector) { + token = o.importExpr(Identifiers.INJECTOR); + } + + // Build up the arguments to the injectFn call. + const injectArgs = [dep.token]; + // If this dependency is optional or otherwise has non-default flags, then additional + // parameters describing how to inject the dependency must be passed to the inject function + // that's being used. + if (flags !== InjectFlags.Default || dep.optional) { + // Either the dependency is optional, or non-default flags are in use. Either of these cases + // necessitates adding an argument for the default value if such an argument is required + // by the inject function (useOptionalParam === true). + if (useOptionalParam) { + // The inject function requires a default value parameter. + injectArgs.push(dep.optional ? o.NULL_EXPR : o.literal(undefined)); + } + // The last parameter is always the InjectFlags, which only need to be specified if they're + // non-default. + if (flags !== InjectFlags.Default) { + injectArgs.push(o.literal(flags)); + } + } + return o.importExpr(injectFn).callFn(injectArgs); + } + case R3ResolvedDependencyType.Attribute: + // In the case of attributes, the attribute name in question is given as the token. + return o.importExpr(R3.injectAttribute).callFn([dep.token]); + case R3ResolvedDependencyType.ElementRef: + return o.importExpr(R3.injectElementRef).callFn([]); + case R3ResolvedDependencyType.TemplateRef: + return o.importExpr(R3.injectTemplateRef).callFn([]); + case R3ResolvedDependencyType.ViewContainerRef: + return o.importExpr(R3.injectViewContainerRef).callFn([]); + default: + return unsupported( + `Unknown R3ResolvedDependencyType: ${R3ResolvedDependencyType[dep.resolved]}`); + } +} + +/** + * A helper function useful for extracting `R3DependencyMetadata` from a Render2 + * `CompileTypeMetadata` instance. + */ +export function dependenciesFromGlobalMetadata( + type: CompileTypeMetadata, outputCtx: OutputContext, + reflector: CompileReflector): R3DependencyMetadata[] { + // Use the `CompileReflector` to look up references to some well-known Angular types. These will + // be compared with the token to statically determine whether the token has significance to + // Angular, and set the correct `R3ResolvedDependencyType` as a result. + const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef); + const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef); + const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef); + const injectorRef = reflector.resolveExternalReference(Identifiers.Injector); + + // Iterate through the type's DI dependencies and produce `R3DependencyMetadata` for each of them. + const deps: R3DependencyMetadata[] = []; + for (let dependency of type.diDeps) { + if (dependency.token) { + const tokenRef = tokenReference(dependency.token); + let resolved: R3ResolvedDependencyType = R3ResolvedDependencyType.Token; + if (tokenRef === elementRef) { + resolved = R3ResolvedDependencyType.ElementRef; + } else if (tokenRef === templateRef) { + resolved = R3ResolvedDependencyType.TemplateRef; + } else if (tokenRef === viewContainerRef) { + resolved = R3ResolvedDependencyType.ViewContainerRef; + } else if (tokenRef === injectorRef) { + resolved = R3ResolvedDependencyType.Injector; + } else if (dependency.isAttribute) { + resolved = R3ResolvedDependencyType.Attribute; + } + + // In the case of most dependencies, the token will be a reference to a type. Sometimes, + // however, it can be a string, in the case of older Angular code or @Attribute injection. + const token = + tokenRef instanceof StaticSymbol ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef); + + // Construct the dependency. + deps.push({ + token, + resolved, + host: !!dependency.isHost, + optional: !!dependency.isOptional, + self: !!dependency.isSelf, + skipSelf: !!dependency.isSkipSelf, + }); + } else { + unsupported('dependency without a token'); + } + } + + return deps; +} diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index b0a6c9af22..030a2b9b94 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -90,11 +90,21 @@ export class Identifiers { static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE}; + static ComponentDef: o.ExternalReference = { + name: 'ComponentDef', + moduleName: CORE, + }; + static defineDirective: o.ExternalReference = { name: 'ɵdefineDirective', moduleName: CORE, }; + static DirectiveDef: o.ExternalReference = { + name: 'DirectiveDef', + moduleName: CORE, + }; + static defineInjector: o.ExternalReference = { name: 'defineInjector', moduleName: CORE, diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index f1a849c33c..98c75db184 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -12,8 +12,9 @@ import {DefinitionKind} from '../constant_pool'; import * as o from '../output/output_ast'; import {OutputContext, error} from '../util'; +import {compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; -import {createFactory} from './r3_view_compiler_local'; + /** * Write a pipe definition to the output context. @@ -30,7 +31,14 @@ export function compilePipe( {key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false}); // e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }` - const templateFactory = createFactory(pipe.type, outputCtx, reflector, []); + const deps = dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector); + const templateFactory = compileFactoryFunction({ + name: identifierName(pipe.type) !, + fnOrClass: outputCtx.importExpr(pipe.type.reference), deps, + useNew: true, + injectFn: R3.directiveInject, + useOptionalParam: false, + }); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); // e.g. `pure: true` diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts new file mode 100644 index 0000000000..71f1624a69 --- /dev/null +++ b/packages/compiler/src/render3/view/api.ts @@ -0,0 +1,178 @@ +/** + * @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 o from '../../output/output_ast'; +import {ParseSourceSpan} from '../../parse_util'; +import * as t from '../r3_ast'; +import {R3DependencyMetadata} from '../r3_factory'; + +/** + * Information needed to compile a directive for the render3 runtime. + */ +export interface R3DirectiveMetadata { + /** + * Name of the directive type. + */ + name: string; + + /** + * An expression representing a reference to the directive itself. + */ + type: o.Expression; + + /** + * A source span for the directive type. + */ + typeSourceSpan: ParseSourceSpan; + + /** + * Dependencies of the directive's constructor. + */ + deps: R3DependencyMetadata[]; + + /** + * Unparsed selector of the directive, or `null` if there was no selector. + */ + selector: string|null; + + /** + * Information about the content queries made by the directive. + */ + queries: R3QueryMetadata[]; + + /** + * Mappings indicating how the directive interacts with its host element (host bindings, + * listeners, etc). + */ + host: { + /** + * A mapping of attribute binding keys to unparsed expressions. + */ + attributes: {[key: string]: string}; + + /** + * A mapping of event binding keys to unparsed expressions. + */ + listeners: {[key: string]: string}; + + /** + * A mapping of property binding keys to unparsed expressions. + */ + properties: {[key: string]: string}; + }; + + /** + * A mapping of input field names to the property names. + */ + inputs: {[field: string]: string}; + + /** + * A mapping of output field names to the property names. + */ + outputs: {[field: string]: string}; +} + +/** + * Information needed to compile a component for the render3 runtime. + */ +export interface R3ComponentMetadata extends R3DirectiveMetadata { + /** + * Information about the component's template. + */ + template: { + /** + * Parsed nodes of the template. + */ + nodes: t.Node[]; + + /** + * Whether the template includes tags. + */ + hasNgContent: boolean; + + /** + * Selectors found in the tags in the template. + */ + ngContentSelectors: string[]; + }; + + /** + * Information about usage of specific lifecycle events which require special treatment in the + * code generator. + */ + lifecycle: { + /** + * Whether the component uses NgOnChanges. + */ + usesOnChanges: boolean; + }; + + /** + * Information about the view queries made by the component. + */ + viewQueries: R3QueryMetadata[]; + + /** + * A map of pipe names to an expression referencing the pipe type which are in the scope of the + * compilation. + */ + pipes: Map; + + /** + * A map of directive selectors to an expression referencing the directive type which are in the + * scope of the compilation. + */ + directives: Map; +} + +/** + * Information needed to compile a query (view or content). + */ +export interface R3QueryMetadata { + /** + * Name of the property on the class to update with query results. + */ + propertyName: string; + + /** + * Whether to read only the first matching result, or an array of results. + */ + first: boolean; + + /** + * Either an expression representing a type for the query predicate, or a set of string selectors. + */ + predicate: o.Expression|string[]; + + /** + * Whether to include only direct children or all descendants. + */ + descendants: boolean; + + /** + * An expression representing a type to read from each matched node, or null if the node itself + * is to be returned. + */ + read: o.Expression|null; +} + +/** + * Output of render3 directive compilation. + */ +export interface R3DirectiveDef { + expression: o.Expression; + type: o.Type; +} + +/** + * Output of render3 component compilation. + */ +export interface R3ComponentDef { + expression: o.Expression; + type: o.Type; +} diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts new file mode 100644 index 0000000000..cba9e7be1f --- /dev/null +++ b/packages/compiler/src/render3/view/compiler.ts @@ -0,0 +1,444 @@ +/** + * @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 {StaticSymbol} from '../../aot/static_symbol'; +import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../../compile_metadata'; +import {CompileReflector} from '../../compile_reflector'; +import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; +import {ConstantPool, DefinitionKind} from '../../constant_pool'; +import * as core from '../../core'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {Identifiers} from '../../identifiers'; +import {LifecycleHooks} from '../../lifecycle_reflector'; +import * as o from '../../output/output_ast'; +import {ParseSourceSpan, typeSourceSpan} from '../../parse_util'; +import {CssSelector, SelectorMatcher} from '../../selector'; +import {BindingParser} from '../../template_parser/binding_parser'; +import {OutputContext, error} from '../../util'; + +import * as t from './../r3_ast'; +import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from './../r3_factory'; +import {Identifiers as R3} from './../r3_identifiers'; +import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; +import {BindingScope, TemplateDefinitionBuilder} from './template'; +import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; + +function baseDirectiveFields( + meta: R3DirectiveMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): DefinitionMap { + const definitionMap = new DefinitionMap(); + + // e.g. `type: MyDirective` + definitionMap.set('type', meta.type); + + // e.g. `selectors: [['', 'someDir', '']]` + definitionMap.set('selectors', createDirectiveSelector(meta.selector !)); + + const queryDefinitions = createQueryDefinitions(meta.queries, constantPool); + + // e.g. `factory: () => new MyApp(injectElementRef())` + definitionMap.set('factory', compileFactoryFunction({ + name: meta.name, + fnOrClass: meta.type, + deps: meta.deps, + useNew: true, + injectFn: R3.directiveInject, + useOptionalParam: false, + extraResults: queryDefinitions, + })); + + // e.g. `hostBindings: (dirIndex, elIndex) => { ... } + definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser)); + + // e.g. `attributes: ['role', 'listbox']` + definitionMap.set('attributes', createHostAttributesArray(meta)); + + // e.g 'inputs: {a: 'a'}` + definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs)); + + // e.g 'outputs: {a: 'a'}` + definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); + + return definitionMap; +} + +/** + * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`. + */ +export function compileDirective( + meta: R3DirectiveMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): R3DirectiveDef { + const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]); + const type = + new o.ExpressionType(o.importExpr(R3.DirectiveDef, [new o.ExpressionType(meta.type)])); + return {expression, type}; +} + +/** + * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. + */ +export function compileComponent( + meta: R3ComponentMetadata, constantPool: ConstantPool, + bindingParser: BindingParser): R3ComponentDef { + const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); + + const selector = meta.selector && CssSelector.parse(meta.selector); + const firstSelector = selector && selector[0]; + + // e.g. `attr: ["class", ".my.app"]` + // This is optional an only included if the first selector of a component specifies attributes. + if (firstSelector) { + const selectorAttributes = firstSelector.getAttrs(); + if (selectorAttributes.length) { + definitionMap.set( + 'attrs', constantPool.getConstLiteral( + o.literalArr(selectorAttributes.map( + value => value != null ? o.literal(value) : o.literal(undefined))), + /* forceShared */ true)); + } + } + + // Generate the CSS matcher that recognize directive + let directiveMatcher: SelectorMatcher|null = null; + + if (meta.directives.size) { + const matcher = new SelectorMatcher(); + meta.directives.forEach((expression, selector: string) => { + matcher.addSelectables(CssSelector.parse(selector), expression); + }); + directiveMatcher = matcher; + } + + // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` + const templateTypeName = meta.name; + const templateName = templateTypeName ? `${templateTypeName}_Template` : null; + + const directivesUsed = new Set(); + const pipesUsed = new Set(); + + const template = meta.template; + const templateFunctionExpression = + new TemplateDefinitionBuilder( + constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName, + meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed) + .buildTemplateFunction( + template.nodes, [], template.hasNgContent, template.ngContentSelectors); + + definitionMap.set('template', templateFunctionExpression); + + // e.g. `directives: [MyDirective]` + if (directivesUsed.size) { + definitionMap.set('directives', o.literalArr(Array.from(directivesUsed))); + } + + // e.g. `pipes: [MyPipe]` + if (pipesUsed.size) { + definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); + } + + // e.g. `features: [NgOnChangesFeature(MyComponent)]` + const features: o.Expression[] = []; + if (meta.lifecycle.usesOnChanges) { + features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); + } + if (features.length) { + definitionMap.set('features', o.literalArr(features)); + } + + const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); + const type = + new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)])); + + return {expression, type}; +} + +/** + * A wrapper around `compileDirective` which depends on render2 global analysis data as its input + * instead of the `R3DirectiveMetadata`. + * + * `R3DirectiveMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected + * information. + */ +export function compileDirectiveFromRender2( + outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector, + bindingParser: BindingParser) { + const name = identifierName(directive.type) !; + name || error(`Cannot resolver the name of ${directive.type}`); + + const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); + + const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector); + const res = compileDirective(meta, outputCtx.constantPool, bindingParser); + + // Create the partial class to be merged with the actual class. + outputCtx.statements.push(new o.ClassStmt( + name, null, + [new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)], + [], new o.ClassMethod(null, [], []), [])); +} + +/** + * A wrapper around `compileComponent` which depends on render2 global analysis data as its input + * instead of the `R3DirectiveMetadata`. + * + * `R3ComponentMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected + * information. + */ +export function compileComponentFromRender2( + outputCtx: OutputContext, component: CompileDirectiveMetadata, nodes: t.Node[], + hasNgContent: boolean, ngContentSelectors: string[], reflector: CompileReflector, + bindingParser: BindingParser, directiveTypeBySel: Map, + pipeTypeByName: Map) { + const name = identifierName(component.type) !; + name || error(`Cannot resolver the name of ${component.type}`); + + const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component); + + const summary = component.toSummary(); + + // Compute the R3ComponentMetadata from the CompileDirectiveMetadata + const meta: R3ComponentMetadata = { + ...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector), + selector: component.selector, + template: { + nodes, hasNgContent, ngContentSelectors, + }, + lifecycle: { + usesOnChanges: + component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), + }, + directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), + pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), + viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), + }; + const res = compileComponent(meta, outputCtx.constantPool, bindingParser); + + // Create the partial class to be merged with the actual class. + outputCtx.statements.push(new o.ClassStmt( + name, null, + [new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)], + [], new o.ClassMethod(null, [], []), [])); +} + +/** + * Compute `R3DirectiveMetadata` given `CompileDirectiveMetadata` and a `CompileReflector`. + */ +function directiveMetadataFromGlobalMetadata( + directive: CompileDirectiveMetadata, outputCtx: OutputContext, + reflector: CompileReflector): R3DirectiveMetadata { + const summary = directive.toSummary(); + const name = identifierName(directive.type) !; + name || error(`Cannot resolver the name of ${directive.type}`); + + return { + name, + type: outputCtx.importExpr(directive.type.reference), + typeSourceSpan: + typeSourceSpan(directive.isComponent ? 'Component' : 'Directive', directive.type), + selector: directive.selector, + deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector), + queries: queriesFromGlobalMetadata(directive.queries, outputCtx), + host: { + attributes: directive.hostAttributes, + listeners: summary.hostListeners, + properties: summary.hostProperties, + }, + inputs: directive.inputs, + outputs: directive.outputs, + }; +} + +/** + * Convert `CompileQueryMetadata` into `R3QueryMetadata`. + */ +function queriesFromGlobalMetadata( + queries: CompileQueryMetadata[], outputCtx: OutputContext): R3QueryMetadata[] { + return queries.map(query => { + let read: o.Expression|null = null; + if (query.read && query.read.identifier) { + read = outputCtx.importExpr(query.read.identifier.reference); + } + return { + propertyName: query.propertyName, + first: query.first, + predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx), + descendants: query.descendants, read, + }; + }); +} + +/** + * Convert `CompileTokenMetadata` for query selectors into either an expression for a predicate + * type, or a list of string predicates. + */ +function selectorsFromGlobalMetadata( + selectors: CompileTokenMetadata[], outputCtx: OutputContext): o.Expression|string[] { + if (selectors.length > 1 || (selectors.length == 1 && selectors[0].value)) { + const selectorStrings = selectors.map(value => value.value as string); + selectorStrings.some(value => !value) && + error('Found a type among the string selectors expected'); + return outputCtx.constantPool.getConstLiteral( + o.literalArr(selectorStrings.map(value => o.literal(value)))); + } + + if (selectors.length == 1) { + const first = selectors[0]; + if (first.identifier) { + return outputCtx.importExpr(first.identifier.reference); + } + } + + error('Unexpected query form'); + return o.NULL_EXPR; +} + +/** + * + * @param meta + * @param constantPool + */ +function createQueryDefinitions( + queries: R3QueryMetadata[], constantPool: ConstantPool): o.Expression[]|undefined { + const queryDefinitions: o.Expression[] = []; + for (let i = 0; i < queries.length; i++) { + const query = queries[i]; + const predicate = getQueryPredicate(query, constantPool); + + // e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false) + const parameters = [ + o.literal(null, o.INFERRED_TYPE), + predicate, + o.literal(query.descendants), + ]; + + if (query.read) { + parameters.push(query.read); + } + + queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); + } + return queryDefinitions.length > 0 ? queryDefinitions : undefined; +} + +// Turn a directive selector into an R3-compatible selector for directive def +function createDirectiveSelector(selector: string): o.Expression { + return asLiteral(core.parseSelectorToR3Selector(selector)); +} + +function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null { + const values: o.Expression[] = []; + const attributes = meta.host.attributes; + for (let key of Object.getOwnPropertyNames(attributes)) { + const value = attributes[key]; + values.push(o.literal(key), o.literal(value)); + } + if (values.length > 0) { + return o.literalArr(values); + } + return null; +} + +// Return a host binding function or null if one is not necessary. +function createHostBindingsFunction( + meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null { + const statements: o.Statement[] = []; + + const temporary = temporaryAllocator(statements, TEMPORARY_NAME); + + const hostBindingSourceSpan = meta.typeSourceSpan; + + // Calculate the queries + for (let index = 0; index < meta.queries.length; index++) { + const query = meta.queries[index]; + + // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); + const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + // The query list is at the query index + 1 because the directive itself is in slot 0. + const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); + const assignToTemporary = temporary().set(getQueryList); + const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]); + const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE)) + .prop(query.propertyName) + .set(query.first ? temporary().prop('first') : temporary()); + const andExpression = callQueryRefresh.and(updateDirective); + statements.push(andExpression.toStmt()); + } + + const directiveSummary = metadataAsSummary(meta); + + // Calculate the host property bindings + const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); + const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + if (bindings) { + for (const binding of bindings) { + const bindingExpr = convertPropertyBinding( + null, bindingContext, binding.expression, 'b', BindingForm.TrySimple, + () => error('Unexpected interpolation')); + statements.push(...bindingExpr.stmts); + statements.push(o.importExpr(R3.elementProperty) + .callFn([ + o.variable('elIndex'), + o.literal(binding.name), + o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), + ]) + .toStmt()); + } + } + + // Calculate host event bindings + const eventBindings = + bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); + if (eventBindings) { + for (const binding of eventBindings) { + const bindingExpr = convertActionBinding( + null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation')); + const bindingName = binding.name && sanitizeIdentifier(binding.name); + const typeName = meta.name; + const functionName = + typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; + const handler = o.fn( + [new o.FnParam('$event', o.DYNAMIC_TYPE)], + [...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE, + null, functionName); + statements.push( + o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt()); + } + } + + if (statements.length > 0) { + const typeName = meta.name; + return o.fn( + [ + new o.FnParam('dirIndex', o.NUMBER_TYPE), + new o.FnParam('elIndex', o.NUMBER_TYPE), + ], + statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null); + } + + return null; +} + +function metadataAsSummary(meta: R3DirectiveMetadata): CompileDirectiveSummary { + // clang-format off + return { + hostAttributes: meta.host.attributes, + hostListeners: meta.host.listeners, + hostProperties: meta.host.properties, + } as CompileDirectiveSummary; + // clang-format on +} + + +function typeMapToExpressionMap( + map: Map, outputCtx: OutputContext): Map { + // Convert each map entry into another entry where the value is an expression importing the type. + const entries = Array.from(map).map( + ([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]); + return new Map(entries); +} \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_view_compiler_local.ts b/packages/compiler/src/render3/view/template.ts similarity index 55% rename from packages/compiler/src/render3/r3_view_compiler_local.ts rename to packages/compiler/src/render3/view/template.ts index 4e1a4f9c1c..a27851d86b 100644 --- a/packages/compiler/src/render3/r3_view_compiler_local.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -6,212 +6,21 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileQueryMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../compile_metadata'; -import {CompileReflector} from '../compile_reflector'; -import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter'; -import {ConstantPool, DefinitionKind} from '../constant_pool'; -import * as core from '../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../expression_parser/ast'; -import {Identifiers} from '../identifiers'; -import {LifecycleHooks} from '../lifecycle_reflector'; -import * as o from '../output/output_ast'; -import {ParseSourceSpan, typeSourceSpan} from '../parse_util'; -import {CssSelector, SelectorMatcher} from '../selector'; -import {BindingParser} from '../template_parser/binding_parser'; -import {OutputContext, error} from '../util'; +import {flatten, sanitizeIdentifier} from '../../compile_metadata'; +import {CompileReflector} from '../../compile_reflector'; +import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; +import {ConstantPool} from '../../constant_pool'; +import * as core from '../../core'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import * as o from '../../output/output_ast'; +import {ParseSourceSpan} from '../../parse_util'; +import {CssSelector, SelectorMatcher} from '../../selector'; +import {OutputContext, error} from '../../util'; +import * as t from '../r3_ast'; +import {Identifiers as R3} from '../r3_identifiers'; -import * as t from './r3_ast'; -import {Identifiers as R3} from './r3_identifiers'; - - - -/** Name of the context parameter passed into a template function */ -const CONTEXT_NAME = 'ctx'; - -/** Name of the RenderFlag passed into a template function */ -const RENDER_FLAGS = 'rf'; - -/** Name of the temporary to use during data binding */ -const TEMPORARY_NAME = '_t'; - -/** The prefix reference variables */ -const REFERENCE_PREFIX = '_r'; - -/** The name of the implicit context reference */ -const IMPLICIT_REFERENCE = '$implicit'; - -/** Name of the i18n attributes **/ -const I18N_ATTR = 'i18n'; -const I18N_ATTR_PREFIX = 'i18n-'; - -/** I18n separators for metadata **/ -const MEANING_SEPARATOR = '|'; -const ID_SEPARATOR = '@@'; - -export function compileDirective( - outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector, - bindingParser: BindingParser) { - const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; - - const field = (key: string, value: o.Expression | null) => { - if (value) { - definitionMapValues.push({key, value, quoted: false}); - } - }; - - // e.g. `type: MyDirective` - field('type', outputCtx.importExpr(directive.type.reference)); - - // e.g. `selectors: [['', 'someDir', '']]` - field('selectors', createDirectiveSelector(directive.selector !)); - - // e.g. `factory: () => new MyApp(injectElementRef())` - field('factory', createFactory(directive.type, outputCtx, reflector, directive.queries)); - - // e.g. `hostBindings: (dirIndex, elIndex) => { ... } - field('hostBindings', createHostBindingsFunction(directive, outputCtx, bindingParser)); - - // e.g. `attributes: ['role', 'listbox']` - field('attributes', createHostAttributesArray(directive, outputCtx)); - - // e.g 'inputs: {a: 'a'}` - field('inputs', conditionallyCreateMapObjectLiteral(directive.inputs)); - - // e.g 'outputs: {a: 'a'}` - field('outputs', conditionallyCreateMapObjectLiteral(directive.outputs)); - - const className = identifierName(directive.type) !; - className || error(`Cannot resolver the name of ${directive.type}`); - - const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); - const definitionFunction = - o.importExpr(R3.defineDirective).callFn([o.literalMap(definitionMapValues)]); - - // Create the partial class to be merged with the actual class. - outputCtx.statements.push(new o.ClassStmt( - className, null, - [new o.ClassField( - definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], definitionFunction)], - [], new o.ClassMethod(null, [], []), [])); -} - -export function compileComponent( - outputCtx: OutputContext, component: CompileDirectiveMetadata, nodes: t.Node[], - hasNgContent: boolean, ngContentSelectors: string[], reflector: CompileReflector, - bindingParser: BindingParser, directiveTypeBySel: Map, - pipeTypeByName: Map) { - const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; - - const field = (key: string, value: o.Expression | null) => { - if (value) { - definitionMapValues.push({key, value, quoted: false}); - } - }; - - // Generate the CSS matcher that recognize directive - let directiveMatcher: SelectorMatcher|null = null; - - if (directiveTypeBySel.size) { - const matcher = new SelectorMatcher(); - directiveTypeBySel.forEach((staticType: any, selector: string) => { - matcher.addSelectables(CssSelector.parse(selector), staticType); - }); - directiveMatcher = matcher; - } - - // Directives and Pipes used from the template - const directives = new Set(); - const pipes = new Set(); - - // e.g. `type: MyApp` - field('type', outputCtx.importExpr(component.type.reference)); - - // e.g. `selectors: [['my-app']]` - field('selectors', createDirectiveSelector(component.selector !)); - - const selector = component.selector && CssSelector.parse(component.selector); - const firstSelector = selector && selector[0]; - - // e.g. `attr: ["class", ".my.app"]` - // This is optional an only included if the first selector of a component specifies attributes. - if (firstSelector) { - const selectorAttributes = firstSelector.getAttrs(); - if (selectorAttributes.length) { - field( - 'attrs', outputCtx.constantPool.getConstLiteral( - o.literalArr(selectorAttributes.map( - value => value != null ? o.literal(value) : o.literal(undefined))), - /* forceShared */ true)); - } - } - - // e.g. `factory: function MyApp_Factory() { return new MyApp(injectElementRef()); }` - field('factory', createFactory(component.type, outputCtx, reflector, component.queries)); - - // e.g `hostBindings: function MyApp_HostBindings { ... } - field('hostBindings', createHostBindingsFunction(component, outputCtx, bindingParser)); - - // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` - const templateTypeName = component.type.reference.name; - const templateName = templateTypeName ? `${templateTypeName}_Template` : null; - - const templateFunctionExpression = - new TemplateDefinitionBuilder( - outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, - templateTypeName, templateName, component.viewQueries, directiveMatcher, directives, - pipeTypeByName, pipes) - .buildTemplateFunction(nodes, [], hasNgContent, ngContentSelectors); - - field('template', templateFunctionExpression); - - // e.g. `directives: [MyDirective]` - if (directives.size) { - const expressions = Array.from(directives).map(d => outputCtx.importExpr(d)); - field('directives', o.literalArr(expressions)); - } - - // e.g. `pipes: [MyPipe]` - if (pipes.size) { - const expressions = Array.from(pipes).map(d => outputCtx.importExpr(d)); - field('pipes', o.literalArr(expressions)); - } - - // e.g `inputs: {a: 'a'}` - field('inputs', conditionallyCreateMapObjectLiteral(component.inputs)); - - // e.g 'outputs: {a: 'a'}` - field('outputs', conditionallyCreateMapObjectLiteral(component.outputs)); - - // e.g. `features: [NgOnChangesFeature(MyComponent)]` - const features: o.Expression[] = []; - if (component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges)) { - features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([outputCtx.importExpr( - component.type.reference)])); - } - if (features.length) { - field('features', o.literalArr(features)); - } - - const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component); - const definitionFunction = - o.importExpr(R3.defineComponent).callFn([o.literalMap(definitionMapValues)]); - const className = identifierName(component.type) !; - className || error(`Cannot resolver the name of ${component.type}`); - - // Create the partial class to be merged with the actual class. - outputCtx.statements.push(new o.ClassStmt( - className, null, - [new o.ClassField( - definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], definitionFunction)], - [], new o.ClassMethod(null, [], []), [])); -} - -function unsupported(feature: string): never { - if (this) { - throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`); - } - throw new Error(`Feature ${feature} is not supported yet`); -} +import {R3QueryMetadata} from './api'; +import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util'; const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { [BoundElementBindingType.Property]: R3.elementProperty, @@ -220,164 +29,7 @@ const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { [BoundElementBindingType.Style]: R3.elementStyleNamed, }; -function interpolate(args: o.Expression[]): o.Expression { - args = args.slice(1); // Ignore the length prefix added for render2 - switch (args.length) { - case 3: - return o.importExpr(R3.interpolation1).callFn(args); - case 5: - return o.importExpr(R3.interpolation2).callFn(args); - case 7: - return o.importExpr(R3.interpolation3).callFn(args); - case 9: - return o.importExpr(R3.interpolation4).callFn(args); - case 11: - return o.importExpr(R3.interpolation5).callFn(args); - case 13: - return o.importExpr(R3.interpolation6).callFn(args); - case 15: - return o.importExpr(R3.interpolation7).callFn(args); - case 17: - return o.importExpr(R3.interpolation8).callFn(args); - } - (args.length >= 19 && args.length % 2 == 1) || - error(`Invalid interpolation argument length ${args.length}`); - return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); -} - -// Pipes always have at least one parameter, the value they operate on -const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; - -function pipeBinding(args: o.Expression[]): o.ExternalReference { - return pipeBindingIdentifiers[args.length] || R3.pipeBindV; -} - -const pureFunctionIdentifiers = [ - R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, - R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 -]; -function getLiteralFactory( - outputContext: OutputContext, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { - const {literalFactory, literalFactoryArguments} = - outputContext.constantPool.getLiteralFactory(literal); - literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); - let pureFunctionIdent = - pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; - - // Literal factories are pure functions that only need to be re-invoked when the parameters - // change. - return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); -} - -function noop() {} - -/** - * Function which is executed whenever a variable is referenced for the first time in a given - * scope. - * - * It is expected that the function creates the `const localName = expression`; statement. - */ -type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void; - -class BindingScope implements LocalResolver { - /** - * Keeps a map from local variables to their expressions. - * - * This is used when one refers to variable such as: 'let abc = a.b.c`. - * - key to the map is the string literal `"abc"`. - * - value `lhs` is the left hand side which is an AST representing `abc`. - * - value `rhs` is the right hand side which is an AST representing `a.b.c`. - * - value `declared` is true if the `declareLocalVarCallback` has been called for this scope - * already. - */ - private map = new Map < string, { - lhs: o.ReadVarExpr; - rhs: o.Expression|undefined; - declared: boolean; - } - > (); - private referenceNameIndex = 0; - - static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); - - private constructor( - private parent: BindingScope|null = null, - private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} - - get(name: string): o.Expression|null { - let current: BindingScope|null = this; - while (current) { - let value = current.map.get(name); - if (value != null) { - if (current !== this) { - // make a local copy and reset the `declared` state. - value = {lhs: value.lhs, rhs: value.rhs, declared: false}; - // Cache the value locally. - this.map.set(name, value); - } - if (value.rhs && !value.declared) { - // if it is first time we are referencing the variable in the scope - // than invoke the callback to insert variable declaration. - this.declareLocalVarCallback(value.lhs, value.rhs); - value.declared = true; - } - return value.lhs; - } - current = current.parent; - } - return null; - } - - /** - * Create a local variable for later reference. - * - * @param name Name of the variable. - * @param lhs AST representing the left hand side of the `let lhs = rhs;`. - * @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be - * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` - * declaration. - */ - set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope { - !this.map.has(name) || - error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`); - this.map.set(name, {lhs: lhs, rhs: rhs, declared: false}); - return this; - } - - getLocal(name: string): (o.Expression|null) { return this.get(name); } - - nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope { - return new BindingScope(this, declareCallback); - } - - freshReferenceName(): string { - let current: BindingScope = this; - // Find the top scope as it maintains the global reference count - while (current.parent) current = current.parent; - const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`; - return ref; - } -} - -// Pasted from render3/interfaces/definition since it cannot be referenced directly -/** - * Flags passed into template functions to determine which blocks (i.e. creation, update) - * should be executed. - * - * Typically, a template runs both the creation block and the update block on initialization and - * subsequent runs only execute the update block. However, dynamically created views require that - * the creation block be executed separately from the update block (for backwards compat). - */ -// TODO(vicb): move to ../core -export const enum RenderFlags { - /* Whether to run the creation block (e.g. create elements and directives) */ - Create = 0b01, - - /* Whether to run the update block (e.g. refresh bindings) */ - Update = 0b10 -} - -class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { +export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _dataIndex = 0; private _bindingContext = 0; private _prefixCode: o.Statement[] = []; @@ -398,19 +50,19 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}]; constructor( - private outputCtx: OutputContext, private constantPool: ConstantPool, - private reflector: CompileReflector, private contextParameter: string, + private constantPool: ConstantPool, private contextParameter: string, parentBindingScope: BindingScope, private level = 0, private contextName: string|null, - private templateName: string|null, private viewQueries: CompileQueryMetadata[], - private directiveMatcher: SelectorMatcher|null, private directives: Set, - private pipeTypeByName: Map, private pipes: Set) { + private templateName: string|null, private viewQueries: R3QueryMetadata[], + private directiveMatcher: SelectorMatcher|null, private directives: Set, + private pipeTypeByName: Map, private pipes: Set) { this._bindingScope = parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => { this._bindingCode.push( lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); }); this._valueConverter = new ValueConverter( - outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value: o.ReadVarExpr) => { + constantPool, () => this.allocateDataSlot(), + (name, localName, slot, value: o.ReadVarExpr) => { const pipeType = pipeTypeByName.get(name); if (pipeType) { this.pipes.add(pipeType); @@ -443,9 +95,8 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { if (ngContentSelectors.length > 1) { const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s)); // `projectionDef` needs both the parsed and raw value of the selectors - const parsed = this.outputCtx.constantPool.getConstLiteral(asLiteral(r3Selectors), true); - const unParsed = - this.outputCtx.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true); + const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true); + const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true); parameters.push(parsed, unParsed); } @@ -456,15 +107,15 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { for (let query of this.viewQueries) { // e.g. r3.Q(0, somePredicate, true); const querySlot = this.allocateDataSlot(); - const predicate = getQueryPredicate(query, this.outputCtx); - const args = [ + const predicate = getQueryPredicate(query, this.constantPool); + const args: o.Expression[] = [ o.literal(querySlot, o.INFERRED_TYPE), predicate, o.literal(query.descendants, o.INFERRED_TYPE), ]; if (query.read) { - args.push(this.outputCtx.importExpr(query.read.identifier !.reference)); + args.push(query.read); } this.instruction(this._creationCode, null, R3.query, ...args); @@ -482,13 +133,13 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { const creationCode = this._creationCode.length > 0 ? [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(RenderFlags.Create), null, false), + o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), this._creationCode)] : []; const updateCode = this._bindingCode.length > 0 ? [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(RenderFlags.Update), null, false), + o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Update), null, false), this._bindingCode)] : []; @@ -624,7 +275,7 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { let attrArg: o.Expression = o.TYPED_NULL_EXPR; if (attributes.length > 0) { - attrArg = hasI18nAttr ? getLiteralFactory(this.outputCtx, o.literalArr(attributes)) : + attrArg = hasI18nAttr ? getLiteralFactory(this.constantPool, o.literalArr(attributes)) : this.constantPool.getConstLiteral(o.literalArr(attributes), true); } @@ -770,9 +421,8 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { // Create the template function const templateVisitor = new TemplateDefinitionBuilder( - this.outputCtx, this.constantPool, this.reflector, templateContext, this._bindingScope, - this.level + 1, contextName, templateName, [], this.directiveMatcher, this.directives, - this.pipeTypeByName, this.pipes); + this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName, + templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes); const templateFunctionExpr = templateVisitor.buildTemplateFunction(template.children, template.variables); this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null)); @@ -839,223 +489,9 @@ class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { } } -function getQueryPredicate(query: CompileQueryMetadata, outputCtx: OutputContext): o.Expression { - if (query.selectors.length > 1 || (query.selectors.length == 1 && query.selectors[0].value)) { - const selectors = query.selectors.map(value => value.value as string); - selectors.some(value => !value) && error('Found a type among the string selectors expected'); - return outputCtx.constantPool.getConstLiteral( - o.literalArr(selectors.map(value => o.literal(value)))); - } - - if (query.selectors.length == 1) { - const first = query.selectors[0]; - if (first.identifier) { - return outputCtx.importExpr(first.identifier.reference); - } - } - - error('Unexpected query form'); - return o.NULL_EXPR; -} - -export function createFactory( - type: CompileTypeMetadata, outputCtx: OutputContext, reflector: CompileReflector, - queries: CompileQueryMetadata[]): o.Expression { - let args: o.Expression[] = []; - - const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef); - const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef); - const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef); - - for (let dependency of type.diDeps) { - const token = dependency.token; - if (token) { - const tokenRef = tokenReference(token); - if (tokenRef === elementRef) { - args.push(o.importExpr(R3.injectElementRef).callFn([])); - } else if (tokenRef === templateRef) { - args.push(o.importExpr(R3.injectTemplateRef).callFn([])); - } else if (tokenRef === viewContainerRef) { - args.push(o.importExpr(R3.injectViewContainerRef).callFn([])); - } else if (dependency.isAttribute) { - args.push(o.importExpr(R3.injectAttribute).callFn([o.literal(dependency.token !.value)])); - } else { - const tokenValue = - token.identifier != null ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef); - const directiveInjectArgs = [tokenValue]; - const flags = extractFlags(dependency); - if (flags != core.InjectFlags.Default) { - // Append flag information if other than default. - directiveInjectArgs.push(o.literal(flags)); - } - args.push(o.importExpr(R3.directiveInject).callFn(directiveInjectArgs)); - } - } else { - unsupported('dependency without a token'); - } - } - - const queryDefinitions: o.Expression[] = []; - for (let query of queries) { - const predicate = getQueryPredicate(query, outputCtx); - - // e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false) - const parameters = [ - o.literal(null, o.INFERRED_TYPE), - predicate, - o.literal(query.descendants), - ]; - - if (query.read) { - parameters.push(outputCtx.importExpr(query.read.identifier !.reference)); - } - - queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); - } - - const createInstance = new o.InstantiateExpr(outputCtx.importExpr(type.reference), args); - const result = queryDefinitions.length > 0 ? o.literalArr([createInstance, ...queryDefinitions]) : - createInstance; - - return o.fn( - [], [new o.ReturnStatement(result)], o.INFERRED_TYPE, null, - type.reference.name ? `${type.reference.name}_Factory` : null); -} - -function extractFlags(dependency: CompileDiDependencyMetadata): core.InjectFlags { - let flags = core.InjectFlags.Default; - if (dependency.isHost) { - flags |= core.InjectFlags.Host; - } - if (dependency.isOptional) { - flags |= core.InjectFlags.Optional; - } - if (dependency.isSelf) { - flags |= core.InjectFlags.Self; - } - if (dependency.isSkipSelf) { - flags |= core.InjectFlags.SkipSelf; - } - if (dependency.isValue) { - unsupported('value dependencies'); - } - return flags; -} - -/** - * Remove trailing null nodes as they are implied. - */ -function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] { - while (o.isNull(parameters[parameters.length - 1])) { - parameters.pop(); - } - return parameters; -} - -// Turn a directive selector into an R3-compatible selector for directive def -function createDirectiveSelector(selector: string): o.Expression { - return asLiteral(core.parseSelectorToR3Selector(selector)); -} - -function createHostAttributesArray( - directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext): o.Expression|null { - const values: o.Expression[] = []; - const attributes = directiveMetadata.hostAttributes; - for (let key of Object.getOwnPropertyNames(attributes)) { - const value = attributes[key]; - values.push(o.literal(key), o.literal(value)); - } - if (values.length > 0) { - return outputCtx.constantPool.getConstLiteral(o.literalArr(values)); - } - return null; -} - -// Return a host binding function or null if one is not necessary. -function createHostBindingsFunction( - directiveMetadata: CompileDirectiveMetadata, outputCtx: OutputContext, - bindingParser: BindingParser): o.Expression|null { - const statements: o.Statement[] = []; - - const temporary = temporaryAllocator(statements, TEMPORARY_NAME); - - const hostBindingSourceSpan = typeSourceSpan( - directiveMetadata.isComponent ? 'Component' : 'Directive', directiveMetadata.type); - - // Calculate the queries - for (let index = 0; index < directiveMetadata.queries.length; index++) { - const query = directiveMetadata.queries[index]; - - // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); - const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); - // The query list is at the query index + 1 because the directive itself is in slot 0. - const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); - const assignToTemporary = temporary().set(getQueryList); - const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]); - const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE)) - .prop(query.propertyName) - .set(query.first ? temporary().prop('first') : temporary()); - const andExpression = callQueryRefresh.and(updateDirective); - statements.push(andExpression.toStmt()); - } - - const directiveSummary = directiveMetadata.toSummary(); - - // Calculate the host property bindings - const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); - const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); - if (bindings) { - for (const binding of bindings) { - const bindingExpr = convertPropertyBinding( - null, bindingContext, binding.expression, 'b', BindingForm.TrySimple, - () => error('Unexpected interpolation')); - statements.push(...bindingExpr.stmts); - statements.push(o.importExpr(R3.elementProperty) - .callFn([ - o.variable('elIndex'), - o.literal(binding.name), - o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]), - ]) - .toStmt()); - } - } - - // Calculate host event bindings - const eventBindings = - bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); - if (eventBindings) { - for (const binding of eventBindings) { - const bindingExpr = convertActionBinding( - null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation')); - const bindingName = binding.name && sanitizeIdentifier(binding.name); - const typeName = identifierName(directiveMetadata.type); - const functionName = - typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null; - const handler = o.fn( - [new o.FnParam('$event', o.DYNAMIC_TYPE)], - [...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE, - null, functionName); - statements.push( - o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt()); - } - } - - if (statements.length > 0) { - const typeName = directiveMetadata.type.reference.name; - return o.fn( - [ - new o.FnParam('dirIndex', o.NUMBER_TYPE), - new o.FnParam('elIndex', o.NUMBER_TYPE), - ], - statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null); - } - - return null; -} - class ValueConverter extends AstMemoryEfficientTransformer { constructor( - private outputCtx: OutputContext, private allocateSlot: () => number, + private constantPool: ConstantPool, private allocateSlot: () => number, private definePipe: (name: string, localName: string, slot: number, value: o.Expression) => void) { super(); @@ -1082,9 +518,8 @@ class ValueConverter extends AstMemoryEfficientTransformer { // calls to literal factories that compose the literal and will cache intermediate // values. Otherwise, just return an literal array that contains the values. const literal = o.literalArr(values); - return values.every(a => a.isConstant()) ? - this.outputCtx.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.outputCtx, literal); + return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal); }); } @@ -1095,51 +530,144 @@ class ValueConverter extends AstMemoryEfficientTransformer { // values. Otherwise, just return an literal array that contains the values. const literal = o.literalMap(values.map( (value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted}))); - return values.every(a => a.isConstant()) ? - this.outputCtx.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.outputCtx, literal); + return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal); }); } } -function invalid(arg: o.Expression | o.Statement | t.Node): never { - throw new Error( - `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); + + +// Pipes always have at least one parameter, the value they operate on +const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; + +function pipeBinding(args: o.Expression[]): o.ExternalReference { + return pipeBindingIdentifiers[args.length] || R3.pipeBindV; } -function asLiteral(value: any): o.Expression { - if (Array.isArray(value)) { - return o.literalArr(value.map(asLiteral)); - } - return o.literal(value, o.INFERRED_TYPE); -} +const pureFunctionIdentifiers = [ + R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, + R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 +]; +function getLiteralFactory( + constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { + const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal); + literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); + let pureFunctionIdent = + pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; -function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|null { - if (Object.getOwnPropertyNames(keys).length > 0) { - return mapToExpression(keys); - } - return null; -} - -function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression { - return o.literalMap( - Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])}))); + // Literal factories are pure functions that only need to be re-invoked when the parameters + // change. + return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); } /** - * Creates an allocator for a temporary variable. + * Function which is executed whenever a variable is referenced for the first time in a given + * scope. * - * A variable declaration is added to the statements the first time the allocator is invoked. + * It is expected that the function creates the `const localName = expression`; statement. */ -function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr { - let temp: o.ReadVarExpr|null = null; - return () => { - if (!temp) { - statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); - temp = o.variable(name); +export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void; + +export class BindingScope implements LocalResolver { + /** + * Keeps a map from local variables to their expressions. + * + * This is used when one refers to variable such as: 'let abc = a.b.c`. + * - key to the map is the string literal `"abc"`. + * - value `lhs` is the left hand side which is an AST representing `abc`. + * - value `rhs` is the right hand side which is an AST representing `a.b.c`. + * - value `declared` is true if the `declareLocalVarCallback` has been called for this scope + * already. + */ + private map = new Map < string, { + lhs: o.ReadVarExpr; + rhs: o.Expression|undefined; + declared: boolean; + } + > (); + private referenceNameIndex = 0; + + static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event')); + + private constructor( + private parent: BindingScope|null = null, + private declareLocalVarCallback: DeclareLocalVarCallback = noop) {} + + get(name: string): o.Expression|null { + let current: BindingScope|null = this; + while (current) { + let value = current.map.get(name); + if (value != null) { + if (current !== this) { + // make a local copy and reset the `declared` state. + value = {lhs: value.lhs, rhs: value.rhs, declared: false}; + // Cache the value locally. + this.map.set(name, value); + } + if (value.rhs && !value.declared) { + // if it is first time we are referencing the variable in the scope + // than invoke the callback to insert variable declaration. + this.declareLocalVarCallback(value.lhs, value.rhs); + value.declared = true; + } + return value.lhs; + } + current = current.parent; } - return temp; - }; + return null; + } + + /** + * Create a local variable for later reference. + * + * @param name Name of the variable. + * @param lhs AST representing the left hand side of the `let lhs = rhs;`. + * @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be + * `undefined` for variable that are ambient such as `$event` and which don't have `rhs` + * declaration. + */ + set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope { + !this.map.has(name) || + error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`); + this.map.set(name, {lhs: lhs, rhs: rhs, declared: false}); + return this; + } + + getLocal(name: string): (o.Expression|null) { return this.get(name); } + + nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope { + return new BindingScope(this, declareCallback); + } + + freshReferenceName(): string { + let current: BindingScope = this; + // Find the top scope as it maintains the global reference count + while (current.parent) current = current.parent; + const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`; + return ref; + } +} + +/** + * Creates a `CssSelector` given a tag name and a map of attributes + */ +function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector { + const cssSelector = new CssSelector(); + + cssSelector.setElement(tag); + + Object.getOwnPropertyNames(attributes).forEach((name) => { + const value = attributes[name]; + + cssSelector.addAttribute(name, value); + if (name.toLowerCase() === 'class') { + const classes = value.trim().split(/\s+/g); + classes.forEach(className => cssSelector.addClassName(className)); + } + }); + + return cssSelector; } // Parse i18n metas like: @@ -1167,23 +695,27 @@ function parseI18nMeta(i18n?: string): {description?: string, id?: string, meani return {description, id, meaning}; } -/** - * Creates a `CssSelector` given a tag name and a map of attributes - */ -function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector { - const cssSelector = new CssSelector(); - - cssSelector.setElement(tag); - - Object.getOwnPropertyNames(attributes).forEach((name) => { - const value = attributes[name]; - - cssSelector.addAttribute(name, value); - if (name.toLowerCase() === 'class') { - const classes = value.trim().split(/\s+/g); - classes.forEach(className => cssSelector.addClassName(className)); - } - }); - - return cssSelector; +function interpolate(args: o.Expression[]): o.Expression { + args = args.slice(1); // Ignore the length prefix added for render2 + switch (args.length) { + case 3: + return o.importExpr(R3.interpolation1).callFn(args); + case 5: + return o.importExpr(R3.interpolation2).callFn(args); + case 7: + return o.importExpr(R3.interpolation3).callFn(args); + case 9: + return o.importExpr(R3.interpolation4).callFn(args); + case 11: + return o.importExpr(R3.interpolation5).callFn(args); + case 13: + return o.importExpr(R3.interpolation6).callFn(args); + case 15: + return o.importExpr(R3.interpolation7).callFn(args); + case 17: + return o.importExpr(R3.interpolation8).callFn(args); + } + (args.length >= 19 && args.length % 2 == 1) || + error(`Invalid interpolation argument length ${args.length}`); + return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); } diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts new file mode 100644 index 0000000000..e96e4ed2cd --- /dev/null +++ b/packages/compiler/src/render3/view/util.ts @@ -0,0 +1,122 @@ +/** + * @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 {ConstantPool} from '../../constant_pool'; +import * as o from '../../output/output_ast'; +import * as t from '../r3_ast'; + +import {R3QueryMetadata} from './api'; + + +/** Name of the temporary to use during data binding */ +export const TEMPORARY_NAME = '_t'; + + + +/** Name of the context parameter passed into a template function */ +export const CONTEXT_NAME = 'ctx'; + +/** Name of the RenderFlag passed into a template function */ +export const RENDER_FLAGS = 'rf'; + +/** The prefix reference variables */ +export const REFERENCE_PREFIX = '_r'; + +/** The name of the implicit context reference */ +export const IMPLICIT_REFERENCE = '$implicit'; + +/** Name of the i18n attributes **/ +export const I18N_ATTR = 'i18n'; +export const I18N_ATTR_PREFIX = 'i18n-'; + +/** I18n separators for metadata **/ +export const MEANING_SEPARATOR = '|'; +export const ID_SEPARATOR = '@@'; + +/** + * Creates an allocator for a temporary variable. + * + * A variable declaration is added to the statements the first time the allocator is invoked. + */ +export function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr { + let temp: o.ReadVarExpr|null = null; + return () => { + if (!temp) { + statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); + temp = o.variable(name); + } + return temp; + }; +} + + +export function unsupported(feature: string): never { + if (this) { + throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`); + } + throw new Error(`Feature ${feature} is not supported yet`); +} + +export function invalid(arg: o.Expression | o.Statement | t.Node): never { + throw new Error( + `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); +} + +export function asLiteral(value: any): o.Expression { + if (Array.isArray(value)) { + return o.literalArr(value.map(asLiteral)); + } + return o.literal(value, o.INFERRED_TYPE); +} + +export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression| + null { + if (Object.getOwnPropertyNames(keys).length > 0) { + return mapToExpression(keys); + } + return null; +} + +export function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression { + return o.literalMap( + Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])}))); +} + +/** + * Remove trailing null nodes as they are implied. + */ +export function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] { + while (o.isNull(parameters[parameters.length - 1])) { + parameters.pop(); + } + return parameters; +} + +export function getQueryPredicate( + query: R3QueryMetadata, constantPool: ConstantPool): o.Expression { + if (Array.isArray(query.predicate)) { + return constantPool.getConstLiteral( + o.literalArr(query.predicate.map(selector => o.literal(selector) as o.Expression))); + } else { + return query.predicate; + } +} + +export function noop() {} + +export class DefinitionMap { + values: {key: string, quoted: boolean, value: o.Expression}[] = []; + + set(key: string, value: o.Expression|null): void { + if (value) { + this.values.push({key, value, quoted: false}); + } + } + + toLiteralMap(): o.LiteralMapExpr { return o.literalMap(this.values); } +} diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index 7cdcb2f0ee..bd798b81f2 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -17,8 +17,6 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector} from '../selector'; import {splitAtColon, splitAtPeriod} from '../util'; -import {BoundElementPropertyAst, PropertyBindingType} from './template_ast'; - const PROPERTY_PARTS_SEPARATOR = '.'; const ATTRIBUTE_PREFIX = 'attr'; const CLASS_PREFIX = 'class'; diff --git a/packages/compiler/test/render3/mock_compile.ts b/packages/compiler/test/render3/mock_compile.ts index 58c570b377..d74aa6d304 100644 --- a/packages/compiler/test/render3/mock_compile.ts +++ b/packages/compiler/test/render3/mock_compile.ts @@ -17,7 +17,7 @@ import {removeWhitespaces} from '../../src/ml_parser/html_whitespaces'; import * as o from '../../src/output/output_ast'; import {compilePipe} from '../../src/render3/r3_pipe_compiler'; import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform'; -import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler_local'; +import {compileComponentFromRender2, compileDirectiveFromRender2} from '../../src/render3/view/compiler'; import {BindingParser} from '../../src/template_parser/binding_parser'; import {OutputContext, escapeRegExp} from '../../src/util'; import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, toMockFileArray} from '../aot/test_util'; @@ -313,11 +313,11 @@ export function compile( const hasNgContent = transform.hasNgContent; const ngContentSelectors = transform.ngContentSelectors; - compileComponent( + compileComponentFromRender2( outputCtx, directive, nodes, hasNgContent, ngContentSelectors, reflector, hostBindingParser, directiveTypeBySel, pipeTypeByName); } else { - compileDirective(outputCtx, directive, reflector, hostBindingParser); + compileDirectiveFromRender2(outputCtx, directive, reflector, hostBindingParser); } } else if (resolver.isPipe(pipeOrDirective)) { const metadata = resolver.getPipeMetadata(pipeOrDirective); diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 41c19bc162..fdea13d17a 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -1022,8 +1022,8 @@ describe('compiler compliance', () => { type: LifecycleComp, selectors: [['lifecycle-comp']], factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, - template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, inputs: {nameMin: 'name'}, + template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)] });`; diff --git a/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts b/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts index ad101293d3..bfdbc3ab87 100644 --- a/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_input_outputs_spec.ts @@ -62,6 +62,7 @@ describe('compiler compliance: listen()', () => { componentOutput: 'componentOutput', originalComponentOutput: 'renamedComponentOutput' } + … });`; const directiveDef = ` @@ -75,6 +76,7 @@ describe('compiler compliance: listen()', () => { directiveOutput: 'directiveOutput', originalDirectiveOutput: 'renamedDirectiveOutput' } + … });`; From 971e78dc3502e208250da5e1d4497ae2fcbe7ed8 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 9 May 2018 06:28:05 -0700 Subject: [PATCH 017/582] ci: Remove Chuck from pullapprove (#23798) Jason takes over his role on core, Keen for everything else PR Close #23798 --- .pullapprove.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pullapprove.yml b/.pullapprove.yml index 59e3839df5..9ca33a0c8d 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -10,7 +10,6 @@ # andrewseguin - Andrew Seguin # brandonroberts - Brandon Roberts # brocco - Mike Brocchi -# chuckjaz - Chuck Jazdzewski # filipesilva - Filipe Silva # gkalpak - George Kalpakas # hansl - Hans Larsen @@ -18,6 +17,7 @@ # jasonaden - Jason Aden # kapunahelewong - Kapunahele Wong # kara - Kara Erickson +# kyliau - Keen Yee Liau # matsko - Matias Niemelä # mhevery - Misko Hevery # petebacondarwin - Pete Bacon Darwin @@ -94,7 +94,7 @@ groups: - "tools/bazel.rc" users: - alexeagle #primary - - chuckjaz + - kyliau - IgorMinar #fallback - mhevery - vikerman #fallback @@ -133,7 +133,7 @@ groups: - "packages/core/*" users: - mhevery #primary - - chuckjaz + - jasonaden - kara - vicb - IgorMinar #fallback @@ -237,7 +237,7 @@ groups: files: - "packages/language-service/*" users: - - chuckjaz #primary + - kyliau #primary # needs secondary - vicb - IgorMinar #fallback From a800ccd92210fca4847364fbfef1a4f93f516f05 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 9 May 2018 13:49:54 +0300 Subject: [PATCH 018/582] fix(aio): add link to v5 docs (#23794) Fixes #23781 PR Close #23794 --- aio/content/navigation.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aio/content/navigation.json b/aio/content/navigation.json index b6336b1e72..407c5efe0f 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -498,18 +498,18 @@ "title": "Keeping Up-to-Date", "tooltip": "Angular release practices, planning for updates, and update resources.", "children": [ - { + { "url": "guide/updating", "title": "Updating Your Projects", "tooltip": "Information about updating Angular applications and libraries to the latest version." }, - { + { "url": "guide/releases", "title": "Angular Releases", "tooltip": "Angular versioning, release, support, and deprecation policies and practices." } ] - }, + }, { "title": "Upgrading from AngularJS", @@ -660,9 +660,9 @@ ], "docVersions": [ - { "title": "v4 (LTS)", "url": "https://v4.angular.io" }, + { "title": "v5", "url": "https://v5.angular.io" }, + { "title": "v4", "url": "https://v4.angular.io" }, { "title": "v2", "url": "https://v2.angular.io" }, { "title": "AngularDart", "url": "https://webdev.dartlang.org/angular" } - ] } From 61170856ee26e44825f1d30d4590de746c6482b5 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 4 May 2018 08:31:46 +0100 Subject: [PATCH 019/582] build(aio): include `navigation.json` changes in docs-watch (#23698) Closes #23582 PR Close #23698 --- aio/tools/transforms/authors-package/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/tools/transforms/authors-package/index.js b/aio/tools/transforms/authors-package/index.js index 31a5b89a7d..6fe25eb86b 100644 --- a/aio/tools/transforms/authors-package/index.js +++ b/aio/tools/transforms/authors-package/index.js @@ -8,7 +8,7 @@ /* eslint no-console: "off" */ function createPackage(changedFile) { - const marketingMatch = /^aio\/content\/marketing\/(.*)/.exec(changedFile); + const marketingMatch = /^aio\/content\/(?:marketing\/|navigation\.json)/.exec(changedFile); if (marketingMatch) { console.log('Building marketing docs'); return require('./marketing-package').createPackage(); From c5ca5c0d9fa8e5c851e0646d234d25caf295923e Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Mon, 30 Apr 2018 10:33:02 -0700 Subject: [PATCH 020/582] build(bazel): update to rules_typescript 0.12.3 (#23617) PR Close #23617 --- WORKSPACE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 9328952214..c3964d26bd 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -26,9 +26,9 @@ yarn_install( http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/0.12.1.zip", - strip_prefix = "rules_typescript-0.12.1", - sha256 = "24e2c36f60508c6d270ae4265b89b381e3f66d550e70c367ed3755ad8d7ce3b0", + url = "https://github.com/bazelbuild/rules_typescript/archive/0.12.3.zip", + strip_prefix = "rules_typescript-0.12.3", + sha256 = "967068c3540f59407716fbeb49949c1600dbf387faeeab3089085784dd21f60c", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") From 46674d5fac345eea3b421cbe4bddeebaa37dbce0 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 24 Apr 2018 14:22:55 -0700 Subject: [PATCH 021/582] test(ivy): add html to ivy ast transformer tests (#23546) PR Close #23546 --- .../compiler/src/expression_parser/ast.ts | 7 +- packages/compiler/src/render3/r3_ast.ts | 17 +- .../src/render3/r3_template_transform.ts | 6 +- .../compiler/src/render3/view/template.ts | 14 +- packages/compiler/src/render3/view/util.ts | 3 - .../src/template_parser/binding_parser.ts | 16 +- .../src/template_parser/template_ast.ts | 12 +- packages/compiler/test/BUILD.bazel | 3 +- .../test/expression_parser/parser_spec.ts | 4 +- .../test/expression_parser/utils/BUILD.bazel | 15 + .../expression_parser/{ => utils}/unparser.ts | 4 +- .../{ => utils}/validator.ts | 2 +- packages/compiler/test/render3/BUILD.bazel | 2 + .../render3/r3_template_transform_spec.ts | 486 ++++++++++++++++++ .../template_parser/template_parser_spec.ts | 43 +- 15 files changed, 567 insertions(+), 67 deletions(-) create mode 100644 packages/compiler/test/expression_parser/utils/BUILD.bazel rename packages/compiler/test/expression_parser/{ => utils}/unparser.ts (97%) rename packages/compiler/test/expression_parser/{ => utils}/validator.ts (98%) create mode 100644 packages/compiler/test/render3/r3_template_transform_spec.ts diff --git a/packages/compiler/src/expression_parser/ast.ts b/packages/compiler/src/expression_parser/ast.ts index 08977f7251..c511a4dc23 100644 --- a/packages/compiler/src/expression_parser/ast.ts +++ b/packages/compiler/src/expression_parser/ast.ts @@ -705,7 +705,7 @@ export class ParsedVariable { constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} } -export const enum BoundElementBindingType { +export const enum BindingType { // A regular binding to a property (e.g. `[property]="expression"`). Property, // A binding to an element attribute (e.g. `[attr.name]="expression"`). @@ -720,7 +720,6 @@ export const enum BoundElementBindingType { export class BoundElementProperty { constructor( - public name: string, public type: BoundElementBindingType, - public securityContext: SecurityContext, public value: AST, public unit: string|null, - public sourceSpan: ParseSourceSpan) {} + public name: string, public type: BindingType, public securityContext: SecurityContext, + public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {} } diff --git a/packages/compiler/src/render3/r3_ast.ts b/packages/compiler/src/render3/r3_ast.ts index 029516eb98..52767919ad 100644 --- a/packages/compiler/src/render3/r3_ast.ts +++ b/packages/compiler/src/render3/r3_ast.ts @@ -7,7 +7,7 @@ */ import {SecurityContext} from '../core'; -import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast'; +import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast'; import {ParseSourceSpan} from '../parse_util'; export interface Node { @@ -29,14 +29,13 @@ export class TextAttribute implements Node { constructor( public name: string, public value: string, public sourceSpan: ParseSourceSpan, public valueSpan?: ParseSourceSpan) {} - visit(visitor: Visitor): Result { return visitor.visitAttribute(this); } + visit(visitor: Visitor): Result { return visitor.visitTextAttribute(this); } } export class BoundAttribute implements Node { constructor( - public name: string, public type: BoundElementBindingType, - public securityContext: SecurityContext, public value: AST, public unit: string|null, - public sourceSpan: ParseSourceSpan) {} + public name: string, public type: BindingType, public securityContext: SecurityContext, + public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {} static fromBoundElementProperty(prop: BoundElementProperty) { return new BoundAttribute( @@ -106,7 +105,7 @@ export interface Visitor { visitContent(content: Content): Result; visitVariable(variable: Variable): Result; visitReference(reference: Reference): Result; - visitAttribute(attribute: TextAttribute): Result; + visitTextAttribute(attribute: TextAttribute): Result; visitBoundAttribute(attribute: BoundAttribute): Result; visitBoundEvent(attribute: BoundEvent): Result; visitText(text: Text): Result; @@ -119,7 +118,7 @@ export class NullVisitor implements Visitor { visitContent(content: Content): void {} visitVariable(variable: Variable): void {} visitReference(reference: Reference): void {} - visitAttribute(attribute: TextAttribute): void {} + visitTextAttribute(attribute: TextAttribute): void {} visitBoundAttribute(attribute: BoundAttribute): void {} visitBoundEvent(attribute: BoundEvent): void {} visitText(text: Text): void {} @@ -141,7 +140,7 @@ export class RecursiveVisitor implements Visitor { visitContent(content: Content): void {} visitVariable(variable: Variable): void {} visitReference(reference: Reference): void {} - visitAttribute(attribute: TextAttribute): void {} + visitTextAttribute(attribute: TextAttribute): void {} visitBoundAttribute(attribute: BoundAttribute): void {} visitBoundEvent(attribute: BoundEvent): void {} visitText(text: Text): void {} @@ -185,7 +184,7 @@ export class TransformVisitor implements Visitor { visitVariable(variable: Variable): Node { return variable; } visitReference(reference: Reference): Node { return reference; } - visitAttribute(attribute: TextAttribute): Node { return attribute; } + visitTextAttribute(attribute: TextAttribute): Node { return attribute; } visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; } visitBoundEvent(attribute: BoundEvent): Node { return attribute; } visitText(text: Text): Node { return text; } diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index b5679db9d0..44a4b92e8b 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -111,9 +111,12 @@ export class HtmlToTemplateTransform implements html.Visitor { inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan; + const parsedVariables: ParsedVariable[] = []; this.bindingParser.parseInlineTemplateBinding( templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes, - templateParsedProperties, templateVariables); + templateParsedProperties, parsedVariables); + templateVariables.push( + ...parsedVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan))); } else { // Check for variables, events, property bindings, interpolation hasBinding = this.parseAttribute( @@ -272,7 +275,6 @@ export class HtmlToTemplateTransform implements html.Visitor { return hasBinding; } - private parseVariable( identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) { if (identifier.indexOf('-') > -1) { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index a27851d86b..9563936249 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; import {CssSelector, SelectorMatcher} from '../../selector'; @@ -23,10 +23,10 @@ import {R3QueryMetadata} from './api'; import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util'; const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { - [BoundElementBindingType.Property]: R3.elementProperty, - [BoundElementBindingType.Attribute]: R3.elementAttribute, - [BoundElementBindingType.Class]: R3.elementClassNamed, - [BoundElementBindingType.Style]: R3.elementStyleNamed, + [BindingType.Property]: R3.elementProperty, + [BindingType.Attribute]: R3.elementAttribute, + [BindingType.Class]: R3.elementClassNamed, + [BindingType.Style]: R3.elementStyleNamed, }; export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { @@ -331,7 +331,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Generate element input bindings element.inputs.forEach((input: t.BoundAttribute) => { - if (input.type === BoundElementBindingType.Animation) { + if (input.type === BindingType.Animation) { this._unsupported('animations'); } const convertedBinding = this.convertPropertyBinding(implicit, input.value); @@ -431,7 +431,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // These should be handled in the template or element directly. readonly visitReference = invalid; readonly visitVariable = invalid; - readonly visitAttribute = invalid; + readonly visitTextAttribute = invalid; readonly visitBoundAttribute = invalid; readonly visitBoundEvent = invalid; diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index e96e4ed2cd..20e548515a 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -12,12 +12,9 @@ import * as t from '../r3_ast'; import {R3QueryMetadata} from './api'; - /** Name of the temporary to use during data binding */ export const TEMPORARY_NAME = '_t'; - - /** Name of the context parameter passed into a template function */ export const CONTEXT_NAME = 'ctx'; diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index bd798b81f2..90b71d08eb 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -8,7 +8,7 @@ import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; import {SecurityContext} from '../core'; -import {ASTWithSource, BindingPipe, BoundElementBindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; +import {ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {mergeNsAndName} from '../ml_parser/tags'; @@ -239,12 +239,12 @@ export class BindingParser { BoundElementProperty { if (boundProp.isAnimation) { return new BoundElementProperty( - boundProp.name, BoundElementBindingType.Animation, SecurityContext.NONE, - boundProp.expression, null, boundProp.sourceSpan); + boundProp.name, BindingType.Animation, SecurityContext.NONE, boundProp.expression, null, + boundProp.sourceSpan); } let unit: string|null = null; - let bindingType: BoundElementBindingType = undefined !; + let bindingType: BindingType = undefined !; let boundPropertyName: string|null = null; const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR); let securityContexts: SecurityContext[] = undefined !; @@ -264,15 +264,15 @@ export class BindingParser { boundPropertyName = mergeNsAndName(ns, name); } - bindingType = BoundElementBindingType.Attribute; + bindingType = BindingType.Attribute; } else if (parts[0] == CLASS_PREFIX) { boundPropertyName = parts[1]; - bindingType = BoundElementBindingType.Class; + bindingType = BindingType.Class; securityContexts = [SecurityContext.NONE]; } else if (parts[0] == STYLE_PREFIX) { unit = parts.length > 2 ? parts[2] : null; boundPropertyName = parts[1]; - bindingType = BoundElementBindingType.Style; + bindingType = BindingType.Style; securityContexts = [SecurityContext.STYLE]; } } @@ -282,7 +282,7 @@ export class BindingParser { boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name); securityContexts = calcPossibleSecurityContexts( this._schemaRegistry, elementSelector, boundPropertyName, false); - bindingType = BoundElementBindingType.Property; + bindingType = BindingType.Property; this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false); } diff --git a/packages/compiler/src/template_parser/template_ast.ts b/packages/compiler/src/template_parser/template_ast.ts index 438a8b491a..4f69a54608 100644 --- a/packages/compiler/src/template_parser/template_ast.ts +++ b/packages/compiler/src/template_parser/template_ast.ts @@ -9,7 +9,7 @@ import {AstPath} from '../ast_path'; import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata'; import {SecurityContext} from '../core'; -import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast'; +import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast'; import {LifecycleHooks} from '../lifecycle_reflector'; import {ParseSourceSpan} from '../parse_util'; @@ -72,11 +72,11 @@ export enum PropertyBindingType { } const BoundPropertyMapping = { - [BoundElementBindingType.Animation]: PropertyBindingType.Animation, - [BoundElementBindingType.Attribute]: PropertyBindingType.Attribute, - [BoundElementBindingType.Class]: PropertyBindingType.Class, - [BoundElementBindingType.Property]: PropertyBindingType.Property, - [BoundElementBindingType.Style]: PropertyBindingType.Style, + [BindingType.Animation]: PropertyBindingType.Animation, + [BindingType.Attribute]: PropertyBindingType.Attribute, + [BindingType.Class]: PropertyBindingType.Class, + [BindingType.Property]: PropertyBindingType.Property, + [BindingType.Style]: PropertyBindingType.Style, }; /** diff --git a/packages/compiler/test/BUILD.bazel b/packages/compiler/test/BUILD.bazel index b944d7dcc2..e7aaa323cf 100644 --- a/packages/compiler/test/BUILD.bazel +++ b/packages/compiler/test/BUILD.bazel @@ -5,7 +5,6 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") NODE_ONLY = [ "**/*_node_only_spec.ts", "aot/**/*.ts", - "render3/**/*.ts", ] UTILS = [ @@ -37,6 +36,7 @@ ts_library( "//packages:types", "//packages/common", "//packages/compiler", + "//packages/compiler/test/expression_parser/utils", "//packages/compiler/testing", "//packages/core", "//packages/core/testing", @@ -58,6 +58,7 @@ ts_library( ":test_utils", "//packages/compiler", "//packages/compiler-cli", + "//packages/compiler/test/expression_parser/utils", "//packages/compiler/testing", "//packages/core", ], diff --git a/packages/compiler/test/expression_parser/parser_spec.ts b/packages/compiler/test/expression_parser/parser_spec.ts index 6090e8a73f..e028ea9c3e 100644 --- a/packages/compiler/test/expression_parser/parser_spec.ts +++ b/packages/compiler/test/expression_parser/parser_spec.ts @@ -12,8 +12,8 @@ import {Parser, SplitInterpolation, TemplateBindingParseResult} from '@angular/c import {expect} from '@angular/platform-browser/testing/src/matchers'; -import {unparse} from './unparser'; -import {validate} from './validator'; +import {unparse} from './utils/unparser'; +import {validate} from './utils/validator'; describe('parser', () => { describe('parseAction', () => { diff --git a/packages/compiler/test/expression_parser/utils/BUILD.bazel b/packages/compiler/test/expression_parser/utils/BUILD.bazel new file mode 100644 index 0000000000..3030462016 --- /dev/null +++ b/packages/compiler/test/expression_parser/utils/BUILD.bazel @@ -0,0 +1,15 @@ +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "utils", + testonly = 1, + srcs = glob( + ["*.ts"], + ), + visibility = [ + "//packages/compiler/test:__subpackages__", + ], + deps = [ + "//packages/compiler", + ], +) diff --git a/packages/compiler/test/expression_parser/unparser.ts b/packages/compiler/test/expression_parser/utils/unparser.ts similarity index 97% rename from packages/compiler/test/expression_parser/unparser.ts rename to packages/compiler/test/expression_parser/utils/unparser.ts index 4f9aa33e4e..da27bfcbfb 100644 --- a/packages/compiler/test/expression_parser/unparser.ts +++ b/packages/compiler/test/expression_parser/utils/unparser.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast'; -import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config'; +import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../../src/expression_parser/ast'; +import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/interpolation_config'; class Unparser implements AstVisitor { private static _quoteRegExp = /"/g; diff --git a/packages/compiler/test/expression_parser/validator.ts b/packages/compiler/test/expression_parser/utils/validator.ts similarity index 98% rename from packages/compiler/test/expression_parser/validator.ts rename to packages/compiler/test/expression_parser/utils/validator.ts index c4fd221712..cfdc851da5 100644 --- a/packages/compiler/test/expression_parser/validator.ts +++ b/packages/compiler/test/expression_parser/utils/validator.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast'; +import {AST, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PrefixNot, PropertyRead, PropertyWrite, Quote, RecursiveAstVisitor, SafeMethodCall, SafePropertyRead} from '../../../src/expression_parser/ast'; import {unparse} from './unparser'; diff --git a/packages/compiler/test/render3/BUILD.bazel b/packages/compiler/test/render3/BUILD.bazel index 5f2f64d936..82c60a34e3 100644 --- a/packages/compiler/test/render3/BUILD.bazel +++ b/packages/compiler/test/render3/BUILD.bazel @@ -11,6 +11,8 @@ ts_library( "//packages:types", "//packages/compiler", "//packages/compiler/test:test_utils", + "//packages/compiler/test/expression_parser/utils", + "//packages/compiler/testing", "//packages/core", ], ) diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts new file mode 100644 index 0000000000..8880d5b584 --- /dev/null +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -0,0 +1,486 @@ +/** + * @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 {BindingType} from '../../src/expression_parser/ast'; +import {Lexer} from '../../src/expression_parser/lexer'; +import {Parser} from '../../src/expression_parser/parser'; +import {visitAll} from '../../src/ml_parser/ast'; +import {HtmlParser} from '../../src/ml_parser/html_parser'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config'; +import {ParseError} from '../../src/parse_util'; +import * as t from '../../src/render3/r3_ast'; +import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform'; +import {BindingParser} from '../../src/template_parser/binding_parser'; +import {MockSchemaRegistry} from '../../testing'; +import {unparse} from '../expression_parser/utils/unparser'; + +// Parse an html string to IVY specific info +function parse(html: string) { + const htmlParser = new HtmlParser(); + + const parseResult = htmlParser.parse(html, 'path:://to/template', true); + + if (parseResult.errors.length > 0) { + const msg = parseResult.errors.map(e => e.toString()).join('\n'); + throw new Error(msg); + } + + const htmlNodes = parseResult.rootNodes; + const expressionErrors: ParseError[] = []; + const expressionParser = new Parser(new Lexer()); + const schemaRegistry = new MockSchemaRegistry( + {'invalidProp': false}, {'mappedAttr': 'mappedProp'}, {'unknown': false, 'un-known': false}, + ['onEvent'], ['onEvent']); + const bindingParser = new BindingParser( + expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, expressionErrors); + const r3Transform = new HtmlToTemplateTransform(bindingParser); + + const r3Nodes = visitAll(r3Transform, htmlNodes); + + if (r3Transform.errors) { + const msg = r3Transform.errors.map(e => e.toString()).join('\n'); + throw new Error(msg); + } + + return { + nodes: r3Nodes, + hasNgContent: r3Transform.hasNgContent, + ngContentSelectors: r3Transform.ngContentSelectors, + }; +} + +// Transform an IVY AST to a flat list of nodes to ease testing +class R3AstHumanizer implements t.Visitor { + result: any[] = []; + + visitElement(element: t.Element) { + this.result.push(['Element', element.name]); + this.visitAll([ + element.attributes, + element.inputs, + element.outputs, + element.references, + element.children, + ]); + } + + visitTemplate(template: t.Template) { + this.result.push(['Template']); + this.visitAll([ + template.attributes, + template.inputs, + template.references, + template.variables, + template.children, + ]); + } + + visitContent(content: t.Content) { + this.result.push(['Content', content.selectorIndex]); + t.visitAll(this, content.attributes); + } + + visitVariable(variable: t.Variable) { + this.result.push(['Variable', variable.name, variable.value]); + } + + visitReference(reference: t.Reference) { + this.result.push(['Reference', reference.name, reference.value]); + } + + visitTextAttribute(attribute: t.TextAttribute) { + this.result.push(['TextAttribute', attribute.name, attribute.value]); + } + + visitBoundAttribute(attribute: t.BoundAttribute) { + this.result.push([ + 'BoundAttribute', + attribute.type, + attribute.name, + unparse(attribute.value), + ]); + } + + visitBoundEvent(event: t.BoundEvent) { + this.result.push([ + 'BoundEvent', + event.name, + event.target, + unparse(event.handler), + ]); + } + + visitText(text: t.Text) { this.result.push(['Text', text.value]); } + + visitBoundText(text: t.BoundText) { this.result.push(['BoundText', unparse(text.value)]); } + + private visitAll(nodes: t.Node[][]) { nodes.forEach(node => t.visitAll(this, node)); } +} + +function expectFromHtml(html: string) { + const res = parse(html); + return expectFromR3Nodes(res.nodes); +} + +function expectFromR3Nodes(nodes: t.Node[]) { + const humanizer = new R3AstHumanizer(); + t.visitAll(humanizer, nodes); + return expect(humanizer.result); +} + +describe('R3 template transform', () => { + describe('Nodes without binding', () => { + it('should parse text nodes', () => { + expectFromHtml('a').toEqual([ + ['Text', 'a'], + ]); + }); + + it('should parse elements with attributes', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'a', 'b'], + ]); + }); + + it('should parse ngContent', () => { + const res = parse(''); + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual(['a']); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 1], + ['TextAttribute', 'select', 'a'], + ]); + }); + + it('should parse ngContent when it contains WS only', () => { + expectFromHtml(' \n ').toEqual([ + ['Content', 1], + ['TextAttribute', 'select', 'a'], + ]); + }); + + it('should parse ngContent regardless the namespace', () => { + expectFromHtml('').toEqual([ + ['Element', ':svg:svg'], + ['Content', 1], + ['TextAttribute', 'select', 'a'], + ]); + }); + }); + + describe('Bound text nodes', () => { + it('should parse bound text nodes', () => { + expectFromHtml('{{a}}').toEqual([ + ['BoundText', '{{ a }}'], + ]); + }); + }); + + describe('Bound attributes', () => { + it('should parse mixed case bound properties', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'someProp', 'v'], + ]); + }); + + it('should parse bound properties via bind- ', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'prop', 'v'], + ]); + }); + + it('should parse bound properties via {{...}}', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'prop', '{{ v }}'], + ]); + }); + + it('should parse dash case bound properties', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'some-prop', 'v'], + ]); + }); + + it('should parse dotted name bound properties', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'd.ot', 'v'], + ]); + }); + + it('should normalize property names via the element schema', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'mappedProp', 'v'], + ]); + }); + + it('should parse mixed case bound attributes', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Attribute, 'someAttr', 'v'], + ]); + }); + + it('should parse and dash case bound classes', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Class, 'some-class', 'v'], + ]); + }); + + it('should parse mixed case bound classes', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Class, 'someClass', 'v'], + ]); + }); + + it('should parse mixed case bound styles', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Style, 'someStyle', 'v'], + ]); + }); + }); + + describe('templates', () => { + it('should support * directives', () => { + expectFromHtml('
').toEqual([ + ['Template'], + ['TextAttribute', 'ngIf', ''], + ['Element', 'div'], + ]); + }); + + it('should support ', () => { + expectFromHtml('').toEqual([ + ['Template'], + ]); + }); + + it('should support regardless the namespace', () => { + expectFromHtml('').toEqual([ + ['Element', ':svg:svg'], + ['Template'], + ]); + }); + + it('should support reference via #...', () => { + expectFromHtml('').toEqual([ + ['Template'], + ['Reference', 'a', ''], + ]); + }); + + it('should support reference via ref-...', () => { + expectFromHtml('').toEqual([ + ['Template'], + ['Reference', 'a', ''], + ]); + }); + + it('should parse variables via let-...', () => { + expectFromHtml('').toEqual([ + ['Template'], + ['Variable', 'a', 'b'], + ]); + }); + }); + + describe('inline templates', () => { + it('should parse variables via let ...', () => { + expectFromHtml('
').toEqual([ + ['Template'], + ['TextAttribute', 'ngIf', ''], + ['Variable', 'a', 'b'], + ['Element', 'div'], + ]); + }); + + it('should parse variables via as ...', () => { + expectFromHtml('
').toEqual([ + ['Template'], + ['TextAttribute', 'ngIf', 'expr '], + ['BoundAttribute', BindingType.Property, 'ngIf', 'expr'], + ['Variable', 'local', 'ngIf'], + ['Element', 'div'], + ]); + }); + }); + + describe('events', () => { + it('should parse bound events with a target', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundEvent', 'event', 'window', 'v'], + ]); + }); + + it('should parse event names case sensitive', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundEvent', 'some-event', null, 'v'], + ]); + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundEvent', 'someEvent', null, 'v'], + ]); + }); + + it('should parse bound events via on-', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundEvent', 'event', null, 'v'], + ]); + }); + + it('should parse bound events and properties via [(...)]', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'prop', 'v'], + ['BoundEvent', 'propChange', null, 'v = $event'], + ]); + }); + + it('should parse bound events and properties via bindon-', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['BoundAttribute', BindingType.Property, 'prop', 'v'], + ['BoundEvent', 'propChange', null, 'v = $event'], + ]); + }); + + // TODO(vicb): Should Error + xit('should report an error on empty expression', () => { + expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); + + expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); + }); + }); + + describe('references', () => { + it('should parse references via #...', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['Reference', 'a', ''], + ]); + }); + + it('should parse references via ref-', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['Reference', 'a', ''], + ]); + }); + + it('should parse camel case references', () => { + expectFromHtml('
').toEqual([ + ['Element', 'div'], + ['Reference', 'someA', ''], + ]); + }); + }); + + // TODO(vicb): Add ng-content test + describe('ng-content', () => {}); + + describe('Ignored elements', () => { + it('should ignore a').toEqual([ + ['Text', 'a'], + ]); + }); + + it('should ignore a').toEqual([ + ['Text', 'a'], + ]); + }); + }); + + describe('', () => { + it('should keep elements if they have an absolute url', () => { + expectFromHtml('').toEqual([ + ['Element', 'link'], + ['TextAttribute', 'rel', 'stylesheet'], + ['TextAttribute', 'href', 'http://someurl'], + ]); + expectFromHtml('').toEqual([ + ['Element', 'link'], + ['TextAttribute', 'REL', 'stylesheet'], + ['TextAttribute', 'href', 'http://someurl'], + ]); + }); + + it('should keep elements if they have no uri', () => { + expectFromHtml('').toEqual([ + ['Element', 'link'], + ['TextAttribute', 'rel', 'stylesheet'], + ]); + expectFromHtml('').toEqual([ + ['Element', 'link'], + ['TextAttribute', 'REL', 'stylesheet'], + ]); + }); + + it('should ignore elements if they have a relative uri', () => { + expectFromHtml('').toEqual([]); + expectFromHtml('').toEqual([]); + }); + }); + + describe('ngNonBindable', () => { + it('should ignore bindings on children of elements with ngNonBindable', () => { + expectFromHtml('
{{b}}
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'ngNonBindable', ''], + ['Text', '{{b}}'], + ]); + }); + + it('should keep nested children of elements with ngNonBindable', () => { + expectFromHtml('
{{b}}
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'ngNonBindable', ''], + ['Element', 'span'], + ['Text', '{{b}}'], + ]); + }); + + it('should ignore a
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'ngNonBindable', ''], + ['Text', 'a'], + ]); + }); + + it('should ignore a
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'ngNonBindable', ''], + ['Text', 'a'], + ]); + }); + + it('should ignore elements inside of elements with ngNonBindable', + () => { + expectFromHtml('
a
').toEqual([ + ['Element', 'div'], + ['TextAttribute', 'ngNonBindable', ''], + ['Text', 'a'], + ]); + }); + }); +}); \ No newline at end of file diff --git a/packages/compiler/test/template_parser/template_parser_spec.ts b/packages/compiler/test/template_parser/template_parser_spec.ts index 527db692ef..fc0834d9d9 100644 --- a/packages/compiler/test/template_parser/template_parser_spec.ts +++ b/packages/compiler/test/template_parser/template_parser_spec.ts @@ -21,7 +21,7 @@ import {Identifiers, createTokenForExternalReference, createTokenForReference} f import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config'; import {noUndefined} from '../../src/util'; import {MockSchemaRegistry} from '../../testing'; -import {unparse} from '../expression_parser/unparser'; +import {unparse} from '../expression_parser/utils/unparser'; import {TEST_COMPILER_PROVIDERS} from '../test_bindings'; const someModuleUrl = 'package:someModule'; @@ -139,12 +139,12 @@ class TemplateHumanizer implements TemplateAstVisitor { visitNgContent(ast: NgContentAst, context: any): any { const res = [NgContentAst]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { const res = [EmbeddedTemplateAst]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); templateVisitAll(this, ast.attrs); templateVisitAll(this, ast.outputs); templateVisitAll(this, ast.references); @@ -155,7 +155,7 @@ class TemplateHumanizer implements TemplateAstVisitor { } visitElement(ast: ElementAst, context: any): any { const res = [ElementAst, ast.name]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); templateVisitAll(this, ast.attrs); templateVisitAll(this, ast.inputs); templateVisitAll(this, ast.outputs); @@ -166,18 +166,18 @@ class TemplateHumanizer implements TemplateAstVisitor { } visitReference(ast: ReferenceAst, context: any): any { const res = [ReferenceAst, ast.name, ast.value]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitVariable(ast: VariableAst, context: any): any { const res = [VariableAst, ast.name, ast.value]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitEvent(ast: BoundEventAst, context: any): any { const res = [BoundEventAst, ast.name, ast.target, unparse(ast.handler, this.interpolationConfig)]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitElementProperty(ast: BoundElementPropertyAst, context: any): any { @@ -185,27 +185,27 @@ class TemplateHumanizer implements TemplateAstVisitor { BoundElementPropertyAst, ast.type, ast.name, unparse(ast.value, this.interpolationConfig), ast.unit ]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitAttr(ast: AttrAst, context: any): any { const res = [AttrAst, ast.name, ast.value]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitBoundText(ast: BoundTextAst, context: any): any { const res = [BoundTextAst, unparse(ast.value, this.interpolationConfig)]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitText(ast: TextAst, context: any): any { const res = [TextAst, ast.value]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } visitDirective(ast: DirectiveAst, context: any): any { const res = [DirectiveAst, ast.directive]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); templateVisitAll(this, ast.inputs); templateVisitAll(this, ast.hostProperties); templateVisitAll(this, ast.hostEvents); @@ -215,11 +215,11 @@ class TemplateHumanizer implements TemplateAstVisitor { const res = [ BoundDirectivePropertyAst, ast.directiveName, unparse(ast.value, this.interpolationConfig) ]; - this.result.push(this._appendContext(ast, res)); + this.result.push(this._appendSourceSpan(ast, res)); return null; } - private _appendContext(ast: TemplateAst, input: any[]): any[] { + private _appendSourceSpan(ast: TemplateAst, input: any[]): any[] { if (!this.includeSourceSpan) return input; input.push(ast.sourceSpan !.toString()); return input; @@ -1965,14 +1965,13 @@ Property binding a not used by any directive on an embedded template. Make sure describe('', () => { - it('should keep elements if they have an absolute non package: url', - () => { - expect(humanizeTplAst(parse('a', []))) - .toEqual([ - [ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], - [AttrAst, 'href', 'http://someurl'], [TextAst, 'a'] - ]); - }); + it('should keep elements if they have an absolute url', () => { + expect(humanizeTplAst(parse('a', []))) + .toEqual([ + [ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], + [AttrAst, 'href', 'http://someurl'], [TextAst, 'a'] + ]); + }); it('should keep elements if they have no uri', () => { expect(humanizeTplAst(parse('a', [ From 08e7efc69e16ca76d9b15300e84ae7728eb6a6f1 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 27 Apr 2018 14:39:07 -0700 Subject: [PATCH 022/582] feat(ivy): add error reporting to the html to ivy transformer (#23546) PR Close #23546 --- packages/compiler/src/aot/compiler.ts | 11 +-- .../src/render3/r3_template_transform.ts | 39 +++++++- .../compiler/src/render3/view/compiler.ts | 18 ++-- .../src/template_parser/binding_parser.ts | 5 +- .../src/template_parser/template_parser.ts | 12 --- .../compiler/test/render3/mock_compile.ts | 12 ++- .../render3/r3_template_transform_spec.ts | 88 ++++++++++++++----- 7 files changed, 121 insertions(+), 64 deletions(-) diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 829691e6e8..af87fc8db7 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -24,7 +24,7 @@ import * as o from '../output/output_ast'; import {ParseError} from '../parse_util'; import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler'; import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler'; -import {HtmlToTemplateTransform} from '../render3/r3_template_transform'; +import {htmlAstToRender3Ast} from '../render3/r3_template_transform'; import {compileComponentFromRender2 as compileIvyComponent, compileDirectiveFromRender2 as compileIvyDirective} from '../render3/view/compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; @@ -391,10 +391,7 @@ export class AotCompiler { if (!preserveWhitespaces) { htmlAst = removeWhitespaces(htmlAst); } - const transform = new HtmlToTemplateTransform(hostBindingParser); - const nodes = html.visitAll(transform, htmlAst.rootNodes, null); - const hasNgContent = transform.hasNgContent; - const ngContentSelectors = transform.ngContentSelectors; + const render3Ast = htmlAstToRender3Ast(htmlAst.rootNodes, hostBindingParser); // Map of StaticType by directive selectors const directiveTypeBySel = new Map(); @@ -417,8 +414,8 @@ export class AotCompiler { pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); }); compileIvyComponent( - context, directiveMetadata, nodes, hasNgContent, ngContentSelectors, this.reflector, - hostBindingParser, directiveTypeBySel, pipeTypeByName); + context, directiveMetadata, render3Ast, this.reflector, hostBindingParser, + directiveTypeBySel, pipeTypeByName); } else { compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser); } diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index 44a4b92e8b..6de81d5b5a 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ParsedEvent, ParsedProperty, ParsedVariable} from '../expression_parser/ast'; +import {ParsedEvent, ParsedProperty, ParsedVariable, ParserError} from '../expression_parser/ast'; import * as html from '../ml_parser/ast'; import {replaceNgsp} from '../ml_parser/html_whitespaces'; import {isNgTemplate} from '../ml_parser/tags'; @@ -14,9 +14,10 @@ import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util'; import {isStyleUrlResolvable} from '../style_url_resolver'; import {BindingParser} from '../template_parser/binding_parser'; import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser'; - +import {syntaxError} from '../util'; import * as t from './r3_ast'; + const BIND_NAME_REGEXP = /^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/; @@ -46,9 +47,39 @@ const CLASS_ATTR = 'class'; // Default selector used by `` if none specified const DEFAULT_CONTENT_SELECTOR = '*'; -export class HtmlToTemplateTransform implements html.Visitor { - errors: ParseError[]; +// Result of the html AST to Ivy AST transformation +export type Render3ParseResult = { + nodes: t.Node[]; errors: ParseError[]; + // Any non default (empty or '*') selector found in the template + ngContentSelectors: string[]; + // Wether the template contains any `` + hasNgContent: boolean; +}; +export function htmlAstToRender3Ast( + htmlNodes: html.Node[], bindingParser: BindingParser): Render3ParseResult { + const transformer = new HtmlAstToIvyAst(bindingParser); + const ivyNodes = html.visitAll(transformer, htmlNodes); + + // Errors might originate in either the binding parser or the html to ivy transformer + const allErrors = bindingParser.errors.concat(transformer.errors); + const errors: ParseError[] = allErrors.filter(e => e.level === ParseErrorLevel.ERROR); + + if (errors.length > 0) { + const errorString = errors.join('\n'); + throw syntaxError(`Template parse errors:\n${errorString}`, errors); + } + + return { + nodes: ivyNodes, + errors: allErrors, + ngContentSelectors: transformer.ngContentSelectors, + hasNgContent: transformer.hasNgContent, + }; +} + +class HtmlAstToIvyAst implements html.Visitor { + errors: ParseError[] = []; // Selectors for the `ng-content` tags. Only non `*` selectors are recorded here ngContentSelectors: string[] = []; // Any `` in the template ? diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index cba9e7be1f..c43bbec1f2 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -12,7 +12,7 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../../constant_pool'; import * as core from '../../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; import {Identifiers} from '../../identifiers'; import {LifecycleHooks} from '../../lifecycle_reflector'; import * as o from '../../output/output_ast'; @@ -21,9 +21,10 @@ import {CssSelector, SelectorMatcher} from '../../selector'; import {BindingParser} from '../../template_parser/binding_parser'; import {OutputContext, error} from '../../util'; -import * as t from './../r3_ast'; -import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from './../r3_factory'; -import {Identifiers as R3} from './../r3_identifiers'; +import * as t from '../r3_ast'; +import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory'; +import {Identifiers as R3} from '../r3_identifiers'; +import {Render3ParseResult} from '../r3_template_transform'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; import {BindingScope, TemplateDefinitionBuilder} from './template'; import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; @@ -191,9 +192,8 @@ export function compileDirectiveFromRender2( * information. */ export function compileComponentFromRender2( - outputCtx: OutputContext, component: CompileDirectiveMetadata, nodes: t.Node[], - hasNgContent: boolean, ngContentSelectors: string[], reflector: CompileReflector, - bindingParser: BindingParser, directiveTypeBySel: Map, + outputCtx: OutputContext, component: CompileDirectiveMetadata, render3Ast: Render3ParseResult, + reflector: CompileReflector, bindingParser: BindingParser, directiveTypeBySel: Map, pipeTypeByName: Map) { const name = identifierName(component.type) !; name || error(`Cannot resolver the name of ${component.type}`); @@ -207,7 +207,9 @@ export function compileComponentFromRender2( ...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector), selector: component.selector, template: { - nodes, hasNgContent, ngContentSelectors, + nodes: render3Ast.nodes, + hasNgContent: render3Ast.hasNgContent, + ngContentSelectors: render3Ast.ngContentSelectors, }, lifecycle: { usesOnChanges: diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index 90b71d08eb..04aa7ba9f7 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -29,12 +29,13 @@ const ANIMATE_PROP_PREFIX = 'animate-'; */ export class BindingParser { pipesByName: Map|null = null; + private _usedPipes: Map = new Map(); constructor( private _exprParser: Parser, private _interpolationConfig: InterpolationConfig, private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null, - private _targetErrors: ParseError[]) { + public errors: ParseError[]) { // When the `pipes` parameter is `null`, do not check for used pipes // This is used in IVY when we might not know the available pipes at compile time if (pipes) { @@ -364,7 +365,7 @@ export class BindingParser { private _reportError( message: string, sourceSpan: ParseSourceSpan, level: ParseErrorLevel = ParseErrorLevel.ERROR) { - this._targetErrors.push(new ParseError(sourceSpan, message, level)); + this.errors.push(new ParseError(sourceSpan, message, level)); } private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) { diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts index e3d18553ae..e7a26c865f 100644 --- a/packages/compiler/src/template_parser/template_parser.ts +++ b/packages/compiler/src/template_parser/template_parser.ts @@ -59,18 +59,6 @@ const CLASS_ATTR = 'class'; const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; -let warningCounts: {[warning: string]: number} = {}; - -function warnOnlyOnce(warnings: string[]): (warning: ParseError) => boolean { - return (error: ParseError) => { - if (warnings.indexOf(error.msg) !== -1) { - warningCounts[error.msg] = (warningCounts[error.msg] || 0) + 1; - return warningCounts[error.msg] <= 1; - } - return true; - }; -} - export class TemplateParseError extends ParseError { constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) { super(span, message, level); diff --git a/packages/compiler/test/render3/mock_compile.ts b/packages/compiler/test/render3/mock_compile.ts index d74aa6d304..d7ec3bdad0 100644 --- a/packages/compiler/test/render3/mock_compile.ts +++ b/packages/compiler/test/render3/mock_compile.ts @@ -16,7 +16,7 @@ import * as html from '../../src/ml_parser/ast'; import {removeWhitespaces} from '../../src/ml_parser/html_whitespaces'; import * as o from '../../src/output/output_ast'; import {compilePipe} from '../../src/render3/r3_pipe_compiler'; -import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform'; +import {htmlAstToRender3Ast} from '../../src/render3/r3_template_transform'; import {compileComponentFromRender2, compileDirectiveFromRender2} from '../../src/render3/view/compiler'; import {BindingParser} from '../../src/template_parser/binding_parser'; import {OutputContext, escapeRegExp} from '../../src/util'; @@ -308,14 +308,12 @@ export function compile( if (!preserveWhitespaces) { htmlAst = removeWhitespaces(htmlAst); } - const transform = new HtmlToTemplateTransform(hostBindingParser); - const nodes = html.visitAll(transform, htmlAst.rootNodes, null); - const hasNgContent = transform.hasNgContent; - const ngContentSelectors = transform.ngContentSelectors; + + const render3Ast = htmlAstToRender3Ast(htmlAst.rootNodes, hostBindingParser); compileComponentFromRender2( - outputCtx, directive, nodes, hasNgContent, ngContentSelectors, reflector, - hostBindingParser, directiveTypeBySel, pipeTypeByName); + outputCtx, directive, render3Ast, reflector, hostBindingParser, + directiveTypeBySel, pipeTypeByName); } else { compileDirectiveFromRender2(outputCtx, directive, reflector, hostBindingParser); } diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts index 8880d5b584..f172257929 100644 --- a/packages/compiler/test/render3/r3_template_transform_spec.ts +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -14,13 +14,14 @@ import {HtmlParser} from '../../src/ml_parser/html_parser'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config'; import {ParseError} from '../../src/parse_util'; import * as t from '../../src/render3/r3_ast'; -import {HtmlToTemplateTransform} from '../../src/render3/r3_template_transform'; +import {Render3ParseResult, htmlAstToRender3Ast} from '../../src/render3/r3_template_transform'; import {BindingParser} from '../../src/template_parser/binding_parser'; import {MockSchemaRegistry} from '../../testing'; import {unparse} from '../expression_parser/utils/unparser'; + // Parse an html string to IVY specific info -function parse(html: string) { +function parse(html: string): Render3ParseResult { const htmlParser = new HtmlParser(); const parseResult = htmlParser.parse(html, 'path:://to/template', true); @@ -31,27 +32,13 @@ function parse(html: string) { } const htmlNodes = parseResult.rootNodes; - const expressionErrors: ParseError[] = []; const expressionParser = new Parser(new Lexer()); const schemaRegistry = new MockSchemaRegistry( {'invalidProp': false}, {'mappedAttr': 'mappedProp'}, {'unknown': false, 'un-known': false}, ['onEvent'], ['onEvent']); - const bindingParser = new BindingParser( - expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, expressionErrors); - const r3Transform = new HtmlToTemplateTransform(bindingParser); - - const r3Nodes = visitAll(r3Transform, htmlNodes); - - if (r3Transform.errors) { - const msg = r3Transform.errors.map(e => e.toString()).join('\n'); - throw new Error(msg); - } - - return { - nodes: r3Nodes, - hasNgContent: r3Transform.hasNgContent, - ngContentSelectors: r3Transform.ngContentSelectors, - }; + const bindingParser = + new BindingParser(expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, []); + return htmlAstToRender3Ast(htmlNodes, bindingParser); } // Transform an IVY AST to a flat list of nodes to ease testing @@ -361,10 +348,8 @@ describe('R3 template transform', () => { ]); }); - // TODO(vicb): Should Error - xit('should report an error on empty expression', () => { + it('should report an error on empty expression', () => { expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); - expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); }); }); @@ -392,8 +377,63 @@ describe('R3 template transform', () => { }); }); - // TODO(vicb): Add ng-content test - describe('ng-content', () => {}); + describe('ng-content', () => { + it('should parse ngContent without selector', () => { + const res = parse(''); + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual([]); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 0], + ]); + }); + + it('should parse ngContent with a * selector', () => { + const res = parse(''); + const selectors = ['']; + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual([]); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 0], + ]); + }); + + it('should parse ngContent with a specific selector', () => { + const res = parse(''); + const selectors = ['', 'tag[attribute]']; + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual(['tag[attribute]']); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 1], + ['TextAttribute', 'select', selectors[1]], + ]); + }); + + it('should parse ngContent with a selector', () => { + const res = parse( + ''); + const selectors = ['', 'a', 'b']; + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual(['a', 'b']); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 1], + ['TextAttribute', 'select', selectors[1]], + ['Content', 0], + ['Content', 2], + ['TextAttribute', 'select', selectors[2]], + ]); + }); + + it('should parse ngProjectAs as an attribute', () => { + const res = parse(''); + const selectors = ['']; + expect(res.hasNgContent).toEqual(true); + expect(res.ngContentSelectors).toEqual([]); + expectFromR3Nodes(res.nodes).toEqual([ + ['Content', 0], + ['TextAttribute', 'ngProjectAs', 'a'], + ]); + }); + }); describe('Ignored elements', () => { it('should ignore `); + expect(s.lastSanitizedValue).toEqual(outputValue); + }); + + it('should bypass resourceUrl sanitization if marked by the service', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createScript, undefined, null, null, s); + const inputValue = s.bypassSecurityTrustResourceUrl('file://all-my-secrets.pdf'); + const outputValue = 'file://all-my-secrets.pdf'; + + t.update(() => elementAttribute(0, 'src', inputValue, sanitizeResourceUrl)); + expect(t.html).toEqual(``); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + + it('should bypass ivy-level resourceUrl sanitization if a custom sanitizer is used', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createScript, undefined, null, null, s); + const inputValue = bypassSanitizationTrustResourceUrl('file://all-my-secrets.pdf'); + const outputValue = 'file://all-my-secrets.pdf-ivy'; + + t.update(() => elementAttribute(0, 'src', inputValue, sanitizeResourceUrl)); + expect(t.html).toEqual(``); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + + it('should work for script sanitization', () => { + const s = new LocalMockSanitizer(value => `${value} //sanitized`); + const t = new TemplateFixture(createScript, undefined, null, null, s); + const inputValue = 'fn();'; + const outputValue = 'fn(); //sanitized'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript)); + expect(t.html).toEqual(``); + expect(s.lastSanitizedValue).toEqual(outputValue); + }); + + it('should bypass script sanitization if marked by the service', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createScript, undefined, null, null, s); + const inputValue = s.bypassSecurityTrustScript('alert("bar")'); + const outputValue = 'alert("bar")'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript)); + expect(t.html).toEqual(``); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + + it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createScript, undefined, null, null, s); + const inputValue = bypassSanitizationTrustScript('alert("bar")'); + const outputValue = 'alert("bar")-ivy'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript)); + expect(t.html).toEqual(``); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + + it('should work for html sanitization', () => { + const s = new LocalMockSanitizer(value => `${value} `); + const t = new TemplateFixture(createDiv, undefined, null, null, s); + const inputValue = '
'; + const outputValue = '
'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml)); + expect(t.html).toEqual(`
${outputValue}
`); + expect(s.lastSanitizedValue).toEqual(outputValue); + }); + + it('should bypass html sanitization if marked by the service', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createDiv, undefined, null, null, s); + const inputValue = s.bypassSecurityTrustHtml('
'); + const outputValue = '
'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml)); + expect(t.html).toEqual(`
${outputValue}
`); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + + it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => { + const s = new LocalMockSanitizer(value => ''); + const t = new TemplateFixture(createDiv, undefined, null, null, s); + const inputValue = bypassSanitizationTrustHtml('
'); + const outputValue = '
-ivy'; + + t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml)); + expect(t.html).toEqual(`
${outputValue}
`); + expect(s.lastSanitizedValue).toBeFalsy(); + }); + }); }); + +class LocalSanitizedValue { + constructor(public value: any) {} + + toString() { return this.value; } +} + +class LocalMockSanitizer implements Sanitizer { + public lastSanitizedValue: string|null; + + constructor(private _interceptor: (value: string|null|any) => string) {} + + sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null|any): string|null { + if (value instanceof String) { + return value.toString() + '-ivy'; + } + + if (value instanceof LocalSanitizedValue) { + return value.toString(); + } + + return this.lastSanitizedValue = this._interceptor(value); + } + + bypassSecurityTrustHtml(value: string) { return new LocalSanitizedValue(value); } + + bypassSecurityTrustStyle(value: string) { return new LocalSanitizedValue(value); } + + bypassSecurityTrustScript(value: string) { return new LocalSanitizedValue(value); } + + bypassSecurityTrustUrl(value: string) { return new LocalSanitizedValue(value); } + + bypassSecurityTrustResourceUrl(value: string) { return new LocalSanitizedValue(value); } +} + +function stripStyleWsCharacters(value: string): string { + // color: blue; => color:blue + return value.replace(/;/g, '').replace(/:\s+/g, ':'); +} diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index d1ae1f67a5..50d3a0d0f2 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -11,6 +11,8 @@ import {RenderFlags} from '@angular/core/src/render3'; import {defineComponent, defineDirective} from '../../src/render3/index'; import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LViewFlags} from '../../src/render3/interfaces/view'; +import {sanitizeUrl} from '../../src/sanitization/sanitization'; +import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; import {ComponentFixture, containerEl, renderToHtml} from './render_util'; @@ -847,4 +849,65 @@ describe('render3 integration test', () => { }); + describe('sanitization', () => { + it('should sanitize data using the provided sanitization interface', () => { + class SanitizationComp { + static ngComponentDef = defineComponent({ + type: SanitizationComp, + selectors: [['sanitize-this']], + factory: () => new SanitizationComp(), + template: (rf: RenderFlags, ctx: SanitizationComp) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'a'); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'href', bind(ctx.href), sanitizeUrl); + } + } + }); + + private href = ''; + + updateLink(href: any) { this.href = href; } + } + + const sanitizer = new LocalSanitizer((value) => { return 'http://bar'; }); + + const fixture = new ComponentFixture(SanitizationComp, {sanitizer}); + fixture.component.updateLink('http://foo'); + fixture.update(); + + const element = fixture.hostElement.querySelector('a') !; + expect(element.getAttribute('href')).toEqual('http://bar'); + + fixture.component.updateLink(sanitizer.bypassSecurityTrustUrl('http://foo')); + fixture.update(); + + expect(element.getAttribute('href')).toEqual('http://foo'); + }); + }); }); + +class LocalSanitizedValue { + constructor(public value: any) {} + toString() { return this.value; } +} + +class LocalSanitizer implements Sanitizer { + constructor(private _interceptor: (value: string|null|any) => string) {} + + sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null): string|null { + if (value instanceof LocalSanitizedValue) { + return value.toString(); + } + return this._interceptor(value); + } + + bypassSecurityTrustHtml(value: string) {} + bypassSecurityTrustStyle(value: string) {} + bypassSecurityTrustScript(value: string) {} + bypassSecurityTrustResourceUrl(value: string) {} + + bypassSecurityTrustUrl(value: string) { return new LocalSanitizedValue(value); } +} diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 9e523f86bd..655ff053d5 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -16,6 +16,7 @@ import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions'; import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition'; import {LElementNode} from '../../src/render3/interfaces/node'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; +import {Sanitizer} from '../../src/sanitization/security'; import {Type} from '../../src/type'; import {getRendererFactory2} from './imported_renderer2'; @@ -51,6 +52,8 @@ export class TemplateFixture extends BaseFixture { hostNode: LElementNode; private _directiveDefs: DirectiveDefList|null; private _pipeDefs: PipeDefList|null; + private _sanitizer: Sanitizer|null; + /** * * @param createBlock Instructions which go into the creation block: @@ -60,10 +63,12 @@ export class TemplateFixture extends BaseFixture { */ constructor( private createBlock: () => void, private updateBlock: () => void = noop, - directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null) { + directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null, + sanitizer?: Sanitizer) { super(); this._directiveDefs = toDefs(directives, extractDirectiveDef); this._pipeDefs = toDefs(pipes, extractPipeDef); + this._sanitizer = sanitizer || null; this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => { if (rf & RenderFlags.Create) { this.createBlock(); @@ -71,7 +76,7 @@ export class TemplateFixture extends BaseFixture { if (rf & RenderFlags.Update) { this.updateBlock(); } - }, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs); + }, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs, sanitizer); } /** @@ -82,7 +87,7 @@ export class TemplateFixture extends BaseFixture { update(updateBlock?: () => void): void { renderTemplate( this.hostNode.native, updateBlock || this.updateBlock, null !, domRendererFactory3, - this.hostNode, this._directiveDefs, this._pipeDefs); + this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer); } } @@ -94,7 +99,9 @@ export class ComponentFixture extends BaseFixture { component: T; requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];}; - constructor(private componentType: ComponentType, opts: {injector?: Injector} = {}) { + constructor( + private componentType: ComponentType, + opts: {injector?: Injector, sanitizer?: Sanitizer} = {}) { super(); this.requestAnimationFrame = function(fn: () => void) { requestAnimationFrame.queue.push(fn); @@ -106,9 +113,12 @@ export class ComponentFixture extends BaseFixture { } }; - this.component = _renderComponent( - componentType, - {host: this.hostElement, scheduler: this.requestAnimationFrame, injector: opts.injector}); + this.component = _renderComponent(componentType, { + host: this.hostElement, + scheduler: this.requestAnimationFrame, + injector: opts.injector, + sanitizer: opts.sanitizer + }); } update(): void { @@ -195,6 +205,7 @@ export function renderComponent(type: ComponentType, opts?: CreateComponen rendererFactory: opts && opts.rendererFactory || testRendererFactory, host: containerEl, scheduler: requestAnimationFrame, + sanitizer: opts ? opts.sanitizer : undefined, hostFeatures: opts && opts.hostFeatures }); } From d889f57ae2d9a992192115d5b634daadcb0d3430 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 11 May 2018 10:17:43 +0100 Subject: [PATCH 045/582] build(aio): display types of API const docs correctly (#23850) Previously these docs always displayed `any` as the type of the const export. Now the type is computed correctly from the declared type or initializer of the constant. PR Close #23850 --- aio/package.json | 2 +- aio/tools/transforms/templates/api/var.template.html | 2 +- aio/yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aio/package.json b/aio/package.json index 28eb70a792..b8bc9757e7 100644 --- a/aio/package.json +++ b/aio/package.json @@ -112,7 +112,7 @@ "cross-spawn": "^5.1.0", "css-selector-parser": "^1.3.0", "dgeni": "^0.4.7", - "dgeni-packages": "^0.26.0", + "dgeni-packages": "^0.26.1", "entities": "^1.1.1", "eslint": "^3.19.0", "eslint-plugin-jasmine": "^2.2.0", diff --git a/aio/tools/transforms/templates/api/var.template.html b/aio/tools/transforms/templates/api/var.template.html index c8e590b96e..5375b8c117 100644 --- a/aio/tools/transforms/templates/api/var.template.html +++ b/aio/tools/transforms/templates/api/var.template.html @@ -2,7 +2,7 @@ {% block overview %} - const {$ doc.name $}: {$ doc.symbolTypeName or 'any' $}; + const {$ doc.name $}: {$ (doc.type | escape) or 'any' $}; {% endblock %} diff --git a/aio/yarn.lock b/aio/yarn.lock index 012ee1526c..b143e7eff2 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -3086,9 +3086,9 @@ devtools-timeline-model@1.1.6: chrome-devtools-frontend "1.0.401423" resolve "1.1.7" -dgeni-packages@^0.26.0: - version "0.26.0" - resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.0.tgz#4311a0631f8459703001a5e65e390a29321a0562" +dgeni-packages@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.1.tgz#34b5ed880be2f91862095b15085c50cc84507aa8" dependencies: canonical-path "0.0.2" catharsis "^0.8.1" From cfde36da84b5de5248e1cdea932fc75c57ee078c Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 10 May 2018 15:58:27 -0700 Subject: [PATCH 046/582] fix(compiler): generate constant array for i18n attributes (#23837) PR Close #23837 --- packages/compiler/src/constant_pool.ts | 3 ++- packages/compiler/src/render3/view/template.ts | 12 +++--------- .../test/render3/r3_view_compiler_i18n_spec.ts | 14 ++++---------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/constant_pool.ts b/packages/compiler/src/constant_pool.ts index bd5a9b60cc..58a40a7eba 100644 --- a/packages/compiler/src/constant_pool.ts +++ b/packages/compiler/src/constant_pool.ts @@ -295,8 +295,9 @@ class KeyVisitor implements o.ExpressionVisitor { `EX:${ast.value.runtime.name}`; } + visitReadVarExpr(node: o.ReadVarExpr) { return `VAR:${node.name}`; } + visitWrappedNodeExpr = invalid; - visitReadVarExpr = invalid; visitWriteVarExpr = invalid; visitWriteKeyExpr = invalid; visitWritePropExpr = invalid; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 9563936249..8faa85963f 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -257,13 +257,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Add the attributes const i18nMessages: o.Statement[] = []; const attributes: o.Expression[] = []; - let hasI18nAttr = false; Object.getOwnPropertyNames(outputAttrs).forEach(name => { const value = outputAttrs[name]; attributes.push(o.literal(name)); if (attrI18nMetas.hasOwnProperty(name)) { - hasI18nAttr = true; const meta = parseI18nMeta(attrI18nMetas[name]); const variable = this.constantPool.getTranslation(value, meta); attributes.push(variable); @@ -272,13 +270,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } }); - let attrArg: o.Expression = o.TYPED_NULL_EXPR; - - if (attributes.length > 0) { - attrArg = hasI18nAttr ? getLiteralFactory(this.constantPool, o.literalArr(attributes)) : - this.constantPool.getConstLiteral(o.literalArr(attributes), true); - } - + const attrArg: o.Expression = attributes.length > 0 ? + this.constantPool.getConstLiteral(o.literalArr(attributes), true) : + o.TYPED_NULL_EXPR; parameters.push(attrArg); if (element.references && element.references.length > 0) { diff --git a/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts b/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts index d11cacc4c2..3839df2615 100644 --- a/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts @@ -93,10 +93,7 @@ describe('i18n support in the view compiler', () => { * @desc desc */ const $msg_1$ = goog.getMsg('introduction'); - … - const $c1$ = ($a1$:any) => { - return ['title', $a1$]; - }; + const $c1$ = ['title', $msg_1$]; … /** * @desc desc @@ -106,7 +103,7 @@ describe('i18n support in the view compiler', () => { … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div', $r3$.ɵf1($c1$, $msg_1$)); + $r3$.ɵE(0, 'div', $c1$); $r3$.ɵT(1, $msg_2$); $r3$.ɵe(); } @@ -147,14 +144,11 @@ describe('i18n support in the view compiler', () => { * @meaning m */ const $msg_1$ = goog.getMsg('introduction'); - … - const $c1$ = ($a1$:any) => { - return ['id', 'static', 'title', $a1$]; - }; + const $c1$ = ['id', 'static', 'title', $msg_1$]; … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div', $r3$.ɵf1($c1$, $msg_1$)); + $r3$.ɵE(0, 'div', $c1$); $r3$.ɵe(); } } From d4b8b2440673049c58d51b0ae4afceb028d62cc0 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Thu, 10 May 2018 19:35:19 -0700 Subject: [PATCH 047/582] fix(elements): prevent closure renaming of platform properties (#23843) Closure compiler with type based optimizations has a bug where externs for inherited static fields are not being honored. For Angular Elements this meant that 'observedAttributes' static field which is marked as an extern for the base HTMLElement class was getting renamed. This commit works around the bug by using quoted access of 'observedAttributes' that explicitly prevents the renaming. PR Close #23843 --- packages/elements/src/create-custom-element.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/elements/src/create-custom-element.ts b/packages/elements/src/create-custom-element.ts index 34595baa34..ce70f3ae7a 100644 --- a/packages/elements/src/create-custom-element.ts +++ b/packages/elements/src/create-custom-element.ts @@ -131,7 +131,9 @@ export function createCustomElement

( const attributeToPropertyInputs = getDefaultAttributeToPropertyInputs(inputs); class NgElementImpl extends NgElement { - static readonly observedAttributes = Object.keys(attributeToPropertyInputs); + // Work around a bug in closure typed optimizations(b/79557487) where it is not honoring static + // field externs. So using quoted access to explicitly prevent renaming. + static readonly['observedAttributes'] = Object.keys(attributeToPropertyInputs); constructor(injector?: Injector) { super(); From a2e8b3a6a80432d139abff64e9ad6baf1eb13dee Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 14 May 2018 17:25:41 +0100 Subject: [PATCH 048/582] build(aio): ensure usageNotes are copied into decorator API docs (#23901) PR Close #23901 --- .../angular-api-package/processors/mergeDecoratorDocs.js | 3 +-- .../processors/mergeDecoratorDocs.spec.js | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js index 6227dbb3d8..86571ea5fa 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.js @@ -90,8 +90,7 @@ module.exports = function mergeDecoratorDocs(log) { callMember.description.substring(0, 50)); // Merge the documentation found in this call signature into the original decorator decoratorDoc.description = callMember.description; - decoratorDoc.howToUse = callMember.howToUse; - decoratorDoc.whatItDoes = callMember.whatItDoes; + decoratorDoc.usageNotes = callMember.usageNotes; // remove doc from its module doc's exports doc.moduleDoc.exports = diff --git a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js index be6cff2e4b..77b3f2593d 100644 --- a/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/mergeDecoratorDocs.spec.js @@ -32,8 +32,7 @@ describe('mergeDecoratorDocs processor', () => { { isCallMember: true, description: 'The actual description of the call signature', - whatItDoes: 'Does something cool...', - howToUse: 'Use it like this...' + usageNotes: 'Use it like this...' }, { description: 'Some other member' @@ -69,8 +68,7 @@ describe('mergeDecoratorDocs processor', () => { it('should copy across properties from the call signature doc', () => { processor.$process([decoratorDoc, metadataDoc, otherDoc]); expect(decoratorDoc.description).toEqual('The actual description of the call signature'); - expect(decoratorDoc.whatItDoes).toEqual('Does something cool...'); - expect(decoratorDoc.howToUse).toEqual('Use it like this...'); + expect(decoratorDoc.usageNotes).toEqual('Use it like this...'); }); it('should remove the metadataDoc from the module exports', () => { From 02acb5e3e582560068d8c48e7331a2bd643d6515 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 12 May 2018 13:30:19 +0100 Subject: [PATCH 049/582] build(aio): improve `enum` API rendering (#23872) * The member details section is now called "Members", rather than "Properties". * The property table now displays appropriate table headings: "Member", "Value", "Description". * The "Value" column is not shown if none of the members have a value. Closes #22678 PR Close #23872 --- .../rendering/hasValues.js | 9 +++++++++ .../rendering/hasValues.spec.js | 19 +++++++++++++++++++ .../templates/api/enum.template.html | 15 ++++++++++++++- .../templates/api/lib/memberHelpers.html | 11 ++++++++--- 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 aio/tools/transforms/angular-base-package/rendering/hasValues.js create mode 100644 aio/tools/transforms/angular-base-package/rendering/hasValues.spec.js diff --git a/aio/tools/transforms/angular-base-package/rendering/hasValues.js b/aio/tools/transforms/angular-base-package/rendering/hasValues.js new file mode 100644 index 0000000000..7b15501fa2 --- /dev/null +++ b/aio/tools/transforms/angular-base-package/rendering/hasValues.js @@ -0,0 +1,9 @@ +module.exports = function hasValues() { + return { + name: 'hasValues', + process: function(list, property) { + if (!list || !Array.isArray(list)) return false; + return list.some(item => item[property]); + } + }; +}; \ No newline at end of file diff --git a/aio/tools/transforms/angular-base-package/rendering/hasValues.spec.js b/aio/tools/transforms/angular-base-package/rendering/hasValues.spec.js new file mode 100644 index 0000000000..7f22c93e9d --- /dev/null +++ b/aio/tools/transforms/angular-base-package/rendering/hasValues.spec.js @@ -0,0 +1,19 @@ +const factory = require('./hasValues'); + +describe('hasValues filter', () => { + let filter; + + beforeEach(function() { filter = factory(); }); + + it('should be called "hasValues"', function() { expect(filter.name).toEqual('hasValues'); }); + + it('should return true if the specified property is truthy on any item in the list', function() { + expect(filter.process([], 'a')).toEqual(false); + expect(filter.process(0), 'a').toEqual(false); + expect(filter.process({}, 'a')).toEqual(false); + expect(filter.process([{a: 1}], 'a')).toEqual(true); + expect(filter.process([{b: 2}], 'a')).toEqual(false); + expect(filter.process([{a: 1, b: 2}], 'a')).toEqual(true); + expect(filter.process([{b: 2}, {a: 1}], 'a')).toEqual(true); + }); +}); \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/enum.template.html b/aio/tools/transforms/templates/api/enum.template.html index 9c59159b29..0f07f88cbc 100644 --- a/aio/tools/transforms/templates/api/enum.template.html +++ b/aio/tools/transforms/templates/api/enum.template.html @@ -1 +1,14 @@ -{% extends 'class.template.html' -%} \ No newline at end of file +{% import "lib/memberHelpers.html" as memberHelpers -%} +{% extends 'export-base.template.html' -%} + +{% block details %} +

+ +enum {$ doc.name $} {{$ memberHelpers.renderMembers(doc) $} +} + +
+ +{% include "includes/description.html" %} +{$ memberHelpers.renderProperties(doc.properties, 'members', 'member', 'Members', ['Member', 'Value']) $} +{% endblock %} \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/lib/memberHelpers.html b/aio/tools/transforms/templates/api/lib/memberHelpers.html index c278c3c3fc..2fdfb3b6e4 100644 --- a/aio/tools/transforms/templates/api/lib/memberHelpers.html +++ b/aio/tools/transforms/templates/api/lib/memberHelpers.html @@ -135,20 +135,25 @@ {%- endmacro -%} -{%- macro renderProperties(properties, containerClass, propertyClass, headingText) -%} +{%- macro renderProperties(properties, containerClass, propertyClass, headingText, headings) -%} {% set nonInternalProperties = properties | filterByPropertyValue('internal', undefined) %} +{% set hasTypes = properties | hasValues('type') %} {% if nonInternalProperties.length -%}

{$ headingText $}

- + + + {% if hasTypes %}{% endif %} + + {% for property in nonInternalProperties %} - + {% if hasTypes %}{% endif %} + + + + + + From 43597279d691ae784d6f42d85d97d8a39cd64d56 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 16 May 2018 10:10:57 -0700 Subject: [PATCH 065/582] test: don't run unit tests on Firefox (#23942) PR Close #23942 --- tools/defaults.bzl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 8a956ca64f..9954668f3d 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -79,12 +79,16 @@ def ts_web_test_suite(bootstrap = [], deps = [], **kwargs): _ts_web_test_suite( bootstrap = bootstrap, deps = local_deps, - # Run unit tests on Chromium and Firefox by default. + # Run unit tests on local Chromium by default. # You can exclude tests based on tags, e.g. to skip Firefox testing, # `bazel test --test_tag_filters=-browser:firefox-local [targets]` browsers = [ "@io_bazel_rules_webtesting//browsers:chromium-local", - "@io_bazel_rules_webtesting//browsers:firefox-local", + # Don't test on local Firefox by default, for faster builds. + # We think that bugs in Angular tend to be caught the same in any + # evergreen browser. + # "@io_bazel_rules_webtesting//browsers:firefox-local", + # TODO(alexeagle): add remote browsers on SauceLabs ], **kwargs) From 8ee25e6b581219f56c7cd8dbd76d0fe6eafdd563 Mon Sep 17 00:00:00 2001 From: Fabian Wiles Date: Sat, 12 May 2018 07:50:28 +1200 Subject: [PATCH 066/582] ci: ensure github-robot listens on circleci builds (#23863) PR Close #23863 --- .github/angular-robot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/angular-robot.yml b/.github/angular-robot.yml index 0f7e06119d..fcc1653164 100644 --- a/.github/angular-robot.yml +++ b/.github/angular-robot.yml @@ -4,6 +4,7 @@ size: disabled: false maxSizeIncrease: 1000 + circleCiStatusName: "ci/circleci: build-packages-dist" status: disabled: false context: "ci/angular: size" From 20d76374edd6ff2610043a80f341f8c0712c0f1a Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Thu, 10 May 2018 10:30:55 -0500 Subject: [PATCH 067/582] docs(aio): Expose server and CLI configuration for universal in guide (#23842) Closes #23795 PR Close #23842 --- aio/content/guide/universal.md | 74 +++++++++++++++++-- .../shared/boilerplate/cli/angular.json | 2 +- .../shared/boilerplate/i18n/angular.json | 2 +- .../shared/boilerplate/testing/angular.json | 2 +- 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/aio/content/guide/universal.md b/aio/content/guide/universal.md index eb85fa2861..f4653ba73d 100644 --- a/aio/content/guide/universal.md +++ b/aio/content/guide/universal.md @@ -180,7 +180,7 @@ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-lo {@a transition} -### Modify the client app +## Modify the client app A Universal app can act as a dynamic, content-rich "splash screen" that engages the user. It gives the appearance of a near-instant application. @@ -190,7 +190,9 @@ Once loaded, Angular transitions from the static server-rendered page to the dyn You must make a few changes to your application code to support both server-side rendering and the transition to the client app. -#### The root `AppModule` +{@a root-app-module} + +### The root `AppModule` Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata. Replace that import with this one: @@ -206,9 +208,29 @@ You can get runtime information about the current platform and the `appId` by in +{@a cli-output} + +### Build Destination + +A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically. + +The Angular CLI outputs the client-side code in the `dist` directory by default, so you modify the `outputPath` for the __build__ target in the `angular.json` to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server. + +``` +... +"build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/browser", + ... + } +} +... +``` + {@a http-urls} -#### Absolute HTTP URLs +### Absolute HTTP URLs The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data. These services send requests to _relative_ URLs such as `api/heroes`. @@ -262,6 +284,19 @@ The `ModuleMapLoaderModule` is a server-side module that allows lazy-loading of This is also the place to register providers that are specific to running your app under Universal. +{@a app-server-entry-point} + +### App server entry point + +The `Angular CLI` uses the `AppServerModule` to build the server-side bundle. + +Create a `main.server.ts` file in the `src/` directory that exports the `AppServerModule`: + + + + +The `main.server.ts` will be referenced later to add a `server` target to the `Angular CLI` configuration. + {@a web-server} ### Universal web server @@ -421,6 +456,8 @@ This config extends from the root's `tsconfig.json` file. Certain settings are n * The `angularCompilerOptions` section guides the AOT compiler: * `entryModule` - the root module of the server application, expressed as `path/to/file#ClassName`. +{@a universal-webpack-configuration} + ### Universal Webpack configuration Universal applications doesn't need any extra Webpack configuration, the CLI takes care of that for you, @@ -433,13 +470,38 @@ Create a `webpack.server.config.js` file in the project root directory with the **Webpack configuration** is a rich topic beyond the scope of this guide. +{@a universal-cli-configuration} + +### Angular CLI configuration + +The CLI provides builders for different types of __targets__. Commonly known targets such as `build` and `serve` already exist in the `angular.json` configuration. To target a server-side build, add a `server` target to the `architect` configuration object. + +* The `outputPath` tells where the resulting build will be created. +* The `main` provides the main entry point to the previously created `main.server.ts` +* The `tsConfig` uses the `tsconfig.server.json` as configuration for the TypeScript and AOT compilation. + +``` +"architect": { + ... + "server": { + "builder": "@angular-devkit/build-angular:server", + "options": { + "outputPath": "dist/server", + "main": "src/main.server.ts", + "tsConfig": "src/tsconfig.server.json" + } + } + ... +} +``` + ## Build and run with universal -Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application. +Now that you've created the TypeScript and Webpack config files and configured the Angular CLI, you can build and run the Universal application. First add the _build_ and _serve_ commands to the `scripts` section of the `package.json`: - +``` "scripts": { ... "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server", @@ -448,7 +510,7 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack "webpack:server": "webpack --config webpack.server.config.js --progress --colors" ... } - +``` {@a build} diff --git a/aio/tools/examples/shared/boilerplate/cli/angular.json b/aio/tools/examples/shared/boilerplate/cli/angular.json index 6d46cf39da..5d17ddcf30 100644 --- a/aio/tools/examples/shared/boilerplate/cli/angular.json +++ b/aio/tools/examples/shared/boilerplate/cli/angular.json @@ -11,7 +11,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/angular.io-example", + "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", diff --git a/aio/tools/examples/shared/boilerplate/i18n/angular.json b/aio/tools/examples/shared/boilerplate/i18n/angular.json index 00ec0bccd1..d8bc38f374 100644 --- a/aio/tools/examples/shared/boilerplate/i18n/angular.json +++ b/aio/tools/examples/shared/boilerplate/i18n/angular.json @@ -11,7 +11,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/angular.io-example", + "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", diff --git a/aio/tools/examples/shared/boilerplate/testing/angular.json b/aio/tools/examples/shared/boilerplate/testing/angular.json index 9aaab56b56..11ed94218d 100644 --- a/aio/tools/examples/shared/boilerplate/testing/angular.json +++ b/aio/tools/examples/shared/boilerplate/testing/angular.json @@ -11,7 +11,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/angular.io-example", + "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", From 88ab1d0e553238f45e16fabd208134f836564a63 Mon Sep 17 00:00:00 2001 From: Collin Klopfenstein Date: Thu, 10 May 2018 16:57:59 -0500 Subject: [PATCH 068/582] docs(aio): changed 'onVoted' output property to 'voted' to be in line with the styleguide (#23832) PR Close #23832 --- .../component-interaction/src/app/voter.component.ts | 12 ++++++------ .../src/app/votetaker.component.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aio/content/examples/component-interaction/src/app/voter.component.ts b/aio/content/examples/component-interaction/src/app/voter.component.ts index 9583e74466..845e6fba0e 100644 --- a/aio/content/examples/component-interaction/src/app/voter.component.ts +++ b/aio/content/examples/component-interaction/src/app/voter.component.ts @@ -5,18 +5,18 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; selector: 'app-voter', template: `

{{name}}

- - + + ` }) export class VoterComponent { @Input() name: string; - @Output() onVoted = new EventEmitter(); - voted = false; + @Output() voted = new EventEmitter(); + didVote = false; vote(agreed: boolean) { - this.onVoted.emit(agreed); - this.voted = true; + this.voted.emit(agreed); + this.didVote = true; } } // #enddocregion diff --git a/aio/content/examples/component-interaction/src/app/votetaker.component.ts b/aio/content/examples/component-interaction/src/app/votetaker.component.ts index 564cb93261..d553af0d7b 100644 --- a/aio/content/examples/component-interaction/src/app/votetaker.component.ts +++ b/aio/content/examples/component-interaction/src/app/votetaker.component.ts @@ -8,7 +8,7 @@ import { Component } from '@angular/core';

Agree: {{agreed}}, Disagree: {{disagreed}}

+ (voted)="onVoted($event)"> ` }) From ea4321d9122eefaaccfefa5f36f9e1b68365a7bc Mon Sep 17 00:00:00 2001 From: Akihito Tamagawa Date: Fri, 18 May 2018 17:36:13 +0900 Subject: [PATCH 069/582] docs(aio): fix typo (#23990) are are -> are PR Close #23990 --- aio/content/guide/releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/releases.md b/aio/content/guide/releases.md index cb25524111..0d4854388e 100644 --- a/aio/content/guide/releases.md +++ b/aio/content/guide/releases.md @@ -129,6 +129,6 @@ Any changes to the public API surface will be done using the versioning, support Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team. -Angular Labs projects are are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects. +Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects. Angular Labs projects typically are in separate branches in the Angular repo, clearly separated from the main Angular codebase. From 3cdf5afc6e5652151fa9c210cb83478d5f2e806d Mon Sep 17 00:00:00 2001 From: mhartington Date: Tue, 8 May 2018 15:07:32 -0400 Subject: [PATCH 070/582] docs(aio): add mhartington to gde (#23777) PR Close #23777 --- aio/content/images/bios/mhartington.png | Bin 0 -> 29217 bytes aio/content/marketing/contributors.json | 11 ++++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 aio/content/images/bios/mhartington.png diff --git a/aio/content/images/bios/mhartington.png b/aio/content/images/bios/mhartington.png new file mode 100644 index 0000000000000000000000000000000000000000..4127ca3534aecfecaca1ca32eda014a59b0a5c18 GIT binary patch literal 29217 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 P00000NkvXXu0mjfi>8W^ literal 0 HcmV?d00001 diff --git a/aio/content/marketing/contributors.json b/aio/content/marketing/contributors.json index 27a0fdda58..99c88b9251 100644 --- a/aio/content/marketing/contributors.json +++ b/aio/content/marketing/contributors.json @@ -592,7 +592,7 @@ "bio": "Nir is a Principal Frontend Consultant & Head of the Angular department at 500Tech, Google Developer Expert and community leader. He organizes the largest Angular meetup group in Israel (Angular-IL), talks and teaches about front-end technologies around the world. He is also the author of two books about Angular and the founder of the 'Frontend Band'.", "group": "GDE" }, - + "achautard": { "name": "Alain Chautard", "picture": "alainchautard.png", @@ -609,5 +609,14 @@ "website": "https://coryrylan.com", "bio": "Cory is a full time front end web developer. He works full time building responsive web applications and progressive web apps. When not building web apps he is busy teaching Angular and other web technologies in workshops and conferences. He loves the web and is optimistic of the places it can take us.", "group": "GDE" + }, + + "mhartington": { + "name": "Mike Hartington", + "picture": "mhartington.png", + "twitter": "mhartington", + "website": "https://mhartington.io", + "bio": "Mike is a Developer Advocate for the Ionic Framework and a GDE in Angular. He spends most of his time making fast PWAs and exploring emerging web standards. When not behind a keyboard, you'll probably find him with a guitar and beer.", + "group": "GDE" } } From 99d330a1b70c5adcee2e4d6c9eb3aa8f1f0553d9 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 17 May 2018 00:10:30 +0300 Subject: [PATCH 071/582] style(aio): remove background from `lazy-loading` concept icon (#23950) Fixes #23938 PR Close #23950 --- .../logos/concept-icons/lazy-loading.png | Bin 5839 -> 3820 bytes .../logos/concept-icons/lazy-loading.svg | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/src/assets/images/logos/concept-icons/lazy-loading.png b/aio/src/assets/images/logos/concept-icons/lazy-loading.png index b232549430ac9ffd9f14ddd3bf8a80be8fe4f19b..bbb68345572f44015621aebbec6a6861ba74c3a7 100755 GIT binary patch literal 3820 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 V#-~{x7atNbh=#s8QPn#9{{Y|7KYsuK literal 5839 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 ZOSuVl5C8q^3pmsSDhf|fm2wuC{{kiX>G=Qv diff --git a/aio/src/assets/images/logos/concept-icons/lazy-loading.svg b/aio/src/assets/images/logos/concept-icons/lazy-loading.svg index f71a6484ff..91f21c7df5 100644 --- a/aio/src/assets/images/logos/concept-icons/lazy-loading.svg +++ b/aio/src/assets/images/logos/concept-icons/lazy-loading.svg @@ -1 +1 @@ -lazy-loading \ No newline at end of file +lazy-loading \ No newline at end of file From 6e7d071c6b572e0e7d579aaf6ceb8b0a5e62becb Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 11 May 2018 20:57:37 -0700 Subject: [PATCH 072/582] fix(ivy): move next property to TNode (#23869) PR Close #23869 --- packages/core/src/render3/di.ts | 3 +- packages/core/src/render3/instructions.ts | 97 ++++++++++--------- packages/core/src/render3/interfaces/node.ts | 25 ++--- .../core/src/render3/node_manipulation.ts | 54 +++++------ .../bundling/todo/bundle.golden_symbols.json | 6 +- packages/core/test/render3/di_spec.ts | 2 +- .../core/test/render3/integration_spec.ts | 2 +- .../render3/node_selector_matcher_spec.ts | 6 +- .../test/render3/view_container_ref_spec.ts | 2 +- 9 files changed, 102 insertions(+), 95 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index a5b98c2c7c..030afd5ac6 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,7 +20,6 @@ import {Type} from '../type'; import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; -import {LContainer} from './interfaces/container'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node'; @@ -573,6 +572,8 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer const lContainerNode: LContainerNode = createLNodeObject( LNodeType.Container, vcRefHost.view, vcRefHost.parent !, undefined, lContainer, null); + // TODO(kara): Separate into own TNode when moving parent/child properties + lContainerNode.tNode = vcRefHost.tNode; vcRefHost.dynamicLContainerNode = lContainerNode; addToViewTree(vcRefHost.view, lContainer); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 8f2162cca5..9768799c7c 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -17,7 +17,7 @@ import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TDat import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; -import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode} from './node_manipulation'; +import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; @@ -346,7 +346,6 @@ export function createLNodeObject( view: currentView, parent: parent as any, child: null, - next: null, nodeInjector: parent ? parent.nodeInjector : null, data: state, queries: queries, @@ -359,22 +358,31 @@ export function createLNodeObject( /** * A common way of creating the LNode to make sure that all of them have same shape to * keep the execution code monomorphic and fast. + * + * @param index The index at which the LNode should be saved (null if view, since they are not + * saved) + * @param type The type of LNode to create + * @param native The native element for this LNode, if applicable + * @param name The tag name of the associated native element, if applicable + * @param attrs Any attrs for the native element, if applicable + * @param data Any data that should be saved on the LNode */ export function createLNode( index: number | null, type: LNodeType.Element, native: RElement | RText | null, - lView?: LView | null): LElementNode; + name: string | null, attrs: string[] | null, lView?: LView | null): LElementNode; export function createLNode( - index: null, type: LNodeType.View, native: null, lView: LView): LViewNode; + index: null, type: LNodeType.View, native: null, name: null, attrs: null, + lView: LView): LViewNode; export function createLNode( - index: number, type: LNodeType.Container, native: undefined, - lContainer: LContainer): LContainerNode; + index: number, type: LNodeType.Container, native: undefined, name: string | null, + attrs: string[] | null, lContainer: LContainer): LContainerNode; export function createLNode( - index: number, type: LNodeType.Projection, native: null, + index: number, type: LNodeType.Projection, native: null, name: null, attrs: string[] | null, lProjection: LProjection): LProjectionNode; export function createLNode( index: number | null, type: LNodeType, native: RText | RElement | null | undefined, - state?: null | LView | LContainer | LProjection): LElementNode<extNode&LViewNode& - LContainerNode&LProjectionNode { + name: string | null, attrs: string[] | null, state?: null | LView | LContainer | + LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : previousOrParentNode && previousOrParentNode.parent as LNode; let queries = @@ -397,10 +405,12 @@ export function createLNode( // Every node adds a value to the static data array to avoid a sparse array if (index >= tData.length) { - tData[index] = null; - } else { - node.tNode = tData[index] as TNode; + const tNode = tData[index] = createTNode(index, name, attrs, null); + if (!isParent && previousOrParentNode) { + previousOrParentNode.tNode !.next = tNode; + } } + node.tNode = tData[index] as TNode; // Now link ourselves into the tree. if (isParent) { @@ -415,14 +425,6 @@ export function createLNode( } else { // We are adding component view, so we don't link parent node child to this node. } - } else if (previousOrParentNode) { - ngDevMode && assertNull( - previousOrParentNode.next, - `previousOrParentNode's next property should not have been set ${index}.`); - previousOrParentNode.next = node; - if (previousOrParentNode.dynamicLContainerNode) { - previousOrParentNode.dynamicLContainerNode.next = node; - } } } previousOrParentNode = node; @@ -463,7 +465,7 @@ export function renderTemplate( rendererFactory = providedRendererFactory; const tView = getOrCreateTView(template, directives || null, pipes || null); host = createLNode( - null, LNodeType.Element, hostNode, + null, LNodeType.Element, hostNode, null, null, createLView( -1, providedRendererFactory.createRenderer(null, null), tView, null, {}, LViewFlags.CheckAlways, sanitizer)); @@ -500,7 +502,7 @@ export function renderEmbeddedTemplate( const lView = createLView( -1, renderer, tView, template, context, LViewFlags.CheckAlways, getCurrentSanitizer()); - viewNode = createLNode(null, LNodeType.View, null, lView); + viewNode = createLNode(null, LNodeType.View, null, null, null, lView); rf = RenderFlags.Create; } oldView = enterView(viewNode.data, viewNode); @@ -585,7 +587,10 @@ export function elementStart( ngDevMode && ngDevMode.rendererCreateElement++; const native: RElement = renderer.createElement(name); - const node: LElementNode = createLNode(index, LNodeType.Element, native !, null); + ngDevMode && assertDataInRange(index - 1); + + const node: LElementNode = + createLNode(index, LNodeType.Element, native !, name, attrs || null, null); if (attrs) setUpAttributes(native, attrs); appendChild(node.parent !, native, currentView); @@ -608,9 +613,7 @@ function createDirectivesAndLocals( const node = previousOrParentNode; if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; - ngDevMode && assertDataInRange(index - 1); - node.tNode = tData[index] = createTNode(name, attrs || null, inlineViews ? [] : null); - cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null); + cacheMatchingDirectivesForNode(node.tNode !, currentView.tView, localRefs || null); } else { instantiateDirectivesDirectly(); } @@ -870,14 +873,13 @@ export function hostElement( sanitizer?: Sanitizer | null): LElementNode { resetApplicationState(); const node = createLNode( - 0, LNodeType.Element, rNode, + 0, LNodeType.Element, rNode, null, null, createLView( -1, renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); if (firstTemplatePass) { - node.tNode = createTNode(tag as string, null, null); - node.tNode.flags = TNodeFlags.isComponent; + node.tNode !.flags = TNodeFlags.isComponent; if (def.diPublic) def.diPublic(def); currentView.tView.directives = [def]; } @@ -1028,16 +1030,17 @@ export function elementProperty( /** * Constructs a TNode object from the arguments. * + * @param index The index of the TNode in TView.data * @param tagName The tag name of the node - * @param attrs The attributes defined on this ndoe + * @param attrs The attributes defined on this node * @param tViews Any TViews attached to this node - * @param localNames A list of local names and their matching indices * @returns the TNode object */ function createTNode( - tagName: string | null, attrs: string[] | null, tViews: TView[] | null): TNode { + index: number, tagName: string | null, attrs: string[] | null, tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { + index: index, flags: 0, tagName: tagName, attrs: attrs, @@ -1045,7 +1048,8 @@ function createTNode( initialInputs: undefined, inputs: undefined, outputs: undefined, - tViews: tViews + tViews: tViews, + next: null }; } @@ -1240,7 +1244,8 @@ export function text(index: number, value?: any): void { currentView.bindingStartIndex, -1, 'text nodes should be created before bindings'); ngDevMode && ngDevMode.rendererCreateTextNode++; const textNode = createTextNode(value, renderer); - const node = createLNode(index, LNodeType.Element, textNode); + const node = createLNode(index, LNodeType.Element, textNode, null, null); + // Text nodes are self closing. isParent = false; appendChild(node.parent !, textNode, currentView); @@ -1473,7 +1478,10 @@ export function container( const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent !; const lContainer = createLContainer(currentParent, currentView, template); - const node = createLNode(index, LNodeType.Container, undefined, lContainer); + const node = createLNode( + index, LNodeType.Container, undefined, tagName || null, attrs || null, lContainer); + + if (firstTemplatePass && template == null) node.tNode !.tViews = []; // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. @@ -1610,7 +1618,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { newView.queries = lContainer.queries.enterView(lContainer.nextIndex); } - enterView(newView, viewNode = createLNode(null, LNodeType.View, null, newView)); + enterView(newView, viewNode = createLNode(null, LNodeType.View, null, null, null, newView)); } return getRenderFlags(viewNode.data); } @@ -1673,7 +1681,7 @@ export function embeddedViewEnd(): void { function setRenderParentInProjectedNodes( renderParent: LElementNode | null, viewNode: LViewNode): void { if (renderParent != null) { - let node = viewNode.child; + let node: LNode|null = viewNode.child; while (node) { if (node.type === LNodeType.Projection) { let nodeToProject: LNode|null = (node as LProjectionNode).data.head; @@ -1685,7 +1693,7 @@ function setRenderParentInProjectedNodes( nodeToProject = nodeToProject === lastNodeToProject ? null : nodeToProject.pNextOrParent; } } - node = node.next; + node = getNextLNode(node); } } } @@ -1749,8 +1757,8 @@ export function projectionDef( distributedNodes[i] = []; } - const componentNode = findComponentHost(currentView); - let componentChild = componentNode.child; + const componentNode: LElementNode = findComponentHost(currentView); + let componentChild: LNode|null = componentNode.child; while (componentChild !== null) { // execute selector matching logic if and only if: @@ -1763,7 +1771,7 @@ export function projectionDef( distributedNodes[0].push(componentChild); } - componentChild = componentChild.next; + componentChild = getNextLNode(componentChild); } ngDevMode && assertDataNext(index); @@ -1810,11 +1818,8 @@ function appendToProjectionNode( */ export function projection( nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void { - const node = createLNode(nodeIndex, LNodeType.Projection, null, {head: null, tail: null}); - - if (node.tNode == null) { - node.tNode = createTNode(null, attrs || null, null); - } + const node = createLNode( + nodeIndex, LNodeType.Projection, null, null, attrs || null, {head: null, tail: null}); // `` has no content isParent = false; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 0ec9adeb2f..ad323d052f 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -79,12 +79,6 @@ export interface LNode { */ child: LNode|null; - /** - * The next sibling node. Necessary so we can propagate through the root nodes of a view - * to insert them or remove them from the DOM. - */ - next: LNode|null; - /** * If regular LElementNode, then `data` will be null. * If LElementNode with component, then `data` contains LView. @@ -139,7 +133,6 @@ export interface LElementNode extends LNode { readonly native: RElement; child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; /** If Component then data has LView (light DOM) */ readonly data: LView|null; @@ -153,7 +146,6 @@ export interface LTextNode extends LNode { /** The text node associated with this node. */ native: RText; child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ readonly parent: LElementNode|LViewNode; @@ -165,7 +157,6 @@ export interface LTextNode extends LNode { export interface LViewNode extends LNode { readonly native: null; child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - next: LViewNode|null; /** LViewNodes can only be added to LContainerNodes. */ readonly parent: LContainerNode|null; @@ -185,7 +176,6 @@ export interface LContainerNode extends LNode { native: RElement|RText|null|undefined; readonly data: LContainer; child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; /** Containers can be added to elements or views. */ readonly parent: LElementNode|LViewNode|null; @@ -195,7 +185,6 @@ export interface LContainerNode extends LNode { export interface LProjectionNode extends LNode { readonly native: null; child: null; - next: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; readonly data: LProjection; @@ -216,6 +205,14 @@ export interface LProjectionNode extends LNode { * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern */ export interface TNode { + /** + * Index of the TNode in TView.data and corresponding LNode in LView.data. + * + * This is necessary to get from any TNode to its corresponding LNode when + * traversing the node tree. + */ + index: number; + /** * This number stores two values using its bits: * @@ -303,6 +300,12 @@ export interface TNode { * If this TNode corresponds to an LElementNode, tViews will be null . */ tViews: TView|TView[]|null; + + /** + * The next sibling node. Necessary so we can propagate through the root nodes of a view + * to insert them or remove them from the DOM. + */ + next: TNode|null; } /** Static data for an LElementNode */ diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index d6ee59f71a..c7c750d364 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -44,13 +44,13 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem } currentNode = pNextOrParent; } else { - let currentSibling = currentNode.next; + let currentSibling = getNextLNode(currentNode); while (currentSibling) { const nativeNode = findFirstRNode(currentSibling); if (nativeNode) { return nativeNode; } - currentSibling = currentSibling.next; + currentSibling = getNextLNode(currentSibling); } const parentNode = currentNode.parent; currentNode = null; @@ -65,6 +65,16 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem return null; } +/** Retrieves the sibling node for the given node. */ +export function getNextLNode(node: LNode): LNode|null { + // View nodes don't have TNodes, so their next must be retrieved through their LView. + if (node.type === LNodeType.View) { + const lView = node.data as LView; + return lView.next ? (lView.next as LView).node : null; + } + return node.tNode !.next ? node.view.data[node.tNode !.next !.index] : null; +} + /** * Get the next node in the LNode tree, taking into account the place where a node is * projected (in the shadow DOM) rather than where it comes from (in the light DOM). @@ -83,7 +93,7 @@ function getNextLNodeWithProjection(node: LNode): LNode|null { } // returns node.next because the the node is not projected - return node.next; + return getNextLNode(node); } /** @@ -187,7 +197,7 @@ export function addRemoveViewFromContainer( isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, node.native !) : parent.removeChild(node.native !); } - nextNode = node.next; + nextNode = getNextLNode(node); } else if (node.type === LNodeType.Container) { // if we get to a container, it must be a root node of a view because we are only // propagating down into child views / containers and not child elements @@ -267,32 +277,34 @@ export function destroyViewTree(rootView: LView): void { * the container's parent view is added later). * * @param container The container into which the view should be inserted - * @param newView The view to insert + * @param viewNode The view to insert * @param index The index at which to insert the view * @returns The inserted view */ export function insertView( - container: LContainerNode, newView: LViewNode, index: number): LViewNode { + container: LContainerNode, viewNode: LViewNode, index: number): LViewNode { const state = container.data; const views = state.views; if (index > 0) { // This is a new view, we need to add it to the children. - setViewNext(views[index - 1], newView); + views[index - 1].data.next = viewNode.data as LView; } if (index < views.length) { - setViewNext(newView, views[index]); - views.splice(index, 0, newView); + viewNode.data.next = views[index].data; + views.splice(index, 0, viewNode); } else { - views.push(newView); + views.push(viewNode); + viewNode.data.next = null; } // If the container's renderParent is null, we know that it is a root node of its own parent view // and we should wait until that parent processes its nodes (otherwise, we will insert this view's // nodes twice - once now and once when its parent inserts its views). if (container.data.renderParent !== null) { - let beforeNode = findNextRNodeSibling(newView, container); + let beforeNode = findNextRNodeSibling(viewNode, container); + if (!beforeNode) { let containerNextNativeNode = container.native; if (containerNextNativeNode === undefined) { @@ -300,10 +312,10 @@ export function insertView( } beforeNode = containerNextNativeNode; } - addRemoveViewFromContainer(container, newView, true, beforeNode); + addRemoveViewFromContainer(container, viewNode, true, beforeNode); } - return newView; + return viewNode; } /** @@ -321,10 +333,9 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie const views = container.data.views; const viewNode = views[removeIndex]; if (removeIndex > 0) { - setViewNext(views[removeIndex - 1], viewNode.next); + views[removeIndex - 1].data.next = viewNode.data.next as LView; } views.splice(removeIndex, 1); - viewNode.next = null; destroyViewTree(viewNode.data); addRemoveViewFromContainer(container, viewNode, false); // Notify query that view has been removed @@ -332,19 +343,6 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie return viewNode; } -/** - * Sets a next on the view node, so views in for loops can easily jump from - * one view to the next to add/remove elements. Also adds the LView (view.data) - * to the view tree for easy traversal when cleaning up the view. - * - * @param view The view to set up - * @param next The view's new next - */ -export function setViewNext(view: LViewNode, next: LViewNode | null): void { - view.next = next; - view.data.next = next ? next.data : null; -} - /** * Determines which LViewOrLContainer to jump to when traversing back up the * tree in destroyViewTree. diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index d9f7140cfe..2a9c789e3d 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -389,6 +389,9 @@ { "name": "getDirectiveInstance" }, + { + "name": "getNextLNode" + }, { "name": "getNextLNodeWithProjection" }, @@ -617,9 +620,6 @@ { "name": "setUpAttributes" }, - { - "name": "setViewNext" - }, { "name": "stringify" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index ff472f96c3..9ce14e241e 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1367,7 +1367,7 @@ describe('di', () => { createLView(-1, null !, createTView(null, null), null, null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { - const parent = createLNode(0, LNodeType.Element, null, null); + const parent = createLNode(0, LNodeType.Element, null, null, null, null); // Simulate the situation where the previous parent is not initialized. // This happens on first bootstrap because we don't init existing values diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 50d3a0d0f2..e0325ab458 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -32,7 +32,7 @@ describe('render3 integration test', () => { } expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, - tNode: 1, + tNode: 2, tView: 1, rendererCreateElement: 1, }); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index e47cc4aab8..08a68d8cf3 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -12,14 +12,14 @@ import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelecto function testLStaticData(tagName: string, attrs: string[] | null): TNode { return { - flags: 0, - tagName, - attrs, + index: 0, + flags: 0, tagName, attrs, localNames: null, initialInputs: undefined, inputs: undefined, outputs: undefined, tViews: null, + next: null }; } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 91563976de..3bb2328b21 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -271,7 +271,7 @@ describe('ViewContainerRef', () => { * A * % if (condition) { * B - * } + * % } * |after */ class TestComponent { From a7b07defe1ee2ea035e59c7bb14110a2095af62e Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 17 May 2018 13:03:01 +0300 Subject: [PATCH 073/582] refactor(aio): clean up `attribute-utils` (#23960) PR Close #23960 --- aio/src/app/shared/attribute-utils.spec.ts | 18 +++++------ aio/src/app/shared/attribute-utils.ts | 36 ++++++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/aio/src/app/shared/attribute-utils.spec.ts b/aio/src/app/shared/attribute-utils.spec.ts index e40ff28703..dd0ee2c265 100644 --- a/aio/src/app/shared/attribute-utils.spec.ts +++ b/aio/src/app/shared/attribute-utils.spec.ts @@ -1,6 +1,6 @@ import { ElementRef } from '@angular/core'; -import { getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils'; +import { AttrMap, getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils'; describe('Attribute Utilities', () => { let testEl: HTMLElement; @@ -32,17 +32,17 @@ describe('Attribute Utilities', () => { }); describe('getAttrValue', () => { - let attrMap: { [index: string]: string }; + let attrMap: AttrMap; beforeEach(() => { attrMap = getAttrs(testEl); }); - it('should return empty string value for attribute "a"', () => { + it('should return empty string for attribute "a"', () => { expect(getAttrValue(attrMap, 'a')).toBe(''); }); - it('should return empty string value for attribute "A"', () => { + it('should return empty string for attribute "A"', () => { expect(getAttrValue(attrMap, 'A')).toBe(''); }); @@ -50,7 +50,7 @@ describe('Attribute Utilities', () => { expect(getAttrValue(attrMap, 'b')).toBe('true'); }); - it('should return empty string value for attribute "d-E"', () => { + it('should return empty string for attribute "d-E"', () => { expect(getAttrValue(attrMap, 'd-E')).toBe(''); }); @@ -68,12 +68,10 @@ describe('Attribute Utilities', () => { expect(getAttrValue(attrMap, ['d-e', 'd'])).toBe(''); }); - it('should return undefined value for non-existent attribute "x"', () => { + it('should return undefined for non-existent attributes', () => { expect(getAttrValue(attrMap, 'x')).toBeUndefined(); - }); - - it('should return undefined if no argument', () => { - expect(getAttrValue(attrMap)).toBeUndefined(); + expect(getAttrValue(attrMap, '')).toBeUndefined(); + expect(getAttrValue(attrMap, ['', 'x'])).toBeUndefined(); }); }); diff --git a/aio/src/app/shared/attribute-utils.ts b/aio/src/app/shared/attribute-utils.ts index 4340c41b6e..758aaa422a 100644 --- a/aio/src/app/shared/attribute-utils.ts +++ b/aio/src/app/shared/attribute-utils.ts @@ -1,17 +1,19 @@ // Utilities for processing HTML element attributes import { ElementRef } from '@angular/core'; -interface StringMap { [index: string]: string; } +export interface AttrMap { + [key: string]: string; +} /** * Get attribute map from element or ElementRef `attributes`. * Attribute map keys are forced lowercase for case-insensitive lookup. - * @param el The source of the attributes + * @param el The source of the attributes. */ -export function getAttrs(el: HTMLElement | ElementRef): StringMap { +export function getAttrs(el: HTMLElement | ElementRef): AttrMap { const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes; - const attrMap: StringMap = {}; - for (const attr of attrs as any /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) { + const attrMap: AttrMap = {}; + for (const attr of attrs as any as Attr[] /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) { attrMap[attr.name.toLowerCase()] = attr.value; } return attrMap; @@ -19,29 +21,29 @@ export function getAttrs(el: HTMLElement | ElementRef): StringMap { /** * Return the attribute that matches `attr`. - * @param attr Name of the attribute or a string of candidate attribute names + * @param attr Name of the attribute or a string of candidate attribute names. */ -export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string { - return attrs[typeof attr === 'string' ? - attr.toLowerCase() : - attr.find(a => attrs[a.toLowerCase()] !== undefined) || '' - ]; +export function getAttrValue(attrs: AttrMap, attr: string | string[]): string | undefined { + const key = (typeof attr === 'string') + ? attr + : attr.find(a => attrs.hasOwnProperty(a.toLowerCase())); + + return (key === undefined) ? undefined : attrs[key.toLowerCase()]; } /** * Return the boolean state of an attribute value (if supplied) - * @param attrValue The string value of some attribute (or undefined if attribute not present) + * @param attrValue The string value of some attribute (or undefined if attribute not present). * @param def Default boolean value when attribute is undefined. */ -export function boolFromValue(attrValue: string|undefined, def: boolean = false) { - // tslint:disable-next-line:triple-equals - return attrValue == undefined ? def : attrValue.trim() !== 'false'; +export function boolFromValue(attrValue: string | undefined, def: boolean = false) { + return attrValue === undefined ? def : attrValue.trim() !== 'false'; } /** * Return the boolean state of attribute from an element - * @param el The source of the attributes - * @param atty Name of the attribute or a string of candidate attribute names + * @param el The source of the attributes. + * @param atty Name of the attribute or a string of candidate attribute names. * @param def Default boolean value when attribute is undefined. */ export function getBoolFromAttribute( From 41fea84957d4ce12df9da13a9d04f42ccc3bc80e Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 17 May 2018 13:13:47 +0300 Subject: [PATCH 074/582] fix(aio): allow setting live-example title from content (#23960) Previously, it was possible to set the live-example title as content in the `` element. This relied on our custom loader functionality that extracted the content from the DOM element before passing it to the Angular compiler and stored it on a property for later retrieval. Since we switched to custom elements (and got rid of the custom loader), the property is no longer populated with the contents. As a result, many live examples show the default title ("live example") instead of the one specified as content. This commit fixes it by projecting the content into an invisible node for later retrieval (similar to what we do in other components, such as the `CodeExampleComponent`). PR Close #23960 --- .../live-example/live-example.component.html | 3 +++ .../live-example.component.spec.ts | 19 ++++++++----------- .../live-example/live-example.component.ts | 13 +++++++------ 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/aio/src/app/custom-elements/live-example/live-example.component.html b/aio/src/app/custom-elements/live-example/live-example.component.html index b5da2b8c90..a1537bfdc4 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.html +++ b/aio/src/app/custom-elements/live-example/live-example.component.html @@ -1,3 +1,6 @@ + + + {{title}} (not available on this device) diff --git a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts index 9879f0e69e..ea6209ebba 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts @@ -12,7 +12,6 @@ describe('LiveExampleComponent', () => { let liveExampleComponent: LiveExampleComponent; let fixture: ComponentFixture; let testPath: string; - let liveExampleContent: string|null; //////// test helpers //////// @@ -41,12 +40,11 @@ describe('LiveExampleComponent', () => { liveExampleDe = fixture.debugElement.children[0]; liveExampleComponent = liveExampleDe.componentInstance; - // Copy the LiveExample's innerHTML (content) - // into the `liveExampleContent` property as the DocViewer does - liveExampleDe.nativeElement.liveExampleContent = liveExampleContent; - + // Trigger `ngAfterContentInit()`. fixture.detectChanges(); - liveExampleComponent.onResize(1033); // wide by default + + // Ensure wide-screen by default. + liveExampleComponent.onResize(1033); fixture.detectChanges(); testFn(); @@ -64,7 +62,6 @@ describe('LiveExampleComponent', () => { .overrideComponent(EmbeddedStackblitzComponent, {set: {template: 'NO IFRAME'}}); testPath = defaultTestPath; - liveExampleContent = null; }); describe('when not embedded', () => { @@ -196,12 +193,12 @@ describe('LiveExampleComponent', () => { }); it('should add title from body', () => { - liveExampleContent = 'The Greatest Example'; - setHostTemplate(''); + const expectedTitle = 'The Greatest Example'; + setHostTemplate(`${expectedTitle}`); testComponent(() => { const anchor = getLiveExampleAnchor(); - expect(anchor.textContent).toBe(liveExampleContent, 'anchor content'); - expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title'); + expect(anchor.textContent).toBe(expectedTitle, 'anchor content'); + expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title'); }); }); diff --git a/aio/src/app/custom-elements/live-example/live-example.component.ts b/aio/src/app/custom-elements/live-example/live-example.component.ts index 9a19663224..cd4efedf58 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.ts @@ -1,5 +1,5 @@ /* tslint:disable component-selector */ -import { Component, ElementRef, HostListener, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core'; +import { AfterContentInit, AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core'; import { Location } from '@angular/common'; import { CONTENT_URL_PREFIX } from 'app/documents/document.service'; @@ -54,7 +54,7 @@ const zipBase = CONTENT_URL_PREFIX + 'zips/'; selector: 'live-example', templateUrl: 'live-example.component.html' }) -export class LiveExampleComponent implements OnInit { +export class LiveExampleComponent implements AfterContentInit { // Will force to embedded-style when viewport width is narrow // "narrow" value was picked based on phone dimensions from http://screensiz.es/phone @@ -71,6 +71,9 @@ export class LiveExampleComponent implements OnInit { zip: string; zipName: string; + @ViewChild('content') + private content: ElementRef; + constructor( private elementRef: ElementRef, location: Location ) { @@ -111,11 +114,9 @@ export class LiveExampleComponent implements OnInit { this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`; } - ngOnInit() { - // The `liveExampleContent` property is set by the DocViewer when it builds this component. - // It is the original innerHTML of the host element. + ngAfterContentInit() { // Angular will sanitize this title when displayed so should be plain text. - const title = this.elementRef.nativeElement.liveExampleContent; + const title = this.content.nativeElement.textContent; this.title = (title || this.attrs.title || 'live example').trim(); this.onResize(window.innerWidth); } From f8c694720538f63b3557104f069fbe7e082be030 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 17 May 2018 13:15:03 +0300 Subject: [PATCH 075/582] fix(aio): avoid unnecessary re-calculations in live-examples (#23960) With `plnkrs`, we used to choose a different plnkr mode (normal vs embedded) based on the size of the screen. This affected the layout of the plnkr page ("embedded" plnkr mode was usable on small screens, while "normal" mode wasn't). This is not to be confused with the live-example mode we use today to determine whether the live-example should be a link (that open StackBlitz on a new page) or embedded into the document (using an iframe). Since we no longer need to change the live-example URL based on the screen size, there is no need to listen for rezise events on Window. The necessary properties can be computed once and certain variables are obsolete. PR Close #23960 --- .../live-example/live-example.component.html | 3 +- .../live-example.component.spec.ts | 48 +--- .../live-example/live-example.component.ts | 208 +++++++++--------- 3 files changed, 107 insertions(+), 152 deletions(-) diff --git a/aio/src/app/custom-elements/live-example/live-example.component.html b/aio/src/app/custom-elements/live-example/live-example.component.html index a1537bfdc4..6c21e3b6fd 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.html +++ b/aio/src/app/custom-elements/live-example/live-example.component.html @@ -2,7 +2,6 @@ - {{title}} (not available on this device)
@@ -17,7 +16,7 @@ {{title}} - / download example + / download example diff --git a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts index ea6209ebba..8974c4a5d3 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.spec.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.spec.ts @@ -43,10 +43,6 @@ describe('LiveExampleComponent', () => { // Trigger `ngAfterContentInit()`. fixture.detectChanges(); - // Ensure wide-screen by default. - liveExampleComponent.onResize(1033); - fixture.detectChanges(); - testFn(); } @@ -100,15 +96,6 @@ describe('LiveExampleComponent', () => { }); }); - it('should have expected flat-style stackblitz when has `flat-style`', () => { - testPath = '/tutorial/toh-pt1'; - setHostTemplate(''); - testComponent(() => { - // The file should be "stackblitz.html", not "stackblitz.html" - expect(getLiveExampleAnchor().href).toContain('/stackblitz.html'); - }); - }); - it('should have expected stackblitz & download hrefs when has example directory (name)', () => { testPath = '/guide/somewhere'; setHostTemplate(''); @@ -147,15 +134,7 @@ describe('LiveExampleComponent', () => { }); }); - it('should be flat style when flat-style requested', () => { - setHostTemplate(''); - testComponent(() => { - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - - it('should not have a download link when `noDownload` atty present', () => { + it('should not have a download link when `noDownload` attr present', () => { setHostTemplate(''); testComponent(() => { const hrefs = getHrefs(); @@ -164,7 +143,7 @@ describe('LiveExampleComponent', () => { }); }); - it('should only have a download link when `downloadOnly` atty present', () => { + it('should only have a download link when `downloadOnly` attr present', () => { setHostTemplate('download this'); testComponent(() => { const hrefs = getHrefs(); @@ -248,27 +227,4 @@ describe('LiveExampleComponent', () => { }); }); }); - - describe('when narrow display (mobile)', () => { - - it('should be embedded style when no style defined', () => { - setHostTemplate(''); - testComponent(() => { - liveExampleComponent.onResize(600); // narrow - fixture.detectChanges(); - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - - it('should be embedded style even when flat-style requested', () => { - setHostTemplate(''); - testComponent(() => { - liveExampleComponent.onResize(600); // narrow - fixture.detectChanges(); - const hrefs = getHrefs(); - expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html'); - }); - }); - }); }); diff --git a/aio/src/app/custom-elements/live-example/live-example.component.ts b/aio/src/app/custom-elements/live-example/live-example.component.ts index cd4efedf58..bc52ef32d3 100644 --- a/aio/src/app/custom-elements/live-example/live-example.component.ts +++ b/aio/src/app/custom-elements/live-example/live-example.component.ts @@ -1,131 +1,131 @@ /* tslint:disable component-selector */ -import { AfterContentInit, AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core'; +import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; import { Location } from '@angular/common'; import { CONTENT_URL_PREFIX } from 'app/documents/document.service'; +import { AttrMap, boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils'; -import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils'; -const liveExampleBase = CONTENT_URL_PREFIX + 'live-examples/'; -const zipBase = CONTENT_URL_PREFIX + 'zips/'; +const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/'; +const ZIP_BASE = CONTENT_URL_PREFIX + 'zips/'; /** -* Angular.io Live Example Embedded Component -* -* Renders a link to a live/host example of the doc page. -* -* All attributes and the text content are optional -* -* Usage: -* // text for live example link and tooltip -* text // higher precedence way to specify text for live example link and tooltip -* -* Example: -*

Run Try the live example

. -* // ~/resources/live-examples/{page}/stackblitz.json -* -*

Run this example

. -* // ~/resources/live-examples/toh-pt1/stackblitz.json -* -* // Link to the default stackblitz in the toh-pt1 sample -* // The title overrides default ("live example") with "Tour of Heroes - Part 1" -*

Run

. -* // ~/resources/live-examples/toh-pt1/stackblitz.json -* -*

Run

. -* // ~/resources/live-examples/{page}/minimal.stackblitz.json -* -* // Embed the current page's default stackblitz -* // Text within tag is "live example" -* // No title (no tooltip) -* -* // ~/resources/live-examples/{page}/stackblitz.json -* -* // Displays within the document page as an embedded style stackblitz editor -* Tour of Heroes - Part 1 -* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json -*/ + * Angular.io Live Example Embedded Component + * + * Renders a link to a live/host example of the doc page. + * + * All attributes and the text content are optional + * + * Usage: + * // text for live example link and tooltip + * text // higher precedence way to specify text for live example link and tooltip + * + * Example: + *

Run Try the live example

. + * // ~/resources/live-examples/{page}/stackblitz.json + * + *

Run this example

. + * // ~/resources/live-examples/toh-pt1/stackblitz.json + * + * // Link to the default stackblitz in the toh-pt1 sample + * // The title overrides default ("live example") with "Tour of Heroes - Part 1" + *

Run

. + * // ~/resources/live-examples/toh-pt1/stackblitz.json + * + *

Run

. + * // ~/resources/live-examples/{page}/minimal.stackblitz.json + * + * // Embed the current page's default stackblitz + * // Text within tag is "live example" + * // No title (no tooltip) + * + * // ~/resources/live-examples/{page}/stackblitz.json + * + * // Displays within the document page as an embedded style stackblitz editor + * Tour of Heroes - Part 1 + * // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json + */ @Component({ selector: 'live-example', templateUrl: 'live-example.component.html' }) export class LiveExampleComponent implements AfterContentInit { - // Will force to embedded-style when viewport width is narrow - // "narrow" value was picked based on phone dimensions from http://screensiz.es/phone - readonly narrowWidth = 1000; - - attrs: any; - enableDownload = true; - exampleDir: string; - isEmbedded = false; - mode = 'disabled'; - stackblitz: string; - stackblitzName: string; + readonly mode: 'default' | 'embedded' | 'downloadOnly'; + readonly enableDownload: boolean; + readonly stackblitz: string; + readonly zip: string; title: string; - zip: string; - zipName: string; @ViewChild('content') private content: ElementRef; - constructor( - private elementRef: ElementRef, - location: Location ) { + constructor(elementRef: ElementRef, location: Location) { + const attrs = getAttrs(elementRef); + const exampleDir = this.getExampleDir(attrs, location.path(false)); + const stackblitzName = this.getStackblitzName(attrs); - const attrs = this.attrs = getAttrs(this.elementRef); - let exampleDir = attrs.name; - if (!exampleDir) { - // take last segment, excluding hash fragment and query params - exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0]; - } - this.exampleDir = exampleDir.trim(); - this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0]; - this.stackblitzName = attrs.stackblitz ? attrs.stackblitz.trim() + '.' : ''; - this.zip = `${zipBase}${exampleDir}/${this.stackblitzName}${this.zipName}.zip`; - - this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload')); - - if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) { - this.mode = 'downloadOnly'; - } - } - - calcStackblitzLink(width: number) { - - const attrs = this.attrs; - const exampleDir = this.exampleDir; - let urlQuery = ''; - - this.mode = 'default'; // display in another browser tab by default - - this.isEmbedded = boolFromValue(attrs.embedded); - - if (this.isEmbedded) { - this.mode = 'embedded'; // display embedded in the doc - urlQuery = '?ctl=1'; - } - - this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`; + this.mode = this.getMode(attrs); + this.enableDownload = this.getEnableDownload(attrs); + this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded'); + this.zip = this.getZip(exampleDir, stackblitzName); + this.title = this.getTitle(attrs); } ngAfterContentInit() { - // Angular will sanitize this title when displayed so should be plain text. - const title = this.content.nativeElement.textContent; - this.title = (title || this.attrs.title || 'live example').trim(); - this.onResize(window.innerWidth); + // Angular will sanitize this title when displayed, so it should be plain text. + const textContent = this.content.nativeElement.textContent.trim(); + if (textContent) { + this.title = textContent; + } } - @HostListener('window:resize', ['$event.target.innerWidth']) - onResize(width: number) { - if (this.mode !== 'downloadOnly') { - this.calcStackblitzLink(width); + private getEnableDownload(attrs: AttrMap) { + const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload')); + return !downloadDisabled; + } + + private getExampleDir(attrs: AttrMap, path: string) { + let exampleDir = getAttrValue(attrs, 'name'); + if (!exampleDir) { + // Take the last path segment, excluding query params and hash fragment. + const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/); + exampleDir = match ? match[0] : 'index'; } + return exampleDir.trim(); + } + + private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode { + const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly')); + const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded')); + + return downloadOnly ? 'downloadOnly' + : isEmbedded ? 'embedded' : + 'default'; + } + + private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) { + const urlQuery = isEmbedded ? '?ctl=1' : ''; + return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`; + } + + private getStackblitzName(attrs: AttrMap) { + const attrValue = (getAttrValue(attrs, 'stackblitz') || '').trim(); + return attrValue && `${attrValue}.`; + } + + private getTitle(attrs: AttrMap) { + return (getAttrValue(attrs, 'title') || 'live example').trim(); + } + + private getZip(exampleDir: string, stackblitzName: string) { + const zipName = exampleDir.split('/')[0]; + return `${ZIP_BASE}${exampleDir}/${stackblitzName}${zipName}.zip`; } } @@ -137,7 +137,7 @@ export class LiveExampleComponent implements AfterContentInit { @Component({ selector: 'aio-embedded-stackblitz', template: ``, - styles: [ 'iframe { min-height: 400px; }'] + styles: [ 'iframe { min-height: 400px; }' ] }) export class EmbeddedStackblitzComponent implements AfterViewInit { @Input() src: string; From de267e97c99ffe62b1ad22a2ad23136d7e859174 Mon Sep 17 00:00:00 2001 From: VTHINKXIE Date: Thu, 17 May 2018 10:24:08 +0800 Subject: [PATCH 076/582] docs(aio): add ant design of angular in resources (#23953) PR Close #23953 --- aio/content/marketing/resources.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index aa8e079662..52d56d6f1f 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -345,6 +345,12 @@ "title": "Angular Material", "url": "https://github.com/angular/material2" }, + "ngzorro": { + "desc": "A set of enterprise-class UI components based on Ant Design and Angular", + "rev": true, + "title": "Ant Design of Angular (ng-zorro-antd)", + "url": "https://ng.ant.design/docs/introduce/en" + }, "aggrid": { "desc": "A datagrid for Angular with enterprise style features such as sorting, filtering, custom rendering, editing, grouping, aggregation and pivoting.", "rev": true, From db2329ef6a2892aab4e4e62a8e27417a79937d50 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 16 May 2018 15:30:39 -0500 Subject: [PATCH 077/582] docs(aio): Add missing dependencies and files to testing zip file download (#23948) Closes #23060 PR Close #23948 --- aio/content/examples/testing/src/tests.sb.ts | 50 +++++++++---------- .../customizer/package-json/testing.json | 1 + aio/tools/example-zipper/exampleZipper.js | 1 + aio/tools/examples/shared/package.json | 3 +- aio/tools/examples/shared/yarn.lock | 6 +++ 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/aio/content/examples/testing/src/tests.sb.ts b/aio/content/examples/testing/src/tests.sb.ts index 3f1c8fe61a..c255cca180 100644 --- a/aio/content/examples/testing/src/tests.sb.ts +++ b/aio/content/examples/testing/src/tests.sb.ts @@ -1,26 +1,26 @@ // Import spec files individually for Stackblitz -import 'app/about/about.component.spec.ts'; -import 'app/app-initial.component.spec.ts'; -import 'app/app.component.router.spec.ts'; -import 'app/app.component.spec.ts'; -import 'app/banner/banner-initial.component.spec.ts'; -import 'app/banner/banner.component.spec.ts'; -import 'app/banner/banner.component.detect-changes.spec.ts'; -import 'app/banner/banner-external.component.spec.ts'; -import 'app/dashboard/dashboard-hero.component.spec.ts'; -import 'app/dashboard/dashboard.component.no-testbed.spec.ts'; -import 'app/dashboard/dashboard.component.spec.ts'; -import 'app/demo/async-helper.spec.ts'; -import 'app/demo/demo.spec.ts'; -import 'app/demo/demo.testbed.spec.ts'; -import 'app/hero/hero-detail.component.no-testbed.spec.ts'; -import 'app/hero/hero-detail.component.spec.ts'; -import 'app/hero/hero-list.component.spec.ts'; -import 'app/model/hero.service.spec.ts'; -import 'app/model/http-hero.service.spec.ts'; -import 'app/model/testing/http-client.spec.ts'; -import 'app/shared/highlight.directive.spec.ts'; -import 'app/shared/title-case.pipe.spec.ts'; -import 'app/twain/twain.component.spec.ts'; -import 'app/twain/twain.component.marbles.spec.ts'; -import 'app/welcome/welcome.component.spec.ts'; +import './app/about/about.component.spec.ts'; +import './app/app-initial.component.spec.ts'; +import './app/app.component.router.spec.ts'; +import './app/app.component.spec.ts'; +import './app/banner/banner-initial.component.spec.ts'; +import './app/banner/banner.component.spec.ts'; +import './app/banner/banner.component.detect-changes.spec.ts'; +import './app/banner/banner-external.component.spec.ts'; +import './app/dashboard/dashboard-hero.component.spec.ts'; +import './app/dashboard/dashboard.component.no-testbed.spec.ts'; +import './app/dashboard/dashboard.component.spec.ts'; +import './app/demo/async-helper.spec.ts'; +import './app/demo/demo.spec.ts'; +import './app/demo/demo.testbed.spec.ts'; +import './app/hero/hero-detail.component.no-testbed.spec.ts'; +import './app/hero/hero-detail.component.spec.ts'; +import './app/hero/hero-list.component.spec.ts'; +import './app/model/hero.service.spec.ts'; +import './app/model/http-hero.service.spec.ts'; +import './app/model/testing/http-client.spec.ts'; +import './app/shared/highlight.directive.spec.ts'; +import './app/shared/title-case.pipe.spec.ts'; +import './app/twain/twain.component.spec.ts'; +import './app/twain/twain.component.marbles.spec.ts'; +import './app/welcome/welcome.component.spec.ts'; diff --git a/aio/tools/example-zipper/customizer/package-json/testing.json b/aio/tools/example-zipper/customizer/package-json/testing.json index 92f1609f1a..5ce4c4788f 100644 --- a/aio/tools/example-zipper/customizer/package-json/testing.json +++ b/aio/tools/example-zipper/customizer/package-json/testing.json @@ -11,6 +11,7 @@ "devDependencies": [ "@angular/cli", "@types/jasminewd2", + "jasmine-marbles", "jasmine-spec-reporter", "karma-coverage-istanbul-reporter", "ts-node" diff --git a/aio/tools/example-zipper/exampleZipper.js b/aio/tools/example-zipper/exampleZipper.js index fedcebb1ba..996f38a008 100644 --- a/aio/tools/example-zipper/exampleZipper.js +++ b/aio/tools/example-zipper/exampleZipper.js @@ -98,6 +98,7 @@ class ExampleZipper { 'src/favicon.ico', 'src/karma.conf.js', 'src/polyfills.ts', + 'src/test.ts', 'src/typings.d.ts', 'src/environments/**/*', 'src/tsconfig.*', diff --git a/aio/tools/examples/shared/package.json b/aio/tools/examples/shared/package.json index 90e2ff8e32..d3f27b9ee5 100644 --- a/aio/tools/examples/shared/package.json +++ b/aio/tools/examples/shared/package.json @@ -40,11 +40,11 @@ "zone.js": "^0.8.24" }, "devDependencies": { + "@angular-devkit/build-angular": "~0.6.0", "@angular/cli": "^6.0.0", "@angular/compiler-cli": "^6.0.0", "@angular/language-service": "^6.0.0", "@angular/platform-server": "^6.0.0", - "@angular-devkit/build-angular": "~0.6.0", "@types/angular": "^1.5.16", "@types/angular-animate": "^1.5.5", "@types/angular-cookies": "^1.4.2", @@ -66,6 +66,7 @@ "html-webpack-plugin": "^2.16.1", "http-server": "^0.9.0", "jasmine-core": "~2.99.1", + "jasmine-marbles": "^0.3.1", "jasmine-spec-reporter": "~4.2.1", "karma": "~1.7.1", "karma-chrome-launcher": "~2.2.0", diff --git a/aio/tools/examples/shared/yarn.lock b/aio/tools/examples/shared/yarn.lock index fcde5b4085..b85cf758e4 100644 --- a/aio/tools/examples/shared/yarn.lock +++ b/aio/tools/examples/shared/yarn.lock @@ -4986,6 +4986,12 @@ jasmine-core@~2.99.1: version "2.99.1" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.99.1.tgz#e6400df1e6b56e130b61c4bcd093daa7f6e8ca15" +jasmine-marbles@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.3.1.tgz#ef65edecb41b8dd62fc6bda40448222042e32043" + dependencies: + lodash "^4.5.0" + jasmine-spec-reporter@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz#1d632aec0341670ad324f92ba84b4b32b35e9e22" From 1b6b936ef4a3143443fabb5aacd3993c400f1d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Thu, 3 May 2018 10:40:31 -0700 Subject: [PATCH 078/582] test(ivy): Add bazel flag to control building ViewEngine or Ivy (#23833) PR Close #23833 --- packages/core/BUILD.bazel | 22 ++++++++++++++- packages/core/src/core_private_export.ts | 1 + packages/core/src/ivy_switch.ts | 34 ++++++++++++++++++++++++ packages/core/src/ivy_switch_false.ts | 9 +++++++ packages/core/src/ivy_switch_true.ts | 9 +++++++ tools/bazel.rc | 7 +++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/ivy_switch.ts create mode 100644 packages/core/src/ivy_switch_false.ts create mode 100644 packages/core/src/ivy_switch_true.ts diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 80550ecf11..7bbfa1911e 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -9,7 +9,10 @@ ng_module( "*.ts", "src/**/*.ts", ], - ), + exclude = ["src/ivy_switch.ts"], + ) + [ + ":ivy_switch", + ], module_name = "@angular/core", deps = [ "//packages:types", @@ -30,3 +33,20 @@ ng_package( "//packages/core/testing", ], ) + +## Controls if Ivy is enabled. (Temporary target until we permanently switch over to Ivy) +## +## This file generates `src/ivy_switch.ts` file which reexports symbols for `ViewEngine` or `Ivy.` +## - append `--define=ivy=false` (default) to `bazel` command to reexport `./ivy_switch_false` +## and use `ViewEngine`; +## - append `--define=ivy=true` to `bazel` command to rexport `./ivy_switch_true` and use `Ivy`; +## +## NOTE: `--define=ivy=true` works with any `bazel` command or target across the repo. +## +## See: `//tools/bazel.rc` where `--define=ivy=false` is defined as default. +## See: `./src/ivy_switch.ts` for more details. +genrule( + name = "ivy_switch", + outs = ["src/ivy_switch.ts"], + cmd = "echo export '*' from \"'./ivy_switch_$(ivy)';\" > $@", +) diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 7b6101355e..f56c13e4e5 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -15,6 +15,7 @@ export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetection export {Console as ɵConsole} from './console'; export {inject as ɵinject, setCurrentInjector as ɵsetCurrentInjector} from './di/injector'; export {APP_ROOT as ɵAPP_ROOT} from './di/scope'; +export {ivyEnabled as ɵivyEnabled} from './ivy_switch'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver'; export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; diff --git a/packages/core/src/ivy_switch.ts b/packages/core/src/ivy_switch.ts new file mode 100644 index 0000000000..a584e99587 --- /dev/null +++ b/packages/core/src/ivy_switch.ts @@ -0,0 +1,34 @@ +/** + * @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 + */ + +/** + * This file is used to control if the default rendering pipeline should be `ViewEngine` or `Ivy`. + * + * Reexport from: + * - `./ivy_switch_false` => Use `ViewEngine`. + * - `./ivy_switch_true` => Use `Ivy`. + * + * This file is here for your IDE as well as for `google3`. The `bazel` build system + * specifically excludes this file and instead generates a new file which is controlled by + * command line: + * + * - `bazel build packages/core` => Use `ViewEngine` + * - `bazel build packages/core --define=ivy=true` => Use `Ivy` + * + * See: `bazel build packages/core:ivy_switch` for more details. + * + * ## How to use this + * + * Use this mechanism to have the same symbol be aliased to different implementation. + * 1) Create two implementations of a symbol (most likely a `function` or a `class`). + * 2) Export the two implementation under same name in `./ivy_switch_false` and `./ivy_switch_false` + * respectively. + * 3) Import the symbol from `./ivy_switch`. The imported symbol will that point to either the + * symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode. + */ +export * from './ivy_switch_false'; diff --git a/packages/core/src/ivy_switch_false.ts b/packages/core/src/ivy_switch_false.ts new file mode 100644 index 0000000000..599a11d63e --- /dev/null +++ b/packages/core/src/ivy_switch_false.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export const ivyEnabled = false; \ No newline at end of file diff --git a/packages/core/src/ivy_switch_true.ts b/packages/core/src/ivy_switch_true.ts new file mode 100644 index 0000000000..fc5c97d002 --- /dev/null +++ b/packages/core/src/ivy_switch_true.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export const ivyEnabled = true; \ No newline at end of file diff --git a/tools/bazel.rc b/tools/bazel.rc index 1d0f39dfd2..52af863bea 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -53,3 +53,10 @@ test --experimental_ui ################################ # Bazel flags for CircleCI are in /.circleci/bazel.rc + +################################ +# Temporary Settings for Ivy # +################################ +# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=ivy=true` on +# any bazel target. This is a temporary flag until codebase is permanently switched to ViewEngine. +build --define=ivy=false \ No newline at end of file From 919f42fea1df4b9e38b7d688aef5f2de668e9d3e Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 9 May 2018 08:35:25 -0700 Subject: [PATCH 079/582] feat(ivy): first steps towards JIT compilation (#23833) This commit adds a mechanism by which the @angular/core annotations for @Component, @Injectable, and @NgModule become decorators which, when executed at runtime, trigger just-in-time compilation of their associated types. The activation of these decorators is configured by the ivy_switch mechanism, ensuring that the Ivy JIT engine does not get included in Angular bundles unless specifically requested. PR Close #23833 --- .../src/ngtsc/transform/src/injectable.ts | 45 ++--- .../src/ngtsc/transform/src/transform.ts | 2 +- packages/compiler/src/aot/compiler.ts | 14 +- packages/compiler/src/compiler.ts | 7 + packages/compiler/src/identifiers.ts | 1 + .../compiler/src/injectable_compiler_2.ts | 141 ++++++++------- .../compiler/src/output/abstract_emitter.ts | 2 +- .../src/output/abstract_js_emitter.ts | 3 +- packages/compiler/src/output/output_jit.ts | 27 ++- .../compiler/src/render3/r3_identifiers.ts | 2 + packages/compiler/src/render3/r3_jit.ts | 78 ++++++++ .../src/render3/r3_module_compiler.ts | 83 +++++++-- packages/compiler/src/render3/util.ts | 38 ++++ .../compiler/src/render3/view/template.ts | 42 ++++- packages/core/BUILD.bazel | 1 + packages/core/rollup.config.js | 3 +- packages/core/src/application_module.ts | 5 +- packages/core/src/di/injectable.ts | 32 ++-- packages/core/src/ivy_switch.ts | 2 + packages/core/src/ivy_switch_false.ts | 5 +- packages/core/src/ivy_switch_true.ts | 9 +- packages/core/src/metadata.ts | 2 +- packages/core/src/metadata/directives.ts | 5 +- packages/core/src/metadata/ng_module.ts | 48 +++-- packages/core/src/render3/jit/directive.ts | 97 ++++++++++ packages/core/src/render3/jit/environment.ts | 39 ++++ packages/core/src/render3/jit/injectable.ts | 114 ++++++++++++ packages/core/src/render3/jit/module.ts | 94 ++++++++++ packages/core/src/render3/jit/util.ts | 86 +++++++++ .../test/bundling/hello_world_jit/BUILD.bazel | 58 ++++++ .../test/bundling/hello_world_jit/index.html | 31 ++++ .../test/bundling/hello_world_jit/index.ts | 38 ++++ .../hello_world_jit/integration_spec.ts | 23 +++ .../hello_world_r2/bundle.golden_symbols.json | 6 + packages/core/test/render3/BUILD.bazel | 14 +- packages/core/test/render3/ivy/BUILD.bazel | 41 +++++ packages/core/test/render3/ivy/jit_spec.ts | 166 ++++++++++++++++++ 37 files changed, 1248 insertions(+), 156 deletions(-) create mode 100644 packages/compiler/src/render3/r3_jit.ts create mode 100644 packages/compiler/src/render3/util.ts create mode 100644 packages/core/src/render3/jit/directive.ts create mode 100644 packages/core/src/render3/jit/environment.ts create mode 100644 packages/core/src/render3/jit/injectable.ts create mode 100644 packages/core/src/render3/jit/module.ts create mode 100644 packages/core/src/render3/jit/util.ts create mode 100644 packages/core/test/bundling/hello_world_jit/BUILD.bazel create mode 100644 packages/core/test/bundling/hello_world_jit/index.html create mode 100644 packages/core/test/bundling/hello_world_jit/index.ts create mode 100644 packages/core/test/bundling/hello_world_jit/integration_spec.ts create mode 100644 packages/core/test/render3/ivy/BUILD.bazel create mode 100644 packages/core/test/render3/ivy/jit_spec.ts diff --git a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts index a97ffe38a1..d6444bc584 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, IvyInjectableDep, IvyInjectableMetadata, LiteralExpr, WrappedNodeExpr, compileIvyInjectable} from '@angular/compiler'; +import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler'; import * as ts from 'typescript'; import {Decorator} from '../../metadata'; @@ -18,20 +18,20 @@ import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api' /** * Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler. */ -export class InjectableCompilerAdapter implements CompilerAdapter { +export class InjectableCompilerAdapter implements CompilerAdapter { constructor(private checker: ts.TypeChecker) {} detect(decorator: Decorator[]): Decorator|undefined { return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core'); } - analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { + analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { return { analysis: extractInjectableMetadata(node, decorator, this.checker), }; } - compile(node: ts.ClassDeclaration, analysis: IvyInjectableMetadata): AddStaticFieldInstruction { + compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction { const res = compileIvyInjectable(analysis); return { field: 'ngInjectableDef', @@ -47,7 +47,7 @@ export class InjectableCompilerAdapter implements CompilerAdapter getDep(dep, checker))); } - return {name, type, providedIn, useFactory: {factory, deps}}; + return {name, type, providedIn, useFactory: factory, deps}; } else { - const useType = getUseType(clazz, checker); - return {name, type, providedIn, useType}; + const deps = getConstructorDependencies(clazz, checker); + return {name, type, providedIn, deps}; } } else { throw new Error(`Too many arguments to @Injectable`); } } -function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInjectableDep[] { - const useType: IvyInjectableDep[] = []; +function getConstructorDependencies( + clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] { + const useType: R3DependencyMetadata[] = []; const ctorParams = (reflectConstructorParameters(clazz, checker) || []); ctorParams.forEach(param => { let tokenExpr = param.typeValueExpr; @@ -131,18 +132,20 @@ function getUseType(clazz: ts.ClassDeclaration, checker: ts.TypeChecker): IvyInj } }); const token = new WrappedNodeExpr(tokenExpr); - useType.push({token, optional, self, skipSelf, attribute: false}); + useType.push( + {token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token}); }); return useType; } -function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep { - const depObj = { +function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata { + const meta: R3DependencyMetadata = { token: new WrappedNodeExpr(dep), + host: false, + resolved: R3ResolvedDependencyType.Token, optional: false, self: false, skipSelf: false, - attribute: false, }; function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void { @@ -153,17 +156,17 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep { switch (source.name) { case 'Inject': if (token !== undefined) { - depObj.token = new WrappedNodeExpr(token); + meta.token = new WrappedNodeExpr(token); } break; case 'Optional': - depObj.optional = true; + meta.optional = true; break; case 'SkipSelf': - depObj.skipSelf = true; + meta.skipSelf = true; break; case 'Self': - depObj.self = true; + meta.self = true; break; } } @@ -178,5 +181,5 @@ function getDep(dep: ts.Expression, checker: ts.TypeChecker): IvyInjectableDep { } }); } - return depObj; + return meta; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 161369cc4b..85d6fcb9c1 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {WrappedNodeExpr, compileIvyInjectable} from '@angular/compiler'; +import {WrappedNodeExpr} from '@angular/compiler'; import * as ts from 'typescript'; import {IvyCompilation} from './compilation'; diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index af87fc8db7..45d80a6206 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -22,10 +22,10 @@ import {NgModuleCompiler} from '../ng_module_compiler'; import {OutputEmitter} from '../output/abstract_emitter'; import * as o from '../output/output_ast'; import {ParseError} from '../parse_util'; -import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler'; -import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler'; +import {compileNgModuleFromRender2 as compileR3Module} from '../render3/r3_module_compiler'; +import {compilePipe as compileR3Pipe} from '../render3/r3_pipe_compiler'; import {htmlAstToRender3Ast} from '../render3/r3_template_transform'; -import {compileComponentFromRender2 as compileIvyComponent, compileDirectiveFromRender2 as compileIvyDirective} from '../render3/view/compiler'; +import {compileComponentFromRender2 as compileR3Component, compileDirectiveFromRender2 as compileR3Directive} from '../render3/view/compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {SummaryResolver} from '../summary_resolver'; @@ -362,7 +362,7 @@ export class AotCompiler { private _compileShallowModules( fileName: string, shallowModules: CompileShallowModuleMetadata[], context: OutputContext): void { - shallowModules.forEach(module => compileIvyModule(context, module, this._injectableCompiler)); + shallowModules.forEach(module => compileR3Module(context, module, this._injectableCompiler)); } private _compilePartialModule( @@ -413,18 +413,18 @@ export class AotCompiler { pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); }); - compileIvyComponent( + compileR3Component( context, directiveMetadata, render3Ast, this.reflector, hostBindingParser, directiveTypeBySel, pipeTypeByName); } else { - compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser); + compileR3Directive(context, directiveMetadata, this.reflector, hostBindingParser); } }); pipes.forEach(pipeType => { const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType); if (pipeMetadata) { - compileIvyPipe(context, pipeMetadata, this.reflector); + compileR3Pipe(context, pipeMetadata, this.reflector); } }); diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index a0f1c7cbe3..88303c3d46 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -50,6 +50,7 @@ export {JitCompiler} from './jit/compiler'; export * from './compile_reflector'; export * from './url_resolver'; export * from './resource_loader'; +export {ConstantPool} from './constant_pool'; export {DirectiveResolver} from './directive_resolver'; export {PipeResolver} from './pipe_resolver'; export {NgModuleResolver} from './ng_module_resolver'; @@ -79,4 +80,10 @@ export {ViewCompiler} from './view_compiler/view_compiler'; export {getParseErrors, isSyntaxError, syntaxError, Version} from './util'; export {SourceMap} from './output/source_map'; export * from './injectable_compiler_2'; +export * from './render3/view/api'; +export {jitPatchDefinition} from './render3/r3_jit'; +export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; +export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler'; +export {makeBindingParser, parseTemplate} from './render3/view/template'; +export {compileComponent, compileDirective} from './render3/view/compiler'; // This file only reexports content of the `src` folder. Keep it that way. \ No newline at end of file diff --git a/packages/compiler/src/identifiers.ts b/packages/compiler/src/identifiers.ts index 9c85cae1e9..dcfc98598e 100644 --- a/packages/compiler/src/identifiers.ts +++ b/packages/compiler/src/identifiers.ts @@ -65,6 +65,7 @@ export class Identifiers { static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE}; static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE}; static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE}; + static InjectableDef: o.ExternalReference = {name: 'InjectableDef', moduleName: CORE}; static ViewEncapsulation: o.ExternalReference = { name: 'ViewEncapsulation', moduleName: CORE, diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index 0f01f881b0..083d2217f4 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -7,96 +7,107 @@ */ import {InjectFlags} from './core'; +import {Identifiers} from './identifiers'; import * as o from './output/output_ast'; -import {Identifiers} from './render3/r3_identifiers'; - - -type MapEntry = { - key: string; quoted: boolean; value: o.Expression; -}; - -function mapToMapExpression(map: {[key: string]: o.Expression}): o.LiteralMapExpr { - const result = Object.keys(map).map(key => ({key, value: map[key], quoted: false})); - return o.literalMap(result); -} +import {R3DependencyMetadata, compileFactoryFunction} from './render3/r3_factory'; +import {mapToMapExpression} from './render3/util'; export interface InjectableDef { expression: o.Expression; type: o.Type; } -export interface IvyInjectableDep { - token: o.Expression; - optional: boolean; - self: boolean; - skipSelf: boolean; - attribute: boolean; -} - -export interface IvyInjectableMetadata { +export interface R3InjectableMetadata { name: string; type: o.Expression; providedIn: o.Expression; - useType?: IvyInjectableDep[]; useClass?: o.Expression; - useFactory?: {factory: o.Expression; deps: IvyInjectableDep[];}; + useFactory?: o.Expression; useExisting?: o.Expression; useValue?: o.Expression; + deps?: R3DependencyMetadata[]; } -export function compileIvyInjectable(meta: IvyInjectableMetadata): InjectableDef { - let ret: o.Expression = o.NULL_EXPR; - if (meta.useType !== undefined) { - const args = meta.useType.map(dep => injectDep(dep)); - ret = new o.InstantiateExpr(meta.type, args); - } else if (meta.useClass !== undefined) { - const factory = - new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory'); - ret = new o.InvokeFunctionExpr(factory, []); +export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { + let factory: o.Expression = o.NULL_EXPR; + + function makeFn(ret: o.Expression): o.Expression { + return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`); + } + + if (meta.useClass !== undefined || meta.useFactory !== undefined) { + // First, handle useClass and useFactory together, since both involve a similar call to + // `compileFactoryFunction`. Either dependencies are explicitly specified, in which case + // a factory function call is generated, or they're not specified and the calls are special- + // cased. + if (meta.deps !== undefined) { + // Either call `new meta.useClass(...)` or `meta.useFactory(...)`. + const fnOrClass: o.Expression = meta.useClass || meta.useFactory !; + + // useNew: true if meta.useClass, false for meta.useFactory. + const useNew = meta.useClass !== undefined; + + factory = compileFactoryFunction({ + name: meta.name, + fnOrClass, + useNew, + injectFn: Identifiers.inject, + useOptionalParam: true, + deps: meta.deps, + }); + } else if (meta.useClass !== undefined) { + // Special case for useClass where the factory from the class's ngInjectableDef is used. + if (meta.useClass.isEquivalent(meta.type)) { + // For the injectable compiler, useClass represents a foreign type that should be + // instantiated to satisfy construction of the given type. It's not valid to specify + // useClass === type, since the useClass type is expected to already be compiled. + throw new Error( + `useClass is the same as the type, but no deps specified, which is invalid.`); + } + factory = + makeFn(new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory') + .callFn([])); + } else if (meta.useFactory !== undefined) { + // Special case for useFactory where no arguments are passed. + factory = meta.useFactory.callFn([]); + } else { + // Can't happen - outer conditional guards against both useClass and useFactory being + // undefined. + throw new Error('Reached unreachable block in injectable compiler.'); + } } else if (meta.useValue !== undefined) { - ret = meta.useValue; + // Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for + // client code because meta.useValue is an Expression which will be defined even if the actual + // value is undefined. + factory = makeFn(meta.useValue); } else if (meta.useExisting !== undefined) { - ret = o.importExpr(Identifiers.inject).callFn([meta.useExisting]); - } else if (meta.useFactory !== undefined) { - const args = meta.useFactory.deps.map(dep => injectDep(dep)); - ret = new o.InvokeFunctionExpr(meta.useFactory.factory, args); + // useExisting is an `inject` call on the existing token. + factory = makeFn(o.importExpr(Identifiers.inject).callFn([meta.useExisting])); } else { - throw new Error('No instructions for injectable compiler!'); + // A strict type is compiled according to useClass semantics, except the dependencies are + // required. + if (meta.deps === undefined) { + throw new Error(`Type compilation of an injectable requires dependencies.`); + } + factory = compileFactoryFunction({ + name: meta.name, + fnOrClass: meta.type, + useNew: true, + injectFn: Identifiers.inject, + useOptionalParam: true, + deps: meta.deps, + }); } const token = meta.type; const providedIn = meta.providedIn; - const factory = - o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`); - const expression = o.importExpr({ - moduleName: '@angular/core', - name: 'defineInjectable', - }).callFn([mapToMapExpression({token, factory, providedIn})]); - const type = new o.ExpressionType(o.importExpr( - { - moduleName: '@angular/core', - name: 'InjectableDef', - }, - [new o.ExpressionType(meta.type)])); + const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression( + {token, factory, providedIn})]); + const type = new o.ExpressionType( + o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)])); return { expression, type, }; } - -function injectDep(dep: IvyInjectableDep): o.Expression { - const defaultValue = dep.optional ? o.NULL_EXPR : o.literal(undefined); - const flags = o.literal( - InjectFlags.Default | (dep.self && InjectFlags.Self || 0) | - (dep.skipSelf && InjectFlags.SkipSelf || 0)); - if (!dep.optional && !dep.skipSelf && !dep.self) { - return o.importExpr(Identifiers.inject).callFn([dep.token]); - } else { - return o.importExpr(Identifiers.inject).callFn([ - dep.token, - defaultValue, - flags, - ]); - } -} diff --git a/packages/compiler/src/output/abstract_emitter.ts b/packages/compiler/src/output/abstract_emitter.ts index e168a5a28f..9780ded14f 100644 --- a/packages/compiler/src/output/abstract_emitter.ts +++ b/packages/compiler/src/output/abstract_emitter.ts @@ -312,7 +312,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex ctx.print(expr, `)`); return null; } - visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): never { + visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): any { throw new Error('Abstract emitter cannot visit WrappedNodeExpr.'); } visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any { diff --git a/packages/compiler/src/output/abstract_js_emitter.ts b/packages/compiler/src/output/abstract_js_emitter.ts index 943fab81e1..621a67af40 100644 --- a/packages/compiler/src/output/abstract_js_emitter.ts +++ b/packages/compiler/src/output/abstract_js_emitter.ts @@ -70,9 +70,10 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { ctx.println(stmt, `};`); } - visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): never { + visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): any { throw new Error('Cannot emit a WrappedNodeExpr in Javascript.'); } + visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string|null { if (ast.builtin === o.BuiltinVar.This) { ctx.print(ast, 'self'); diff --git a/packages/compiler/src/output/output_jit.ts b/packages/compiler/src/output/output_jit.ts index 7fb32a64cc..3c077aa911 100644 --- a/packages/compiler/src/output/output_jit.ts +++ b/packages/compiler/src/output/output_jit.ts @@ -68,15 +68,12 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { } visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { - 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: value}) || 'val'; - this._evalArgNames.push(`jit_${name}_${id}`); - } - ctx.print(ast, this._evalArgNames[id]); + this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx); + return null; + } + + visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): any { + this._emitReferenceToExternal(ast, ast.node, ctx); return null; } @@ -100,4 +97,16 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { } return super.visitDeclareClassStmt(stmt, ctx); } + + private _emitReferenceToExternal(ast: o.Expression, value: any, ctx: EmitterVisitorContext): + void { + let id = this._evalArgValues.indexOf(value); + if (id === -1) { + id = this._evalArgValues.length; + this._evalArgValues.push(value); + const name = identifierName({reference: value}) || 'val'; + this._evalArgNames.push(`jit_${name}_${id}`); + } + ctx.print(ast, this._evalArgNames[id]); + } } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 030a2b9b94..bbde7f2f02 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -110,6 +110,8 @@ export class Identifiers { moduleName: CORE, }; + static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE}; + static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE}; static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts new file mode 100644 index 0000000000..5d1a93e3d9 --- /dev/null +++ b/packages/compiler/src/render3/r3_jit.ts @@ -0,0 +1,78 @@ +/** + * @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 {CompileReflector} from '../compile_reflector'; +import {ConstantPool} from '../constant_pool'; +import * as o from '../output/output_ast'; +import {jitStatements} from '../output/output_jit'; + +/** + * Implementation of `CompileReflector` which resolves references to @angular/core + * symbols at runtime, according to a consumer-provided mapping. + * + * Only supports `resolveExternalReference`, all other methods throw. + */ +class R3JitReflector implements CompileReflector { + constructor(private context: {[key: string]: any}) {} + + resolveExternalReference(ref: o.ExternalReference): any { + // This reflector only handles @angular/core imports. + if (ref.moduleName !== '@angular/core') { + throw new Error( + `Cannot resolve external reference to ${ref.moduleName}, only references to @angular/core are supported.`); + } + if (!this.context.hasOwnProperty(ref.name !)) { + throw new Error(`No value provided for @angular/core symbol '${ref.name!}'.`); + } + return this.context[ref.name !]; + } + + parameters(typeOrFunc: any): any[][] { throw new Error('Not implemented.'); } + + annotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); } + + shallowAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); } + + tryAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); } + + propMetadata(typeOrFunc: any): {[key: string]: any[];} { throw new Error('Not implemented.'); } + + hasLifecycleHook(type: any, lcProperty: string): boolean { throw new Error('Not implemented.'); } + + guards(typeOrFunc: any): {[key: string]: any;} { throw new Error('Not implemented.'); } + + componentModuleUrl(type: any, cmpMetadata: any): string { throw new Error('Not implemented.'); } +} + +/** + * JIT compiles an expression and monkey-patches the result of executing the expression onto a given + * type. + * + * @param type the type which will receive the monkey-patched result + * @param field name of the field on the type to monkey-patch + * @param def the definition which will be compiled and executed to get the value to patch + * @param context an object map of @angular/core symbol names to symbols which will be available in + * the context of the compiled expression + * @param constantPool an optional `ConstantPool` which contains constants used in the expression + */ +export function jitPatchDefinition( + type: any, field: string, def: o.Expression, context: {[key: string]: any}, + constantPool?: ConstantPool): void { + // The ConstantPool may contain Statements which declare variables used in the final expression. + // Therefore, its statements need to precede the actual JIT operation. The final statement is a + // declaration of $def which is set to the expression being compiled. + const statements: o.Statement[] = [ + ...(constantPool !== undefined ? constantPool.statements : []), + new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]), + ]; + + // Monkey patch the field on the given type with the result of compilation. + // TODO(alxhub): consider a better source url. + type[field] = jitStatements( + `ng://${type && type.name}/${field}`, statements, new R3JitReflector(context), false)['$def']; +} diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index f0d2c93a83..91a365a3dc 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -14,22 +14,72 @@ import * as o from '../output/output_ast'; import {OutputContext} from '../util'; import {Identifiers as R3} from './r3_identifiers'; +import {convertMetaToOutput, mapToMapExpression} from './util'; -function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression { - if (Array.isArray(meta)) { - return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx))); - } - if (meta instanceof StaticSymbol) { - return ctx.importExpr(meta); - } - if (meta == null) { - return o.literal(meta); - } - - throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`); +export interface R3NgModuleDef { + expression: o.Expression; + type: o.Type; + additionalStatements: o.Statement[]; } -export function compileNgModule( +/** + * Metadata required by the module compiler to generate a `ngModuleDef` for a type. + */ +export interface R3NgModuleMetadata { + /** + * An expression representing the module type being compiled. + */ + type: o.Expression; + + /** + * An array of expressions representing the bootstrap components specified by the module. + */ + bootstrap: o.Expression[]; + + /** + * An array of expressions representing the directives and pipes declared by the module. + */ + declarations: o.Expression[]; + + /** + * An array of expressions representing the imports of the module. + */ + imports: o.Expression[]; + + /** + * An array of expressions representing the exports of the module. + */ + exports: o.Expression[]; + + /** + * Whether to emit the selector scope values (declarations, imports, exports) inline into the + * module definition, or to generate additional statements which patch them on. Inline emission + * does not allow components to be tree-shaken, but is useful for JIT mode. + */ + emitInline: boolean; +} + +/** + * Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`. + */ +export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { + const {type: moduleType, bootstrap, declarations, imports, exports} = meta; + const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression({ + type: moduleType, + bootstrap: o.literalArr(bootstrap), + declarations: o.literalArr(declarations), + imports: o.literalArr(imports), + exports: o.literalArr(exports), + })]); + + // TODO(alxhub): write a proper type reference when AOT compilation of @NgModule is implemented. + const type = new o.ExpressionType(o.NULL_EXPR); + const additionalStatements: o.Statement[] = []; + return {expression, type, additionalStatements}; +} + +// TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations. +export function compileNgModuleFromRender2( ctx: OutputContext, ngModule: CompileShallowModuleMetadata, injectableCompiler: InjectableCompiler): void { const className = identifierName(ngModule.type) !; @@ -57,4 +107,9 @@ export function compileNgModule( /* getters */[], /* constructorMethod */ new o.ClassMethod(null, [], []), /* methods */[])); -} \ No newline at end of file +} + +function accessExportScope(module: o.Expression): o.Expression { + const selectorScope = new o.ReadPropExpr(module, 'ngModuleDef'); + return new o.ReadPropExpr(selectorScope, 'exported'); +} diff --git a/packages/compiler/src/render3/util.ts b/packages/compiler/src/render3/util.ts new file mode 100644 index 0000000000..dd5fe5214e --- /dev/null +++ b/packages/compiler/src/render3/util.ts @@ -0,0 +1,38 @@ +/** + * @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 {StaticSymbol} from '../aot/static_symbol'; +import * as o from '../output/output_ast'; +import {OutputContext} from '../util'; + +/** + * Convert an object map with `Expression` values into a `LiteralMapExpr`. + */ +export function mapToMapExpression(map: {[key: string]: o.Expression}): o.LiteralMapExpr { + const result = Object.keys(map).map(key => ({key, value: map[key], quoted: false})); + return o.literalMap(result); +} + +/** + * Convert metadata into an `Expression` in the given `OutputContext`. + * + * This operation will handle arrays, references to symbols, or literal `null` or `undefined`. + */ +export function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression { + if (Array.isArray(meta)) { + return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx))); + } + if (meta instanceof StaticSymbol) { + return ctx.importExpr(meta); + } + if (meta == null) { + return o.literal(meta); + } + + throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`); +} diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 8faa85963f..e79e802377 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -12,12 +12,20 @@ import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, c import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {Lexer} from '../../expression_parser/lexer'; +import {Parser} from '../../expression_parser/parser'; +import * as html from '../../ml_parser/ast'; +import {HtmlParser} from '../../ml_parser/html_parser'; +import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; -import {ParseSourceSpan} from '../../parse_util'; +import {ParseError, ParseSourceSpan} from '../../parse_util'; +import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry'; import {CssSelector, SelectorMatcher} from '../../selector'; +import {BindingParser} from '../../template_parser/binding_parser'; import {OutputContext, error} from '../../util'; import * as t from '../r3_ast'; import {Identifiers as R3} from '../r3_identifiers'; +import {htmlAstToRender3Ast} from '../r3_template_transform'; import {R3QueryMetadata} from './api'; import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util'; @@ -713,3 +721,35 @@ function interpolate(args: o.Expression[]): o.Expression { error(`Invalid interpolation argument length ${args.length}`); return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); } + +/** + * Parse a template into render3 `Node`s and additional metadata, with no other dependencies. + * + * @param template text of the template to parse + * @param templateUrl URL to use for source mapping of the parsed template + */ +export function parseTemplate(template: string, templateUrl: string): + {errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} { + const bindingParser = makeBindingParser(); + const htmlParser = new HtmlParser(); + const parseResult = htmlParser.parse(template, templateUrl); + if (parseResult.errors && parseResult.errors.length > 0) { + return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []}; + } + const {nodes, hasNgContent, ngContentSelectors, errors} = + htmlAstToRender3Ast(parseResult.rootNodes, bindingParser); + if (errors && errors.length > 0) { + return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []}; + } + + return {nodes, hasNgContent, ngContentSelectors}; +} + +/** + * Construct a `BindingParser` with a default configuration. + */ +export function makeBindingParser(): BindingParser { + return new BindingParser( + new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), [], + []); +} diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 7bbfa1911e..68a4cea600 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -16,6 +16,7 @@ ng_module( module_name = "@angular/core", deps = [ "//packages:types", + "//packages/compiler", "@rxjs", ], ) diff --git a/packages/core/rollup.config.js b/packages/core/rollup.config.js index 35c4334b89..93e50a2578 100644 --- a/packages/core/rollup.config.js +++ b/packages/core/rollup.config.js @@ -10,8 +10,9 @@ const resolve = require('rollup-plugin-node-resolve'); const sourcemaps = require('rollup-plugin-sourcemaps'); const globals = { + '@angular/compiler': 'ng.compiler', 'rxjs': 'rxjs', - 'rxjs/operators': 'rxjs.operators' + 'rxjs/operators': 'rxjs.operators', }; module.exports = { diff --git a/packages/core/src/application_module.ts b/packages/core/src/application_module.ts index affd64c99c..55caa5d9b7 100644 --- a/packages/core/src/application_module.ts +++ b/packages/core/src/application_module.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationInitStatus} from './application_init'; +import {APP_INITIALIZER, ApplicationInitStatus} from './application_init'; import {ApplicationRef} from './application_ref'; import {APP_ID_RANDOM_PROVIDER} from './application_tokens'; import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection'; +import {forwardRef} from './di/forward_ref'; import {Inject, Optional, SkipSelf} from './di/metadata'; import {LOCALE_ID} from './i18n/tokens'; import {Compiler} from './linker/compiler'; @@ -46,7 +47,7 @@ export function _localeFactory(locale?: string): string { useFactory: _localeFactory, deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]] }, - ] + ], }) export class ApplicationModule { // Inject ApplicationRef to make it eager... diff --git a/packages/core/src/di/injectable.ts b/packages/core/src/di/injectable.ts index 507623051a..740878d3bf 100644 --- a/packages/core/src/di/injectable.ts +++ b/packages/core/src/di/injectable.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {R3_COMPILE_INJECTABLE} from '../ivy_switch'; import {ReflectionCapabilities} from '../reflection/reflection_capabilities'; import {Type} from '../type'; import {makeDecorator, makeParamDecorator} from '../util/decorators'; @@ -67,10 +68,7 @@ export interface InjectableDecorator { * * @experimental */ -export interface Injectable { - providedIn?: Type|'root'|null; - factory: () => any; -} +export interface Injectable { providedIn?: Type|'root'|null; } const EMPTY_ARRAY: any[] = []; @@ -110,6 +108,20 @@ export function convertInjectableProviderToFactory( } } +/** + * Supports @Injectable() in JIT mode for Render2. + */ +function preR3InjectableCompile( + injectableType: InjectableType, + options: {providedIn?: Type| 'root' | null} & InjectableProvider): void { + if (options && options.providedIn !== undefined && injectableType.ngInjectableDef === undefined) { + injectableType.ngInjectableDef = defineInjectable({ + providedIn: options.providedIn, + factory: convertInjectableProviderToFactory(injectableType, options), + }); + } +} + /** * Injectable decorator and metadata. * @@ -118,16 +130,8 @@ export function convertInjectableProviderToFactory( */ export const Injectable: InjectableDecorator = makeDecorator( 'Injectable', undefined, undefined, undefined, - (injectableType: InjectableType, - options: {providedIn?: Type| 'root' | null} & InjectableProvider) => { - if (options && options.providedIn !== undefined && - injectableType.ngInjectableDef === undefined) { - injectableType.ngInjectableDef = defineInjectable({ - providedIn: options.providedIn, - factory: convertInjectableProviderToFactory(injectableType, options) - }); - } - }); + (type: Type, meta: Injectable) => + (R3_COMPILE_INJECTABLE || preR3InjectableCompile)(type, meta)); /** * Type representing injectable service. diff --git a/packages/core/src/ivy_switch.ts b/packages/core/src/ivy_switch.ts index a584e99587..e461fc900a 100644 --- a/packages/core/src/ivy_switch.ts +++ b/packages/core/src/ivy_switch.ts @@ -32,3 +32,5 @@ * symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode. */ export * from './ivy_switch_false'; + +// TODO(alxhub): debug why metadata doesn't properly propagate through this file. diff --git a/packages/core/src/ivy_switch_false.ts b/packages/core/src/ivy_switch_false.ts index 599a11d63e..b3d9427670 100644 --- a/packages/core/src/ivy_switch_false.ts +++ b/packages/core/src/ivy_switch_false.ts @@ -6,4 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -export const ivyEnabled = false; \ No newline at end of file +export const ivyEnabled = false; +export const R3_COMPILE_COMPONENT: ((type: any, meta: any) => void)|null = null; +export const R3_COMPILE_INJECTABLE: ((type: any, meta: any) => void)|null = null; +export const R3_COMPILE_NGMODULE: ((type: any, meta: any) => void)|null = null; diff --git a/packages/core/src/ivy_switch_true.ts b/packages/core/src/ivy_switch_true.ts index fc5c97d002..0dfe7be6ee 100644 --- a/packages/core/src/ivy_switch_true.ts +++ b/packages/core/src/ivy_switch_true.ts @@ -6,4 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -export const ivyEnabled = true; \ No newline at end of file +import {compileComponentDecorator} from './render3/jit/directive'; +import {compileInjectable} from './render3/jit/injectable'; +import {compileNgModule} from './render3/jit/module'; + +export const ivyEnabled = true; +export const R3_COMPILE_COMPONENT = compileComponentDecorator; +export const R3_COMPILE_INJECTABLE = compileInjectable; +export const R3_COMPILE_NGMODULE = compileNgModule; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index e606c12fa4..2866829128 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -13,7 +13,7 @@ import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di'; import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives'; -import {ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module'; +import {ModuleWithProviders, NgModule, NgModuleDef, SchemaMetadata, defineNgModule} from './metadata/ng_module'; import {ViewEncapsulation} from './metadata/view'; export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di'; diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index 145f5ed18f..34bb977191 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -8,9 +8,9 @@ import {ChangeDetectionStrategy} from '../change_detection/constants'; import {Provider} from '../di'; +import {R3_COMPILE_COMPONENT} from '../ivy_switch'; import {Type} from '../type'; import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators'; - import {ViewEncapsulation} from './view'; @@ -754,7 +754,8 @@ export interface Component extends Directive { */ export const Component: ComponentDecorator = makeDecorator( 'Component', (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), - Directive); + Directive, undefined, + (type: Type, meta: Component) => (R3_COMPILE_COMPONENT || (() => {}))(type, meta)); /** * Type of the Pipe decorator / constructor function. diff --git a/packages/core/src/metadata/ng_module.ts b/packages/core/src/metadata/ng_module.ts index 60e9986efb..4ab8d8744a 100644 --- a/packages/core/src/metadata/ng_module.ts +++ b/packages/core/src/metadata/ng_module.ts @@ -9,9 +9,31 @@ import {InjectorDef, InjectorType, defineInjector} from '../di/defs'; import {convertInjectableProviderToFactory} from '../di/injectable'; import {Provider} from '../di/provider'; +import {R3_COMPILE_NGMODULE} from '../ivy_switch'; import {Type} from '../type'; import {TypeDecorator, makeDecorator} from '../util/decorators'; +export interface NgModuleDef { + type: T; + bootstrap: Type[]; + declarations: Type[]; + imports: Type[]; + exports: Type[]; + + transitiveCompileScope: {directives: any[]; pipes: any[];}|undefined; +} + +export function defineNgModule(def: {type: T} & Partial>): never { + const res: NgModuleDef = { + type: def.type, + bootstrap: def.bootstrap || [], + declarations: def.declarations || [], + imports: def.imports || [], + exports: def.exports || [], + transitiveCompileScope: undefined, + }; + return res as never; +} /** * A wrapper around a module that also includes the providers. @@ -187,6 +209,19 @@ export interface NgModule { id?: string; } +function preR3NgModuleCompile(moduleType: InjectorType, metadata: NgModule): void { + let imports = (metadata && metadata.imports) || []; + if (metadata && metadata.exports) { + imports = [...imports, metadata.exports]; + } + + moduleType.ngInjectorDef = defineInjector({ + factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}), + providers: metadata && metadata.providers, + imports: imports, + }); +} + /** * NgModule decorator and metadata. * @@ -195,15 +230,4 @@ export interface NgModule { */ export const NgModule: NgModuleDecorator = makeDecorator( 'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined, - (moduleType: InjectorType, metadata: NgModule) => { - let imports = (metadata && metadata.imports) || []; - if (metadata && metadata.exports) { - imports = [...imports, metadata.exports]; - } - - moduleType.ngInjectorDef = defineInjector({ - factory: convertInjectableProviderToFactory(moduleType, {useClass: moduleType}), - providers: metadata && metadata.providers, - imports: imports, - }); - }); + (type: Type, meta: NgModule) => (R3_COMPILE_NGMODULE || preR3NgModuleCompile)(type, meta)); diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts new file mode 100644 index 0000000000..7cabe4f363 --- /dev/null +++ b/packages/core/src/render3/jit/directive.ts @@ -0,0 +1,97 @@ +/** + * @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 {compileComponent as compileIvyComponent, parseTemplate, ConstantPool, makeBindingParser, WrappedNodeExpr, jitPatchDefinition,} from '@angular/compiler'; + +import {Component} from '../../metadata/directives'; +import {ReflectionCapabilities} from '../../reflection/reflection_capabilities'; +import {Type} from '../../type'; + +import {angularCoreEnv} from './environment'; +import {reflectDependencies} from './util'; + +let _pendingPromises: Promise[] = []; + +/** + * Compile an Angular component according to its decorator metadata, and patch the resulting + * ngComponentDef onto the component type. + * + * Compilation may be asynchronous (due to the need to resolve URLs for the component template or + * other resources, for example). In the event that compilation is not immediate, `compileComponent` + * will return a `Promise` which will resolve when compilation completes and the component becomes + * usable. + */ +export function compileComponent(type: Type, metadata: Component): Promise|null { + // TODO(alxhub): implement ResourceLoader support for template compilation. + if (!metadata.template) { + throw new Error('templateUrl not yet supported'); + } + + // Parse the template and check for errors. + const template = parseTemplate(metadata.template !, `ng://${type.name}/template.html`); + if (template.errors !== undefined) { + const errors = template.errors.map(err => err.toString()).join(', '); + throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`); + } + + // The ConstantPool is a requirement of the JIT'er. + const constantPool = new ConstantPool(); + + // Compile the component metadata, including template, into an expression. + // TODO(alxhub): implement inputs, outputs, queries, etc. + const res = compileIvyComponent( + { + name: type.name, + type: new WrappedNodeExpr(type), + selector: metadata.selector !, template, + deps: reflectDependencies(type), + directives: new Map(), + pipes: new Map(), + host: { + attributes: {}, + listeners: {}, + properties: {}, + }, + inputs: {}, + outputs: {}, + lifecycle: { + usesOnChanges: false, + }, + queries: [], + typeSourceSpan: null !, + viewQueries: [], + }, + constantPool, makeBindingParser()); + + // Patch the generated expression as ngComponentDef on the type. + jitPatchDefinition(type, 'ngComponentDef', res.expression, angularCoreEnv, constantPool); + return null; +} + +/** + * A wrapper around `compileComponent` which is intended to be used for the `@Component` decorator. + * + * This wrapper keeps track of the `Promise` returned by `compileComponent` and will cause + * `awaitCurrentlyCompilingComponents` to wait on the compilation to be finished. + */ +export function compileComponentDecorator(type: Type, metadata: Component): void { + const res = compileComponent(type, metadata); + if (res !== null) { + _pendingPromises.push(res); + } +} + +/** + * Returns a promise which will await the compilation of any `@Component`s which have been defined + * since the last time `awaitCurrentlyCompilingComponents` was called. + */ +export function awaitCurrentlyCompilingComponents(): Promise { + const res = Promise.all(_pendingPromises).then(() => undefined); + _pendingPromises = []; + return res; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts new file mode 100644 index 0000000000..a5068eee8c --- /dev/null +++ b/packages/core/src/render3/jit/environment.ts @@ -0,0 +1,39 @@ +/** + * @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 {defineInjectable} from '../../di/defs'; +import {inject} from '../../di/injector'; +import {defineNgModule} from '../../metadata/ng_module'; +import * as r3 from '../index'; + + +/** + * A mapping of the @angular/core API surface used in generated expressions to the actual symbols. + * + * This should be kept up to date with the public exports of @angular/core. + */ +export const angularCoreEnv = { + 'ɵdefineComponent': r3.defineComponent, + 'defineInjectable': defineInjectable, + 'ɵdefineNgModule': defineNgModule, + 'ɵdirectiveInject': r3.directiveInject, + 'inject': inject, + 'ɵC': r3.C, + 'ɵE': r3.E, + 'ɵe': r3.e, + 'ɵi1': r3.i1, + 'ɵi2': r3.i2, + 'ɵi3': r3.i3, + 'ɵi4': r3.i4, + 'ɵi5': r3.i5, + 'ɵi6': r3.i6, + 'ɵi7': r3.i7, + 'ɵi8': r3.i8, + 'ɵT': r3.T, + 'ɵt': r3.t, +}; diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts new file mode 100644 index 0000000000..e0b4bc1a26 --- /dev/null +++ b/packages/core/src/render3/jit/injectable.ts @@ -0,0 +1,114 @@ +/** + * @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 {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitPatchDefinition} from '@angular/compiler'; + +import {Injectable} from '../../di/injectable'; +import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider'; +import {Type} from '../../type'; +import {getClosureSafeProperty} from '../../util/property'; + +import {angularCoreEnv} from './environment'; +import {convertDependencies, reflectDependencies} from './util'; + + +/** + * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting + * `ngInjectableDef` onto the injectable type. + */ +export function compileInjectable(type: Type, meta?: Injectable): void { + // TODO(alxhub): handle JIT of bare @Injectable(). + if (!meta) { + return; + } + + // Check whether the injectable metadata includes a provider specification. + const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || + isUseValueProvider(meta) || isUseExistingProvider(meta); + + let deps: R3DependencyMetadata[]|undefined = undefined; + if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { + deps = reflectDependencies(type); + } else if (isUseClassProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps); + } else if (isUseFactoryProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps) || []; + } + + // Decide which flavor of factory to generate, based on the provider specified. + // Only one of the use* fields should be set. + let useClass: Expression|undefined = undefined; + let useFactory: Expression|undefined = undefined; + let useValue: Expression|undefined = undefined; + let useExisting: Expression|undefined = undefined; + + if (!hasAProvider) { + // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. + // The deps will have been reflected above, causing the factory to create the class by calling + // its constructor with injected deps. + useClass = new WrappedNodeExpr(type); + } else if (isUseClassProvider(meta)) { + // The user explicitly specified useClass, and may or may not have provided deps. + useClass = new WrappedNodeExpr(meta.useClass); + } else if (isUseValueProvider(meta)) { + // The user explicitly specified useValue. + useValue = new WrappedNodeExpr(meta.useValue); + } else if (isUseFactoryProvider(meta)) { + // The user explicitly specified useFactory. + useFactory = new WrappedNodeExpr(meta.useFactory); + } else if (isUseExistingProvider(meta)) { + // The user explicitly specified useExisting. + useExisting = new WrappedNodeExpr(meta.useExisting); + } else { + // Can't happen - either hasAProvider will be false, or one of the providers will be set. + throw new Error(`Unreachable state.`); + } + + const {expression} = compileIvyInjectable({ + name: type.name, + type: new WrappedNodeExpr(type), + providedIn: computeProvidedIn(meta.providedIn), + useClass, + useFactory, + useValue, + useExisting, + deps, + }); + + jitPatchDefinition(type, 'ngInjectableDef', expression, angularCoreEnv); +} + +function computeProvidedIn(providedIn: Type| string | null | undefined): Expression { + if (providedIn == null || typeof providedIn === 'string') { + return new LiteralExpr(providedIn); + } else { + return new WrappedNodeExpr(providedIn); + } +} + +type UseClassProvider = Injectable & ClassSansProvider & {deps?: any[]}; + +function isUseClassProvider(meta: Injectable): meta is UseClassProvider { + return (meta as UseClassProvider).useClass !== undefined; +} + +const GET_PROPERTY_NAME = {} as any; +const USE_VALUE = getClosureSafeProperty( + {provide: String, useValue: GET_PROPERTY_NAME}, GET_PROPERTY_NAME); + +function isUseValueProvider(meta: Injectable): meta is Injectable&ValueSansProvider { + return USE_VALUE in meta; +} + +function isUseFactoryProvider(meta: Injectable): meta is Injectable&FactorySansProvider { + return (meta as FactorySansProvider).useFactory !== undefined; +} + +function isUseExistingProvider(meta: Injectable): meta is Injectable&ExistingSansProvider { + return (meta as ExistingSansProvider).useExisting !== undefined; +} diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts new file mode 100644 index 0000000000..991faf2913 --- /dev/null +++ b/packages/core/src/render3/jit/module.ts @@ -0,0 +1,94 @@ +/** + * @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 {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitPatchDefinition} from '@angular/compiler'; + +import {ModuleWithProviders, NgModule, NgModuleDef} from '../../metadata/ng_module'; +import {Type} from '../../type'; +import {ComponentDef} from '../interfaces/definition'; +import {flatten} from '../util'; + +import {angularCoreEnv} from './environment'; + +const EMPTY_ARRAY: Type[] = []; + +export function compileNgModule(type: Type, ngModule: NgModule): void { + const meta: R3NgModuleMetadata = { + type: wrap(type), + bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap), + declarations: flatten(ngModule.declarations || EMPTY_ARRAY).map(wrap), + imports: flatten(ngModule.imports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + exports: flatten(ngModule.exports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + emitInline: true, + }; + const res = compileIvyNgModule(meta); + + // Compute transitiveCompileScope + const transitiveCompileScope = { + directives: [] as any[], + pipes: [] as any[], + }; + flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => { + if (decl.ngPipeDef) { + transitiveCompileScope.pipes.push(decl); + } else if (decl.ngComponentDef) { + transitiveCompileScope.directives.push(decl); + patchComponentWithScope(decl, type as any); + } else { + transitiveCompileScope.directives.push(decl); + decl.ngSelectorScope = type; + } + }); + + function addExportsFrom(module: Type& {ngModuleDef: NgModuleDef}): void { + module.ngModuleDef.exports.forEach((exp: any) => { + if (isNgModule(exp)) { + addExportsFrom(exp); + } else if (exp.ngPipeDef) { + transitiveCompileScope.pipes.push(exp); + } else { + transitiveCompileScope.directives.push(exp); + } + }); + } + + flatten([(ngModule.imports || EMPTY_ARRAY), (ngModule.exports || EMPTY_ARRAY)]) + .filter(importExport => isNgModule(importExport)) + .forEach(mod => addExportsFrom(mod)); + jitPatchDefinition(type, 'ngModuleDef', res.expression, angularCoreEnv); + ((type as any).ngModuleDef as NgModuleDef).transitiveCompileScope = transitiveCompileScope; +} + +export function patchComponentWithScope( + component: Type& {ngComponentDef: ComponentDef}, + module: Type& {ngModuleDef: NgModuleDef}) { + component.ngComponentDef.directiveDefs = () => + module.ngModuleDef.transitiveCompileScope !.directives.map( + dir => dir.ngDirectiveDef || dir.ngComponentDef); + component.ngComponentDef.pipeDefs = () => + module.ngModuleDef.transitiveCompileScope !.pipes.map(pipe => pipe.ngPipeDef); +} + +function expandModuleWithProviders(value: Type| ModuleWithProviders): Type { + if (isModuleWithProviders(value)) { + return value.ngModule; + } + return value; +} + +function wrap(value: Type): Expression { + return new WrappedNodeExpr(value); +} + +function isModuleWithProviders(value: any): value is ModuleWithProviders { + return value.ngModule !== undefined; +} + +function isNgModule(value: any): value is Type&{ngModuleDef: NgModuleDef} { + return value.ngModuleDef !== undefined; +} diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts new file mode 100644 index 0000000000..3c07880c82 --- /dev/null +++ b/packages/core/src/render3/jit/util.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 {LiteralExpr, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler'; + +import {Injector} from '../../di/injector'; +import {Host, Inject, Optional, Self, SkipSelf} from '../../di/metadata'; +import {ElementRef} from '../../linker/element_ref'; +import {TemplateRef} from '../../linker/template_ref'; +import {ViewContainerRef} from '../../linker/view_container_ref'; +import {Attribute} from '../../metadata/di'; +import {ReflectionCapabilities} from '../../reflection/reflection_capabilities'; +import {Type} from '../../type'; + +let _reflect: ReflectionCapabilities|null = null; + +export function reflectDependencies(type: Type): R3DependencyMetadata[] { + _reflect = _reflect || new ReflectionCapabilities(); + return convertDependencies(_reflect.parameters(type)); +} + +export function convertDependencies(deps: any[]): R3DependencyMetadata[] { + return deps.map(dep => reflectDependency(dep)); +} + +function reflectDependency(dep: any | any[]): R3DependencyMetadata { + const meta: R3DependencyMetadata = { + token: new LiteralExpr(null), + host: false, + optional: false, + resolved: R3ResolvedDependencyType.Token, + self: false, + skipSelf: false, + }; + + function setTokenAndResolvedType(token: any): void { + if (token === ElementRef) { + meta.resolved = R3ResolvedDependencyType.ElementRef; + } else if (token === Injector) { + meta.resolved = R3ResolvedDependencyType.Injector; + } else if (token === TemplateRef) { + meta.resolved = R3ResolvedDependencyType.TemplateRef; + } else if (token === ViewContainerRef) { + meta.resolved = R3ResolvedDependencyType.ViewContainerRef; + } else { + meta.resolved = R3ResolvedDependencyType.Token; + } + meta.token = new WrappedNodeExpr(token); + } + + if (Array.isArray(dep)) { + if (dep.length === 0) { + throw new Error('Dependency array must have arguments.'); + } + for (let j = 0; j < dep.length; j++) { + const param = dep[j]; + if (param instanceof Optional || param.__proto__.ngMetadataName === 'Optional') { + meta.optional = true; + } else if (param instanceof SkipSelf || param.__proto__.ngMetadataName === 'SkipSelf') { + meta.skipSelf = true; + } else if (param instanceof Self || param.__proto__.ngMetadataName === 'Self') { + meta.self = true; + } else if (param instanceof Host || param.__proto__.ngMetadataName === 'Host') { + meta.host = true; + } else if (param instanceof Inject) { + meta.token = new WrappedNodeExpr(param.token); + } else if (param instanceof Attribute) { + if (param.attributeName === undefined) { + throw new Error(`Attribute name must be defined.`); + } + meta.token = new LiteralExpr(param.attributeName); + meta.resolved = R3ResolvedDependencyType.Attribute; + } else { + setTokenAndResolvedType(param); + } + } + } else { + setTokenAndResolvedType(dep); + } + return meta; +} diff --git a/packages/core/test/bundling/hello_world_jit/BUILD.bazel b/packages/core/test/bundling/hello_world_jit/BUILD.bazel new file mode 100644 index 0000000000..bd6acab2b3 --- /dev/null +++ b/packages/core/test/bundling/hello_world_jit/BUILD.bazel @@ -0,0 +1,58 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ts_library", "ivy_ng_module") +load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test") +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test", "rollup_bundle") +load("//tools/http-server:http_server.bzl", "http_server") + +ts_library( + name = "hello_world_jit", + srcs = ["index.ts"], + deps = [ + "//packages/core", + ], +) + +rollup_bundle( + name = "bundle", + # TODO(alexeagle): This is inconsistent. + # We try to teach users to always have their workspace at the start of a + # path, to disambiguate from other workspaces. + # Here, the rule implementation is looking in an execroot where the layout + # has an "external" directory for external dependencies. + # This should probably start with "angular/" and let the rule deal with it. + entry_point = "packages/core/test/bundling/hello_world_jit/index.js", + deps = [ + ":hello_world_jit", + "//packages/core", + ], +) + +ts_library( + name = "test_lib", + testonly = 1, + srcs = glob(["*_spec.ts"]), + deps = [ + "//packages:types", + "//packages/core", + "//packages/core/testing", + ], +) + +jasmine_node_test( + name = "test", + data = [ + ":bundle", + ":bundle.js", + ], + deps = [":test_lib"], +) + +http_server( + name = "devserver", + data = [ + "index.html", + ":bundle.min.js", + ":bundle.min_debug.js", + ], +) diff --git a/packages/core/test/bundling/hello_world_jit/index.html b/packages/core/test/bundling/hello_world_jit/index.html new file mode 100644 index 0000000000..c5c7bb3e0b --- /dev/null +++ b/packages/core/test/bundling/hello_world_jit/index.html @@ -0,0 +1,31 @@ + + + + + Angular Hello World Example + + + + + + + + + \ No newline at end of file diff --git a/packages/core/test/bundling/hello_world_jit/index.ts b/packages/core/test/bundling/hello_world_jit/index.ts new file mode 100644 index 0000000000..af6817a05e --- /dev/null +++ b/packages/core/test/bundling/hello_world_jit/index.ts @@ -0,0 +1,38 @@ +/** + * @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 'reflect-metadata'; + +import {Component, NgModule, ɵrenderComponent as renderComponent} from '@angular/core'; + +@Component({ + selector: 'greeting-cmp', + template: 'Hello World!', +}) +export class Greeting { +} + +@NgModule({ + declarations: [Greeting], + exports: [Greeting], +}) +export class GreetingModule { +} + +@Component({selector: 'hello-world', template: ''}) +export class HelloWorld { +} + +@NgModule({ + declarations: [HelloWorld], + imports: [GreetingModule], +}) +export class HelloWorldModule { +} + +renderComponent(HelloWorld); diff --git a/packages/core/test/bundling/hello_world_jit/integration_spec.ts b/packages/core/test/bundling/hello_world_jit/integration_spec.ts new file mode 100644 index 0000000000..ee349da0a6 --- /dev/null +++ b/packages/core/test/bundling/hello_world_jit/integration_spec.ts @@ -0,0 +1,23 @@ +/** + * @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 {ɵivyEnabled as ivyEnabled} from '@angular/core'; +import {withBody} from '@angular/core/testing'; +import * as fs from 'fs'; +import * as path from 'path'; + +const PACKAGE = 'angular/packages/core/test/bundling/hello_world_jit'; + +ivyEnabled && describe('Ivy JIT hello world', () => { + it('should render hello world', withBody('', () => { + require(path.join(PACKAGE, 'bundle.js')); + expect(document.body.textContent).toEqual('Hello World!'); + })); +}); + +xit('ensure at least one spec exists', () => {}); diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index 1c925c3984..c792f20640 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -1325,6 +1325,9 @@ { "name": "Quote" }, + { + "name": "R3_COMPILE_INJECTABLE" + }, { "name": "REMOVE_EVENT_LISTENER" }, @@ -3620,6 +3623,9 @@ { "name": "platformCoreDynamic" }, + { + "name": "preR3InjectableCompile" + }, { "name": "preparseElement" }, diff --git a/packages/core/test/render3/BUILD.bazel b/packages/core/test/render3/BUILD.bazel index b3c3334c71..a3f89ee72f 100644 --- a/packages/core/test/render3/BUILD.bazel +++ b/packages/core/test/render3/BUILD.bazel @@ -12,6 +12,7 @@ ts_library( "**/*_perf.ts", "domino.d.ts", "load_domino.ts", + "jit_spec.ts", ], ), deps = [ @@ -29,19 +30,28 @@ ts_library( ) ts_library( - name = "render3_node_lib", + name = "domino", testonly = 1, srcs = [ "domino.d.ts", "load_domino.ts", ], deps = [ - ":render3_lib", "//packages/platform-browser", "//packages/platform-server", ], ) +ts_library( + name = "render3_node_lib", + testonly = 1, + srcs = [], + deps = [ + ":domino", + ":render3_lib", + ], +) + jasmine_node_test( name = "render3", bootstrap = [ diff --git a/packages/core/test/render3/ivy/BUILD.bazel b/packages/core/test/render3/ivy/BUILD.bazel new file mode 100644 index 0000000000..0a14e7d6d7 --- /dev/null +++ b/packages/core/test/render3/ivy/BUILD.bazel @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +ts_library( + name = "ivy_lib", + testonly = 1, + srcs = glob(["**/*.ts"]), + deps = [ + "//packages:types", + "//packages/core", + ], +) + +ts_library( + name = "ivy_node_lib", + testonly = 1, + srcs = [], + deps = [ + ":ivy_lib", + "//packages/core/test/render3:domino", + ], +) + +jasmine_node_test( + name = "ivy", + bootstrap = [ + "angular/packages/core/test/render3/load_domino", + ], + deps = [ + ":ivy_node_lib", + ], +) + +ts_web_test( + name = "ivy_web", + deps = [ + ":ivy_lib", + ], +) diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts new file mode 100644 index 0000000000..f09edbe042 --- /dev/null +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -0,0 +1,166 @@ +/** + * @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 {Injectable} from '@angular/core/src/di/injectable'; +import {inject, setCurrentInjector} from '@angular/core/src/di/injector'; +import {ivyEnabled} from '@angular/core/src/ivy_switch'; +import {Component} from '@angular/core/src/metadata/directives'; +import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module'; +import {ComponentDef} from '@angular/core/src/render3/interfaces/definition'; + +ivyEnabled && describe('render3 jit', () => { + let injector: any; + beforeAll(() => { injector = setCurrentInjector(null); }); + + afterAll(() => { setCurrentInjector(injector); }); + + it('compiles a component', () => { + @Component({ + template: 'test', + selector: 'test-cmp', + }) + class SomeCmp { + } + const SomeCmpAny = SomeCmp as any; + + expect(SomeCmpAny.ngComponentDef).toBeDefined(); + expect(SomeCmpAny.ngComponentDef.factory() instanceof SomeCmp).toBe(true); + }); + + it('compiles an injectable with a type provider', () => { + @Injectable({providedIn: 'root'}) + class Service { + } + const ServiceAny = Service as any; + + expect(ServiceAny.ngInjectableDef).toBeDefined(); + expect(ServiceAny.ngInjectableDef.providedIn).toBe('root'); + expect(inject(Service) instanceof Service).toBe(true); + }); + + it('compiles an injectable with a useValue provider', () => { + @Injectable({providedIn: 'root', useValue: 'test'}) + class Service { + } + + expect(inject(Service)).toBe('test'); + }); + + it('compiles an injectable with a useExisting provider', () => { + @Injectable({providedIn: 'root', useValue: 'test'}) + class Existing { + } + + @Injectable({providedIn: 'root', useExisting: Existing}) + class Service { + } + + expect(inject(Service)).toBe('test'); + }); + + it('compiles an injectable with a useFactory provider, without deps', () => { + + @Injectable({providedIn: 'root', useFactory: () => 'test'}) + class Service { + } + + expect(inject(Service)).toBe('test'); + }); + + it('compiles an injectable with a useFactory provider, with deps', () => { + @Injectable({providedIn: 'root', useValue: 'test'}) + class Existing { + } + + @Injectable({providedIn: 'root', useFactory: (existing: any) => existing, deps: [Existing]}) + class Service { + } + + expect(inject(Service)).toBe('test'); + }); + + it('compiles an injectable with a useClass provider, with deps', () => { + @Injectable({providedIn: 'root', useValue: 'test'}) + class Existing { + } + + class Other { + constructor(public value: any) {} + } + + @Injectable({providedIn: 'root', useClass: Other, deps: [Existing]}) + class Service { + get value(): any { return null; } + } + const ServiceAny = Service as any; + + expect(inject(Service).value).toBe('test'); + }); + + it('compiles an injectable with a useClass provider, without deps', () => { + let _value = 1; + @Injectable({providedIn: 'root'}) + class Existing { + readonly value = _value++; + } + + @Injectable({providedIn: 'root', useClass: Existing}) + class Service { + get value(): number { return 0; } + } + + expect(inject(Existing).value).toBe(1); + const injected = inject(Service); + expect(injected instanceof Existing).toBe(true); + expect(injected.value).toBe(2); + }); + + it('compiles a module to a definition', () => { + @Component({ + template: 'foo', + selector: 'foo', + }) + class Cmp { + } + + @NgModule({ + declarations: [Cmp], + }) + class Module { + } + + const moduleDef: NgModuleDef = (Module as any).ngModuleDef; + expect(moduleDef).toBeDefined(); + expect(moduleDef.declarations.length).toBe(1); + expect(moduleDef.declarations[0]).toBe(Cmp); + }); + + it('patches a module onto the component', () => { + @Component({ + template: 'foo', + selector: 'foo', + }) + class Cmp { + } + const cmpDef: ComponentDef = (Cmp as any).ngComponentDef; + + expect(cmpDef.directiveDefs).toBeNull(); + + @NgModule({ + declarations: [Cmp], + }) + class Module { + } + + const moduleDef: NgModuleDef = (Module as any).ngModuleDef; + expect(cmpDef.directiveDefs instanceof Function).toBe(true); + expect((cmpDef.directiveDefs as Function)()).toEqual([cmpDef]); + }); +}); + +it('ensure at least one spec exists', () => {}); From 6d8c847e7b7356f269ce1444657974dd6e14885c Mon Sep 17 00:00:00 2001 From: Patrick McDonald Date: Fri, 18 May 2018 12:49:09 -0400 Subject: [PATCH 080/582] docs: fix typo (#23998) "Made" doesn't make sense (redoing and closing #23940) PR Close #23998 --- aio/content/guide/router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/router.md b/aio/content/guide/router.md index f50fc0f2af..d32f8ba9c5 100644 --- a/aio/content/guide/router.md +++ b/aio/content/guide/router.md @@ -3656,7 +3656,7 @@ Lazy loading has multiple benefits. * You can speed up load time for users that only visit certain areas of the application. * You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle. -You're already made part way there. +You're already part of the way there. By organizing the application into modules—`AppModule`, `HeroesModule`, `AdminModule` and `CrisisCenterModule`—you have natural candidates for lazy loading. From 608c3748e870b7ccdb38502aa1f43b603422820a Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 16 May 2018 15:04:27 -0500 Subject: [PATCH 081/582] docs(aio): Remove outdated README.md from cli-quickstart zip (#23947) Closes #23936 PR Close #23947 --- aio/content/examples/cli-quickstart/README.md | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 aio/content/examples/cli-quickstart/README.md diff --git a/aio/content/examples/cli-quickstart/README.md b/aio/content/examples/cli-quickstart/README.md deleted file mode 100644 index efe9ec67ae..0000000000 --- a/aio/content/examples/cli-quickstart/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# MasterProject - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0. - -## Development server -Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. - -## Build - -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -Before running the tests make sure you are serving the app via `ng serve`. - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). From 26fbf1d13c6116f686d33b41390de94a7214dd5a Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Wed, 16 May 2018 10:00:33 -0700 Subject: [PATCH 082/582] feat(platform-browser): add HammerJS lazy-loader symbols to public API (#23943) PR Close #23943 --- packages/platform-browser/src/platform-browser.ts | 3 ++- tools/public_api_guard/platform-browser/platform-browser.d.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/platform-browser/src/platform-browser.ts b/packages/platform-browser/src/platform-browser.ts index 6587f011d8..28cee18de7 100644 --- a/packages/platform-browser/src/platform-browser.ts +++ b/packages/platform-browser/src/platform-browser.ts @@ -14,7 +14,8 @@ export {BrowserTransferStateModule, StateKey, TransferState, makeStateKey} from export {By} from './dom/debug/by'; export {DOCUMENT} from './dom/dom_tokens'; export {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager'; -export {HAMMER_GESTURE_CONFIG, HammerGestureConfig} from './dom/events/hammer_gestures'; +export {HAMMER_GESTURE_CONFIG, HAMMER_LOADER, HammerGestureConfig, HammerLoader} from './dom/events/hammer_gestures'; export {DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl, SafeValue} from './security/dom_sanitization_service'; + export * from './private_export'; export {VERSION} from './version'; diff --git a/tools/public_api_guard/platform-browser/platform-browser.d.ts b/tools/public_api_guard/platform-browser/platform-browser.d.ts index 4c4d8afca5..d56b6164c4 100644 --- a/tools/public_api_guard/platform-browser/platform-browser.d.ts +++ b/tools/public_api_guard/platform-browser/platform-browser.d.ts @@ -46,6 +46,8 @@ export declare class EventManager { /** @experimental */ export declare const HAMMER_GESTURE_CONFIG: InjectionToken; +export declare const HAMMER_LOADER: InjectionToken; + /** @experimental */ export declare class HammerGestureConfig { events: string[]; @@ -65,6 +67,8 @@ export declare class HammerGestureConfig { buildHammer(element: HTMLElement): HammerInstance; } +export declare type HammerLoader = (() => Promise) | null; + /** @experimental */ export declare function makeStateKey(key: string): StateKey; From 373fa78d7fbf10d22cd9f9c829720189e04daf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Tue, 22 May 2018 10:54:37 -0700 Subject: [PATCH 083/582] fix: merge collision (#24054) PR Close #24054 --- packages/core/test/render3/ivy/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/test/render3/ivy/BUILD.bazel b/packages/core/test/render3/ivy/BUILD.bazel index 0a14e7d6d7..57a2439653 100644 --- a/packages/core/test/render3/ivy/BUILD.bazel +++ b/packages/core/test/render3/ivy/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -33,7 +33,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "ivy_web", deps = [ ":ivy_lib", From 0bdd30e34f87305085afc2825749ea2dc04a9409 Mon Sep 17 00:00:00 2001 From: Heo Sangmin Date: Thu, 10 May 2018 00:11:43 +0900 Subject: [PATCH 084/582] fix(service-worker): check platformBrowser before accessing navigator.serviceWorker (#21231) PR Close #21231 --- packages/service-worker/src/low_level.ts | 9 +- packages/service-worker/src/module.ts | 4 +- packages/service-worker/test/comm_spec.ts | 103 ++++++++++++++++-- .../service-worker/test/integration_spec.ts | 2 +- 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/packages/service-worker/src/low_level.ts b/packages/service-worker/src/low_level.ts index fbb201d078..6929c90d22 100644 --- a/packages/service-worker/src/low_level.ts +++ b/packages/service-worker/src/low_level.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {isPlatformBrowser} from '@angular/common'; -import {Inject, PLATFORM_ID} from '@angular/core'; import {ConnectableObservable, Observable, concat, defer, fromEvent, of , throwError} from 'rxjs'; import {filter, map, publish, switchMap, take, tap} from 'rxjs/operators'; @@ -71,11 +69,8 @@ export class NgswCommChannel { */ readonly events: Observable; - constructor( - private serviceWorker: ServiceWorkerContainer|undefined, - @Inject(PLATFORM_ID) platformId: string) { - if (!serviceWorker || !isPlatformBrowser(platformId)) { - this.serviceWorker = undefined; + constructor(private serviceWorker: ServiceWorkerContainer|undefined) { + if (!serviceWorker) { this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED); } else { const controllerChangeEvents = diff --git a/packages/service-worker/src/module.ts b/packages/service-worker/src/module.ts index fd38a8a136..1947582891 100644 --- a/packages/service-worker/src/module.ts +++ b/packages/service-worker/src/module.ts @@ -8,7 +8,6 @@ import {isPlatformBrowser} from '@angular/common'; import {APP_INITIALIZER, ApplicationRef, InjectionToken, Injector, ModuleWithProviders, NgModule, PLATFORM_ID} from '@angular/core'; -import {Observable} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {NgswCommChannel} from './low_level'; @@ -53,7 +52,8 @@ export function ngswAppInitializer( export function ngswCommChannelFactory( opts: RegistrationOptions, platformId: string): NgswCommChannel { return new NgswCommChannel( - opts.enabled !== false ? navigator.serviceWorker : undefined, platformId); + isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker : + undefined); } /** diff --git a/packages/service-worker/test/comm_spec.ts b/packages/service-worker/test/comm_spec.ts index 46400c4242..49a5a312fb 100644 --- a/packages/service-worker/test/comm_spec.ts +++ b/packages/service-worker/test/comm_spec.ts @@ -6,9 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ +import {PLATFORM_ID} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {NgswCommChannel} from '../src/low_level'; +import {RegistrationOptions, ngswCommChannelFactory} from '../src/module'; import {SwPush} from '../src/push'; import {SwUpdate} from '../src/update'; import {MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../testing/mock'; @@ -19,12 +21,12 @@ import {MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../test let comm: NgswCommChannel; beforeEach(() => { mock = new MockServiceWorkerContainer(); - comm = new NgswCommChannel(mock as any, 'browser'); + comm = new NgswCommChannel(mock as any); }); describe('NgswCommsChannel', () => { it('can access the registration when it comes before subscription', (done: DoneFn) => { const mock = new MockServiceWorkerContainer(); - const comm = new NgswCommChannel(mock as any, 'browser'); + const comm = new NgswCommChannel(mock as any); const regPromise = mock.getRegistration() as any as MockServiceWorkerRegistration; mock.setupSw(); @@ -33,18 +35,101 @@ import {MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../test }); it('can access the registration when it comes after subscription', (done: DoneFn) => { const mock = new MockServiceWorkerContainer(); - const comm = new NgswCommChannel(mock as any, 'browser'); + const comm = new NgswCommChannel(mock as any); const regPromise = mock.getRegistration() as any as MockServiceWorkerRegistration; (comm as any).registration.subscribe((reg: any) => { done(); }); mock.setupSw(); }); - it('is disabled for platform-server', () => { - const mock = new MockServiceWorkerContainer(); - const comm = new NgswCommChannel(mock as any, 'server'); - expect(comm.isEnabled).toEqual(false); + }); + describe('ngswCommChannelFactory', () => { + it('gives disabled NgswCommChannel for platform-server', () => { + TestBed.configureTestingModule({ + providers: [ + {provide: PLATFORM_ID, useValue: 'server'}, + {provide: RegistrationOptions, useValue: {enabled: true}}, { + provide: NgswCommChannel, + useFactory: ngswCommChannelFactory, + deps: [RegistrationOptions, PLATFORM_ID] + } + ] + }); + + expect(TestBed.get(NgswCommChannel).isEnabled).toEqual(false); }); + it('gives disabled NgswCommChannel when \'enabled\' option is false', () => { + TestBed.configureTestingModule({ + providers: [ + {provide: PLATFORM_ID, useValue: 'browser'}, + {provide: RegistrationOptions, useValue: {enabled: false}}, { + provide: NgswCommChannel, + useFactory: ngswCommChannelFactory, + deps: [RegistrationOptions, PLATFORM_ID] + } + ] + }); + + expect(TestBed.get(NgswCommChannel).isEnabled).toEqual(false); + }); + it('gives disabled NgswCommChannel when navigator.serviceWorker is undefined', () => { + TestBed.configureTestingModule({ + providers: [ + {provide: PLATFORM_ID, useValue: 'browser'}, + {provide: RegistrationOptions, useValue: {enabled: true}}, + { + provide: NgswCommChannel, + useFactory: ngswCommChannelFactory, + deps: [RegistrationOptions, PLATFORM_ID], + }, + ], + }); + + const context: any = global || window; + const originalDescriptor = Object.getOwnPropertyDescriptor(context, 'navigator'); + const patchedDescriptor = {value: {serviceWorker: undefined}, configurable: true}; + + try { + // Set `navigator` to `{serviceWorker: undefined}`. + Object.defineProperty(context, 'navigator', patchedDescriptor); + expect(TestBed.get(NgswCommChannel).isEnabled).toBe(false); + } finally { + if (originalDescriptor) { + Object.defineProperty(context, 'navigator', originalDescriptor); + } else { + delete context.navigator; + } + } + }); + it('gives enabled NgswCommChannel when browser supports SW and enabled option is true', + () => { + TestBed.configureTestingModule({ + providers: [ + {provide: PLATFORM_ID, useValue: 'browser'}, + {provide: RegistrationOptions, useValue: {enabled: true}}, { + provide: NgswCommChannel, + useFactory: ngswCommChannelFactory, + deps: [RegistrationOptions, PLATFORM_ID] + } + ] + }); + + const context: any = global || window; + const originalDescriptor = Object.getOwnPropertyDescriptor(context, 'navigator'); + const patchedDescriptor = {value: {serviceWorker: mock}, configurable: true}; + + try { + // Set `navigator` to `{serviceWorker: mock}`. + Object.defineProperty(context, 'navigator', patchedDescriptor); + expect(TestBed.get(NgswCommChannel).isEnabled).toBe(true); + } finally { + if (originalDescriptor) { + Object.defineProperty(context, 'navigator', originalDescriptor); + } else { + delete context.navigator; + } + } + }); }); describe('SwPush', () => { let push: SwPush; @@ -76,7 +161,7 @@ import {MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../test expect(() => TestBed.get(SwPush)).not.toThrow(); }); describe('with no SW', () => { - beforeEach(() => { comm = new NgswCommChannel(undefined, 'browser'); }); + beforeEach(() => { comm = new NgswCommChannel(undefined); }); it('can be instantiated', () => { push = new SwPush(comm); }); it('does not crash on subscription to observables', () => { push = new SwPush(comm); @@ -170,7 +255,7 @@ import {MockServiceWorkerContainer, MockServiceWorkerRegistration} from '../test expect(() => TestBed.get(SwUpdate)).not.toThrow(); }); describe('with no SW', () => { - beforeEach(() => { comm = new NgswCommChannel(undefined, 'browser'); }); + beforeEach(() => { comm = new NgswCommChannel(undefined); }); it('can be instantiated', () => { update = new SwUpdate(comm); }); it('does not crash on subscription to observables', () => { update = new SwUpdate(comm); diff --git a/packages/service-worker/test/integration_spec.ts b/packages/service-worker/test/integration_spec.ts index f4ff957fa4..acd1ed0b81 100644 --- a/packages/service-worker/test/integration_spec.ts +++ b/packages/service-worker/test/integration_spec.ts @@ -80,7 +80,7 @@ const serverUpdate = async_beforeEach(async() => { // Fire up the client. mock = new MockServiceWorkerContainer(); - comm = new NgswCommChannel(mock as any, 'browser'); + comm = new NgswCommChannel(mock as any); scope = new SwTestHarnessBuilder().withServerState(server).build(); driver = new Driver(scope, scope, new CacheDatabase(scope, scope)); From fb906a87e88401bdaabaf3216300c4848b8b0888 Mon Sep 17 00:00:00 2001 From: thebyteman Date: Tue, 15 May 2018 16:28:42 -0400 Subject: [PATCH 085/582] docs(aio): fix typo (#23925) PR Close #23925 --- .../shared/boilerplate/cli/src/environments/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/tools/examples/shared/boilerplate/cli/src/environments/environment.ts b/aio/tools/examples/shared/boilerplate/cli/src/environments/environment.ts index 012182efa3..bed42878cd 100644 --- a/aio/tools/examples/shared/boilerplate/cli/src/environments/environment.ts +++ b/aio/tools/examples/shared/boilerplate/cli/src/environments/environment.ts @@ -1,5 +1,5 @@ // This file can be replaced during build by using the `fileReplacements` array. -// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { From bd149e5d674fd15971f80d81d2871b4a94d48c48 Mon Sep 17 00:00:00 2001 From: JoostK Date: Tue, 15 May 2018 21:07:59 +0200 Subject: [PATCH 086/582] fix(ivy): compile interpolated bindings without superfluous bind instruction (#23923) This fixes the case where the compiler would generate a bind(interpolation#()) instruction. PR Close #23923 --- .../compiler/src/render3/r3_identifiers.ts | 2 +- .../compiler/src/render3/view/template.ts | 27 ++-- .../render3/r3_view_compiler_binding_spec.ts | 123 ++++++++++++++++++ 3 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 packages/compiler/test/render3/r3_view_compiler_binding_spec.ts diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index bbde7f2f02..80db996ec2 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -33,7 +33,7 @@ export class Identifiers { static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE}; - static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; + static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index e79e802377..91e2f4b8b9 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; import {Lexer} from '../../expression_parser/lexer'; import {Parser} from '../../expression_parser/parser'; import * as html from '../../ml_parser/ast'; @@ -340,10 +340,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const instruction = BINDING_INSTRUCTION_MAP[input.type]; if (instruction) { // TODO(chuckj): runtime: security context? - const value = o.importExpr(R3.bind).callFn([convertedBinding]); this.instruction( this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex), - o.literal(input.name), value); + o.literal(input.name), convertedBinding); } else { this._unsupported(`binding type ${input.type}`); } @@ -418,7 +417,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const convertedBinding = this.convertPropertyBinding(context, input.value); this.instruction( this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex), - o.literal(input.name), o.importExpr(R3.bind).callFn([convertedBinding])); + o.literal(input.name), convertedBinding); }); // Create the template function @@ -443,7 +442,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex)); this.instruction( - this._bindingCode, text.sourceSpan, R3.textCreateBound, o.literal(nodeIndex), + this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value)); } @@ -483,11 +482,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression { const pipesConvertedValue = value.visit(this._valueConverter); - const convertedPropertyBinding = convertPropertyBinding( - this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, - interpolate); - this._bindingCode.push(...convertedPropertyBinding.stmts); - return convertedPropertyBinding.currValExpr; + if (pipesConvertedValue instanceof Interpolation) { + const convertedPropertyBinding = convertPropertyBinding( + this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, + interpolate); + this._bindingCode.push(...convertedPropertyBinding.stmts); + return convertedPropertyBinding.currValExpr; + } else { + const convertedPropertyBinding = convertPropertyBinding( + this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, + () => error('Unexpected interpolation')); + this._bindingCode.push(...convertedPropertyBinding.stmts); + return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]); + } } } diff --git a/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts new file mode 100644 index 0000000000..8b0a9edcbe --- /dev/null +++ b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts @@ -0,0 +1,123 @@ +/** + * @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 {MockDirectory, setup} from '../aot/test_util'; +import {compile, expectEmit} from './mock_compile'; + +describe('compiler compliance: bindings', () => { + const angularFiles = setup({ + compileAngular: true, + compileAnimations: false, + compileCommon: false, + }); + + describe('text bindings', () => { + it('should generate interpolation instruction', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + @Component({ + selector: 'my-component', + template: \` +
Hello {{ name }}
\` + }) + export class MyComponent { + name = 'World'; + } + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'div'); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵt(1, $i0$.ɵi1('Hello ', $ctx$.name, '')); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect interpolated text binding'); + }); + }); + + describe('property bindings', () => { + it('should generate bind instruction', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-app', + template: '' + }) + export class MyComponent { + title = 'Hello World'; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {}` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'a'); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title)); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect property binding'); + }); + + it('should generate interpolation instruction for {{...}} bindings', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + @Component({ + selector: 'my-component', + template: \` + \` + }) + export class MyComponent { + name = 'World'; + } + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'a'); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, '')); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect interpolated property binding'); + }); + }); + +}); From 23a98b9e5134df3b96dd932173ce86384d72ada0 Mon Sep 17 00:00:00 2001 From: Judy Bogart Date: Wed, 2 May 2018 14:45:19 -0700 Subject: [PATCH 087/582] docs: add doc to event-management api (#23656) PR Close #23656 --- .../src/dom/events/event_manager.ts | 31 +++++++++++++ .../src/dom/events/hammer_gestures.ts | 45 +++++++++++++++++-- .../src/dom/events/key_events.ts | 32 +++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/packages/platform-browser/src/dom/events/event_manager.ts b/packages/platform-browser/src/dom/events/event_manager.ts index 4f245aa315..42217b304f 100644 --- a/packages/platform-browser/src/dom/events/event_manager.ts +++ b/packages/platform-browser/src/dom/events/event_manager.ts @@ -10,29 +10,60 @@ import {Inject, Injectable, InjectionToken, NgZone} from '@angular/core'; import {getDOM} from '../dom_adapter'; +/** + * The injection token for the event-manager plug-in service. + */ export const EVENT_MANAGER_PLUGINS = new InjectionToken('EventManagerPlugins'); +/** + * An injectable service that provides event management for Angular + * through a browser plug-in. + */ @Injectable() export class EventManager { private _plugins: EventManagerPlugin[]; private _eventNameToPlugin = new Map(); + /** + * Initializes an instance of the event-manager service. + */ constructor(@Inject(EVENT_MANAGER_PLUGINS) plugins: EventManagerPlugin[], private _zone: NgZone) { plugins.forEach(p => p.manager = this); this._plugins = plugins.slice().reverse(); } + /** + * Registers a handler for a specific element and event. + * + * @param element The HTML element to receive event notifications. + * @param eventName The name of the event to listen for. + * @param handler A function to call when the notification occurs. Receives the + * event object as an argument. + * @returns A callback function that can be used to remove the handler. + */ addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { const plugin = this._findPluginFor(eventName); return plugin.addEventListener(element, eventName, handler); } + /** + * Registers a global handler for an event in a target view. + * + * @param target A target for global event notifications. One of "window", "document", or "body". + * @param eventName The name of the event to listen for. + * @param handler A function to call when the notification occurs. Receives the + * event object as an argument. + * @returns A callback function that can be used to remove the handler. + */ addGlobalEventListener(target: string, eventName: string, handler: Function): Function { const plugin = this._findPluginFor(eventName); return plugin.addGlobalEventListener(target, eventName, handler); } + /** + * Retrieves the compilation zone in which event listeners are registered. + */ getZone(): NgZone { return this._zone; } /** @internal */ diff --git a/packages/platform-browser/src/dom/events/hammer_gestures.ts b/packages/platform-browser/src/dom/events/hammer_gestures.ts index e51126de6c..f4f4b9c09c 100644 --- a/packages/platform-browser/src/dom/events/hammer_gestures.ts +++ b/packages/platform-browser/src/dom/events/hammer_gestures.ts @@ -12,6 +12,9 @@ import {DOCUMENT} from '../dom_tokens'; import {EventManagerPlugin} from './event_manager'; +/** + * Supported HammerJS recognizer event names. + */ const EVENT_NAMES = { // pan 'pan': true, @@ -51,8 +54,8 @@ const EVENT_NAMES = { }; /** - * A DI token that you can use to provide{@link HammerGestureConfig} to Angular. Use it to configure - * Hammer gestures. + * DI token for providing [HammerJS](http://hammerjs.github.io/) support to Angular. + * @see `HammerGestureConfig` * * @experimental */ @@ -71,14 +74,44 @@ export interface HammerInstance { } /** + * An injectable [HammerJS Manager](http://hammerjs.github.io/api/#hammer.manager) + * for gesture recognition. Configures specific event recognition. * @experimental */ @Injectable() export class HammerGestureConfig { + /** + * A set of supported event names for gestures to be used in Angular. + * Angular supports all built-in recognizers, as listed in + * [HammerJS documentation](http://hammerjs.github.io/). + */ events: string[] = []; + /** + * Maps gesture event names to a set of configuration options + * that specify overrides to the default values for specific properties. + * + * The key is a supported event name to be configured, + * and the options object contains a set of properties, with override values + * to be applied to the named recognizer event. + * For example, to disable recognition of the rotate event, specify + * `{"rotate": {"enable": false}}`. + * + * Properties that are not present take the HammerJS default values. + * For information about which properties are supported for which events, + * and their allowed and default values, see + * [HammerJS documentation](http://hammerjs.github.io/). + * + */ overrides: {[key: string]: Object} = {}; + /** + * Properties whose default values can be overridden for a given event. + * Different sets of properties apply to different events. + * For information about which properties are supported for which events, + * and their allowed and default values, see + * [HammerJS documentation](http://hammerjs.github.io/). + */ options?: { cssProps?: any; domEvents?: boolean; enable?: boolean | ((manager: any) => boolean); preset?: any[]; @@ -88,8 +121,14 @@ export class HammerGestureConfig { inputTarget?: EventTarget; }; + /** + * Creates a [HammerJS Manager](http://hammerjs.github.io/api/#hammer.manager) + * and attaches it to a given HTML element. + * @param element The element that will recognize gestures. + * @returns A HammerJS event-manager object. + */ buildHammer(element: HTMLElement): HammerInstance { - const mc = new Hammer(element, this.options); + const mc = new Hammer !(element, this.options); mc.get('pinch').set({enable: true}); mc.get('rotate').set({enable: true}); diff --git a/packages/platform-browser/src/dom/events/key_events.ts b/packages/platform-browser/src/dom/events/key_events.ts index 5d0d7f2faf..bcf26c9ec9 100644 --- a/packages/platform-browser/src/dom/events/key_events.ts +++ b/packages/platform-browser/src/dom/events/key_events.ts @@ -13,7 +13,14 @@ import {DOCUMENT} from '../dom_tokens'; import {EventManagerPlugin} from './event_manager'; +/** + * Defines supported modifiers for key events. + */ const MODIFIER_KEYS = ['alt', 'control', 'meta', 'shift']; + +/** + * Retrieves modifiers from key-event objects. + */ const MODIFIER_KEY_GETTERS: {[key: string]: (event: KeyboardEvent) => boolean} = { 'alt': (event: KeyboardEvent) => event.altKey, 'control': (event: KeyboardEvent) => event.ctrlKey, @@ -23,13 +30,31 @@ const MODIFIER_KEY_GETTERS: {[key: string]: (event: KeyboardEvent) => boolean} = /** * @experimental + * A browser plug-in that provides support for handling of key events in Angular. */ @Injectable() export class KeyEventsPlugin extends EventManagerPlugin { + /** + * Initializes an instance of the browser plug-in. + * @param doc The document in which key events will be detected. + */ constructor(@Inject(DOCUMENT) doc: any) { super(doc); } + /** + * Reports whether a named key event is supported. + * @param eventName The event name to query. + * @return True if the named key event is supported. + */ supports(eventName: string): boolean { return KeyEventsPlugin.parseEventName(eventName) != null; } + /** + * Registers a handler for a specific element and key event. + * @param element The HTML element to receive event notifications. + * @param eventName The name of the key event to listen for. + * @param handler A function to call when the notification occurs. Receives the + * event object as an argument. + * @returns The key event that was registered. + */ addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { const parsedEvent = KeyEventsPlugin.parseEventName(eventName) !; @@ -93,6 +118,13 @@ export class KeyEventsPlugin extends EventManagerPlugin { return fullKey; } + /** + * Configures a handler callback for a key event. + * @param fullKey The event name that combines all simultaneous keystrokes. + * @param handler The function that responds to the key event. + * @param zone The zone in which the event occurred. + * @returns A callback function. + */ static eventCallback(fullKey: any, handler: Function, zone: NgZone): Function { return (event: any /** TODO #9100 */) => { if (KeyEventsPlugin.getEventFullKey(event) === fullKey) { From 13cb75da8b02ed7aa60508dbb5ecd9065809fd12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 22 May 2018 16:16:26 -0700 Subject: [PATCH 088/582] release: cut the v6.0.3 release --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51041a4512..efd682c40f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22) + + +### Bug Fixes + +* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0ee5b7e](https://github.com/angular/angular/commit/0ee5b7e)) + + + ## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15) diff --git a/package.json b/package.json index b38d5d6dbf..1b0123cf83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.0.2", + "version": "6.0.3", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", From 821665768186a78837671975c26f67ed37cb82eb Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Wed, 16 May 2018 05:56:01 -0700 Subject: [PATCH 089/582] refactor(ivy): add tNodes for view nodes and hosts (#24113) PR Close #24113 --- packages/core/src/render3/component.ts | 2 +- packages/core/src/render3/di.ts | 20 +++-- packages/core/src/render3/instructions.ts | 79 +++++++++++-------- packages/core/src/render3/interfaces/node.ts | 12 ++- packages/core/src/render3/interfaces/view.ts | 12 +++ .../core/src/render3/node_manipulation.ts | 2 +- packages/core/src/render3/query.ts | 5 +- .../core/test/render3/instructions_spec.ts | 6 +- .../core/test/render3/integration_spec.ts | 2 +- .../render3/node_selector_matcher_spec.ts | 3 +- 10 files changed, 92 insertions(+), 51 deletions(-) diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index a0cc5a4602..53f7d15565 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -187,7 +187,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef): v // Root component is always created at dir index 0 queueInitHooks(0, def.onInit, def.doCheck, elementNode.view.tView); - queueLifecycleHooks(elementNode.tNode !.flags, elementNode.view); + queueLifecycleHooks(elementNode.tNode.flags, elementNode.view); } /** diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 030afd5ac6..a62b8a17d7 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -19,7 +19,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie import {Type} from '../type'; import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; -import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; +import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node'; @@ -254,7 +254,7 @@ export function injectAttribute(attrName: string): string|undefined { ngDevMode && assertPreviousIsParent(); const lElement = getPreviousOrParentNode() as LElementNode; ngDevMode && assertNodeType(lElement, LNodeType.Element); - const tElement = lElement.tNode !; + const tElement = lElement.tNode; ngDevMode && assertNotNull(tElement, 'expecting tNode'); const attrs = tElement.attrs; if (attrs) { @@ -278,7 +278,7 @@ export function getOrCreateChangeDetectorRef( if (di.changeDetectorRef) return di.changeDetectorRef; const currentNode = di.node; - if (isComponent(currentNode.tNode !)) { + if (isComponent(currentNode.tNode)) { return di.changeDetectorRef = createViewRef(currentNode.data as LView, context); } else if (currentNode.type === LNodeType.Element) { return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); @@ -298,7 +298,7 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): createViewRef( hostNode.data as LView, hostNode.view - .directives ![hostNode.tNode !.flags >> TNodeFlags.DirectiveStartingIndexShift]); + .directives ![hostNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift]); } /** @@ -361,7 +361,7 @@ export function getOrCreateInjectable( // At this point, we have an injector which *may* contain the token, so we step through the // directives associated with the injector's corresponding node to get the directive instance. const node = injector.node; - const nodeFlags = node.tNode !.flags; + const nodeFlags = node.tNode.flags; const count = nodeFlags & TNodeFlags.DirectiveCountMask; if (count !== 0) { @@ -572,8 +572,12 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer const lContainerNode: LContainerNode = createLNodeObject( LNodeType.Container, vcRefHost.view, vcRefHost.parent !, undefined, lContainer, null); - // TODO(kara): Separate into own TNode when moving parent/child properties - lContainerNode.tNode = vcRefHost.tNode; + const hostTNode = vcRefHost.tNode; + if (!hostTNode.dynamicContainerNode) { + hostTNode.dynamicContainerNode = createTNode(hostTNode.index, null, null, null); + } + + lContainerNode.tNode = hostTNode.dynamicContainerNode; vcRefHost.dynamicLContainerNode = lContainerNode; addToViewTree(vcRefHost.view, lContainer); @@ -699,7 +703,7 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef if (!di.templateRef) { ngDevMode && assertNodeType(di.node, LNodeType.Container); const hostNode = di.node as LContainerNode; - const hostTNode = hostNode.tNode !; + const hostTNode = hostNode.tNode; const hostTView = hostNode.view.tView; if (!hostTNode.tViews) { hostTNode.tViews = createTView(hostTView.directiveRegistry, hostTView.pipeRegistry); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 9768799c7c..4ee9a40b64 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -349,7 +349,7 @@ export function createLNodeObject( nodeInjector: parent ? parent.nodeInjector : null, data: state, queries: queries, - tNode: null, + tNode: null !, pNextOrParent: null, dynamicLContainerNode: null }; @@ -371,7 +371,7 @@ export function createLNode( index: number | null, type: LNodeType.Element, native: RElement | RText | null, name: string | null, attrs: string[] | null, lView?: LView | null): LElementNode; export function createLNode( - index: null, type: LNodeType.View, native: null, name: null, attrs: null, + index: number | null, type: LNodeType.View, native: null, name: null, attrs: null, lView: LView): LViewNode; export function createLNode( index: number, type: LNodeType.Container, native: undefined, name: string | null, @@ -392,14 +392,12 @@ export function createLNode( const node = createLNodeObject(type, currentView, parent, native, isState ? state as any : null, queries); - if ((type & LNodeType.ViewOrElement) === LNodeType.ViewOrElement && isState) { - // Bit of a hack to bust through the readonly because there is a circular dep between - // LView and LNode. - ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized'); - (state as LView as{node: LNode}).node = node; - } - if (index != null) { - // We are Element or Container + if (index === null || type === LNodeType.View) { + // View nodes are not stored in data because they can be added / removed at runtime (which + // would cause indices to change). Their TNodes are instead stored in TView.node. + node.tNode = (state as LView).tView.node || createTNode(index, null, null, null); + } else { + // This is an element or container or projection node ngDevMode && assertDataNext(index); data[index] = node; @@ -407,7 +405,9 @@ export function createLNode( if (index >= tData.length) { const tNode = tData[index] = createTNode(index, name, attrs, null); if (!isParent && previousOrParentNode) { - previousOrParentNode.tNode !.next = tNode; + const previousTNode = previousOrParentNode.tNode; + previousTNode.next = tNode; + if (previousTNode.dynamicContainerNode) previousTNode.dynamicContainerNode.next = tNode; } } node.tNode = tData[index] as TNode; @@ -427,6 +427,16 @@ export function createLNode( } } } + + // View nodes and host elements need to set their host node (components set host nodes later) + if ((type & LNodeType.ViewOrElement) === LNodeType.ViewOrElement && isState) { + // Bit of a hack to bust through the readonly because there is a circular dep between + // LView and LNode. + ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized'); + (state as{node: LNode}).node = node; + if (firstTemplatePass) (state as LView).tView.node = node.tNode; + } + previousOrParentNode = node; isParent = true; return node; @@ -613,7 +623,7 @@ function createDirectivesAndLocals( const node = previousOrParentNode; if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; - cacheMatchingDirectivesForNode(node.tNode !, currentView.tView, localRefs || null); + cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null); } else { instantiateDirectivesDirectly(); } @@ -708,7 +718,7 @@ export function isComponent(tNode: TNode): boolean { * This function instantiates the given directives. */ function instantiateDirectivesDirectly() { - const tNode = previousOrParentNode.tNode !; + const tNode = previousOrParentNode.tNode; const count = tNode.flags & TNodeFlags.DirectiveCountMask; if (count > 0) { @@ -758,7 +768,7 @@ function saveNameToExportMap( * to data[] in the same order as they are loaded in the template with load(). */ function saveResolvedLocalsInData(): void { - const localNames = previousOrParentNode.tNode !.localNames; + const localNames = previousOrParentNode.tNode.localNames; if (localNames) { for (let i = 0; i < localNames.length; i += 2) { const index = localNames[i + 1] as number; @@ -796,6 +806,7 @@ export function createTView( defs: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView { ngDevMode && ngDevMode.tView++; return { + node: null !, data: [], directives: null, firstTemplatePass: true, @@ -879,7 +890,7 @@ export function hostElement( def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); if (firstTemplatePass) { - node.tNode !.flags = TNodeFlags.isComponent; + node.tNode.flags = TNodeFlags.isComponent; if (def.diPublic) def.diPublic(def); currentView.tView.directives = [def]; } @@ -918,11 +929,11 @@ export function listener( cleanupFns.push(eventName, native, wrappedListener, useCapture); } - let tNode: TNode|null = node.tNode !; + let tNode: TNode|null = node.tNode; if (tNode.outputs === undefined) { // if we create TNode here, inputs must be undefined so we know they still need to be // checked - tNode.outputs = generatePropertyAliases(node.tNode !.flags, BindingDirection.Output); + tNode.outputs = generatePropertyAliases(node.tNode.flags, BindingDirection.Output); } const outputs = tNode.outputs; @@ -955,7 +966,7 @@ export function elementEnd() { ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Element); const queries = previousOrParentNode.queries; queries && queries.addNode(previousOrParentNode); - queueLifecycleHooks(previousOrParentNode.tNode !.flags, currentView); + queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView); } /** @@ -1002,12 +1013,12 @@ export function elementProperty( index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { if (value === NO_CHANGE) return; const node = data[index] as LElementNode; - const tNode = node.tNode !; + const tNode = node.tNode; // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't // yet been checked if (tNode && tNode.inputs === undefined) { // mark inputs as checked - tNode.inputs = generatePropertyAliases(node.tNode !.flags, BindingDirection.Input); + tNode.inputs = generatePropertyAliases(node.tNode.flags, BindingDirection.Input); } const inputData = tNode && tNode.inputs; @@ -1036,8 +1047,9 @@ export function elementProperty( * @param tViews Any TViews attached to this node * @returns the TNode object */ -function createTNode( - index: number, tagName: string | null, attrs: string[] | null, tViews: TView[] | null): TNode { +export function createTNode( + index: number | null, tagName: string | null, attrs: string[] | null, + tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { index: index, @@ -1049,7 +1061,8 @@ function createTNode( inputs: undefined, outputs: undefined, tViews: tViews, - next: null + next: null, + dynamicContainerNode: null }; } @@ -1321,8 +1334,11 @@ function addComponentLogic(index: number, instance: T, def: ComponentDef): tView, null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); - (previousOrParentNode.data as any) = hostView; - (hostView.node as any) = previousOrParentNode; + // We need to set the host node/data here because when the component LNode was created, + // we didn't yet know it was a component (just an element). + (previousOrParentNode as{data: LView}).data = hostView; + (hostView as{node: LNode}).node = previousOrParentNode; + if (firstTemplatePass) tView.node = previousOrParentNode.tNode; initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView); @@ -1351,19 +1367,19 @@ export function baseDirectiveCreate( directives[index] = directive; if (firstTemplatePass) { - const flags = previousOrParentNode.tNode !.flags; + const flags = previousOrParentNode.tNode.flags; if ((flags & TNodeFlags.DirectiveCountMask) === 0) { // When the first directive is created: // - save the index, // - set the number of directives to 1 - previousOrParentNode.tNode !.flags = + previousOrParentNode.tNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent | 1; } else { // Only need to bump the size when subsequent directives are created ngDevMode && assertNotEqual( flags & TNodeFlags.DirectiveCountMask, TNodeFlags.DirectiveCountMask, 'Reached the max number of directives'); - previousOrParentNode.tNode !.flags++; + previousOrParentNode.tNode.flags++; } } else { const diPublic = directiveDef !.diPublic; @@ -1481,7 +1497,7 @@ export function container( const node = createLNode( index, LNodeType.Container, undefined, tagName || null, attrs || null, lContainer); - if (firstTemplatePass && template == null) node.tNode !.tViews = []; + if (firstTemplatePass && template == null) node.tNode.tViews = []; // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. @@ -1618,7 +1634,8 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { newView.queries = lContainer.queries.enterView(lContainer.nextIndex); } - enterView(newView, viewNode = createLNode(null, LNodeType.View, null, null, null, newView)); + enterView( + newView, viewNode = createLNode(viewBlockId, LNodeType.View, null, null, null, newView)); } return getRenderFlags(viewNode.data); } @@ -2027,7 +2044,7 @@ export function getRootView(component: any): LView { export function detectChanges(component: T): void { const hostNode = _getComponentHostLElementNode(component); ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView'); - const componentIndex = hostNode.tNode !.flags >> TNodeFlags.DirectiveStartingIndexShift; + const componentIndex = hostNode.tNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const def = hostNode.view.tView.directives ![componentIndex] as ComponentDef; detectChangesInternal(hostNode.data as LView, hostNode, def, component); } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index ad323d052f..1775469c51 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -118,11 +118,12 @@ export interface LNode { * Pointer to the corresponding TNode object, which stores static * data about this node. */ - tNode: TNode|null; + tNode: TNode; /** * A pointer to a LContainerNode created by directives requesting ViewContainerRef */ + // TODO(kara): Remove when removing LNodes dynamicLContainerNode: LContainerNode|null; } @@ -210,8 +211,10 @@ export interface TNode { * * This is necessary to get from any TNode to its corresponding LNode when * traversing the node tree. + * + * If null, this is a view node created from a dynamically created view. */ - index: number; + index: number|null; /** * This number stores two values using its bits: @@ -306,6 +309,11 @@ export interface TNode { * to insert them or remove them from the DOM. */ next: TNode|null; + + /** + * A pointer to a LContainerNode created by directives requesting ViewContainerRef + */ + dynamicContainerNode: TNode|null; } /** Static data for an LElementNode */ diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 1e412140b2..a405cdde93 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -46,6 +46,7 @@ export interface LView { * * If `LElementNode`, this is the LView of a component. */ + // TODO(kara): Remove when we have parent/child on TNodes readonly node: LViewNode|LElementNode; /** @@ -241,6 +242,17 @@ export interface LViewOrLContainer { * Stored on the template function as ngPrivateData. */ export interface TView { + /** + * Pointer to the `TNode` that represents the root of the view. + * + * If this is a `TNode` for an `LViewNode`, this is an embedded view of a container. + * We need this pointer to be able to efficiently find this node when inserting the view + * into an anchor. + * + * If this is a `TNode` for an `LElementNode`, this is the TView of a component. + */ + node: TNode; + /** Whether or not this template has been processed. */ firstTemplatePass: boolean; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index c7c750d364..8a797bfb4e 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -72,7 +72,7 @@ export function getNextLNode(node: LNode): LNode|null { const lView = node.data as LView; return lView.next ? (lView.next as LView).node : null; } - return node.tNode !.next ? node.view.data[node.tNode !.next !.index] : null; + return node.tNode.next ? node.view.data[node.tNode.next !.index as number] : null; } /** diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index e81fcaf1f9..4502b47b9c 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -195,7 +195,7 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null { */ function getIdxOfMatchingDirective(node: LNode, type: Type): number|null { const defs = node.view.tView.directives !; - const flags = node.tNode !.flags; + const flags = node.tNode.flags; const count = flags & TNodeFlags.DirectiveCountMask; const start = flags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; @@ -241,8 +241,7 @@ function add(query: LQuery| null, node: LNode) { } else { const selector = predicate.selector !; for (let i = 0; i < selector.length; i++) { - ngDevMode && assertNotNull(node.tNode, 'node.tNode'); - const directiveIdx = getIdxOfMatchingSelector(node.tNode !, selector[i]); + const directiveIdx = getIdxOfMatchingSelector(node.tNode, selector[i]); if (directiveIdx !== null) { // a node is matching a predicate - determine what to read // note that queries using name selector must specify read strategy diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index 18be0225ca..43a0de4031 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -48,7 +48,7 @@ describe('instructions', () => { expect(t.html).toEqual('
'); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, - tNode: 1, + tNode: 2, // 1 for div, 1 for host element tView: 1, rendererCreateElement: 1, rendererSetAttribute: 2 @@ -69,7 +69,7 @@ describe('instructions', () => { expect(t.html).toEqual('
'); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, - tNode: 1, + tNode: 2, // 1 for div, 1 for host element tView: 1, rendererCreateElement: 1, }); @@ -83,7 +83,7 @@ describe('instructions', () => { expect((t.hostNode.native as HTMLElement).querySelector('div') !.hidden).toEqual(false); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, - tNode: 1, + tNode: 2, // 1 for div, 1 for host element tView: 1, rendererCreateElement: 1, rendererSetProperty: 1 diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index e0325ab458..7fea1a24f3 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -32,7 +32,7 @@ describe('render3 integration test', () => { } expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, - tNode: 2, + tNode: 3, // 1 for div, 1 for text, 1 for host element tView: 1, rendererCreateElement: 1, }); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 08a68d8cf3..e03deec396 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -19,7 +19,8 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { inputs: undefined, outputs: undefined, tViews: null, - next: null + next: null, + dynamicContainerNode: null }; } From 68bf8c36c67929818bce52ec3f308f2eca6dfe18 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 17 May 2018 12:54:57 -0700 Subject: [PATCH 090/582] refactor(ivy): move type from LNode to TNode (#24113) PR Close #24113 --- packages/core/src/render3/di.ts | 27 +++--- packages/core/src/render3/instructions.ts | 83 ++++++++++--------- packages/core/src/render3/interfaces/node.ts | 14 ++-- packages/core/src/render3/node_assert.ts | 20 ++--- .../core/src/render3/node_manipulation.ts | 34 ++++---- packages/core/test/render3/di_spec.ts | 4 +- .../render3/node_selector_matcher_spec.ts | 4 +- 7 files changed, 95 insertions(+), 91 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index a62b8a17d7..05e4205888 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -22,7 +22,7 @@ import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {LContainerNode, LElementNode, LNode, LNodeType, LViewNode, TNodeFlags} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView, TView} from './interfaces/view'; @@ -253,7 +253,7 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { export function injectAttribute(attrName: string): string|undefined { ngDevMode && assertPreviousIsParent(); const lElement = getPreviousOrParentNode() as LElementNode; - ngDevMode && assertNodeType(lElement, LNodeType.Element); + ngDevMode && assertNodeType(lElement, TNodeType.Element); const tElement = lElement.tNode; ngDevMode && assertNotNull(tElement, 'expecting tNode'); const attrs = tElement.attrs; @@ -280,7 +280,7 @@ export function getOrCreateChangeDetectorRef( const currentNode = di.node; if (isComponent(currentNode.tNode)) { return di.changeDetectorRef = createViewRef(currentNode.data as LView, context); - } else if (currentNode.type === LNodeType.Element) { + } else if (currentNode.tNode.type === TNodeType.Element) { return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); } return null !; @@ -307,7 +307,7 @@ function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): * returns itself. */ function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode { - while (node.type === LNodeType.View) { + while (node.tNode.type === TNodeType.View) { node = node.view.node; } return node as LElementNode; @@ -523,7 +523,7 @@ export class ReadFromInjectorFn { */ export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef { return di.elementRef || (di.elementRef = new ElementRef( - di.node.type === LNodeType.Container ? null : di.node.native)); + di.node.tNode.type === TNodeType.Container ? null : di.node.native)); } export const QUERY_READ_TEMPLATE_REF = >>( @@ -540,12 +540,12 @@ export const QUERY_READ_ELEMENT_REF = export const QUERY_READ_FROM_NODE = (new ReadFromInjectorFn((injector: LInjector, node: LNode, directiveIdx: number) => { - ngDevMode && assertNodeOfPossibleTypes(node, LNodeType.Container, LNodeType.Element); + ngDevMode && assertNodeOfPossibleTypes(node, TNodeType.Container, TNodeType.Element); if (directiveIdx > -1) { return node.view.directives ![directiveIdx]; - } else if (node.type === LNodeType.Element) { + } else if (node.tNode.type === TNodeType.Element) { return getOrCreateElementRef(injector); - } else if (node.type === LNodeType.Container) { + } else if (node.tNode.type === TNodeType.Container) { return getOrCreateTemplateRef(injector); } throw new Error('fail'); @@ -567,14 +567,15 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer if (!di.viewContainerRef) { const vcRefHost = di.node; - ngDevMode && assertNodeOfPossibleTypes(vcRefHost, LNodeType.Container, LNodeType.Element); + ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element); const lContainer = createLContainer(vcRefHost.parent !, vcRefHost.view); const lContainerNode: LContainerNode = createLNodeObject( - LNodeType.Container, vcRefHost.view, vcRefHost.parent !, undefined, lContainer, null); + TNodeType.Container, vcRefHost.view, vcRefHost.parent !, undefined, lContainer, null); const hostTNode = vcRefHost.tNode; if (!hostTNode.dynamicContainerNode) { - hostTNode.dynamicContainerNode = createTNode(hostTNode.index, null, null, null); + hostTNode.dynamicContainerNode = + createTNode(TNodeType.Container, hostTNode.index, null, null, null); } lContainerNode.tNode = hostTNode.dynamicContainerNode; @@ -650,7 +651,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { // Look for the parent node and increment its dynamic view count. if (this._lContainerNode.parent !== null && this._lContainerNode.parent.data !== null) { ngDevMode && assertNodeOfPossibleTypes( - this._lContainerNode.parent, LNodeType.View, LNodeType.Element); + this._lContainerNode.parent, TNodeType.View, TNodeType.Element); this._lContainerNode.parent.data.dynamicViewCount++; } } @@ -701,7 +702,7 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { */ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { if (!di.templateRef) { - ngDevMode && assertNodeType(di.node, LNodeType.Container); + ngDevMode && assertNodeType(di.node, TNodeType.Container); const hostNode = di.node as LContainerNode; const hostTNode = hostNode.tNode; const hostTView = hostNode.view.tView; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 4ee9a40b64..d1196f92ca 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,7 +15,7 @@ import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interface import {LQueries} from './interfaces/query'; import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; -import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; @@ -337,11 +337,10 @@ export function createLView( * (same properties assigned in the same order). */ export function createLNodeObject( - type: LNodeType, currentView: LView, parent: LNode, native: RText | RElement | null | undefined, + type: TNodeType, currentView: LView, parent: LNode, native: RText | RElement | null | undefined, state: any, queries: LQueries | null): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { return { - type: type, native: native as any, view: currentView, parent: parent as any, @@ -368,19 +367,19 @@ export function createLNodeObject( * @param data Any data that should be saved on the LNode */ export function createLNode( - index: number | null, type: LNodeType.Element, native: RElement | RText | null, + index: number | null, type: TNodeType.Element, native: RElement | RText | null, name: string | null, attrs: string[] | null, lView?: LView | null): LElementNode; export function createLNode( - index: number | null, type: LNodeType.View, native: null, name: null, attrs: null, + index: number | null, type: TNodeType.View, native: null, name: null, attrs: null, lView: LView): LViewNode; export function createLNode( - index: number, type: LNodeType.Container, native: undefined, name: string | null, + index: number, type: TNodeType.Container, native: undefined, name: string | null, attrs: string[] | null, lContainer: LContainer): LContainerNode; export function createLNode( - index: number, type: LNodeType.Projection, native: null, name: null, attrs: string[] | null, + index: number, type: TNodeType.Projection, native: null, name: null, attrs: string[] | null, lProjection: LProjection): LProjectionNode; export function createLNode( - index: number | null, type: LNodeType, native: RText | RElement | null | undefined, + index: number | null, type: TNodeType, native: RText | RElement | null | undefined, name: string | null, attrs: string[] | null, state?: null | LView | LContainer | LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : @@ -392,10 +391,10 @@ export function createLNode( const node = createLNodeObject(type, currentView, parent, native, isState ? state as any : null, queries); - if (index === null || type === LNodeType.View) { + if (index === null || type === TNodeType.View) { // View nodes are not stored in data because they can be added / removed at runtime (which // would cause indices to change). Their TNodes are instead stored in TView.node. - node.tNode = (state as LView).tView.node || createTNode(index, null, null, null); + node.tNode = (state as LView).tView.node || createTNode(type, index, null, null, null); } else { // This is an element or container or projection node ngDevMode && assertDataNext(index); @@ -403,7 +402,7 @@ export function createLNode( // Every node adds a value to the static data array to avoid a sparse array if (index >= tData.length) { - const tNode = tData[index] = createTNode(index, name, attrs, null); + const tNode = tData[index] = createTNode(type, index, name, attrs, null); if (!isParent && previousOrParentNode) { const previousTNode = previousOrParentNode.tNode; previousTNode.next = tNode; @@ -416,7 +415,7 @@ export function createLNode( if (isParent) { currentQueries = null; if (previousOrParentNode.view === currentView || - previousOrParentNode.type === LNodeType.View) { + previousOrParentNode.tNode.type === TNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. ngDevMode && assertNull( previousOrParentNode.child, @@ -429,7 +428,7 @@ export function createLNode( } // View nodes and host elements need to set their host node (components set host nodes later) - if ((type & LNodeType.ViewOrElement) === LNodeType.ViewOrElement && isState) { + if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) { // Bit of a hack to bust through the readonly because there is a circular dep between // LView and LNode. ngDevMode && assertNull((state as LView).node, 'LView.node should not have been initialized'); @@ -475,7 +474,7 @@ export function renderTemplate( rendererFactory = providedRendererFactory; const tView = getOrCreateTView(template, directives || null, pipes || null); host = createLNode( - null, LNodeType.Element, hostNode, null, null, + null, TNodeType.Element, hostNode, null, null, createLView( -1, providedRendererFactory.createRenderer(null, null), tView, null, {}, LViewFlags.CheckAlways, sanitizer)); @@ -512,7 +511,7 @@ export function renderEmbeddedTemplate( const lView = createLView( -1, renderer, tView, template, context, LViewFlags.CheckAlways, getCurrentSanitizer()); - viewNode = createLNode(null, LNodeType.View, null, null, null, lView); + viewNode = createLNode(null, TNodeType.View, null, null, null, lView); rf = RenderFlags.Create; } oldView = enterView(viewNode.data, viewNode); @@ -600,7 +599,7 @@ export function elementStart( ngDevMode && assertDataInRange(index - 1); const node: LElementNode = - createLNode(index, LNodeType.Element, native !, name, attrs || null, null); + createLNode(index, TNodeType.Element, native !, name, attrs || null, null); if (attrs) setUpAttributes(native, attrs); appendChild(node.parent !, native, currentView); @@ -884,7 +883,7 @@ export function hostElement( sanitizer?: Sanitizer | null): LElementNode { resetApplicationState(); const node = createLNode( - 0, LNodeType.Element, rNode, null, null, + 0, TNodeType.Element, rNode, null, null, createLView( -1, renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); @@ -963,7 +962,7 @@ export function elementEnd() { ngDevMode && assertHasParent(); previousOrParentNode = previousOrParentNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Element); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Element); const queries = previousOrParentNode.queries; queries && queries.addNode(previousOrParentNode); queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView); @@ -1041,6 +1040,7 @@ export function elementProperty( /** * Constructs a TNode object from the arguments. * + * @param type The type of the node * @param index The index of the TNode in TView.data * @param tagName The tag name of the node * @param attrs The attributes defined on this node @@ -1048,10 +1048,11 @@ export function elementProperty( * @returns the TNode object */ export function createTNode( - index: number | null, tagName: string | null, attrs: string[] | null, + type: TNodeType, index: number | null, tagName: string | null, attrs: string[] | null, tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { + type: type, index: index, flags: 0, tagName: tagName, @@ -1257,7 +1258,7 @@ export function text(index: number, value?: any): void { currentView.bindingStartIndex, -1, 'text nodes should be created before bindings'); ngDevMode && ngDevMode.rendererCreateTextNode++; const textNode = createTextNode(value, renderer); - const node = createLNode(index, LNodeType.Element, textNode, null, null); + const node = createLNode(index, TNodeType.Element, textNode, null, null); // Text nodes are self closing. isParent = false; @@ -1386,7 +1387,7 @@ export function baseDirectiveCreate( if (diPublic) diPublic(directiveDef !); } - if (directiveDef !.attributes != null && previousOrParentNode.type == LNodeType.Element) { + if (directiveDef !.attributes != null && previousOrParentNode.tNode.type == TNodeType.Element) { setUpAttributes( (previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]); } @@ -1495,7 +1496,7 @@ export function container( const lContainer = createLContainer(currentParent, currentView, template); const node = createLNode( - index, LNodeType.Container, undefined, tagName || null, attrs || null, lContainer); + index, TNodeType.Container, undefined, tagName || null, attrs || null, lContainer); if (firstTemplatePass && template == null) node.tNode.tViews = []; @@ -1505,7 +1506,7 @@ export function container( createDirectivesAndLocals(index, tagName || null, attrs, localRefs, template == null); isParent = false; - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); const queries = node.queries; if (queries) { // check if a given container node matches @@ -1523,7 +1524,7 @@ export function container( export function containerRefreshStart(index: number): void { ngDevMode && assertDataInRange(index); previousOrParentNode = data[index] as LNode; - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); isParent = true; (previousOrParentNode as LContainerNode).data.nextIndex = 0; ngDevMode && assertSame( @@ -1546,14 +1547,14 @@ export function containerRefreshEnd(): void { if (isParent) { isParent = false; } else { - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); ngDevMode && assertHasParent(); previousOrParentNode = previousOrParentNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); const container = previousOrParentNode as LContainerNode; container.native = undefined; - ngDevMode && assertNodeType(container, LNodeType.Container); + ngDevMode && assertNodeType(container, TNodeType.Container); const nextIndex = container.data.nextIndex; // remove extra views at the end of the container @@ -1616,13 +1617,13 @@ function scanForView( export function embeddedViewStart(viewBlockId: number): RenderFlags { const container = (isParent ? previousOrParentNode : previousOrParentNode.parent !) as LContainerNode; - ngDevMode && assertNodeType(container, LNodeType.Container); + ngDevMode && assertNodeType(container, TNodeType.Container); const lContainer = container.data; let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex, viewBlockId); if (viewNode) { previousOrParentNode = viewNode; - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); isParent = true; enterView(viewNode.data, viewNode); } else { @@ -1635,7 +1636,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { } enterView( - newView, viewNode = createLNode(viewBlockId, LNodeType.View, null, null, null, newView)); + newView, viewNode = createLNode(viewBlockId, TNodeType.View, null, null, null, newView)); } return getRenderFlags(viewNode.data); } @@ -1652,7 +1653,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { * @returns TView */ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TView { - ngDevMode && assertNodeType(parent, LNodeType.Container); + ngDevMode && assertNodeType(parent, TNodeType.Container); const containerTViews = (parent !.tNode as TContainerNode).tViews as TView[]; ngDevMode && assertNotNull(containerTViews, 'TView expected'); ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); @@ -1670,8 +1671,8 @@ export function embeddedViewEnd(): void { const viewNode = previousOrParentNode = currentView.node as LViewNode; const containerNode = previousOrParentNode.parent as LContainerNode; if (containerNode) { - ngDevMode && assertNodeType(viewNode, LNodeType.View); - ngDevMode && assertNodeType(containerNode, LNodeType.Container); + ngDevMode && assertNodeType(viewNode, TNodeType.View); + ngDevMode && assertNodeType(containerNode, TNodeType.Container); const lContainer = containerNode.data; if (creationMode) { @@ -1686,7 +1687,7 @@ export function embeddedViewEnd(): void { } leaveView(currentView !.parent !); ngDevMode && assertEqual(isParent, false, 'isParent'); - ngDevMode && assertNodeType(previousOrParentNode, LNodeType.View); + ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); } /** @@ -1700,7 +1701,7 @@ function setRenderParentInProjectedNodes( if (renderParent != null) { let node: LNode|null = viewNode.child; while (node) { - if (node.type === LNodeType.Projection) { + if (node.tNode.type === TNodeType.Projection) { let nodeToProject: LNode|null = (node as LProjectionNode).data.head; const lastNodeToProject = (node as LProjectionNode).data.tail; while (nodeToProject) { @@ -1726,7 +1727,7 @@ function setRenderParentInProjectedNodes( export function componentRefresh(directiveIndex: number, elementIndex: number): void { ngDevMode && assertDataInRange(elementIndex); const element = data ![elementIndex] as LElementNode; - ngDevMode && assertNodeType(element, LNodeType.Element); + ngDevMode && assertNodeType(element, TNodeType.Element); ngDevMode && assertNotNull(element.data, `Component's host node should have an LView attached.`); const hostView = element.data !; @@ -1836,7 +1837,7 @@ function appendToProjectionNode( export function projection( nodeIndex: number, localIndex: number, selectorIndex: number = 0, attrs?: string[]): void { const node = createLNode( - nodeIndex, LNodeType.Projection, null, null, attrs || null, {head: null, tail: null}); + nodeIndex, TNodeType.Projection, null, null, attrs || null, {head: null, tail: null}); // `` has no content isParent = false; @@ -1850,7 +1851,7 @@ export function projection( // build the linked list of projected nodes: for (let i = 0; i < nodesForSelector.length; i++) { const nodeToProject = nodesForSelector[i]; - if (nodeToProject.type === LNodeType.Projection) { + if (nodeToProject.tNode.type === TNodeType.Projection) { // Reprojecting a projection -> append the list of previously projected nodes const previouslyProjected = (nodeToProject as LProjectionNode).data; appendToProjectionNode(node, previouslyProjected.head, previouslyProjected.tail); @@ -1863,7 +1864,7 @@ export function projection( } if (canInsertNativeNode(currentParent, currentView)) { - ngDevMode && assertNodeType(currentParent, LNodeType.Element); + ngDevMode && assertNodeType(currentParent, TNodeType.Element); // process each node in the list of projected nodes: let nodeToProject: LNode|null = node.data.head; const lastNodeToProject = node.data.tail; @@ -1884,13 +1885,13 @@ export function projection( */ function findComponentHost(lView: LView): LElementNode { let viewRootLNode = lView.node; - while (viewRootLNode.type === LNodeType.View) { + while (viewRootLNode.tNode.type === TNodeType.View) { ngDevMode && assertNotNull(lView.parent, 'lView.parent'); lView = lView.parent !; viewRootLNode = lView.node; } - ngDevMode && assertNodeType(viewRootLNode, LNodeType.Element); + ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element); ngDevMode && assertNotNull(viewRootLNode.data, 'node.data'); return viewRootLNode as LElementNode; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 1775469c51..4608572a15 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -16,10 +16,10 @@ import {LView, TData, TView} from './view'; /** - * LNodeType corresponds to the LNode.type property. It contains information + * TNodeType corresponds to the TNode.type property. It contains information * on how to map a particular set of bits in LNode.flags to the node type. */ -export const enum LNodeType { +export const enum TNodeType { Container = 0b00, Projection = 0b01, View = 0b10, @@ -58,9 +58,6 @@ export const enum TNodeFlags { * instructions. */ export interface LNode { - /** The type of the node (see LNodeFlags) */ - type: LNodeType; - /** * The associated DOM node. Storing this allows us to: * - append children to their element parents in the DOM (e.g. `parent.native.appendChild(...)`) @@ -121,7 +118,7 @@ export interface LNode { tNode: TNode; /** - * A pointer to a LContainerNode created by directives requesting ViewContainerRef + * A pointer to an LContainerNode created by directives requesting ViewContainerRef */ // TODO(kara): Remove when removing LNodes dynamicLContainerNode: LContainerNode|null; @@ -206,6 +203,9 @@ export interface LProjectionNode extends LNode { * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern */ export interface TNode { + /** The type of the TNode. See TNodeType. */ + type: TNodeType; + /** * Index of the TNode in TView.data and corresponding LNode in LView.data. * @@ -311,7 +311,7 @@ export interface TNode { next: TNode|null; /** - * A pointer to a LContainerNode created by directives requesting ViewContainerRef + * A pointer to a TContainerNode created by directives requesting ViewContainerRef */ dynamicContainerNode: TNode|null; } diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index b6200a4ad8..73bf9b6997 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -7,23 +7,23 @@ */ import {assertEqual, assertNotNull} from './assert'; -import {LNode, LNodeType} from './interfaces/node'; +import {LNode, TNodeType} from './interfaces/node'; -export function assertNodeType(node: LNode, type: LNodeType) { +export function assertNodeType(node: LNode, type: TNodeType) { assertNotNull(node, 'should be called with a node'); - assertEqual(node.type, type, `should be a ${typeName(type)}`); + assertEqual(node.tNode.type, type, `should be a ${typeName(type)}`); } -export function assertNodeOfPossibleTypes(node: LNode, ...types: LNodeType[]) { +export function assertNodeOfPossibleTypes(node: LNode, ...types: TNodeType[]) { assertNotNull(node, 'should be called with a node'); - const found = types.some(type => node.type === type); + const found = types.some(type => node.tNode.type === type); assertEqual(found, true, `Should be one of ${types.map(typeName).join(', ')}`); } -function typeName(type: LNodeType): string { - if (type == LNodeType.Projection) return 'Projection'; - if (type == LNodeType.Container) return 'Container'; - if (type == LNodeType.View) return 'View'; - if (type == LNodeType.Element) return 'Element'; +function typeName(type: TNodeType): string { + if (type == TNodeType.Projection) return 'Projection'; + if (type == TNodeType.Container) return 'Container'; + if (type == TNodeType.View) return 'View'; + if (type == TNodeType.Element) return 'Element'; return ''; } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 8a797bfb4e..b1a5b3fa17 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -9,7 +9,7 @@ import {assertNotNull} from './assert'; import {callHooks} from './hooks'; import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, LNodeType, LProjectionNode, LTextNode, LViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; @@ -35,7 +35,7 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem while (currentNode && currentNode !== stopNode) { let pNextOrParent = currentNode.pNextOrParent; if (pNextOrParent) { - while (pNextOrParent.type !== LNodeType.Projection) { + while (pNextOrParent.tNode.type !== TNodeType.Projection) { const nativeNode = findFirstRNode(pNextOrParent); if (nativeNode) { return nativeNode; @@ -55,8 +55,8 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem const parentNode = currentNode.parent; currentNode = null; if (parentNode) { - const parentType = parentNode.type; - if (parentType === LNodeType.Container || parentType === LNodeType.View) { + const parentType = parentNode.tNode.type; + if (parentType === TNodeType.Container || parentType === TNodeType.View) { currentNode = parentNode; } } @@ -68,7 +68,7 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem /** Retrieves the sibling node for the given node. */ export function getNextLNode(node: LNode): LNode|null { // View nodes don't have TNodes, so their next must be retrieved through their LView. - if (node.type === LNodeType.View) { + if (node.tNode.type === TNodeType.View) { const lView = node.data as LView; return lView.next ? (lView.next as LView).node : null; } @@ -87,7 +87,7 @@ function getNextLNodeWithProjection(node: LNode): LNode|null { if (pNextOrParent) { // The node is projected - const isLastProjectedNode = pNextOrParent.type === LNodeType.Projection; + const isLastProjectedNode = pNextOrParent.tNode.type === TNodeType.Projection; // returns pNextOrParent if we are not at the end of the list, null otherwise return isLastProjectedNode ? null : pNextOrParent; } @@ -132,16 +132,16 @@ function findFirstRNode(rootNode: LNode): RElement|RText|null { let node: LNode|null = rootNode; while (node) { let nextNode: LNode|null = null; - if (node.type === LNodeType.Element) { + if (node.tNode.type === TNodeType.Element) { // A LElementNode has a matching RNode in LElementNode.native return (node as LElementNode).native; - } else if (node.type === LNodeType.Container) { + } else if (node.tNode.type === TNodeType.Container) { const lContainerNode: LContainerNode = (node as LContainerNode); const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ? lContainerNode.dynamicLContainerNode.data : lContainerNode.data; nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; - } else if (node.type === LNodeType.Projection) { + } else if (node.tNode.type === TNodeType.Projection) { // For Projection look at the first projected node nextNode = (node as LProjectionNode).data.head; } else { @@ -179,8 +179,8 @@ export function addRemoveViewFromContainer( export function addRemoveViewFromContainer( container: LContainerNode, rootNode: LViewNode, insertMode: boolean, beforeNode?: RNode | null): void { - ngDevMode && assertNodeType(container, LNodeType.Container); - ngDevMode && assertNodeType(rootNode, LNodeType.View); + ngDevMode && assertNodeType(container, TNodeType.Container); + ngDevMode && assertNodeType(rootNode, TNodeType.View); const parentNode = container.data.renderParent; const parent = parentNode ? parentNode.native : null; let node: LNode|null = rootNode.child; @@ -188,7 +188,7 @@ export function addRemoveViewFromContainer( while (node) { let nextNode: LNode|null = null; const renderer = container.view.renderer; - if (node.type === LNodeType.Element) { + if (node.tNode.type === TNodeType.Element) { if (insertMode) { isProceduralRenderer(renderer) ? renderer.insertBefore(parent, node.native !, beforeNode as RNode | null) : @@ -198,13 +198,13 @@ export function addRemoveViewFromContainer( parent.removeChild(node.native !); } nextNode = getNextLNode(node); - } else if (node.type === LNodeType.Container) { + } else if (node.tNode.type === TNodeType.Container) { // if we get to a container, it must be a root node of a view because we are only // propagating down into child views / containers and not child elements const childContainerData: LContainer = (node as LContainerNode).data; childContainerData.renderParent = parentNode; nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; - } else if (node.type === LNodeType.Projection) { + } else if (node.tNode.type === TNodeType.Projection) { nextNode = (node as LProjectionNode).data.head; } else { nextNode = (node as LViewNode).child; @@ -357,7 +357,7 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie */ export function getParentState(state: LViewOrLContainer, rootView: LView): LViewOrLContainer|null { let node; - if ((node = (state as LView) !.node) && node.type === LNodeType.View) { + if ((node = (state as LView) !.node) && node.tNode.type === TNodeType.View) { // if it's an embedded view, the state needs to go up to the container, in case the // container has a next return node.parent !.data as any; @@ -429,7 +429,7 @@ function executePipeOnDestroys(view: LView): void { * @return boolean Whether the child element should be inserted. */ export function canInsertNativeNode(parent: LNode, currentView: LView): boolean { - const parentIsElement = parent.type === LNodeType.Element; + const parentIsElement = parent.tNode.type === TNodeType.Element; return parentIsElement && (parent.view !== currentView || parent.data === null /* Regular Element. */); @@ -486,7 +486,7 @@ export function insertChild(node: LNode, currentView: LView): void { export function appendProjectedNode( node: LElementNode | LTextNode | LContainerNode, currentParent: LElementNode, currentView: LView): void { - if (node.type !== LNodeType.Container) { + if (node.tNode.type !== TNodeType.Container) { appendChild(currentParent, (node as LElementNode | LTextNode).native, currentView); } else { // The node we are adding is a Container and we are adding it to Element which diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 9ce14e241e..cc3bded98b 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -14,7 +14,7 @@ import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttr import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; -import {LNodeType} from '../../src/render3/interfaces/node'; +import {TNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; import {ViewRef} from '../../src/render3/view_ref'; @@ -1367,7 +1367,7 @@ describe('di', () => { createLView(-1, null !, createTView(null, null), null, null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { - const parent = createLNode(0, LNodeType.Element, null, null, null, null); + const parent = createLNode(0, TNodeType.Element, null, null, null, null); // Simulate the situation where the previous parent is not initialized. // This happens on first bootstrap because we don't init existing values diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index e03deec396..638d73b58c 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -6,12 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {TNode} from '../../src/render3/interfaces/node'; +import {TNode, TNodeType} from '../../src/render3/interfaces/node'; + import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags,} from '../../src/render3/interfaces/projection'; import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelector} from '../../src/render3/node_selector_matcher'; function testLStaticData(tagName: string, attrs: string[] | null): TNode { return { + type: TNodeType.Element, index: 0, flags: 0, tagName, attrs, localNames: null, From 609e6b97878f01f5aaac42d0121df5b34cdd9c2b Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 24 May 2018 13:13:51 -0700 Subject: [PATCH 091/582] refactor(ivy): move child from LNode to TNode (#24113) PR Close #24113 --- packages/core/src/render3/instructions.ts | 17 +++---- packages/core/src/render3/interfaces/node.ts | 47 ++++++++++++++----- .../core/src/render3/node_manipulation.ts | 21 +++++++-- .../bundling/todo/bundle.golden_symbols.json | 3 ++ .../render3/node_selector_matcher_spec.ts | 1 + 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index d1196f92ca..0a23c5b424 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -17,7 +17,7 @@ import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TDat import {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; -import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode} from './node_manipulation'; +import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; @@ -344,7 +344,6 @@ export function createLNodeObject( native: native as any, view: currentView, parent: parent as any, - child: null, nodeInjector: parent ? parent.nodeInjector : null, data: state, queries: queries, @@ -414,15 +413,10 @@ export function createLNode( // Now link ourselves into the tree. if (isParent) { currentQueries = null; - if (previousOrParentNode.view === currentView || + if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === currentView || previousOrParentNode.tNode.type === TNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. - ngDevMode && assertNull( - previousOrParentNode.child, - `previousOrParentNode's child should not have been set.`); - previousOrParentNode.child = node; - } else { - // We are adding component view, so we don't link parent node child to this node. + previousOrParentNode.tNode.child = node.tNode; } } } @@ -1063,6 +1057,7 @@ export function createTNode( outputs: undefined, tViews: tViews, next: null, + child: null, dynamicContainerNode: null }; } @@ -1699,7 +1694,7 @@ export function embeddedViewEnd(): void { function setRenderParentInProjectedNodes( renderParent: LElementNode | null, viewNode: LViewNode): void { if (renderParent != null) { - let node: LNode|null = viewNode.child; + let node: LNode|null = getChildLNode(viewNode); while (node) { if (node.tNode.type === TNodeType.Projection) { let nodeToProject: LNode|null = (node as LProjectionNode).data.head; @@ -1776,7 +1771,7 @@ export function projectionDef( } const componentNode: LElementNode = findComponentHost(currentView); - let componentChild: LNode|null = componentNode.child; + let componentChild: LNode|null = getChildLNode(componentNode); while (componentChild !== null) { // execute selector matching logic if and only if: diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 4608572a15..0701f960eb 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -71,11 +71,6 @@ export interface LNode { */ readonly parent: LNode|null; - /** - * First child of the current node. - */ - child: LNode|null; - /** * If regular LElementNode, then `data` will be null. * If LElementNode with component, then `data` contains LView. @@ -130,8 +125,6 @@ export interface LElementNode extends LNode { /** The DOM element associated with this node. */ readonly native: RElement; - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; - /** If Component then data has LView (light DOM) */ readonly data: LView|null; @@ -143,7 +136,6 @@ export interface LElementNode extends LNode { export interface LTextNode extends LNode { /** The text node associated with this node. */ native: RText; - child: null; /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ readonly parent: LElementNode|LViewNode; @@ -154,7 +146,6 @@ export interface LTextNode extends LNode { /** Abstract node which contains root nodes of a view. */ export interface LViewNode extends LNode { readonly native: null; - child: LContainerNode|LElementNode|LTextNode|LProjectionNode|null; /** LViewNodes can only be added to LContainerNodes. */ readonly parent: LContainerNode|null; @@ -173,7 +164,6 @@ export interface LContainerNode extends LNode { */ native: RElement|RText|null|undefined; readonly data: LContainer; - child: null; /** Containers can be added to elements or views. */ readonly parent: LElementNode|LViewNode|null; @@ -182,7 +172,6 @@ export interface LContainerNode extends LNode { export interface LProjectionNode extends LNode { readonly native: null; - child: null; readonly data: LProjection; @@ -310,6 +299,14 @@ export interface TNode { */ next: TNode|null; + /** + * First child of the current node. + * + * For component nodes, the child will always be a ContentChild (in same view). + * For embedded view nodes, the child will be in their child view. + */ + child: TNode|null; + /** * A pointer to a TContainerNode created by directives requesting ViewContainerRef */ @@ -317,10 +314,34 @@ export interface TNode { } /** Static data for an LElementNode */ -export interface TElementNode extends TNode { tViews: null; } +export interface TElementNode extends TNode { + child: TContainerNode|TElementNode|TProjectionNode|null; + tViews: null; +} + +/** Static data for an LTextNode */ +export interface TTextNode extends TNode { + child: null; + tViews: null; +} /** Static data for an LContainerNode */ -export interface TContainerNode extends TNode { tViews: TView|TView[]|null; } +export interface TContainerNode extends TNode { + child: null; + tViews: TView|TView[]|null; +} + +/** Static data for an LViewNode */ +export interface TViewNode extends TNode { + child: TContainerNode|TElementNode|TProjectionNode|null; + tViews: null; +} + +/** Static data for an LProjectionNode */ +export interface TProjectionNode extends TNode { + child: null; + tViews: null; +} /** * This mapping is necessary so we can set input properties and output listeners diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index b1a5b3fa17..3ae554dd92 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -75,6 +75,15 @@ export function getNextLNode(node: LNode): LNode|null { return node.tNode.next ? node.view.data[node.tNode.next !.index as number] : null; } +/** Retrieves the first child of a given node */ +export function getChildLNode(node: LNode): LNode|null { + if (node.tNode.child) { + const view = node.tNode.type === TNodeType.View ? node.data as LView : node.view; + return view.data[node.tNode.child.index as number]; + } + return null; +} + /** * Get the next node in the LNode tree, taking into account the place where a node is * projected (in the shadow DOM) rather than where it comes from (in the light DOM). @@ -140,13 +149,14 @@ function findFirstRNode(rootNode: LNode): RElement|RText|null { const childContainerData: LContainer = lContainerNode.dynamicLContainerNode ? lContainerNode.dynamicLContainerNode.data : lContainerNode.data; - nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; + nextNode = + childContainerData.views.length ? getChildLNode(childContainerData.views[0]) : null; } else if (node.tNode.type === TNodeType.Projection) { // For Projection look at the first projected node nextNode = (node as LProjectionNode).data.head; } else { // Otherwise look at the first child - nextNode = (node as LViewNode).child; + nextNode = getChildLNode(node as LViewNode); } node = nextNode === null ? getNextOrParentSiblingNode(node, rootNode) : nextNode; @@ -183,7 +193,7 @@ export function addRemoveViewFromContainer( ngDevMode && assertNodeType(rootNode, TNodeType.View); const parentNode = container.data.renderParent; const parent = parentNode ? parentNode.native : null; - let node: LNode|null = rootNode.child; + let node: LNode|null = getChildLNode(rootNode); if (parent) { while (node) { let nextNode: LNode|null = null; @@ -203,11 +213,12 @@ export function addRemoveViewFromContainer( // propagating down into child views / containers and not child elements const childContainerData: LContainer = (node as LContainerNode).data; childContainerData.renderParent = parentNode; - nextNode = childContainerData.views.length ? childContainerData.views[0].child : null; + nextNode = + childContainerData.views.length ? getChildLNode(childContainerData.views[0]) : null; } else if (node.tNode.type === TNodeType.Projection) { nextNode = (node as LProjectionNode).data.head; } else { - nextNode = (node as LViewNode).child; + nextNode = getChildLNode(node as LViewNode); } if (nextNode === null) { node = getNextOrParentSiblingNode(node, rootNode); diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 2a9c789e3d..7cdd8fb784 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -383,6 +383,9 @@ { "name": "generatePropertyAliases" }, + { + "name": "getChildLNode" + }, { "name": "getCurrentSanitizer" }, diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 638d73b58c..d98944e264 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -22,6 +22,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { outputs: undefined, tViews: null, next: null, + child: null, dynamicContainerNode: null }; } From 186118e684b7e7b95c84a099b7fdaae5a833425e Mon Sep 17 00:00:00 2001 From: Stephen Fluin Date: Thu, 24 May 2018 09:50:03 -0700 Subject: [PATCH 092/582] docs(aio): remove outdated rangle link (#24108) PR Close #24108 --- aio/content/marketing/resources.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index 52d56d6f1f..264fd0c3eb 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -528,13 +528,6 @@ "title": "Pluralsight", "url": "https://www.pluralsight.com/search?q=angular+2&categories=all" }, - "ab": { - "desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.", - "logo": "", - "rev": true, - "title": "Rangle.io", - "url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/" - }, "ab3": { "desc": "Angular courses hosted by Udemy", "logo": "", From 01b5acd7cff198c60cb6ab5735e0eea1fcb08be9 Mon Sep 17 00:00:00 2001 From: alexzuza Date: Thu, 24 May 2018 01:07:09 +0400 Subject: [PATCH 093/582] fix(compiler): generate core-compliant hostBindings property (#24087) This makes `hostBindings` code, generated by compiler and used in core package, identical. Fix #24013 PR Close #24087 --- packages/compiler/src/render3/r3_identifiers.ts | 1 + packages/compiler/src/render3/view/compiler.ts | 6 +++--- .../compiler/test/render3/r3_compiler_compliance_spec.ts | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 80db996ec2..599ec956d8 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -65,6 +65,7 @@ export class Identifiers { static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE}; static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE}; + static loadDirective: o.ExternalReference = {name: 'ɵd', moduleName: CORE}; static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index c43bbec1f2..6075ef0591 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -359,8 +359,8 @@ function createHostBindingsFunction( for (let index = 0; index < meta.queries.length; index++) { const query = meta.queries[index]; - // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); - const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + // e.g. r3.qR(tmp = r3.d(dirIndex)[1]) && (r3.d(dirIndex)[0].someDir = tmp); + const getDirectiveMemory = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]); // The query list is at the query index + 1 because the directive itself is in slot 0. const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); const assignToTemporary = temporary().set(getQueryList); @@ -376,7 +376,7 @@ function createHostBindingsFunction( // Calculate the host property bindings const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); - const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]); if (bindings) { for (const binding of bindings) { const bindingExpr = convertPropertyBinding( diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index fdea13d17a..8dd56318ab 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -291,7 +291,7 @@ describe('compiler compliance', () => { factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, hostBindings: function HostBindingDir_HostBindings( dirIndex: $number$, elIndex: $number$) { - $r3$.ɵp(elIndex, 'id', $r3$.ɵb($r3$.ɵld(dirIndex).dirId)); + $r3$.ɵp(elIndex, 'id', $r3$.ɵb($r3$.ɵd(dirIndex).dirId)); } }); `; @@ -823,7 +823,7 @@ describe('compiler compliance', () => { hostBindings: function ContentQueryComponent_HostBindings( dirIndex: $number$, elIndex: $number$) { var $tmp$: $any$; - ($r3$.ɵqR(($tmp$ = $r3$.ɵld(dirIndex)[1])) && ($r3$.ɵld(dirIndex)[0].someDir = $tmp$.first)); + ($r3$.ɵqR(($tmp$ = $r3$.ɵd(dirIndex)[1])) && ($r3$.ɵd(dirIndex)[0].someDir = $tmp$.first)); }, template: function ContentQueryComponent_Template( rf: $RenderFlags$, ctx: $ContentQueryComponent$) { From 36cc72ee5beb97c2e992f938f515d70dcf92ad39 Mon Sep 17 00:00:00 2001 From: Juri Date: Wed, 23 May 2018 23:06:02 +0200 Subject: [PATCH 094/582] docs(aio): add Juri Strumpflohner to GDE resources (#24086) PR Close #24086 --- aio/content/images/bios/juristr.jpg | Bin 0 -> 212126 bytes aio/content/marketing/contributors.json | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 aio/content/images/bios/juristr.jpg diff --git a/aio/content/images/bios/juristr.jpg b/aio/content/images/bios/juristr.jpg new file mode 100644 index 0000000000000000000000000000000000000000..be3df97e0174aa78a78ac7309cf0adbd3e62ae9f GIT binary patch literal 212126 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

-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�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 Date: Wed, 23 May 2018 17:19:30 +0300 Subject: [PATCH 095/582] refactor(aio): improve logging output in `update-preview-server.sh` (#24071) PR Close #24071 --- aio/aio-builds-setup/scripts/update-preview-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/aio-builds-setup/scripts/update-preview-server.sh b/aio/aio-builds-setup/scripts/update-preview-server.sh index 42c9ec6029..aa341c4282 100755 --- a/aio/aio-builds-setup/scripts/update-preview-server.sh +++ b/aio/aio-builds-setup/scripts/update-preview-server.sh @@ -3,7 +3,7 @@ set -eux -o pipefail exec 3>&1 -echo "\n\n[`date`] - Updating the preview server..." +echo -e "\n\n[`date`] - Updating the preview server..." # Input readonly HOST_REPO_DIR=$1 From 83bb5d19226b1307d413ab9af7f7b5a1e178422b Mon Sep 17 00:00:00 2001 From: Tiago Temporin Date: Tue, 22 May 2018 06:44:12 +0100 Subject: [PATCH 096/582] docs(aio): add material community components (#24042) PR Close #24042 --- aio/content/marketing/resources.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index 264fd0c3eb..f8423a78ae 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -345,6 +345,13 @@ "title": "Angular Material", "url": "https://github.com/angular/material2" }, + "mcc": { + "desc": "Material components made by the community", + "logo": "", + "rev": true, + "title": "Material Community Components", + "url": "https://github.com/tiaguinho/material-community-components" + }, "ngzorro": { "desc": "A set of enterprise-class UI components based on Ant Design and Angular", "rev": true, From 4f36340de7d2c7a4cbeeaaa0a31fc9999de7a159 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 21 May 2018 15:59:25 -0700 Subject: [PATCH 097/582] feat(ivy): add support for short-circuiting (#24039) Short-circuitable expressions (using ternary & binary operators) could not use the regular binding mechanism as it relies on the bindings being checked every single time - the index is incremented as part of checking the bindings. Then for pure function kind of bindings we use a different mechanism with a fixed index. As such short circuiting a binding check does not mess with the expected binding index. Note that all pure function bindings are handled the same wether or not they actually are short-circuitable. This allows to keep the compiler and compiled code simple - and there is no runtime perf cost anyway. PR Close #24039 --- .../compiler/src/render3/r3_identifiers.ts | 3 + .../compiler/src/render3/view/template.ts | 41 ++++-- .../render3/r3_compiler_compliance_spec.ts | 69 +++++++++- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/index.ts | 2 + packages/core/src/render3/instructions.ts | 69 +++++++++- packages/core/src/render3/pipe.ts | 32 +++-- packages/core/src/render3/pure_function.ts | 122 +++++++++++++----- .../component_directives_spec.ts | 24 ++-- .../render3/compiler_canonical/pipes_spec.ts | 13 +- packages/core/test/render3/pipe_spec.ts | 39 ++++-- .../core/test/render3/pure_function_spec.ts | 48 ++++--- .../test/render3/view_container_ref_spec.ts | 15 ++- 13 files changed, 362 insertions(+), 116 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 599ec956d8..b9bf96b0d3 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -121,4 +121,7 @@ export class Identifiers { static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE}; static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE}; + + // Reserve slots for pure functions + static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE}; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 91e2f4b8b9..c6dbadfe54 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -57,6 +57,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Maps of placeholder to node indexes for each of the i18n section private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}]; + // Number of slots to reserve for pureFunctions + private _pureFunctionSlots = 0; + constructor( private constantPool: ConstantPool, private contextParameter: string, parentBindingScope: BindingScope, private level = 0, private contextName: string|null, @@ -70,6 +73,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver }); this._valueConverter = new ValueConverter( constantPool, () => this.allocateDataSlot(), + (numSlots: number): number => this._pureFunctionSlots += numSlots, (name, localName, slot, value: o.ReadVarExpr) => { const pipeType = pipeTypeByName.get(name); if (pipeType) { @@ -139,6 +143,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver t.visitAll(this, nodes); + if (this._pureFunctionSlots > 0) { + this.instruction( + this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots)); + } + const creationCode = this._creationCode.length > 0 ? [o.ifStmt( o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), @@ -501,6 +510,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver class ValueConverter extends AstMemoryEfficientTransformer { constructor( private constantPool: ConstantPool, private allocateSlot: () => number, + private allocatePureFunctionSlots: (numSlots: number) => number, private definePipe: (name: string, localName: string, slot: number, value: o.Expression) => void) { super(); @@ -511,14 +521,20 @@ class ValueConverter extends AstMemoryEfficientTransformer { // Allocate a slot to create the pipe const slot = this.allocateSlot(); const slotPseudoLocal = `PIPE:${slot}`; + // Allocate one slot for the result plus one slot per pipe argument + const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length); const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal); const bindingId = pipeBinding(pipe.args); this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId)); const value = pipe.exp.visit(this); const args = this.visitAll(pipe.args); - return new FunctionCall( - pipe.span, target, [new LiteralPrimitive(pipe.span, slot), value, ...args]); + return new FunctionCall(pipe.span, target, [ + new LiteralPrimitive(pipe.span, slot), + new LiteralPrimitive(pipe.span, pureFunctionSlot), + value, + ...args, + ]); } visitLiteralArray(array: LiteralArray, context: any): AST { @@ -527,8 +543,9 @@ class ValueConverter extends AstMemoryEfficientTransformer { // calls to literal factories that compose the literal and will cache intermediate // values. Otherwise, just return an literal array that contains the values. const literal = o.literalArr(values); - return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.constantPool, literal); + return values.every(a => a.isConstant()) ? + this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots); }); } @@ -539,14 +556,13 @@ class ValueConverter extends AstMemoryEfficientTransformer { // values. Otherwise, just return an literal array that contains the values. const literal = o.literalMap(values.map( (value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted}))); - return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.constantPool, literal); + return values.every(a => a.isConstant()) ? + this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots); }); } } - - // Pipes always have at least one parameter, the value they operate on const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; @@ -559,15 +575,20 @@ const pureFunctionIdentifiers = [ R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 ]; function getLiteralFactory( - constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { + constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr, + allocateSlots: (numSlots: number) => number): o.Expression { const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal); + // Allocate 1 slot for the result plus 1 per argument + const startSlot = allocateSlots(1 + literalFactoryArguments.length); literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); let pureFunctionIdent = pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; // Literal factories are pure functions that only need to be re-invoked when the parameters // change. - return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); + return o.importExpr(pureFunctionIdent).callFn([ + o.literal(startSlot), literalFactory, ...literalFactoryArguments + ]); } /** diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 8dd56318ab..127de2a3b4 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -107,6 +107,57 @@ describe('compiler compliance', () => { expectEmit(result.source, template, 'Incorrect template'); }); + it('should reserve slots for pure functions', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \`

\` + }) + export class MyComponent { + id = 'one'; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; + const template = ` + … + template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { + if (rf & 1) { + $r3$.ɵE(0, 'div'); + $r3$.ɵPp(1,'pipe'); + $r3$.ɵe(); + $r3$.ɵrS(10); + } + if (rf & 2) { + $r3$.ɵp(0, 'ternary', $r3$.ɵb((ctx.cond ? $r3$.ɵf1(2, _c0, ctx.a): _c1))); + $r3$.ɵp(0, 'pipe', $r3$.ɵb($r3$.ɵpb3(6, 1, ctx.value, 1, 2))); + $r3$.ɵp(0, 'and', $r3$.ɵb((ctx.cond && $r3$.ɵf1(4, _c0, ctx.b)))); + $r3$.ɵp(0, 'or', $r3$.ɵb((ctx.cond || $r3$.ɵf1(6, _c0, ctx.c)))); + } + } + `; + + + const result = compile(files, angularFiles); + + expectEmit(result.source, factory, 'Incorrect factory'); + expectEmit(result.source, template, 'Incorrect template'); + }); + it('should bind to class and style names', () => { const files = { app: { @@ -415,9 +466,10 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(2); } if (rf & 2) { - $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); + $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.customName))); } }, directives: [MyComp] @@ -494,11 +546,12 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(10); } if (rf & 2) { $r3$.ɵp( 0, 'names', - $r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); + $r3$.ɵb($r3$.ɵfV(10, $e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); } }, directives: [MyComp] @@ -555,9 +608,10 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'object-comp'); $r3$.ɵe(); + $r3$.ɵrS(2); } if (rf & 2) { - $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); + $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.name))); } }, directives: [ObjectComp] @@ -620,12 +674,12 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'nested-comp'); $r3$.ɵe(); + $r3$.ɵrS(7); } if (rf & 2) { $r3$.ɵp( 0, 'config', - $r3$.ɵb($r3$.ɵf2( - $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + $r3$.ɵb($r3$.ɵf2(7, $e0_ff_2$, ctx.name, $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration))))); } }, directives: [NestedComp] @@ -912,10 +966,11 @@ describe('compiler compliance', () => { $r3$.ɵT(4); $r3$.ɵPp(5, 'myPurePipe'); $r3$.ɵe(); + $r3$.ɵrS(9); } if (rf & 2) { - $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), '')); - $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, ctx.name, ctx.size), '')); + $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, 3, $r3$.ɵpb2(2, 6, ctx.name, ctx.size), ctx.size), '')); + $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, 9, ctx.name, ctx.size), '')); } }, pipes: [MyPurePipe, MyPipe] diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 136b4fd3e4..ff8c7449b4 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -65,6 +65,7 @@ export { e as ɵe, p as ɵp, pD as ɵpD, + rS as ɵrS, a as ɵa, s as ɵs, sn as ɵsn, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index ea2206fa10..421b421364 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -64,6 +64,8 @@ export { text as T, textBinding as t, + reserveSlots as rS, + embeddedViewStart as V, embeddedViewEnd as v, detectChanges, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 0a23c5b424..345e8730dd 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1446,12 +1446,10 @@ function generateInitialInputs( return initialInputData; } - ////////////////////////// //// ViewContainer & View ////////////////////////// - export function createLContainer( parentLNode: LNode, currentView: LView, template?: ComponentTemplate): LContainer { ngDevMode && assertNotNull(parentLNode, 'containers should have a parent'); @@ -2146,6 +2144,57 @@ export function bind(value: T | NO_CHANGE): T|NO_CHANGE { return changed ? value : NO_CHANGE; } +/** + * Reserves slots for pure functions (`pureFunctionX` instructions) + * + * Binding for pure functions are store after the LNodes in the data array but before the binding. + * + * ---------------------------------------------------------------------------- + * | LNodes ... | pure function bindings | regular bindings / interpolations | + * ---------------------------------------------------------------------------- + * ^ + * LView.bindingStartIndex + * + * Pure function instructions are given an offset from LView.bindingStartIndex. + * Subtracting the offset from LView.bindingStartIndex gives the first index where the bindings + * are stored. + * + * NOTE: reserveSlots instructions are only ever allowed at the very end of the creation block + */ +export function reserveSlots(numSlots: number) { + // Init the slots with a unique `NO_CHANGE` value so that the first change is always detected + // whether is happens or not during the first change detection pass - pure functions checks + // might be skipped when short-circuited. + data.length += numSlots; + data.fill(NO_CHANGE, -numSlots); + // We need to initialize the binding in case a `pureFunctionX` kind of binding instruction is + // called first in the update section. + initBindings(); +} + +/** + * Sets up the binding index before execute any `pureFunctionX` instructions. + * + * The index must be restored after the pure function is executed + * + * {@link reserveSlots} + */ +export function moveBindingIndexToReservedSlot(offset: number): number { + const currentSlot = currentView.bindingIndex; + currentView.bindingIndex = currentView.bindingStartIndex - offset; + return currentSlot; +} + +/** + * Restores the binding index to the given value. + * + * This function is typically used to restore the index after a `pureFunctionX` has + * been executed. + */ +export function restoreBindingIndex(index: number): void { + currentView.bindingIndex = index; +} + /** * Create interpolation bindings with a variable number of expressions. * @@ -2378,6 +2427,22 @@ function assertDataNext(index: number, arr?: any[]) { arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); } +/** + * On the first template pass the reserved slots should be set `NO_CHANGE`. + * + * If not they might not have been actually reserved. + */ +export function assertReservedSlotInitialized(slotOffset: number, numSlots: number) { + if (firstTemplatePass) { + const startIndex = currentView.bindingStartIndex - slotOffset; + for (let i = 0; i < numSlots; i++) { + assertEqual( + data[startIndex + i], NO_CHANGE, + 'The reserved slots should be set to `NO_CHANGE` on first template pass'); + } + } +} + export function _getComponentHostLElementNode(component: T): LElementNode { ngDevMode && assertNotNull(component, 'expecting component got null'); const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index c8f449148a..fd9031ca07 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -65,11 +65,12 @@ function getPipeDef(name: string, registry: PipeDefList | null): PipeDef { * the pipe only when an input to the pipe changes. * * @param index Pipe index where the pipe was stored on creation. + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param v1 1st argument to {@link PipeTransform#transform}. */ -export function pipeBind1(index: number, v1: any): any { +export function pipeBind1(index: number, slotOffset: number, v1: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction1(pipeInstance.transform, v1, pipeInstance) : + return isPure(index) ? pureFunction1(slotOffset, pipeInstance.transform, v1, pipeInstance) : pipeInstance.transform(v1); } @@ -80,12 +81,13 @@ export function pipeBind1(index: number, v1: any): any { * the pipe only when an input to the pipe changes. * * @param index Pipe index where the pipe was stored on creation. + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param v1 1st argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}. */ -export function pipeBind2(index: number, v1: any, v2: any): any { +export function pipeBind2(index: number, slotOffset: number, v1: any, v2: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction2(pipeInstance.transform, v1, v2, pipeInstance) : + return isPure(index) ? pureFunction2(slotOffset, pipeInstance.transform, v1, v2, pipeInstance) : pipeInstance.transform(v1, v2); } @@ -96,14 +98,16 @@ export function pipeBind2(index: number, v1: any, v2: any): any { * the pipe only when an input to the pipe changes. * * @param index Pipe index where the pipe was stored on creation. + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param v1 1st argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}. * @param v3 4rd argument to {@link PipeTransform#transform}. */ -export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { +export function pipeBind3(index: number, slotOffset: number, v1: any, v2: any, v3: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction3(pipeInstance.transform, v1, v2, v3, pipeInstance) : - pipeInstance.transform(v1, v2, v3); + return isPure(index) ? + pureFunction3(slotOffset, pipeInstance.transform, v1, v2, v3, pipeInstance) : + pipeInstance.transform(v1, v2, v3); } /** @@ -113,15 +117,18 @@ export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { * the pipe only when an input to the pipe changes. * * @param index Pipe index where the pipe was stored on creation. + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param v1 1st argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}. * @param v3 3rd argument to {@link PipeTransform#transform}. * @param v4 4th argument to {@link PipeTransform#transform}. */ -export function pipeBind4(index: number, v1: any, v2: any, v3: any, v4: any): any { +export function pipeBind4( + index: number, slotOffset: number, v1: any, v2: any, v3: any, v4: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction4(pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : - pipeInstance.transform(v1, v2, v3, v4); + return isPure(index) ? + pureFunction4(slotOffset, pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : + pipeInstance.transform(v1, v2, v3, v4); } /** @@ -131,11 +138,12 @@ export function pipeBind4(index: number, v1: any, v2: any, v3: any, v4: any): an * the pipe only when an input to the pipe changes. * * @param index Pipe index where the pipe was stored on creation. + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param values Array of arguments to pass to {@link PipeTransform#transform} method. */ -export function pipeBindV(index: number, values: any[]): any { +export function pipeBindV(index: number, slotOffset: number, values: any[]): any { const pipeInstance = load(index); - return isPure(index) ? pureFunctionV(pipeInstance.transform, values, pipeInstance) : + return isPure(index) ? pureFunctionV(slotOffset, pipeInstance.transform, values, pipeInstance) : pipeInstance.transform.apply(pipeInstance, values); } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index dad4424647..ecf5be9666 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, consumeBinding, getCreationMode} from './instructions'; +import {assertReservedSlotInitialized, bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, consumeBinding, getCreationMode, moveBindingIndexToReservedSlot, restoreBindingIndex} from './instructions'; @@ -15,92 +15,121 @@ import {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, * value. If it has been saved, returns the saved value. * * @param pureFn Function that returns a value + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param thisArg Optional calling context of pureFn * @returns value */ -export function pureFunction0(pureFn: () => T, thisArg?: any): T { - return getCreationMode() ? checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) : - consumeBinding(); +export function pureFunction0(slotOffset: number, pureFn: () => T, thisArg?: any): T { + ngDevMode && assertReservedSlotInitialized(slotOffset, 1); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = getCreationMode() ? + checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) : + consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of the provided exp has changed, calls the pure function to return * an updated value. Or if the value has not changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn Function that returns an updated value * @param exp Updated expression value * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ -export function pureFunction1(pureFn: (v: any) => any, exp: any, thisArg?: any): any { - return bindingUpdated(exp) ? +export function pureFunction1( + slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 2); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = bindingUpdated(exp) ? checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction2( - pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, thisArg?: any): any { - return bindingUpdated2(exp1, exp2) ? + slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, + thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 3); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = bindingUpdated2(exp1, exp2) ? checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param exp3 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction3( - pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, + slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 4); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated2(exp1, exp2); - return bindingUpdated(exp3) || different ? + const value = bindingUpdated(exp3) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param exp3 * @param exp4 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction4( - pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, - thisArg?: any): any { - return bindingUpdated4(exp1, exp2, exp3, exp4) ? + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, + exp3: any, exp4: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 5); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = bindingUpdated4(exp1, exp2, exp3, exp4) ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -108,23 +137,28 @@ export function pureFunction4( * @param exp4 * @param exp5 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction5( - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any, - exp4: any, exp5: any, thisArg?: any): any { + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, + exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 6); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated4(exp1, exp2, exp3, exp4); - return bindingUpdated(exp5) || different ? + const value = bindingUpdated(exp5) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) : pureFn(exp1, exp2, exp3, exp4, exp5)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -133,23 +167,28 @@ export function pureFunction5( * @param exp5 * @param exp6 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction6( - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, - exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any { + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, + exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 7); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated4(exp1, exp2, exp3, exp4); - return bindingUpdated2(exp5, exp6) || different ? + const value = bindingUpdated2(exp5, exp6) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -159,24 +198,30 @@ export function pureFunction6( * @param exp6 * @param exp7 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction7( + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 8); + const index = moveBindingIndexToReservedSlot(slotOffset); let different = bindingUpdated4(exp1, exp2, exp3, exp4); different = bindingUpdated2(exp5, exp6) || different; - return bindingUpdated(exp7) || different ? + const value = bindingUpdated(exp7) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -187,18 +232,23 @@ export function pureFunction7( * @param exp7 * @param exp8 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction8( + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 9); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated4(exp1, exp2, exp3, exp4); - return bindingUpdated4(exp5, exp6, exp7, exp8) || different ? + const value = bindingUpdated4(exp5, exp6, exp7, exp8) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** @@ -207,17 +257,23 @@ export function pureFunction8( * If the value of any provided exp has changed, calls the pure function to return * an updated value. Or if no values have changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn A pure function that takes binding values and builds an object or array * containing those values. * @param exps An array of binding values * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ -export function pureFunctionV(pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any { - let different = false; +export function pureFunctionV( + slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, exps.length + 1); + const index = moveBindingIndexToReservedSlot(slotOffset); + let different = false; for (let i = 0; i < exps.length; i++) { bindingUpdated(exps[i]) && (different = true); } - return different ? checkAndUpdateBinding(pureFn.apply(thisArg, exps)) : consumeBinding(); + const value = different ? checkAndUpdateBinding(pureFn.apply(thisArg, exps)) : consumeBinding(); + restoreBindingIndex(index); + return value; } diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts index 65a49ecec9..7c1e6a7845 100644 --- a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -502,9 +502,10 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'my-array-comp'); $r3$.ɵe(); + $r3$.ɵrS(1); } if (rf & 2) { - $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0($e0_ff$)))); + $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0(1, $e0_ff$)))); } } }); @@ -563,9 +564,10 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(1); } if (rf & 2) { - $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0($e0_ff$).length + 1)); + $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0(1, $e0_ff$).length + 1)); } } }); @@ -606,9 +608,10 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'my-array-comp'); $r3$.ɵe(); + $r3$.ɵrS(2); } if (rf & 2) { - $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); + $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.customName))); } } }); @@ -716,12 +719,13 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(10); } if (rf & 2) { $r3$.ɵp( 0, 'names', - $r3$.ɵb( - $r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); + $r3$.ɵb($r3$.ɵfV( + 10, $e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); } } }); @@ -794,9 +798,10 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'object-comp'); $r3$.ɵe(); + $r3$.ɵrS(2); } if (rf & 2) { - $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); + $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.name))); } } }); @@ -879,12 +884,13 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'nested-comp'); $r3$.ɵe(); + $r3$.ɵrS(7); } if (rf & 2) { $r3$.ɵp( - 0, 'config', $r3$.ɵf2( - $e0_ff_2$, ctx.name, - $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + 0, 'config', $r3$.ɵb($r3$.ɵf2( + 7, $e0_ff_2$, ctx.name, + $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration))))); } } }); diff --git a/packages/core/test/render3/compiler_canonical/pipes_spec.ts b/packages/core/test/render3/compiler_canonical/pipes_spec.ts index 625b4e2c32..ea6316fdac 100644 --- a/packages/core/test/render3/compiler_canonical/pipes_spec.ts +++ b/packages/core/test/render3/compiler_canonical/pipes_spec.ts @@ -88,9 +88,12 @@ describe('pipes', () => { $r3$.ɵT(0); $r3$.ɵPp(1, 'myPipe'); $r3$.ɵPp(2, 'myPurePipe'); + $r3$.ɵrS(6); } if (rf & 2) { - $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2, ctx.name, ctx.size), ctx.size), '')); + $r3$.ɵt( + 0, + $r3$.ɵi1('', $r3$.ɵpb2(1, 6, $r3$.ɵpb2(2, 3, ctx.name, ctx.size), ctx.size), '')); } } }); @@ -166,10 +169,11 @@ describe('pipes', () => { $r3$.ɵT(2); $r3$.ɵPp(3, 'myPurePipe'); $r3$.ɵC(4, C4, '', ['oneTimeIf', '']); + $r3$.ɵrS(6); } if (rf & 2) { - $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, ctx.name, ctx.size), '')); - $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(3, ctx.name, ctx.size), '')); + $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, 3, ctx.name, ctx.size), '')); + $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(3, 6, ctx.name, ctx.size), '')); $r3$.ɵp(4, 'oneTimeIf', $r3$.ɵb(ctx.more)); $r3$.ɵcR(4); $r3$.ɵcr(); @@ -181,9 +185,10 @@ describe('pipes', () => { $r3$.ɵT(1); $r3$.ɵPp(2, 'myPurePipe'); $r3$.ɵe(); + $r3$.ɵrS(3); } if (rf & 2) { - $r3$.ɵt(1, $r3$.ɵi1('', $r3$.ɵpb2(2, ctx.name, ctx.size), '')); + $r3$.ɵt(1, $r3$.ɵi1('', $r3$.ɵpb2(2, 3, ctx.name, ctx.size), '')); } } } diff --git a/packages/core/test/render3/pipe_spec.ts b/packages/core/test/render3/pipe_spec.ts index 2a49d26b4e..e5da9bbcbe 100644 --- a/packages/core/test/render3/pipe_spec.ts +++ b/packages/core/test/render3/pipe_spec.ts @@ -10,7 +10,7 @@ import {Directive, OnChanges, OnDestroy, Pipe, PipeTransform} from '@angular/cor import {expect} from '@angular/platform-browser/testing/src/matchers'; import {defineDirective, definePipe} from '../../src/render3/definition'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, reserveSlots, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {pipe, pipeBind1, pipeBind3, pipeBind4, pipeBindV} from '../../src/render3/pipe'; @@ -38,9 +38,10 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'countingPipe'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), '')); } } @@ -53,9 +54,10 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'randomPipeName'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, ctx.value), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, ctx.value), '')); } }, [], pipes); @@ -97,9 +99,10 @@ describe('pipe', () => { elementStart(0, 'div', ['myDir', '']); pipe(1, 'double'); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'elprop', bind(pipeBind1(1, ctx))); + elementProperty(0, 'elprop', bind(pipeBind1(1, 2, ctx))); directive = loadDirective(0); } } @@ -112,10 +115,11 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'multiArgPipe'); + reserveSlots(4); } if (rf & RenderFlags.Update) { textBinding( - 0, interpolation1('', pipeBind3(1, person.name, 'one', person.address !.city), '')); + 0, interpolation1('', pipeBind3(1, 4, person.name, 'one', person.address !.city), '')); } } @@ -129,11 +133,12 @@ describe('pipe', () => { text(0); pipe(1, 'multiArgPipe'); pipe(2, 'multiArgPipe'); + reserveSlots(9); } if (rf & RenderFlags.Update) { textBinding( - 0, - interpolation1('', pipeBind4(2, pipeBindV(1, [person.name, 'a', 'b']), 0, 1, 2), '')); + 0, interpolation1( + '', pipeBind4(2, 9, pipeBindV(1, 4, [person.name, 'a', 'b']), 0, 1, 2), '')); } } @@ -158,9 +163,10 @@ describe('pipe', () => { elementStart(0, 'div'); pipe(1, 'identityPipe'); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'someProp', bind(pipeBind1(1, 'Megatron'))); + elementProperty(0, 'someProp', bind(pipeBind1(1, 2, 'Megatron'))); } } @@ -178,9 +184,10 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'countingPipe'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), '')); } } @@ -207,9 +214,10 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'countingImpurePipe'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), '')); } } @@ -228,10 +236,11 @@ describe('pipe', () => { pipe(3, 'countingImpurePipe'); elementEnd(); container(4); + reserveSlots(4); } if (rf & RenderFlags.Update) { - elementProperty(0, 'someProp', bind(pipeBind1(1, true))); - elementProperty(2, 'someProp', bind(pipeBind1(3, true))); + elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true))); + elementProperty(2, 'someProp', bind(pipeBind1(3, 4, true))); pipeInstances.push(load(1), load(3)); containerRefreshStart(4); { @@ -242,9 +251,10 @@ describe('pipe', () => { elementStart(0, 'div'); pipe(1, 'countingImpurePipe'); elementEnd(); + reserveSlots(2); } if (rf1 & RenderFlags.Update) { - elementProperty(0, 'someProp', bind(pipeBind1(1, true))); + elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true))); pipeInstances.push(load(1)); } } @@ -296,9 +306,10 @@ describe('pipe', () => { if (rf1 & RenderFlags.Create) { text(0); pipe(1, 'pipeWithOnDestroy'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, person.age), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, person.age), '')); } } embeddedViewEnd(); diff --git a/packages/core/test/render3/pure_function_spec.ts b/packages/core/test/render3/pure_function_spec.ts index 4cb71ae428..c3290895f1 100644 --- a/packages/core/test/render3/pure_function_spec.ts +++ b/packages/core/test/render3/pure_function_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {defineComponent} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, reserveSlots} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction5, pureFunction6, pureFunction7, pureFunction8, pureFunctionV} from '../../src/render3/pure_function'; import {renderToHtml} from '../../test/render3/render_util'; @@ -36,9 +36,10 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names', bind(pureFunction1(e0_ff, ctx.customName))); + elementProperty(0, 'names', bind(pureFunction1(2, e0_ff, ctx.customName))); } } @@ -90,10 +91,11 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'many-prop-comp'); elementEnd(); + reserveSlots(4); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names1', bind(pureFunction1(e0_ff, ctx.customName))); - elementProperty(0, 'names2', bind(pureFunction1(e0_ff_1, ctx.customName2))); + elementProperty(0, 'names1', bind(pureFunction1(2, e0_ff, ctx.customName))); + elementProperty(0, 'names2', bind(pureFunction1(4, e0_ff_1, ctx.customName2))); } } @@ -130,9 +132,10 @@ describe('array literals', () => { elementStart(0, 'my-comp'); myComps.push(loadDirective(0)); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names', bind(ctx.someFn(pureFunction1(e0_ff, ctx.customName)))); + elementProperty(0, 'names', bind(ctx.someFn(pureFunction1(2, e0_ff, ctx.customName)))); } }, directives: directives @@ -170,9 +173,10 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(3); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names', bind(pureFunction2(e0_ff, ctx.customName, ctx.customName2))); + elementProperty(0, 'names', bind(pureFunction2(3, e0_ff, ctx.customName, ctx.customName2))); } } @@ -241,17 +245,19 @@ describe('array literals', () => { elementStart(5, 'my-comp'); f8Comp = loadDirective(5); elementEnd(); + reserveSlots(39); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names', bind(pureFunction3(e0_ff, c[5], c[6], c[7]))); - elementProperty(1, 'names', bind(pureFunction4(e2_ff, c[4], c[5], c[6], c[7]))); - elementProperty(2, 'names', bind(pureFunction5(e4_ff, c[3], c[4], c[5], c[6], c[7]))); - elementProperty(3, 'names', bind(pureFunction6(e6_ff, c[2], c[3], c[4], c[5], c[6], c[7]))); + elementProperty(0, 'names', bind(pureFunction3(4, e0_ff, c[5], c[6], c[7]))); + elementProperty(1, 'names', bind(pureFunction4(9, e2_ff, c[4], c[5], c[6], c[7]))); + elementProperty(2, 'names', bind(pureFunction5(15, e4_ff, c[3], c[4], c[5], c[6], c[7]))); elementProperty( - 4, 'names', bind(pureFunction7(e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); + 3, 'names', bind(pureFunction6(22, e6_ff, c[2], c[3], c[4], c[5], c[6], c[7]))); + elementProperty( + 4, 'names', bind(pureFunction7(30, e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); elementProperty( 5, 'names', - bind(pureFunction8(e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); + bind(pureFunction8(39, e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); } } @@ -295,11 +301,12 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(12); } if (rf & RenderFlags.Update) { elementProperty( - 0, 'names', bind(pureFunctionV(e0_ff, [ - c[0], c[1], c[2], c[3], pureFunction1(e0_ff_1, c[4]), c[5], c[6], c[7], c[8] + 0, 'names', bind(pureFunctionV(12, e0_ff, [ + c[0], c[1], c[2], c[3], pureFunction1(2, e0_ff_1, c[4]), c[5], c[6], c[7], c[8] ]))); } } @@ -345,9 +352,10 @@ describe('object literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'object-comp'); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'config', bind(pureFunction1(e0_ff, ctx.name))); + elementProperty(0, 'config', bind(pureFunction1(2, e0_ff, ctx.name))); } } @@ -380,12 +388,13 @@ describe('object literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'object-comp'); elementEnd(); + reserveSlots(7); } if (rf & RenderFlags.Update) { elementProperty( - 0, 'config', - bind(pureFunction2( - e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration))))); + 0, 'config', bind(pureFunction2( + 7, e0_ff, ctx.name, + pureFunction1(4, e0_ff_1, pureFunction1(2, e0_ff_2, ctx.duration))))); } } @@ -451,11 +460,12 @@ describe('object literals', () => { elementStart(0, 'object-comp'); objectComps.push(loadDirective(0)); elementEnd(); + reserveSlots(3); } if (rf1 & RenderFlags.Update) { elementProperty( 0, 'config', - bind(pureFunction2(e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration))); + bind(pureFunction2(3, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration))); } embeddedViewEnd(); } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 3bb2328b21..8dc809fa6e 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -9,7 +9,7 @@ import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {pipe, pipeBind1} from '../../src/render3/pipe'; @@ -383,22 +383,25 @@ describe('ViewContainerRef', () => { elementStart(0, 'child'); elementEnd(); pipe(1, 'starPipe'); + reserveSlots(2); } - if (rf & RenderFlags.Create) { - elementProperty(0, 'name', bind(pipeBind1(1, 'C'))); + if (rf & RenderFlags.Update) { + elementProperty(0, 'name', bind(pipeBind1(1, 2, 'C'))); } }); pipe(1, 'starPipe'); elementStart(2, 'child', ['vcref', '']); elementEnd(); - elementStart(3, 'child'); + pipe(3, 'starPipe'); + elementStart(4, 'child'); elementEnd(); + reserveSlots(4); } if (rf & RenderFlags.Update) { const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); elementProperty(2, 'tplRef', bind(tplRef)); - elementProperty(2, 'name', bind(pipeBind1(1, 'A'))); - elementProperty(3, 'name', bind(pipeBind1(1, 'B'))); + elementProperty(2, 'name', bind(pipeBind1(1, 2, 'A'))); + elementProperty(4, 'name', bind(pipeBind1(1, 4, 'B'))); } }, directives: [Child, DirectiveWithVCRef], From 280a784fe368e17ef5c35ba11262469615dcb97b Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 22 May 2018 10:43:18 -0700 Subject: [PATCH 098/582] refactor(ivy): remove insignificant '...' in the compiler tests (#24039) PR Close #24039 --- packages/compiler/test/render3/r3_compiler_compliance_spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 127de2a3b4..3c8c9935e9 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -88,7 +88,6 @@ describe('compiler compliance', () => { const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; const template = ` - … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div'); @@ -134,7 +133,6 @@ describe('compiler compliance', () => { const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; const template = ` - … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div'); @@ -181,7 +179,6 @@ describe('compiler compliance', () => { const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; const template = ` - … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div'); From 188ff848d261bee62b828ed94d6772172853b6d5 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 22 May 2018 10:47:50 -0700 Subject: [PATCH 099/582] fix(ivy): `pureFunctionV` takes an array of values (#24039) PR Close #24039 --- .../compiler/src/render3/view/template.ts | 27 +++++++++++++++---- .../render3/r3_compiler_compliance_spec.ts | 2 +- .../hello_world_r2/bundle.golden_symbols.json | 3 +++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index c6dbadfe54..d50dbb1f2c 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -574,6 +574,15 @@ const pureFunctionIdentifiers = [ R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 ]; + +function pureFunctionCallInfo(args: o.Expression[]) { + const identifier = pureFunctionIdentifiers[args.length]; + return { + identifier: identifier || R3.pureFunctionV, + isVarLength: !identifier, + }; +} + function getLiteralFactory( constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr, allocateSlots: (numSlots: number) => number): o.Expression { @@ -581,14 +590,22 @@ function getLiteralFactory( // Allocate 1 slot for the result plus 1 per argument const startSlot = allocateSlots(1 + literalFactoryArguments.length); literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); - let pureFunctionIdent = - pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; + const {identifier, isVarLength} = pureFunctionCallInfo(literalFactoryArguments); // Literal factories are pure functions that only need to be re-invoked when the parameters // change. - return o.importExpr(pureFunctionIdent).callFn([ - o.literal(startSlot), literalFactory, ...literalFactoryArguments - ]); + const args = [ + o.literal(startSlot), + literalFactory, + ]; + + if (isVarLength) { + args.push(o.literalArr(literalFactoryArguments)); + } else { + args.push(...literalFactoryArguments); + } + + return o.importExpr(identifier).callFn(args); } /** diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 3c8c9935e9..44af890457 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -548,7 +548,7 @@ describe('compiler compliance', () => { if (rf & 2) { $r3$.ɵp( 0, 'names', - $r3$.ɵb($r3$.ɵfV(10, $e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); + $r3$.ɵb($r3$.ɵfV(10, $e0_ff$, [ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8]))); } }, directives: [MyComp] diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index c792f20640..c35362fae4 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -3662,6 +3662,9 @@ { "name": "pureArrayDef" }, + { + "name": "pureFunctionCallInfo" + }, { "name": "pureFunctionIdentifiers" }, From 729c7978904cec5bf25a939e66f19f2cc1624e90 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 22 May 2018 10:48:11 -0700 Subject: [PATCH 100/582] fix(ivy): `pipeBindV` takes an array of values (#24039) PR Close #24039 --- .../compiler/src/render3/view/template.ts | 20 +++++++++++-------- .../render3/r3_compiler_compliance_spec.ts | 13 +++++++----- .../hello_world_r2/bundle.golden_symbols.json | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index d50dbb1f2c..fc1b2a8513 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -524,16 +524,16 @@ class ValueConverter extends AstMemoryEfficientTransformer { // Allocate one slot for the result plus one slot per pipe argument const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length); const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal); - const bindingId = pipeBinding(pipe.args); - this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId)); - const value = pipe.exp.visit(this); - const args = this.visitAll(pipe.args); + const {identifier, isVarLength} = pipeBindingCallInfo(pipe.args); + this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(identifier)); + const args: AST[] = [pipe.exp, ...pipe.args]; + const convertedArgs: AST[] = + isVarLength ? this.visitAll([new LiteralArray(pipe.span, args)]) : this.visitAll(args); return new FunctionCall(pipe.span, target, [ new LiteralPrimitive(pipe.span, slot), new LiteralPrimitive(pipe.span, pureFunctionSlot), - value, - ...args, + ...convertedArgs, ]); } @@ -566,8 +566,12 @@ class ValueConverter extends AstMemoryEfficientTransformer { // Pipes always have at least one parameter, the value they operate on const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; -function pipeBinding(args: o.Expression[]): o.ExternalReference { - return pipeBindingIdentifiers[args.length] || R3.pipeBindV; +function pipeBindingCallInfo(args: o.Expression[]) { + const identifier = pipeBindingIdentifiers[args.length]; + return { + identifier: identifier || R3.pipeBindV, + isVarLength: !identifier, + }; } const pureFunctionIdentifiers = [ diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 44af890457..3bd371cb21 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -921,7 +921,7 @@ describe('compiler compliance', () => { @Component({ selector: 'my-app', - template: '{{name | myPipe:size | myPurePipe:size }}

{{ name | myPurePipe:size }}

' + template: '{{name | myPipe:size | myPurePipe:size }}

{{ name | myPipe:1:2:3:4:5 }}

' }) export class MyApp { name = 'World'; @@ -949,7 +949,10 @@ describe('compiler compliance', () => { });`; const MyAppDefinition = ` - … + const $c0$ = ($a0$: any) => { + return [$a0$, 1, 2, 3, 4, 5]; + }; + // ... static ngComponentDef = $r3$.ɵdefineComponent({ type: MyApp, selectors: [['my-app']], @@ -961,13 +964,13 @@ describe('compiler compliance', () => { $r3$.ɵPp(2, 'myPipe'); $r3$.ɵE(3, 'p'); $r3$.ɵT(4); - $r3$.ɵPp(5, 'myPurePipe'); + $r3$.ɵPp(5, 'myPipe'); $r3$.ɵe(); - $r3$.ɵrS(9); + $r3$.ɵrS(15); } if (rf & 2) { $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, 3, $r3$.ɵpb2(2, 6, ctx.name, ctx.size), ctx.size), '')); - $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, 9, ctx.name, ctx.size), '')); + $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpbV(5, 13 , $r3$.ɵf1(15, $c0$, ctx.name)), '')); } }, pipes: [MyPurePipe, MyPipe] diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index c35362fae4..fe5e7d7bbd 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -3603,7 +3603,7 @@ "name": "parseIntAutoRadix" }, { - "name": "pipeBinding" + "name": "pipeBindingCallInfo" }, { "name": "pipeBindingIdentifiers" From 3de80fc7fb47e314abe9d5c102c900a099dae8ce Mon Sep 17 00:00:00 2001 From: Mashhood Rastgar Date: Wed, 9 May 2018 11:07:54 -0700 Subject: [PATCH 101/582] docs(aio): Added Mashhood as GDE in contributors (#24157) PR Close #24157 --- aio/content/images/bios/mashhood.jpg | Bin 0 -> 9621 bytes aio/content/marketing/contributors.json | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 aio/content/images/bios/mashhood.jpg diff --git a/aio/content/images/bios/mashhood.jpg b/aio/content/images/bios/mashhood.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ce1a828b4ff56aeb4c0b5630b056bc1981c2a2e GIT binary patch literal 9621 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 D{vf7p literal 0 HcmV?d00001 diff --git a/aio/content/marketing/contributors.json b/aio/content/marketing/contributors.json index 8823376274..4e1674e549 100644 --- a/aio/content/marketing/contributors.json +++ b/aio/content/marketing/contributors.json @@ -627,5 +627,14 @@ "website": "https://juristr.com", "bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.", "group": "GDE" + }, + + "mashhoodr": { + "name": "Mashhood Rastgar", + "picture": "mashhood.jpg", + "twitter": "mashhoodr", + "website": "http://imars.info/", + "bio": "Mashhood is the principal technical consultant at Recurship and a Google Developer Expert. He works with different startups in US and EU to helps them crawl through the technical maze and quickly build amazing products focused around the problems they are trying to solve. He specializes in using the latest web technologies available to execute the best possible solutions.", + "group": "GDE" } } From 76575357183209695ac028bfa1d23c2f57ea4b9a Mon Sep 17 00:00:00 2001 From: Juri Date: Sat, 26 May 2018 21:37:13 +0200 Subject: [PATCH 102/582] docs(aio): fix link to correct bio image (#24150) PR Close #24150 --- aio/content/marketing/contributors.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/marketing/contributors.json b/aio/content/marketing/contributors.json index 4e1674e549..25dda612b1 100644 --- a/aio/content/marketing/contributors.json +++ b/aio/content/marketing/contributors.json @@ -622,7 +622,7 @@ "juristr": { "name": "Juri Strumpflohner", - "picture": "juristr.png", + "picture": "juristr.jpg", "twitter": "juristr", "website": "https://juristr.com", "bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.", From e43d3fa4b7c4cb07d51d8c79c0e5e3afa4a92e2f Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Tue, 29 May 2018 11:55:59 -0700 Subject: [PATCH 103/582] build: pick up rules_typescript karma fix (#24184) Error was Cannot find module 'karma/bin/karma' PR Close #24184 --- WORKSPACE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index d18ec153e0..9d0d875b2b 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -20,9 +20,9 @@ http_archive( http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/v0.13.0.zip", - strip_prefix = "rules_typescript-0.13.0", - sha256 = "8f2767ff56ad68c80c62e9a1cdc2ba2c2ba0b19d350f713365e5333045df02e3", + url = "https://github.com/bazelbuild/rules_typescript/archive/3b86d6d46269fb52d4c6f1416868869e847feac2.zip", + strip_prefix = "rules_typescript-3b86d6d46269fb52d4c6f1416868869e847feac2", + sha256 = "f67e5fbe4a2b34b3ead9fe56f22b713540c23b501bd24d661d3fb047071dc2c1", ) http_archive( From 96a0e131bf47ef4ae2ca04c49850319563f8df93 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 21 May 2018 12:54:34 +0100 Subject: [PATCH 104/582] feat(aio): render hover status on code-tabs (#24027) The Material Design spec states that there should be a change of background when hovering over a tab label. See https://material.io/design/components/tabs.html#states Related to #23985 PR Close #24027 --- aio/src/styles/2-modules/_code.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index b2806bfa25..0e788ae48f 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -106,6 +106,9 @@ aio-code pre { } .code-tab-group .mat-tab-label { + &:hover { + background: rgba(black, 0.04); + } white-space: nowrap; } From ddd61248021c3d9344ccae2614c992b24d450d29 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 21 May 2018 12:55:22 +0100 Subject: [PATCH 105/582] feat(aio): display code-tabs in a Material Design "card" (#24027) This helps to connect the "content" of the tab to its label. Closes #23985 PR Close #24027 --- .../code/code-tabs.component.ts | 30 ++++++++++--------- .../custom-elements/code/code-tabs.module.ts | 4 +-- aio/src/styles/2-modules/_code.scss | 15 ++++++++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/aio/src/app/custom-elements/code/code-tabs.component.ts b/aio/src/app/custom-elements/code/code-tabs.component.ts index fc585e7198..d7759955c2 100644 --- a/aio/src/app/custom-elements/code/code-tabs.component.ts +++ b/aio/src/app/custom-elements/code/code-tabs.component.ts @@ -25,20 +25,22 @@ export interface TabInfo {
- - - - {{ tab.title }} - - - - - + + + + + {{ tab.title }} + + + + + + `, }) export class CodeTabsComponent implements OnInit, AfterViewInit { diff --git a/aio/src/app/custom-elements/code/code-tabs.module.ts b/aio/src/app/custom-elements/code/code-tabs.module.ts index 7dffaa19b6..eac0e29b37 100644 --- a/aio/src/app/custom-elements/code/code-tabs.module.ts +++ b/aio/src/app/custom-elements/code/code-tabs.module.ts @@ -1,12 +1,12 @@ import { NgModule, Type } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CodeTabsComponent } from './code-tabs.component'; -import { MatTabsModule } from '@angular/material'; +import { MatCardModule, MatTabsModule } from '@angular/material'; import { CodeModule } from './code.module'; import { WithCustomElementComponent } from '../element-registry'; @NgModule({ - imports: [ CommonModule, MatTabsModule, CodeModule ], + imports: [ CommonModule, MatCardModule, MatTabsModule, CodeModule ], declarations: [ CodeTabsComponent ], exports: [ CodeTabsComponent ], entryComponents: [ CodeTabsComponent ] diff --git a/aio/src/styles/2-modules/_code.scss b/aio/src/styles/2-modules/_code.scss index 0e788ae48f..166b6ef114 100644 --- a/aio/src/styles/2-modules/_code.scss +++ b/aio/src/styles/2-modules/_code.scss @@ -3,8 +3,9 @@ code-example, code-tabs { display: block; } -code-example, -code-tabs mat-tab-body { +code-example { + + &:not(.no-box) { background-color: rgba($backgroundgray, 0.2); border: 0.5px solid $lightgray; @@ -27,6 +28,16 @@ code-tabs mat-tab-body { } } +code-example, code-tabs { + .mat-card { + padding: 0; + border-radius: 5px; + } + code { + overflow: auto; + } +} + // TERMINAL / SHELL TEXT STYLES code-example.code-shell, code-example[language=sh], code-example[language=bash] { From d74078fb88f9c523cf7efff04a54c6a7b53e8d4b Mon Sep 17 00:00:00 2001 From: Danny Rademacher <3715737+drademacher@users.noreply.github.com> Date: Thu, 3 May 2018 11:54:46 +0200 Subject: [PATCH 106/582] docs(http): correct spelling error (#23675) Correct a spelling error. I changed HttpParms to HttpParams PR Close #23675 --- aio/content/guide/http.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/content/guide/http.md b/aio/content/guide/http.md index 0c6d903f31..ceee6e1067 100644 --- a/aio/content/guide/http.md +++ b/aio/content/guide/http.md @@ -450,7 +450,7 @@ Here is a `searchHeroes` method that queries for heroes whose names contain the If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`. -The `HttpParms` are immutable so you'll have to use the `set()` method to update the options. +The `HttpParams` are immutable so you'll have to use the `set()` method to update the options. ### Debouncing requests @@ -1044,4 +1044,4 @@ Alternatively, you can call `request.error()` with an `ErrorEvent`. path="http/src/testing/http-client.spec.ts" region="network-error" linenums="false"> - \ No newline at end of file + From a6f34be9f598bfa45abbb8426dddd46f6ed1e4e3 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 24 May 2018 12:23:43 +0300 Subject: [PATCH 107/582] build(aio): update payload size limits (#23944) PR Close #23944 --- aio/scripts/_payload-limits.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index c6a729d55a..ac9b44a215 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -2,11 +2,11 @@ "aio": { "master": { "uncompressed": { - "runtime": 2689, - "main": 478529, + "runtime": 2712, + "main": 479417, "polyfills": 38453, "prettify": 14913 } } } -} \ No newline at end of file +} From ae86cb3be0c413544f8b17cfcd0e29ae21655b4c Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 15 May 2018 19:41:13 +0300 Subject: [PATCH 108/582] fix(aio): do not load custom elements again while already loading (#23944) PR Close #23944 --- aio/scripts/_payload-limits.json | 2 +- .../custom-elements/elements-loader.spec.ts | 282 +++++++++++++----- .../app/custom-elements/elements-loader.ts | 64 ++-- 3 files changed, 251 insertions(+), 97 deletions(-) diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index ac9b44a215..a3fdfa790d 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime": 2712, - "main": 479417, + "main": 479729, "polyfills": 38453, "prettify": 14913 } diff --git a/aio/src/app/custom-elements/elements-loader.spec.ts b/aio/src/app/custom-elements/elements-loader.spec.ts index 1fc4eed22c..906e74283e 100644 --- a/aio/src/app/custom-elements/elements-loader.spec.ts +++ b/aio/src/app/custom-elements/elements-loader.spec.ts @@ -4,49 +4,19 @@ import { NgModuleRef, Type } from '@angular/core'; -import {TestBed, fakeAsync, tick} from '@angular/core/testing'; +import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing'; import { ElementsLoader } from './elements-loader'; import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry'; -class FakeComponentFactory extends ComponentFactory { - selector: string; - componentType: Type; - ngContentSelectors: string[]; - inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}]; - outputs = []; - constructor(private identifyingInput: string) { super(); } - - create(injector: Injector, - projectableNodes?: any[][], - rootSelectorOrNode?: string | any, - ngModule?: NgModuleRef): ComponentRef { - return (jasmine.createSpy('ComponentRef') as any) as ComponentRef; - }; +interface Deferred { + resolve(): void; + reject(err: any): void; } -const FAKE_COMPONENT_FACTORIES = new Map([ - ['element-a-module-path', new FakeComponentFactory('element-a-input')], - ['element-b-module-path', new FakeComponentFactory('element-b-input')], -]); - describe('ElementsLoader', () => { let elementsLoader: ElementsLoader; - let actualCustomElementsDefine; - let fakeCustomElementsDefine; - - // ElementsLoader uses the window's customElements API. Provide a fake for this test. - beforeEach(() => { - actualCustomElementsDefine = window.customElements.define; - - fakeCustomElementsDefine = jasmine.createSpy('define'); - - window.customElements.define = fakeCustomElementsDefine; - }); - afterEach(() => { - window.customElements.define = actualCustomElementsDefine; - }); beforeEach(() => { const injector = TestBed.configureTestingModule({ @@ -63,63 +33,196 @@ describe('ElementsLoader', () => { elementsLoader = injector.get(ElementsLoader); }); - it('should be able to register an element', fakeAsync(() => { - // Verify that the elements loader considered `element-a-selector` to be unregistered. - expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy(); + describe('loadContainingCustomElements()', () => { + let loadCustomElementSpy: jasmine.Spy; - const hostEl = document.createElement('div'); - hostEl.innerHTML = ``; + beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement')); - elementsLoader.loadContainingCustomElements(hostEl); - tick(); + it('should attempt to load and register all contained elements', fakeAsync(() => { + expect(loadCustomElementSpy).not.toHaveBeenCalled(); - const defineArgs = fakeCustomElementsDefine.calls.argsFor(0); - expect(defineArgs[0]).toBe('element-a-selector'); + const hostEl = document.createElement('div'); + hostEl.innerHTML = ` + + + `; - // Verify the right component was loaded/created - expect(defineArgs[1].observedAttributes[0]).toBe('element-a-input'); + elementsLoader.loadContainingCustomElements(hostEl); + flushMicrotasks(); - expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy(); - })); + expect(loadCustomElementSpy).toHaveBeenCalledTimes(2); + expect(loadCustomElementSpy).toHaveBeenCalledWith('element-a-selector'); + expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector'); + })); - it('should be able to register multiple elements', fakeAsync(() => { - // Verify that the elements loader considered `element-a-selector` to be unregistered. - expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy(); + it('should attempt to load and register only contained elements', fakeAsync(() => { + expect(loadCustomElementSpy).not.toHaveBeenCalled(); - const hostEl = document.createElement('div'); - hostEl.innerHTML = ` - - - `; + const hostEl = document.createElement('div'); + hostEl.innerHTML = ` + + `; - elementsLoader.loadContainingCustomElements(hostEl); - tick(); + elementsLoader.loadContainingCustomElements(hostEl); + flushMicrotasks(); - const defineElementA = fakeCustomElementsDefine.calls.argsFor(0); - expect(defineElementA[0]).toBe('element-a-selector'); - expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input'); - expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy(); + expect(loadCustomElementSpy).toHaveBeenCalledTimes(1); + expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector'); + })); - const defineElementB = fakeCustomElementsDefine.calls.argsFor(1); - expect(defineElementB[0]).toBe('element-b-selector'); - expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input'); - expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy(); - })); + it('should wait for all contained elements to load and register', fakeAsync(() => { + const deferreds = returnPromisesFromSpy(loadCustomElementSpy); - it('should only register an element one time', fakeAsync(() => { - const hostEl = document.createElement('div'); - hostEl.innerHTML = ``; + const hostEl = document.createElement('div'); + hostEl.innerHTML = ` + + + `; - elementsLoader.loadContainingCustomElements(hostEl); - tick(); // Tick for the module factory loader's async `load` function + const log: any[] = []; + elementsLoader.loadContainingCustomElements(hostEl).subscribe( + v => log.push(`emitted: ${v}`), + e => log.push(`errored: ${e}`), + () => log.push('completed'), + ); - // Call again to to check how many times customElements.define was called. - elementsLoader.loadContainingCustomElements(hostEl); - tick(); // Tick for the module factory loader's async `load` function + flushMicrotasks(); + expect(log).toEqual([]); - // Should have only been called once, since the second load would not query for element-a - expect(window.customElements.define).toHaveBeenCalledTimes(1); - })); + deferreds[0].resolve(); + flushMicrotasks(); + expect(log).toEqual([]); + + deferreds[1].resolve(); + flushMicrotasks(); + expect(log).toEqual(['emitted: undefined', 'completed']); + })); + + it('should fail if any of the contained elements fails to load and register', fakeAsync(() => { + const deferreds = returnPromisesFromSpy(loadCustomElementSpy); + + const hostEl = document.createElement('div'); + hostEl.innerHTML = ` + + + `; + + const log: any[] = []; + elementsLoader.loadContainingCustomElements(hostEl).subscribe( + v => log.push(`emitted: ${v}`), + e => log.push(`errored: ${e}`), + () => log.push('completed'), + ); + + flushMicrotasks(); + expect(log).toEqual([]); + + deferreds[0].resolve(); + flushMicrotasks(); + expect(log).toEqual([]); + + deferreds[1].reject('foo'); + flushMicrotasks(); + expect(log).toEqual(['errored: foo']); + })); + }); + + describe('loadCustomElement()', () => { + let definedSpy: jasmine.Spy; + let whenDefinedSpy: jasmine.Spy; + let whenDefinedDeferreds: Deferred[]; + + beforeEach(() => { + // `loadCustomElement()` uses the `window.customElements` API. Provide mocks for these tests. + definedSpy = spyOn(window.customElements, 'define'); + whenDefinedSpy = spyOn(window.customElements, 'whenDefined'); + whenDefinedDeferreds = returnPromisesFromSpy(whenDefinedSpy); + }); + + it('should be able to load and register an element', fakeAsync(() => { + elementsLoader.loadCustomElement('element-a-selector'); + flushMicrotasks(); + + expect(definedSpy).toHaveBeenCalledTimes(1); + expect(definedSpy).toHaveBeenCalledWith('element-a-selector', jasmine.any(Function)); + + // Verify the right component was loaded/registered. + const Ctor = definedSpy.calls.argsFor(0)[1]; + expect(Ctor.observedAttributes).toEqual(['element-a-module-path']); + })); + + it('should wait until the element is defined', fakeAsync(() => { + let state = 'pending'; + elementsLoader.loadCustomElement('element-b-selector').then(() => state = 'resolved'); + flushMicrotasks(); + + expect(state).toBe('pending'); + expect(whenDefinedSpy).toHaveBeenCalledTimes(1); + expect(whenDefinedSpy).toHaveBeenCalledWith('element-b-selector'); + + whenDefinedDeferreds[0].resolve(); + flushMicrotasks(); + expect(state).toBe('resolved'); + })); + + it('should not load and register the same element more than once', fakeAsync(() => { + elementsLoader.loadCustomElement('element-a-selector'); + flushMicrotasks(); + expect(definedSpy).toHaveBeenCalledTimes(1); + + definedSpy.calls.reset(); + + // While loading/registering is still in progress: + elementsLoader.loadCustomElement('element-a-selector'); + flushMicrotasks(); + expect(definedSpy).not.toHaveBeenCalled(); + + definedSpy.calls.reset(); + whenDefinedDeferreds[0].resolve(); + + // Once loading/registering is already completed: + let state = 'pending'; + elementsLoader.loadCustomElement('element-a-selector').then(() => state = 'resolved'); + flushMicrotasks(); + expect(state).toBe('resolved'); + expect(definedSpy).not.toHaveBeenCalled(); + })); + + it('should fail if defining the the custom element fails', fakeAsync(() => { + let state = 'pending'; + elementsLoader.loadCustomElement('element-b-selector').catch(e => state = `rejected: ${e}`); + flushMicrotasks(); + expect(state).toBe('pending'); + + whenDefinedDeferreds[0].reject('foo'); + flushMicrotasks(); + expect(state).toBe('rejected: foo'); + })); + + it('should be able to load and register an element again if previous attempt failed', + fakeAsync(() => { + elementsLoader.loadCustomElement('element-a-selector'); + flushMicrotasks(); + expect(definedSpy).toHaveBeenCalledTimes(1); + + definedSpy.calls.reset(); + + // While loading/registering is still in progress: + elementsLoader.loadCustomElement('element-a-selector').catch(() => undefined); + flushMicrotasks(); + expect(definedSpy).not.toHaveBeenCalled(); + + whenDefinedDeferreds[0].reject('foo'); + flushMicrotasks(); + expect(definedSpy).not.toHaveBeenCalled(); + + // Once loading/registering has already failed: + elementsLoader.loadCustomElement('element-a-selector'); + flushMicrotasks(); + expect(definedSpy).toHaveBeenCalledTimes(1); + }) + ); + }); }); // TEST CLASSES/HELPERS @@ -128,11 +231,28 @@ class FakeCustomElementModule implements WithCustomElementComponent { customElementComponent: Type; } +class FakeComponentFactory extends ComponentFactory { + selector: string; + componentType: Type; + ngContentSelectors: string[]; + inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}]; + outputs = []; + + constructor(private identifyingInput: string) { super(); } + + create(injector: Injector, + projectableNodes?: any[][], + rootSelectorOrNode?: string | any, + ngModule?: NgModuleRef): ComponentRef { + return jasmine.createSpy('ComponentRef') as any; + }; +} + class FakeComponentFactoryResolver extends ComponentFactoryResolver { constructor(private modulePath) { super(); } resolveComponentFactory(component: Type): ComponentFactory { - return FAKE_COMPONENT_FACTORIES.get(this.modulePath)!; + return new FakeComponentFactory(this.modulePath); } } @@ -168,3 +288,9 @@ class FakeModuleFactoryLoader extends NgModuleFactoryLoader { return Promise.resolve(fakeModuleFactory); } } + +function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] { + const deferreds: Deferred[] = []; + spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject}))); + return deferreds; +} diff --git a/aio/src/app/custom-elements/elements-loader.ts b/aio/src/app/custom-elements/elements-loader.ts index 1316608e50..0dd45a008d 100644 --- a/aio/src/app/custom-elements/elements-loader.ts +++ b/aio/src/app/custom-elements/elements-loader.ts @@ -11,11 +11,13 @@ import { createCustomElement } from '@angular/elements'; @Injectable() export class ElementsLoader { /** Map of unregistered custom elements and their respective module paths to load. */ - elementsToLoad: Map; + private elementsToLoad: Map; + /** Map of custom elements that are in the process of being loaded and registered. */ + private elementsLoading = new Map>(); constructor(private moduleFactoryLoader: NgModuleFactoryLoader, private moduleRef: NgModuleRef, - @Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths) { + @Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map) { this.elementsToLoad = new Map(elementModulePaths); } @@ -25,30 +27,56 @@ export class ElementsLoader { * elements so that they will not be queried in subsequent calls. */ loadContainingCustomElements(element: HTMLElement): Observable { - const selectors: any[] = Array.from(this.elementsToLoad.keys()) + const unregisteredSelectors = Array.from(this.elementsToLoad.keys()) .filter(s => element.querySelector(s)); - if (!selectors.length) { return of(undefined); } + if (!unregisteredSelectors.length) { return of(undefined); } // Returns observable that completes when all discovered elements have been registered. - return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => undefined)); + const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s))); + return fromPromise(allRegistered.then(() => undefined)); } - /** Registers the custom element defined on the WithCustomElement module factory. */ - private register(selector: string) { - const modulePath = this.elementsToLoad.get(selector)!; - return this.moduleFactoryLoader.load(modulePath).then(elementModuleFactory => { - if (!this.elementsToLoad.has(selector)) { return; } + /** Loads and registers the custom element defined on the `WithCustomElement` module factory. */ + loadCustomElement(selector: string): Promise { + if (this.elementsLoading.has(selector)) { + // The custom element is in the process of being loaded and registered. + return this.elementsLoading.get(selector)!; + } - const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector); - const CustomElementComponent = elementModuleRef.instance.customElementComponent; - const CustomElement = - createCustomElement(CustomElementComponent, {injector: elementModuleRef.injector}); + if (this.elementsToLoad.has(selector)) { + // Load and register the custom element (for the first time). + const modulePath = this.elementsToLoad.get(selector)!; + const loadedAndRegistered = this.moduleFactoryLoader + .load(modulePath) + .then(elementModuleFactory => { + const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector); + const injector = elementModuleRef.injector; + const CustomElementComponent = elementModuleRef.instance.customElementComponent; + const CustomElement = createCustomElement(CustomElementComponent, {injector}); - customElements!.define(selector, CustomElement); - this.elementsToLoad.delete(selector); + customElements!.define(selector, CustomElement); + return customElements.whenDefined(selector); + }) + .then(() => { + // The custom element has been successfully loaded and registered. + // Remove from `elementsLoading` and `elementsToLoad`. + this.elementsLoading.delete(selector); + this.elementsToLoad.delete(selector); + }) + .catch(err => { + // The custom element has failed to load and register. + // Remove from `elementsLoading`. + // (Do not remove from `elementsToLoad` in case it was a temporary error.) + this.elementsLoading.delete(selector); + return Promise.reject(err); + }); - return customElements.whenDefined(selector); - }); + this.elementsLoading.set(selector, loadedAndRegistered); + return loadedAndRegistered; + } + + // The custom element has already been loaded and registered. + return Promise.resolve(); } } From 7a9c987e560643eea3ff2c65768a2235dcb727be Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 15 May 2018 19:49:50 +0300 Subject: [PATCH 109/582] refactor(aio): order custom elements by selector (#23944) PR Close #23944 --- .../app/custom-elements/element-registry.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/aio/src/app/custom-elements/element-registry.ts b/aio/src/app/custom-elements/element-registry.ts index ca7fed36f4..9d07396ed5 100644 --- a/aio/src/app/custom-elements/element-registry.ts +++ b/aio/src/app/custom-elements/element-registry.ts @@ -13,8 +13,8 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ loadChildren: './api/api-list.module#ApiListModule' }, { - selector: 'live-example', - loadChildren: './live-example/live-example.module#LiveExampleModule' + selector: 'aio-contributor-list', + loadChildren: './contributor/contributor-list.module#ContributorListModule' }, { selector: 'aio-file-not-found-search', @@ -25,25 +25,25 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ loadChildren: './resource/resource-list.module#ResourceListModule' }, { - selector: 'current-location', - loadChildren: './current-location/current-location.module#CurrentLocationModule' - }, - { - selector: 'aio-contributor-list', - loadChildren: './contributor/contributor-list.module#ContributorListModule' + selector: 'code-example', + loadChildren: './code/code-example.module#CodeExampleModule' }, { selector: 'code-tabs', loadChildren: './code/code-tabs.module#CodeTabsModule' }, { - selector: 'code-example', - loadChildren: './code/code-example.module#CodeExampleModule' + selector: 'current-location', + loadChildren: './current-location/current-location.module#CurrentLocationModule' }, { selector: 'expandable-section', loadChildren: './expandable-section/expandable-section.module#ExpandableSectionModule' - } + }, + { + selector: 'live-example', + loadChildren: './live-example/live-example.module#LiveExampleModule' + }, ]; /** From 431a42a238863f4b436e3cc88950eafb9e555595 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 15 May 2018 21:37:16 +0300 Subject: [PATCH 110/582] feat(aio): add component for lazy-loading custom element (#23944) PR Close #23944 --- .../custom-elements/custom-elements.module.ts | 3 + .../lazy-custom-element.component.spec.ts | 67 +++++++++++++++++++ .../lazy-custom-element.component.ts | 27 ++++++++ 3 files changed, 97 insertions(+) create mode 100644 aio/src/app/custom-elements/lazy-custom-element.component.spec.ts create mode 100644 aio/src/app/custom-elements/lazy-custom-element.component.ts diff --git a/aio/src/app/custom-elements/custom-elements.module.ts b/aio/src/app/custom-elements/custom-elements.module.ts index ebc5b3704a..f2000a391f 100644 --- a/aio/src/app/custom-elements/custom-elements.module.ts +++ b/aio/src/app/custom-elements/custom-elements.module.ts @@ -6,8 +6,11 @@ import { ELEMENT_MODULE_PATHS_AS_ROUTES, ELEMENT_MODULE_PATHS_TOKEN } from './element-registry'; +import { LazyCustomElementComponent } from './lazy-custom-element.component'; @NgModule({ + declarations: [ LazyCustomElementComponent ], + exports: [ LazyCustomElementComponent ], providers: [ ElementsLoader, { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }, diff --git a/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts b/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts new file mode 100644 index 0000000000..3e7c9a5fa5 --- /dev/null +++ b/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Logger } from 'app/shared/logger.service'; +import { MockLogger } from 'testing/logger.service'; +import { LazyCustomElementComponent } from './lazy-custom-element.component'; +import { ElementsLoader } from './elements-loader'; + +describe('LazyCustomElementComponent', () => { + let mockElementsLoader: jasmine.SpyObj; + let mockLogger: MockLogger; + let fixture: ComponentFixture; + + beforeEach(() => { + mockElementsLoader = jasmine.createSpyObj('ElementsLoader', [ + 'loadContainingCustomElements', + 'loadCustomElement', + ]); + + const injector = TestBed.configureTestingModule({ + declarations: [ LazyCustomElementComponent ], + providers: [ + { provide: ElementsLoader, useValue: mockElementsLoader }, + { provide: Logger, useClass: MockLogger }, + ], + }); + + mockLogger = injector.get(Logger); + fixture = TestBed.createComponent(LazyCustomElementComponent); + }); + + it('should set the HTML content based on the selector', () => { + const elem = fixture.nativeElement; + + expect(elem.innerHTML).toBe(''); + + fixture.componentInstance.selector = 'foo-bar'; + fixture.detectChanges(); + + expect(elem.innerHTML).toBe(''); + }); + + it('should load the specified custom element', () => { + expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled(); + + fixture.componentInstance.selector = 'foo-bar'; + fixture.detectChanges(); + + expect(mockElementsLoader.loadCustomElement).toHaveBeenCalledWith('foo-bar'); + }); + + it('should log an error (and abort) if the selector is empty', () => { + fixture.detectChanges(); + + expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled(); + expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]); + expect(mockLogger.output.error[0][0].message).toBe('Invalid selector for \'aio-lazy-ce\': '); + }); + + it('should log an error (and abort) if the selector is invalid', () => { + fixture.componentInstance.selector = 'foo-bar>`; + this.elementsLoader.loadCustomElement(this.selector); + } +} From 6e05ae02a2bcc054043f1e3380dd326a08bc65f6 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 16 May 2018 20:24:24 +0300 Subject: [PATCH 111/582] fix(aio): show embedded ToC (#23944) On narrow screens (where there is not enough room on the right to show the floating ToC), an embedded ToC is shown (via an `` element in the document). Since ToC was not a custom element, the component was not instantiated for the embedded element. This commit fixes it by making `aio-toc` a custom element and loading it manually for the floating ToC (if necessary). PR Close #23944 --- aio/scripts/_payload-limits.json | 4 +- aio/src/app/app.component.html | 2 +- aio/src/app/app.component.spec.ts | 37 ++++++++++++------- aio/src/app/app.module.ts | 3 -- .../app/custom-elements/element-registry.ts | 4 ++ .../toc/toc.component.html | 0 .../toc/toc.component.spec.ts | 2 +- .../toc/toc.component.ts | 0 aio/src/app/custom-elements/toc/toc.module.ts | 14 +++++++ 9 files changed, 45 insertions(+), 21 deletions(-) rename aio/src/app/{layout => custom-elements}/toc/toc.component.html (100%) rename aio/src/app/{layout => custom-elements}/toc/toc.component.spec.ts (100%) rename aio/src/app/{layout => custom-elements}/toc/toc.component.ts (100%) create mode 100644 aio/src/app/custom-elements/toc/toc.module.ts diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index a3fdfa790d..6861676969 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -2,8 +2,8 @@ "aio": { "master": { "uncompressed": { - "runtime": 2712, - "main": 479729, + "runtime": 2768, + "main": 475857, "polyfills": 38453, "prettify": 14913 } diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index 1e7ad72a53..7a35fd6884 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -58,7 +58,7 @@
- +
diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index e3c2522063..3597f8acd2 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http'; import { MatProgressBar, MatSidenav } from '@angular/material'; import { By } from '@angular/platform-browser'; -import { timer } from 'rxjs'; +import { of, timer } from 'rxjs'; import { first, mapTo } from 'rxjs/operators'; import { AppComponent } from './app.component'; @@ -14,6 +14,7 @@ import { AppModule } from './app.module'; import { DocumentService } from 'app/documents/document.service'; import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component'; import { Deployment } from 'app/shared/deployment.service'; +import { ElementsLoader } from 'app/custom-elements/elements-loader'; import { GaService } from 'app/shared/ga.service'; import { LocationService } from 'app/shared/location.service'; import { Logger } from 'app/shared/logger.service'; @@ -26,7 +27,6 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; import { SearchResultsComponent } from 'app/shared/search-results/search-results.component'; import { SearchService } from 'app/search/search.service'; import { SelectComponent } from 'app/shared/select/select.component'; -import { TocComponent } from 'app/layout/toc/toc.component'; import { TocItem, TocService } from 'app/shared/toc.service'; const sideBySideBreakPoint = 992; @@ -621,53 +621,53 @@ describe('AppComponent', () => { }); describe('aio-toc', () => { - let tocDebugElement: DebugElement; - let tocContainer: DebugElement|null; + let tocContainer: HTMLElement|null; + let toc: HTMLElement|null; const setHasFloatingToc = (hasFloatingToc: boolean) => { component.hasFloatingToc = hasFloatingToc; fixture.detectChanges(); - tocDebugElement = fixture.debugElement.query(By.directive(TocComponent)); - tocContainer = tocDebugElement && tocDebugElement.parent; + tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container'); + toc = tocContainer && tocContainer.querySelector('aio-toc'); }; beforeEach(() => setHasFloatingToc(true)); it('should show/hide `` based on `hasFloatingToc`', () => { - expect(tocDebugElement).toBeTruthy(); expect(tocContainer).toBeTruthy(); + expect(toc).toBeTruthy(); setHasFloatingToc(false); - expect(tocDebugElement).toBeFalsy(); expect(tocContainer).toBeFalsy(); + expect(toc).toBeFalsy(); setHasFloatingToc(true); - expect(tocDebugElement).toBeTruthy(); expect(tocContainer).toBeTruthy(); + expect(toc).toBeTruthy(); }); it('should have a non-embedded `` element', () => { - expect(tocDebugElement.classes['embedded']).toBeFalsy(); + expect(toc!.classList.contains('embedded')).toBe(false); }); it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => { - expect(tocContainer!.styles['max-height']).toBeNull(); + expect(tocContainer!.style['max-height']).toBe(''); component.tocMaxHeight = '100'; fixture.detectChanges(); - expect(tocContainer!.styles['max-height']).toBe('100px'); + expect(tocContainer!.style['max-height']).toBe('100px'); }); it('should restrain scrolling inside the ToC container', () => { const restrainScrolling = spyOn(component, 'restrainScrolling'); - const evt = {}; + const evt = new MouseEvent('mousewheel'); expect(restrainScrolling).not.toHaveBeenCalled(); - tocContainer!.triggerEventHandler('mousewheel', evt); + tocContainer!.dispatchEvent(evt); expect(restrainScrolling).toHaveBeenCalledWith(evt); }); }); @@ -1280,6 +1280,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') { imports: [ AppModule ], providers: [ { provide: APP_BASE_HREF, useValue: '/' }, + { provide: ElementsLoader, useClass: TestElementsLoader }, { provide: GaService, useClass: TestGaService }, { provide: HttpClient, useClass: TestHttpClient }, { provide: LocationService, useFactory: () => mockLocationService }, @@ -1294,6 +1295,14 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') { }); } +class TestElementsLoader { + loadContainingCustomElements = jasmine.createSpy('loadContainingCustomElements') + .and.returnValue(of(undefined)); + + loadCustomElement = jasmine.createSpy('loadCustomElement') + .and.returnValue(Promise.resolve()); +} + class TestGaService { locationChanged = jasmine.createSpy('locationChanged'); } diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts index 78fe5b905e..a3e89e24ce 100644 --- a/aio/src/app/app.module.ts +++ b/aio/src/app/app.module.ts @@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service'; import { ScrollSpyService } from 'app/shared/scroll-spy.service'; import { SearchBoxComponent } from 'app/search/search-box/search-box.component'; import { NotificationComponent } from 'app/layout/notification/notification.component'; -import { TocComponent } from 'app/layout/toc/toc.component'; import { TocService } from 'app/shared/toc.service'; import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date'; import { WindowToken, windowProvider } from 'app/shared/window'; @@ -111,7 +110,6 @@ export const svgIconProviders = [ NavItemComponent, SearchBoxComponent, NotificationComponent, - TocComponent, TopMenuComponent, ], providers: [ @@ -133,7 +131,6 @@ export const svgIconProviders = [ { provide: CurrentDateToken, useFactory: currentDateProvider }, { provide: WindowToken, useFactory: windowProvider }, ], - entryComponents: [ TocComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } diff --git a/aio/src/app/custom-elements/element-registry.ts b/aio/src/app/custom-elements/element-registry.ts index 9d07396ed5..d8ca727b67 100644 --- a/aio/src/app/custom-elements/element-registry.ts +++ b/aio/src/app/custom-elements/element-registry.ts @@ -24,6 +24,10 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ selector: 'aio-resource-list', loadChildren: './resource/resource-list.module#ResourceListModule' }, + { + selector: 'aio-toc', + loadChildren: './toc/toc.module#TocModule' + }, { selector: 'code-example', loadChildren: './code/code-example.module#CodeExampleModule' diff --git a/aio/src/app/layout/toc/toc.component.html b/aio/src/app/custom-elements/toc/toc.component.html similarity index 100% rename from aio/src/app/layout/toc/toc.component.html rename to aio/src/app/custom-elements/toc/toc.component.html diff --git a/aio/src/app/layout/toc/toc.component.spec.ts b/aio/src/app/custom-elements/toc/toc.component.spec.ts similarity index 100% rename from aio/src/app/layout/toc/toc.component.spec.ts rename to aio/src/app/custom-elements/toc/toc.component.spec.ts index a13e2afacf..c7f228724f 100644 --- a/aio/src/app/layout/toc/toc.component.spec.ts +++ b/aio/src/app/custom-elements/toc/toc.component.spec.ts @@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser'; import { asapScheduler as asap, BehaviorSubject } from 'rxjs'; import { ScrollService } from 'app/shared/scroll.service'; -import { TocComponent } from './toc.component'; import { TocItem, TocService } from 'app/shared/toc.service'; +import { TocComponent } from './toc.component'; describe('TocComponent', () => { let tocComponentDe: DebugElement; diff --git a/aio/src/app/layout/toc/toc.component.ts b/aio/src/app/custom-elements/toc/toc.component.ts similarity index 100% rename from aio/src/app/layout/toc/toc.component.ts rename to aio/src/app/custom-elements/toc/toc.component.ts diff --git a/aio/src/app/custom-elements/toc/toc.module.ts b/aio/src/app/custom-elements/toc/toc.module.ts new file mode 100644 index 0000000000..848176261e --- /dev/null +++ b/aio/src/app/custom-elements/toc/toc.module.ts @@ -0,0 +1,14 @@ +import { NgModule, Type } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatIconModule } from '@angular/material/icon'; +import { WithCustomElementComponent } from '../element-registry'; +import { TocComponent } from './toc.component'; + +@NgModule({ + imports: [ CommonModule, MatIconModule ], + declarations: [ TocComponent ], + entryComponents: [ TocComponent ], +}) +export class TocModule implements WithCustomElementComponent { + customElementComponent: Type = TocComponent; +} From 7866684f2b7b5de2d911f18934638d8509361c3e Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 16 May 2018 20:37:04 +0300 Subject: [PATCH 112/582] fix(aio): avoid loading the ToC component until it is necessary (#23944) PR Close #23944 --- aio/src/app/app.component.spec.ts | 28 +++++++++++++++++++--------- aio/src/app/app.component.ts | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 3597f8acd2..6431d9c79c 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -92,11 +92,11 @@ describe('AppComponent', () => { }); describe('hasFloatingToc', () => { - it('should initially be true', () => { + it('should initially be false', () => { const fixture2 = TestBed.createComponent(AppComponent); const component2 = fixture2.componentInstance; - expect(component2.hasFloatingToc).toBe(true); + expect(component2.hasFloatingToc).toBe(false); }); it('should be false on narrow screens', () => { @@ -632,27 +632,28 @@ describe('AppComponent', () => { toc = tocContainer && tocContainer.querySelector('aio-toc'); }; - beforeEach(() => setHasFloatingToc(true)); - it('should show/hide `` based on `hasFloatingToc`', () => { - expect(tocContainer).toBeTruthy(); - expect(toc).toBeTruthy(); - - setHasFloatingToc(false); expect(tocContainer).toBeFalsy(); expect(toc).toBeFalsy(); setHasFloatingToc(true); expect(tocContainer).toBeTruthy(); expect(toc).toBeTruthy(); + + setHasFloatingToc(false); + expect(tocContainer).toBeFalsy(); + expect(toc).toBeFalsy(); }); it('should have a non-embedded `` element', () => { + setHasFloatingToc(true); expect(toc!.classList.contains('embedded')).toBe(false); }); it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => { + setHasFloatingToc(true); + expect(tocContainer!.style['max-height']).toBe(''); component.tocMaxHeight = '100'; @@ -665,11 +666,20 @@ describe('AppComponent', () => { const restrainScrolling = spyOn(component, 'restrainScrolling'); const evt = new MouseEvent('mousewheel'); + setHasFloatingToc(true); expect(restrainScrolling).not.toHaveBeenCalled(); tocContainer!.dispatchEvent(evt); expect(restrainScrolling).toHaveBeenCalledWith(evt); }); + + it('should not be loaded/registered until necessary', () => { + const loader: TestElementsLoader = fixture.debugElement.injector.get(ElementsLoader); + expect(loader.loadCustomElement).not.toHaveBeenCalled(); + + setHasFloatingToc(true); + expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc'); + }); }); describe('footer', () => { @@ -1377,7 +1387,7 @@ class TestHttpClient { const id = match[1]!; // Make up a title for test purposes const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase()); - const h1 = (id === 'no-title') ? '' : `

${title}

`; + const h1 = (id === 'no-title') ? '' : `

${title}

`; const contents = `${h1}

Some heading

`; data = { id, contents }; } diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index 24344af74a..e587d25ad1 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -69,7 +69,7 @@ export class AppComponent implements OnInit { topMenuNodes: NavigationNode[]; topMenuNarrowNodes: NavigationNode[]; - hasFloatingToc = true; + hasFloatingToc = false; private showFloatingToc = new BehaviorSubject(false); private showFloatingTocWidth = 800; tocMaxHeight: string; From c2e131119bedbb9761dcc88fae6c12cbdef25e07 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 22 May 2018 17:31:05 +0300 Subject: [PATCH 113/582] refactor(aio): rename method (loadContainingCustomElements --> loadContainedCustomElements) (#23944) PR Close #23944 --- aio/scripts/_payload-limits.json | 2 +- aio/src/app/app.component.spec.ts | 2 +- aio/src/app/custom-elements/elements-loader.spec.ts | 10 +++++----- aio/src/app/custom-elements/elements-loader.ts | 2 +- .../lazy-custom-element.component.spec.ts | 2 +- .../app/layout/doc-viewer/doc-viewer.component.spec.ts | 2 +- aio/src/app/layout/doc-viewer/doc-viewer.component.ts | 2 +- aio/src/testing/doc-viewer-utils.ts | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index 6861676969..b02773aeef 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime": 2768, - "main": 475857, + "main": 475855, "polyfills": 38453, "prettify": 14913 } diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 6431d9c79c..5b646af0a5 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -1306,7 +1306,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') { } class TestElementsLoader { - loadContainingCustomElements = jasmine.createSpy('loadContainingCustomElements') + loadContainedCustomElements = jasmine.createSpy('loadContainedCustomElements') .and.returnValue(of(undefined)); loadCustomElement = jasmine.createSpy('loadCustomElement') diff --git a/aio/src/app/custom-elements/elements-loader.spec.ts b/aio/src/app/custom-elements/elements-loader.spec.ts index 906e74283e..817dd71a57 100644 --- a/aio/src/app/custom-elements/elements-loader.spec.ts +++ b/aio/src/app/custom-elements/elements-loader.spec.ts @@ -33,7 +33,7 @@ describe('ElementsLoader', () => { elementsLoader = injector.get(ElementsLoader); }); - describe('loadContainingCustomElements()', () => { + describe('loadContainedCustomElements()', () => { let loadCustomElementSpy: jasmine.Spy; beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement')); @@ -47,7 +47,7 @@ describe('ElementsLoader', () => { `; - elementsLoader.loadContainingCustomElements(hostEl); + elementsLoader.loadContainedCustomElements(hostEl); flushMicrotasks(); expect(loadCustomElementSpy).toHaveBeenCalledTimes(2); @@ -63,7 +63,7 @@ describe('ElementsLoader', () => { `; - elementsLoader.loadContainingCustomElements(hostEl); + elementsLoader.loadContainedCustomElements(hostEl); flushMicrotasks(); expect(loadCustomElementSpy).toHaveBeenCalledTimes(1); @@ -80,7 +80,7 @@ describe('ElementsLoader', () => { `; const log: any[] = []; - elementsLoader.loadContainingCustomElements(hostEl).subscribe( + elementsLoader.loadContainedCustomElements(hostEl).subscribe( v => log.push(`emitted: ${v}`), e => log.push(`errored: ${e}`), () => log.push('completed'), @@ -108,7 +108,7 @@ describe('ElementsLoader', () => { `; const log: any[] = []; - elementsLoader.loadContainingCustomElements(hostEl).subscribe( + elementsLoader.loadContainedCustomElements(hostEl).subscribe( v => log.push(`emitted: ${v}`), e => log.push(`errored: ${e}`), () => log.push('completed'), diff --git a/aio/src/app/custom-elements/elements-loader.ts b/aio/src/app/custom-elements/elements-loader.ts index 0dd45a008d..8eef788c5b 100644 --- a/aio/src/app/custom-elements/elements-loader.ts +++ b/aio/src/app/custom-elements/elements-loader.ts @@ -26,7 +26,7 @@ export class ElementsLoader { * the browser. Custom elements that are registered will be removed from the list of unregistered * elements so that they will not be queried in subsequent calls. */ - loadContainingCustomElements(element: HTMLElement): Observable { + loadContainedCustomElements(element: HTMLElement): Observable { const unregisteredSelectors = Array.from(this.elementsToLoad.keys()) .filter(s => element.querySelector(s)); diff --git a/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts b/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts index 3e7c9a5fa5..5bb0eeefa5 100644 --- a/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts +++ b/aio/src/app/custom-elements/lazy-custom-element.component.spec.ts @@ -11,7 +11,7 @@ describe('LazyCustomElementComponent', () => { beforeEach(() => { mockElementsLoader = jasmine.createSpyObj('ElementsLoader', [ - 'loadContainingCustomElements', + 'loadContainedCustomElements', 'loadCustomElement', ]); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts index 42ba737be3..1f1ae32a71 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts @@ -300,7 +300,7 @@ describe('DocViewerComponent', () => { beforeEach(() => { const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader; - loadElementsSpy = elementsLoader.loadContainingCustomElements.and.returnValue(of(undefined)); + loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined)); prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc'); swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined)); }); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts index 26a4513c99..a1b5ddabb0 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts @@ -136,7 +136,7 @@ export class DocViewerComponent implements OnDestroy { // and is considered to be safe. tap(() => this.nextViewContainer.innerHTML = doc.contents || ''), tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)), - switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)), + switchMap(() => this.elementsLoader.loadContainedCustomElements(this.nextViewContainer)), tap(() => this.docReady.emit()), switchMap(() => this.swapViews(addTitleAndToc)), tap(() => this.docRendered.emit()), diff --git a/aio/src/testing/doc-viewer-utils.ts b/aio/src/testing/doc-viewer-utils.ts index cc7a207805..a078e492ec 100644 --- a/aio/src/testing/doc-viewer-utils.ts +++ b/aio/src/testing/doc-viewer-utils.ts @@ -56,8 +56,8 @@ export class MockTocService { } export class MockElementsLoader { - loadContainingCustomElements = - jasmine.createSpy('MockElementsLoader#loadContainingCustomElements'); + loadContainedCustomElements = + jasmine.createSpy('MockElementsLoader#loadContainedCustomElements'); } @NgModule({ From 00c4751f374a561cc980b82adfd5c18747b869ed Mon Sep 17 00:00:00 2001 From: jenniferfell Date: Mon, 14 May 2018 17:38:27 -0600 Subject: [PATCH 114/582] docs: update lts and labs practices (#23922) PR Close #23922 --- aio/content/guide/releases.md | 60 +++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/aio/content/guide/releases.md b/aio/content/guide/releases.md index 0d4854388e..f7a8c4ae63 100644 --- a/aio/content/guide/releases.md +++ b/aio/content/guide/releases.md @@ -74,22 +74,58 @@ The following table contains our current target release dates for the next two m {@a lts} -## Long-term support +{@a support} +## Support policy -All of our releases are supported actively for about 6 months (until the next major release), and then they are supported through long-term support (LTS) for another 12 months. +All of our major releases are supported for 18 months. -During the LTS period, only critical fixes and security patches will be merged and released. +* 6 months of active support, during which regularly-scheduled updates and patches are released, as described above in [Release frequency](#frequency "Release frequency"). -The LTS state of one major version starts on the day of the next major release. LTS status ends approximately one year later, when we release another major version. +* 12 months of long-term support (LTS). During the LTS period, only critical fixes and security patches will be released. +The following table provides the support status and key dates for Angular version 4.0.0 and higher. + + +
PropertyTypeDescription
{$ headings[0] or 'Property' $}{$ headings[1] or 'Type' $}{$ headings[2] or 'Description' $}
{$ property.name $} {%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}Read-only.{% endif %} {% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %} From 67b8d57a8d51655143deeeb819e6a756abfc234b Mon Sep 17 00:00:00 2001 From: Jon Gear Date: Sun, 13 May 2018 19:22:27 -0500 Subject: [PATCH 050/582] docs(aio): use heroesUrl (#23884) PR Close #23884 --- aio/content/examples/toh-pt6/src/app/hero.service.ts | 2 +- aio/content/examples/universal/src/app/hero.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/content/examples/toh-pt6/src/app/hero.service.ts b/aio/content/examples/toh-pt6/src/app/hero.service.ts index 068da510b2..c98030c9d2 100644 --- a/aio/content/examples/toh-pt6/src/app/hero.service.ts +++ b/aio/content/examples/toh-pt6/src/app/hero.service.ts @@ -84,7 +84,7 @@ export class HeroService { // if not search term, return empty hero array. return of([]); } - return this.http.get(`api/heroes/?name=${term}`).pipe( + return this.http.get(`${this.heroesUrl}/?name=${term}`).pipe( tap(_ => this.log(`found heroes matching "${term}"`)), catchError(this.handleError('searchHeroes', [])) ); diff --git a/aio/content/examples/universal/src/app/hero.service.ts b/aio/content/examples/universal/src/app/hero.service.ts index ef490ecff6..18f9b72d2d 100644 --- a/aio/content/examples/universal/src/app/hero.service.ts +++ b/aio/content/examples/universal/src/app/hero.service.ts @@ -64,7 +64,7 @@ export class HeroService { // if not search term, return empty hero array. return of([]); } - return this.http.get(`api/heroes/?name=${term}`).pipe( + return this.http.get(`${this.heroesUrl}/?name=${term}`).pipe( tap(_ => this.log(`found heroes matching "${term}"`)), catchError(this.handleError('searchHeroes', [])) ); From 66b2d78305d07ba1ca7f2844696d372bd424c465 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Mon, 14 May 2018 09:33:30 -0700 Subject: [PATCH 051/582] build: only match version tags for BUILD_SCM_VERSION (#23903) PR Close #23903 --- tools/bazel_stamp_vars.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/bazel_stamp_vars.sh b/tools/bazel_stamp_vars.sh index 2e70a4c1f0..3092f6c6cb 100755 --- a/tools/bazel_stamp_vars.sh +++ b/tools/bazel_stamp_vars.sh @@ -26,7 +26,10 @@ if [[ "$(git tag)" == "" ]]; then echo "" fi -BUILD_SCM_VERSION_RAW=$(git describe --abbrev=7 --tags HEAD) +# Only match the latest tag that is a version such as 6.0.0, 6.0.0-rc.5, etc... +# This will ignore non-version tags which would break unit tests expecting a valid version +# number in the package headers +BUILD_SCM_VERSION_RAW=$(git describe --match [0-9].[0-9].[0-9]* --abbrev=7 --tags HEAD) # Find out if there are any uncommitted local changes # TODO(i): is it ok to use "--untracked-files=no" to ignore untracked files since they should not affect anything? From d4b6c41a5f8ca84082f90a5d738b23d3ba71bbb0 Mon Sep 17 00:00:00 2001 From: Omar Griffin Date: Thu, 19 Apr 2018 14:26:48 -0700 Subject: [PATCH 052/582] fix(benchpress): Fix promise chain in chrome_driver_extension. (#23458) Occasionally the promise to clear the chrome buffer resolves after the subsequent call to start the timer. This problem causes flakiness in our tests that rely on benchpress, usually manifesting itself as a "Tried too often to get the ending mark: 21" error thrown by this line: https://github.com/angular/angular/blob/master/packages/benchpress/src/metric/perflog_metric.ts#L162 PR Close #23458 --- packages/benchpress/src/webdriver/chrome_driver_extension.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/benchpress/src/webdriver/chrome_driver_extension.ts b/packages/benchpress/src/webdriver/chrome_driver_extension.ts index d1ceba67b1..c255f2ff33 100644 --- a/packages/benchpress/src/webdriver/chrome_driver_extension.ts +++ b/packages/benchpress/src/webdriver/chrome_driver_extension.ts @@ -51,12 +51,12 @@ export class ChromeDriverExtension extends WebDriverExtension { gc() { return this._driver.executeScript('window.gc()'); } - timeBegin(name: string): Promise { + async timeBegin(name: string): Promise { if (this._firstRun) { this._firstRun = false; // Before the first run, read out the existing performance logs // so that the chrome buffer does not fill up. - this._driver.logs('performance'); + await this._driver.logs('performance'); } return this._driver.executeScript(`console.time('${name}');`); } From 83631b28cb76dbc16d14010dc105c66f99b135cb Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 13 May 2018 18:24:12 +0200 Subject: [PATCH 053/582] perf(ivy): avoid creating bound function in pipeBind3 (#23882) PR Close #23882 --- packages/core/src/render3/pipe.ts | 2 +- packages/core/src/render3/pure_function.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 83880d3004..c8f449148a 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -102,7 +102,7 @@ export function pipeBind2(index: number, v1: any, v2: any): any { */ export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction3(pipeInstance.transform.bind(pipeInstance), v1, v2, v3) : + return isPure(index) ? pureFunction3(pipeInstance.transform, v1, v2, v3, pipeInstance) : pipeInstance.transform(v1, v2, v3); } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index 071c6d2966..dad4424647 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -15,6 +15,7 @@ import {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, * value. If it has been saved, returns the saved value. * * @param pureFn Function that returns a value + * @param thisArg Optional calling context of pureFn * @returns value */ export function pureFunction0(pureFn: () => T, thisArg?: any): T { @@ -28,6 +29,7 @@ export function pureFunction0(pureFn: () => T, thisArg?: any): T { * * @param pureFn Function that returns an updated value * @param exp Updated expression value + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction1(pureFn: (v: any) => any, exp: any, thisArg?: any): any { @@ -43,6 +45,7 @@ export function pureFunction1(pureFn: (v: any) => any, exp: any, thisArg?: any): * @param pureFn * @param exp1 * @param exp2 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction2( @@ -60,6 +63,7 @@ export function pureFunction2( * @param exp1 * @param exp2 * @param exp3 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction3( @@ -81,6 +85,7 @@ export function pureFunction3( * @param exp2 * @param exp3 * @param exp4 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction4( @@ -102,6 +107,7 @@ export function pureFunction4( * @param exp3 * @param exp4 * @param exp5 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction5( @@ -126,6 +132,7 @@ export function pureFunction5( * @param exp4 * @param exp5 * @param exp6 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction6( @@ -151,6 +158,7 @@ export function pureFunction6( * @param exp5 * @param exp6 * @param exp7 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction7( @@ -178,6 +186,7 @@ export function pureFunction7( * @param exp6 * @param exp7 * @param exp8 + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunction8( @@ -200,7 +209,8 @@ export function pureFunction8( * * @param pureFn A pure function that takes binding values and builds an object or array * containing those values. - * @param exp An array of binding values + * @param exps An array of binding values + * @param thisArg Optional calling context of pureFn * @returns Updated value */ export function pureFunctionV(pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any { From 017d67cdf81e05496e4cf73f0f912ecef94e7bb3 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 11 May 2018 09:43:06 -0700 Subject: [PATCH 054/582] test: switch to ts_web_test_suite (#23859) Unit tests now run on Firefox too PR Close #23859 --- WORKSPACE | 6 +++--- packages/common/test/BUILD.bazel | 4 ++-- packages/compiler-cli/test/BUILD.bazel | 2 +- packages/compiler-cli/test/diagnostics/BUILD.bazel | 2 +- packages/compiler-cli/test/metadata/BUILD.bazel | 2 +- packages/compiler-cli/test/ngtsc/BUILD.bazel | 2 +- packages/compiler-cli/test/transformers/BUILD.bazel | 2 +- packages/compiler/test/BUILD.bazel | 4 ++-- packages/compiler/test/render3/BUILD.bazel | 2 +- packages/core/test/BUILD.bazel | 4 ++-- packages/core/test/render3/BUILD.bazel | 4 ++-- packages/elements/test/BUILD.bazel | 4 ++-- packages/forms/test/BUILD.bazel | 4 ++-- packages/http/test/BUILD.bazel | 4 ++-- packages/language-service/test/BUILD.bazel | 2 +- packages/platform-browser-dynamic/test/BUILD.bazel | 4 ++-- packages/platform-browser/test/BUILD.bazel | 4 ++-- packages/platform-webworker/test/BUILD.bazel | 4 ++-- packages/router/test/BUILD.bazel | 4 ++-- packages/service-worker/test/BUILD.bazel | 4 ++-- packages/upgrade/test/BUILD.bazel | 4 ++-- tools/defaults.bzl | 13 ++++++++++--- 22 files changed, 46 insertions(+), 39 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 263151e343..d18ec153e0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -13,9 +13,9 @@ http_archive( http_archive( name = "io_bazel_rules_webtesting", - url = "https://github.com/bazelbuild/rules_webtesting/archive/ca7b8062d9cf4ef2fde9193c7d37a0764c4262d7.zip", - strip_prefix = "rules_webtesting-ca7b8062d9cf4ef2fde9193c7d37a0764c4262d7", - sha256 = "28c73cf9d310fa6dba30e66bdb98071341c99c3feb8662f2d3883a632de97d72", + url = "https://github.com/bazelbuild/rules_webtesting/archive/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip", + strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683", + sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3", ) http_archive( diff --git a/packages/common/test/BUILD.bazel b/packages/common/test/BUILD.bazel index 2d6f2e59e1..268d0a8bfc 100644 --- a/packages/common/test/BUILD.bazel +++ b/packages/common/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -29,7 +29,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/compiler-cli/test/BUILD.bazel b/packages/compiler-cli/test/BUILD.bazel index e6cb2ebf07..f0e842f65b 100644 --- a/packages/compiler-cli/test/BUILD.bazel +++ b/packages/compiler-cli/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") # Uses separate test rules to allow the tests to run in parallel diff --git a/packages/compiler-cli/test/diagnostics/BUILD.bazel b/packages/compiler-cli/test/diagnostics/BUILD.bazel index baa020bf9b..18cc00ffc2 100644 --- a/packages/compiler-cli/test/diagnostics/BUILD.bazel +++ b/packages/compiler-cli/test/diagnostics/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/compiler-cli/test/metadata/BUILD.bazel b/packages/compiler-cli/test/metadata/BUILD.bazel index 1b075bb62b..e118490d37 100644 --- a/packages/compiler-cli/test/metadata/BUILD.bazel +++ b/packages/compiler-cli/test/metadata/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/compiler-cli/test/ngtsc/BUILD.bazel b/packages/compiler-cli/test/ngtsc/BUILD.bazel index a7bbbb77a3..dfa3f80bfa 100644 --- a/packages/compiler-cli/test/ngtsc/BUILD.bazel +++ b/packages/compiler-cli/test/ngtsc/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/compiler-cli/test/transformers/BUILD.bazel b/packages/compiler-cli/test/transformers/BUILD.bazel index 2e4649fe9c..d42c527516 100644 --- a/packages/compiler-cli/test/transformers/BUILD.bazel +++ b/packages/compiler-cli/test/transformers/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/compiler/test/BUILD.bazel b/packages/compiler/test/BUILD.bazel index e7aaa323cf..fb32cbad43 100644 --- a/packages/compiler/test/BUILD.bazel +++ b/packages/compiler/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") # Test that should only be run in node @@ -80,7 +80,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/compiler/test/render3/BUILD.bazel b/packages/compiler/test/render3/BUILD.bazel index 82c60a34e3..27bd131ce3 100644 --- a/packages/compiler/test/render3/BUILD.bazel +++ b/packages/compiler/test/render3/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/core/test/BUILD.bazel b/packages/core/test/BUILD.bazel index dc353d14bc..bf1c7473b0 100644 --- a/packages/core/test/BUILD.bazel +++ b/packages/core/test/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -60,7 +60,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/core/test/render3/BUILD.bazel b/packages/core/test/render3/BUILD.bazel index 091e1506c0..b3c3334c71 100644 --- a/packages/core/test/render3/BUILD.bazel +++ b/packages/core/test/render3/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -52,7 +52,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "render3_web", deps = [ ":render3_lib", diff --git a/packages/elements/test/BUILD.bazel b/packages/elements/test/BUILD.bazel index 68817ebd99..bdf3c5912c 100644 --- a/packages/elements/test/BUILD.bazel +++ b/packages/elements/test/BUILD.bazel @@ -1,5 +1,5 @@ load("//tools:defaults.bzl", "ts_library") -load("@build_bazel_rules_typescript//:defs.bzl", "ts_web_test") +load("@build_bazel_rules_typescript//:defs.bzl", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -31,7 +31,7 @@ filegroup( ], ) -ts_web_test( +ts_web_test_suite( name = "test", bootstrap = [ ":elements_test_bootstrap_scripts", diff --git a/packages/forms/test/BUILD.bazel b/packages/forms/test/BUILD.bazel index f4023b41be..7daeea9391 100644 --- a/packages/forms/test/BUILD.bazel +++ b/packages/forms/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -25,7 +25,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/http/test/BUILD.bazel b/packages/http/test/BUILD.bazel index 5359429195..05c83ed83a 100644 --- a/packages/http/test/BUILD.bazel +++ b/packages/http/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -25,7 +25,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/language-service/test/BUILD.bazel b/packages/language-service/test/BUILD.bazel index d4e73e9c0d..89e4f08f24 100644 --- a/packages/language-service/test/BUILD.bazel +++ b/packages/language-service/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( diff --git a/packages/platform-browser-dynamic/test/BUILD.bazel b/packages/platform-browser-dynamic/test/BUILD.bazel index 234a193ac3..35d9f51d27 100644 --- a/packages/platform-browser-dynamic/test/BUILD.bazel +++ b/packages/platform-browser-dynamic/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -27,7 +27,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", # disable since tests are running but not yet passing tags = ["manual"], diff --git a/packages/platform-browser/test/BUILD.bazel b/packages/platform-browser/test/BUILD.bazel index 4858aa258e..996e9a29cf 100644 --- a/packages/platform-browser/test/BUILD.bazel +++ b/packages/platform-browser/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -33,7 +33,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", # disable since tests are running but not yet passing tags = ["manual"], diff --git a/packages/platform-webworker/test/BUILD.bazel b/packages/platform-webworker/test/BUILD.bazel index 89b2f0de0a..d0937bdf77 100644 --- a/packages/platform-webworker/test/BUILD.bazel +++ b/packages/platform-webworker/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -27,7 +27,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/router/test/BUILD.bazel b/packages/router/test/BUILD.bazel index 1b20bb54ff..7dfa2b62b4 100644 --- a/packages/router/test/BUILD.bazel +++ b/packages/router/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -29,7 +29,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/service-worker/test/BUILD.bazel b/packages/service-worker/test/BUILD.bazel index 54fababb35..fcf967139b 100644 --- a/packages/service-worker/test/BUILD.bazel +++ b/packages/service-worker/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -27,7 +27,7 @@ jasmine_node_test( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", deps = [ ":test_lib", diff --git a/packages/upgrade/test/BUILD.bazel b/packages/upgrade/test/BUILD.bazel index 9e40647ba0..635eef6a57 100644 --- a/packages/upgrade/test/BUILD.bazel +++ b/packages/upgrade/test/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ts_library", "ts_web_test") +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") ts_library( @@ -18,7 +18,7 @@ ts_library( ], ) -ts_web_test( +ts_web_test_suite( name = "test_web", bootstrap = [ # "//:angularjs", diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 8c0591f153..8a956ca64f 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -1,6 +1,6 @@ """Re-export of some bazel rules with repository-wide defaults.""" load("@build_bazel_rules_nodejs//:defs.bzl", _npm_package = "npm_package") -load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library", _ts_web_test = "ts_web_test") +load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library", _ts_web_test_suite = "ts_web_test_suite") load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package") load("//packages/bazel/src:ng_module.bzl", _ivy_ng_module = "internal_ivy_ng_module") @@ -68,7 +68,7 @@ def npm_package(name, replacements = {}, **kwargs): replacements = dict(replacements, **PKG_GROUP_REPLACEMENTS), **kwargs) -def ts_web_test(bootstrap = [], deps = [], **kwargs): +def ts_web_test_suite(bootstrap = [], deps = [], **kwargs): if not bootstrap: bootstrap = ["//:web_test_bootstrap_scripts"] local_deps = [ @@ -76,9 +76,16 @@ def ts_web_test(bootstrap = [], deps = [], **kwargs): "//tools/testing:browser", ] + deps - _ts_web_test( + _ts_web_test_suite( bootstrap = bootstrap, deps = local_deps, + # Run unit tests on Chromium and Firefox by default. + # You can exclude tests based on tags, e.g. to skip Firefox testing, + # `bazel test --test_tag_filters=-browser:firefox-local [targets]` + browsers = [ + "@io_bazel_rules_webtesting//browsers:chromium-local", + "@io_bazel_rules_webtesting//browsers:firefox-local", + ], **kwargs) def ivy_ng_module(name, tsconfig = None, **kwargs): From 1d378e298721541eee7477d48cd62fba58044575 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Sat, 28 Apr 2018 02:01:28 +0300 Subject: [PATCH 055/582] fix(service-worker): deprecate `versionedFiles` in asset-group resources (#23584) Since `versionedFiles` behaves in the exact same way as `files`, there is no reaason to have both. Users should use `files` instead. This commit deprecates the property and prints a warning when coming across an asset-group that uses it. It should be completely removed in a future version. Note, it has also been removed from the default `ngsw-config.json` template in angular/devkit#754. PR Close #23584 --- .../src/ngsw-config.json | 11 ++++------- aio/content/guide/service-worker-config.md | 3 ++- packages/service-worker/config/src/generator.ts | 7 +++++++ packages/service-worker/config/src/in.ts | 8 +++++++- tools/public_api_guard/service-worker/config.d.ts | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/aio/content/examples/service-worker-getting-started/src/ngsw-config.json b/aio/content/examples/service-worker-getting-started/src/ngsw-config.json index 8b78a4f893..19133dc879 100755 --- a/aio/content/examples/service-worker-getting-started/src/ngsw-config.json +++ b/aio/content/examples/service-worker-getting-started/src/ngsw-config.json @@ -7,12 +7,9 @@ "resources": { "files": [ "/favicon.ico", - "/index.html" - ], - "versionedFiles": [ - "/*.bundle.css", - "/*.bundle.js", - "/*.chunk.js" + "/index.html", + "/*.css", + "/*.js" ] } }, { @@ -25,4 +22,4 @@ ] } }] -} \ No newline at end of file +} diff --git a/aio/content/guide/service-worker-config.md b/aio/content/guide/service-worker-config.md index 8f7beaeba6..d2293d0e48 100644 --- a/aio/content/guide/service-worker-config.md +++ b/aio/content/guide/service-worker-config.md @@ -70,6 +70,7 @@ interface AssetGroup { updateMode?: 'prefetch' | 'lazy'; resources: { files?: string[]; + /** @deprecated As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead. */ versionedFiles?: string[]; urls?: string[]; }; @@ -102,7 +103,7 @@ This section describes the resources to cache, broken up into three groups. * `files` lists patterns that match files in the distribution directory. These can be single files or glob-like patterns that match a number of files. -* `versionedFiles` is like `files` but should be used for build artifacts that already include a hash in the filename, which is used for cache busting. The Angular service worker can optimize some aspects of its operation if it can assume file contents are immutable. +* `versionedFiles` has been deprecated. As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead. * `urls` includes both URLs and URL patterns that will be matched at runtime. These resources are not fetched directly and do not have content hashes, but they will be cached according to their HTTP headers. This is most useful for CDNs such as the Google Fonts service.
_(Negative glob patterns are not supported.)_ diff --git a/packages/service-worker/config/src/generator.ts b/packages/service-worker/config/src/generator.ts index 399d733e67..fc9dfb31a6 100644 --- a/packages/service-worker/config/src/generator.ts +++ b/packages/service-worker/config/src/generator.ts @@ -44,6 +44,13 @@ export class Generator { Promise { const seenMap = new Set(); return Promise.all((config.assetGroups || []).map(async(group) => { + if (group.resources.versionedFiles) { + console.warn( + `Asset-group '${group.name}' in 'ngsw-config.json' uses the 'versionedFiles' option.\n` + + 'As of v6 \'versionedFiles\' and \'files\' options have the same behavior. ' + + 'Use \'files\' instead.'); + } + const fileMatcher = globListToMatcher(group.resources.files || []); const versionedMatcher = globListToMatcher(group.resources.versionedFiles || []); diff --git a/packages/service-worker/config/src/in.ts b/packages/service-worker/config/src/in.ts index bf3e27a6c3..0328a53356 100644 --- a/packages/service-worker/config/src/in.ts +++ b/packages/service-worker/config/src/in.ts @@ -38,7 +38,13 @@ export interface AssetGroup { name: string; installMode?: 'prefetch'|'lazy'; updateMode?: 'prefetch'|'lazy'; - resources: {files?: Glob[]; versionedFiles?: Glob[]; urls?: Glob[];}; + resources: { + files?: Glob[]; + /** @deprecated As of v6 `versionedFiles` and `files` options have the same behavior. Use + `files` instead. */ + versionedFiles?: Glob[]; + urls?: Glob[]; + }; } /** diff --git a/tools/public_api_guard/service-worker/config.d.ts b/tools/public_api_guard/service-worker/config.d.ts index f1f664af34..6e68fd291e 100644 --- a/tools/public_api_guard/service-worker/config.d.ts +++ b/tools/public_api_guard/service-worker/config.d.ts @@ -4,7 +4,7 @@ export interface AssetGroup { name: string; resources: { files?: Glob[]; - versionedFiles?: Glob[]; + /** @deprecated */ versionedFiles?: Glob[]; urls?: Glob[]; }; updateMode?: 'prefetch' | 'lazy'; From e5e5c24d48c67e0d2d16308ad65c5bd76351ec97 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 15 May 2018 12:34:04 -0700 Subject: [PATCH 056/582] release: cut the v6.0.2 release --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2bd4b2ce1..51041a4512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15) + + +### Bug Fixes + +* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([752b83a](https://github.com/angular/angular/commit/752b83a)) +* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([c6b618d](https://github.com/angular/angular/commit/c6b618d)) + + + # [6.0.1](https://github.com/angular/angular/compare/6.0.0...6.0.1) (2018-05-11) diff --git a/package.json b/package.json index d59fc77785..8da8259111 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.0.1", + "version": "6.0.2", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", From 5cf82f8f3fef4dbeb00c63a328334a167d129344 Mon Sep 17 00:00:00 2001 From: Lucas Sloan Date: Tue, 8 May 2018 13:37:54 -0700 Subject: [PATCH 057/582] build: upgrade to TypeScript 2.8 (#23782) PR Close #23782 --- integration/typings_test_ts27/package.json | 2 +- integration/typings_test_ts28/include-all.ts | 47 +++++++++++++++++++ integration/typings_test_ts28/package.json | 30 ++++++++++++ integration/typings_test_ts28/tsconfig.json | 24 ++++++++++ package.json | 4 +- packages/bazel/package.json | 2 +- packages/compiler-cli/package.json | 2 +- .../compiler-cli/src/transformers/program.ts | 2 +- .../core/src/sanitization/html_sanitizer.ts | 4 +- packages/core/src/sanitization/inert_body.ts | 2 +- packages/language-service/src/ts_plugin.ts | 11 +++-- .../service-worker/worker/testing/cache.ts | 3 +- .../src/common/downgrade_component_adapter.ts | 2 +- packages/upgrade/src/common/util.ts | 13 ----- yarn.lock | 12 ++--- 15 files changed, 127 insertions(+), 33 deletions(-) create mode 100644 integration/typings_test_ts28/include-all.ts create mode 100644 integration/typings_test_ts28/package.json create mode 100644 integration/typings_test_ts28/tsconfig.json diff --git a/integration/typings_test_ts27/package.json b/integration/typings_test_ts27/package.json index 766ba3fec9..cfacf41182 100644 --- a/integration/typings_test_ts27/package.json +++ b/integration/typings_test_ts27/package.json @@ -1,6 +1,6 @@ { "name": "angular-integration", - "description": "Assert that users with TypeScript 2.6 can type-check an Angular application", + "description": "Assert that users with TypeScript 2.7 can type-check an Angular application", "version": "0.0.0", "license": "MIT", "dependencies": { diff --git a/integration/typings_test_ts28/include-all.ts b/integration/typings_test_ts28/include-all.ts new file mode 100644 index 0000000000..78cefe0030 --- /dev/null +++ b/integration/typings_test_ts28/include-all.ts @@ -0,0 +1,47 @@ +/** + * @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 platformBrowser from '@angular/platform-browser'; +import * as platformBrowserTesting from '@angular/platform-browser/testing'; +import * as platformBrowserDynamic from '@angular/platform-browser-dynamic'; +import * as platformServer from '@angular/platform-server'; +import * as platformServerTesting from '@angular/platform-server/testing'; +import * as platformWebworker from '@angular/platform-webworker'; +import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic'; +import * as router from '@angular/router'; +import * as routerTesting from '@angular/router/testing'; +import * as serviceWorker from '@angular/service-worker'; +import * as upgrade from '@angular/upgrade'; + +export default { + compiler, + compilerTesting, + core, + coreTesting, + forms, + http, + httpTesting, + platformBrowser, + platformBrowserTesting, + platformBrowserDynamic, + platformServer, + platformServerTesting, + platformWebworker, + platformWebworkerDynamic, + router, + routerTesting, + serviceWorker, + upgrade, +}; diff --git a/integration/typings_test_ts28/package.json b/integration/typings_test_ts28/package.json new file mode 100644 index 0000000000..8c3ddac65e --- /dev/null +++ b/integration/typings_test_ts28/package.json @@ -0,0 +1,30 @@ +{ + "name": "angular-integration", + "description": "Assert that users with TypeScript 2.8 can type-check an Angular application", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@angular/animations": "file:../../dist/packages-dist/animations", + "@angular/common": "file:../../dist/packages-dist/common", + "@angular/compiler": "file:../../dist/packages-dist/compiler", + "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli", + "@angular/core": "file:../../dist/packages-dist/core", + "@angular/forms": "file:../../dist/packages-dist/forms", + "@angular/http": "file:../../dist/packages-dist/http", + "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser", + "@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic", + "@angular/platform-server": "file:../../dist/packages-dist/platform-server", + "@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker", + "@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic", + "@angular/router": "file:../../dist/packages-dist/router", + "@angular/service-worker": "file:../../dist/packages-dist/service-worker", + "@angular/upgrade": "file:../../dist/packages-dist/upgrade", + "@types/jasmine": "2.5.41", + "rxjs": "file:../../node_modules/rxjs", + "typescript": "2.8.x", + "zone.js": "file:../../node_modules/zone.js" + }, + "scripts": { + "test": "tsc" + } +} diff --git a/integration/typings_test_ts28/tsconfig.json b/integration/typings_test_ts28/tsconfig.json new file mode 100644 index 0000000000..165bd7a957 --- /dev/null +++ b/integration/typings_test_ts28/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "module": "commonjs", + "moduleResolution": "node", + "outDir": "../../dist/typings_test_ts28/", + "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 8da8259111..b38d5d6dbf 100644 --- a/package.json +++ b/package.json @@ -110,11 +110,11 @@ "source-map": "0.5.7", "source-map-support": "0.4.18", "systemjs": "0.18.10", - "tsickle": "^0.27.2", + "tsickle": "^0.28.0", "tslint": "5.7.0", "tslint-eslint-rules": "4.1.1", "tsutils": "2.20.0", - "typescript": "2.7.x", + "typescript": "2.8.x", "uglify-es": "^3.3.9", "universal-analytics": "0.4.15", "vlq": "0.2.2", diff --git a/packages/bazel/package.json b/packages/bazel/package.json index 19d1fde5c6..820bddaf98 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -13,7 +13,7 @@ }, "peerDependencies": { "@angular/compiler-cli": "0.0.0-PLACEHOLDER", - "typescript": ">=2.7.2 <2.8" + "typescript": ">=2.7.2 <2.9" }, "repository": { "type": "git", diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 25f420400f..8249b66deb 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -15,7 +15,7 @@ "chokidar": "^1.4.2" }, "peerDependencies": { - "typescript": ">=2.7.2 <2.8", + "typescript": ">=2.7.2 <2.9", "@angular/compiler": "0.0.0-PLACEHOLDER" }, "engines" : { diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index f58dd7d465..78764efca3 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -108,7 +108,7 @@ const MIN_TS_VERSION = '2.7.2'; * ∀ supported typescript version v, v < MAX_TS_VERSION * MAX_TS_VERSION is not considered as a supported TypeScript version */ -const MAX_TS_VERSION = '2.8.0'; +const MAX_TS_VERSION = '2.9.0'; class AngularCompilerProgram implements Program { private rootNames: string[]; diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts index dc49a6d073..cfa7e58035 100644 --- a/packages/core/src/sanitization/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -141,13 +141,13 @@ class SanitizingHtmlSerializer { const elAttrs = element.attributes; for (let i = 0; i < elAttrs.length; i++) { const elAttr = elAttrs.item(i); - const attrName = elAttr.name; + const attrName = elAttr !.name; const lower = attrName.toLowerCase(); if (!VALID_ATTRS.hasOwnProperty(lower)) { this.sanitizedSomething = true; continue; } - let value = elAttr.value; + let value = elAttr !.value; // TODO(martinprobst): Special case image URIs for data:image/... if (URI_ATTRS[lower]) value = _sanitizeUrl(value); if (SRCSET_ATTRS[lower]) value = sanitizeSrcset(value); diff --git a/packages/core/src/sanitization/inert_body.ts b/packages/core/src/sanitization/inert_body.ts index 85463c7c9d..fb90f71a54 100644 --- a/packages/core/src/sanitization/inert_body.ts +++ b/packages/core/src/sanitization/inert_body.ts @@ -146,7 +146,7 @@ export class InertBodyHelper { // loop backwards so that we can support removals. for (let i = elAttrs.length - 1; 0 < i; i--) { const attrib = elAttrs.item(i); - const attrName = attrib.name; + const attrName = attrib !.name; if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) { el.removeAttribute(attrName); } diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index b7336d9bbd..6457c4ef52 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -64,7 +64,7 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS } function typescriptOnly(ls: ts.LanguageService): ts.LanguageService { - return { + const languageService: ts.LanguageService = { cleanupSemanticCache: () => ls.cleanupSemanticCache(), getSyntacticDiagnostics: tryFilenameCall(ls.getSyntacticDiagnostics), getSemanticDiagnostics: tryFilenameCall(ls.getSemanticDiagnostics), @@ -117,8 +117,13 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS getDefinitionAndBoundSpan: tryFilenameOneCall(ls.getDefinitionAndBoundSpan), getCombinedCodeFix: (scope: ts.CombinedCodeFixScope, fixId: {}, formatOptions: ts.FormatCodeSettings) => - tryCall(undefined, () => ls.getCombinedCodeFix(scope, fixId, formatOptions)) - }; + tryCall(undefined, () => ls.getCombinedCodeFix(scope, fixId, formatOptions)), + // TODO(kyliau): dummy implementation to compile with ts 2.8, create real one + getSuggestionDiagnostics: (fileName: string) => [], + // TODO(kyliau): dummy implementation to compile with ts 2.8, create real one + organizeImports: (scope: ts.CombinedCodeFixScope, formatOptions: ts.FormatCodeSettings) => [], + } as ts.LanguageService; + return languageService; } oldLS = typescriptOnly(oldLS); diff --git a/packages/service-worker/worker/testing/cache.ts b/packages/service-worker/worker/testing/cache.ts index e56c43410a..b24147e2f6 100644 --- a/packages/service-worker/worker/testing/cache.ts +++ b/packages/service-worker/worker/testing/cache.ts @@ -156,7 +156,8 @@ export class MockCache { headers: {}, } as DehydratedResponse; - resp.headers.forEach((value, name) => { dehydratedResp.headers[name] = value; }); + resp.headers.forEach( + (value: string, name: string) => { dehydratedResp.headers[name] = value; }); dehydrated[url] = dehydratedResp; }); diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index 7d15d0c4a5..fd20b79e0a 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -11,7 +11,7 @@ import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, Event import * as angular from './angular1'; import {PropertyBinding} from './component_info'; import {$SCOPE} from './constants'; -import {getAttributesAsArray, getComponentName, hookupNgModel, strictEquals} from './util'; +import {getComponentName, hookupNgModel, strictEquals} from './util'; const INITIAL_VALUE = { __UNINITIALIZED__: true diff --git a/packages/upgrade/src/common/util.ts b/packages/upgrade/src/common/util.ts index 15213dd986..0407753b10 100644 --- a/packages/upgrade/src/common/util.ts +++ b/packages/upgrade/src/common/util.ts @@ -32,19 +32,6 @@ export function directiveNormalize(name: string): string { .replace(DIRECTIVE_SPECIAL_CHARS_REGEXP, (_, letter) => letter.toUpperCase()); } -export function getAttributesAsArray(node: Node): [string, string][] { - const attributes = node.attributes; - let asArray: [string, string][] = undefined !; - if (attributes) { - let attrLen = attributes.length; - asArray = new Array(attrLen); - for (let i = 0; i < attrLen; i++) { - asArray[i] = [attributes[i].nodeName, attributes[i].nodeValue !]; - } - } - return asArray || []; -} - export function getComponentName(component: Type): string { // Return the name of the component or the first line of its stringified version. return (component as any).overriddenName || component.name || component.toString().split('\n')[0]; diff --git a/yarn.lock b/yarn.lock index 5b87332be0..a04ffc296d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5886,9 +5886,9 @@ trim-off-newlines@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" -tsickle@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736" +tsickle@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.28.0.tgz#6cd6fa004766c6ad9261b599c83866ee97cc7875" dependencies: minimist "^1.2.0" mkdirp "^0.5.1" @@ -5989,9 +5989,9 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.7.x: - version "2.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" +typescript@2.8.x: + version "2.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" typescript@~2.6.2: version "2.6.2" From 313bdce590ece63a45691bc36b15b624df762250 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Mon, 14 May 2018 18:39:15 -0700 Subject: [PATCH 058/582] feat(platform-browser): allow lazy-loading HammerJS (#23906) PR Close #23906 --- .../src/dom/events/hammer_gestures.ts | 57 ++++++- .../test/dom/events/hammer_gestures_spec.ts | 148 ++++++++++++++++-- 2 files changed, 185 insertions(+), 20 deletions(-) diff --git a/packages/platform-browser/src/dom/events/hammer_gestures.ts b/packages/platform-browser/src/dom/events/hammer_gestures.ts index c4c6856b95..e51126de6c 100644 --- a/packages/platform-browser/src/dom/events/hammer_gestures.ts +++ b/packages/platform-browser/src/dom/events/hammer_gestures.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, InjectionToken, ɵConsole as Console} from '@angular/core'; +import {Inject, Injectable, InjectionToken, Optional, ɵConsole as Console} from '@angular/core'; import {DOCUMENT} from '../dom_tokens'; @@ -58,6 +58,13 @@ const EVENT_NAMES = { */ export const HAMMER_GESTURE_CONFIG = new InjectionToken('HammerGestureConfig'); + +/** Function that loads HammerJS, returning a promise that is resolved once HammerJs is loaded. */ +export type HammerLoader = (() => Promise) | null; + +/** Injection token used to provide a {@link HammerLoader} to Angular. */ +export const HAMMER_LOADER = new InjectionToken('HammerLoader'); + export interface HammerInstance { on(eventName: string, callback?: Function): void; off(eventName: string, callback?: Function): void; @@ -99,8 +106,8 @@ export class HammerGestureConfig { export class HammerGesturesPlugin extends EventManagerPlugin { constructor( @Inject(DOCUMENT) doc: any, - @Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig, - private console: Console) { + @Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig, private console: Console, + @Optional() @Inject(HAMMER_LOADER) private loader?: HammerLoader) { super(doc); } @@ -109,8 +116,10 @@ export class HammerGesturesPlugin extends EventManagerPlugin { return false; } - if (!(window as any).Hammer) { - this.console.warn(`Hammer.js is not loaded, can not bind '${eventName}' event.`); + if (!(window as any).Hammer && !this.loader) { + this.console.warn( + `The "${eventName}" event cannot be bound because Hammer.JS is not ` + + `loaded and no custom loader has been specified.`); return false; } @@ -121,6 +130,44 @@ export class HammerGesturesPlugin extends EventManagerPlugin { const zone = this.manager.getZone(); eventName = eventName.toLowerCase(); + // If Hammer is not present but a loader is specified, we defer adding the event listener + // until Hammer is loaded. + if (!(window as any).Hammer && this.loader) { + // This `addEventListener` method returns a function to remove the added listener. + // Until Hammer is loaded, the returned function needs to *cancel* the registration rather + // than remove anything. + let cancelRegistration = false; + let deregister: Function = () => { cancelRegistration = true; }; + + this.loader() + .then(() => { + // If Hammer isn't actually loaded when the custom loader resolves, give up. + if (!(window as any).Hammer) { + this.console.warn( + `The custom HAMMER_LOADER completed, but Hammer.JS is not present.`); + deregister = () => {}; + return; + } + + if (!cancelRegistration) { + // Now that Hammer is loaded and the listener is being loaded for real, + // the deregistration function changes from canceling registration to removal. + deregister = this.addEventListener(element, eventName, handler); + } + }) + .catch(() => { + this.console.warn( + `The "${eventName}" event cannot be bound because the custom ` + + `Hammer.JS loader failed.`); + deregister = () => {}; + }); + + // Return a function that *executes* `deregister` (and not `deregister` itself) so that we + // can change the behavior of `deregister` once the listener is added. Using a closure in + // this way allows us to avoid any additional data structures to track listener removal. + return () => { deregister(); }; + } + return zone.runOutsideAngular(() => { // Creating the manager bind events, must be done outside of angular const mc = this._config.buildHammer(element); diff --git a/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts b/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts index d9d87393da..cf82361a9b 100644 --- a/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts +++ b/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts @@ -5,32 +5,150 @@ * 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 {describe, expect, it} from '@angular/core/testing/src/testing_internal'; -import {HammerGestureConfig, HammerGesturesPlugin} from '@angular/platform-browser/src/dom/events/hammer_gestures'; +import {NgZone} from '@angular/core'; +import {fakeAsync, inject, tick} from '@angular/core/testing'; +import {afterEach, beforeEach, describe, expect, it,} from '@angular/core/testing/src/testing_internal'; +import {EventManager} from '@angular/platform-browser'; +import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-browser/src/dom/events/hammer_gestures'; { describe('HammerGesturesPlugin', () => { let plugin: HammerGesturesPlugin; - let mockConsole: any; + let fakeConsole: any; if (isNode) return; - beforeEach(() => { - mockConsole = {warn: () => {}}; - plugin = new HammerGesturesPlugin(document, new HammerGestureConfig(), mockConsole); + beforeEach(() => { fakeConsole = {warn: jasmine.createSpy('console.warn')}; }); + + describe('with no custom loader', () => { + beforeEach(() => { + plugin = new HammerGesturesPlugin(document, new HammerGestureConfig(), fakeConsole); + }); + + it('should implement addGlobalEventListener', () => { + spyOn(plugin, 'addEventListener').and.callFake(() => {}); + + expect(() => { + plugin.addGlobalEventListener('document', 'swipe', () => {}); + }).not.toThrowError(); + }); + + it('should warn user and do nothing when Hammer.js not loaded', () => { + expect(plugin.supports('swipe')).toBe(false); + expect(fakeConsole.warn) + .toHaveBeenCalledWith( + `The "swipe" event cannot be bound because Hammer.JS is not ` + + `loaded and no custom loader has been specified.`); + }); }); - it('should implement addGlobalEventListener', () => { - spyOn(plugin, 'addEventListener').and.callFake(() => {}); + describe('with a custom loader', () => { + // Use a fake custom loader for tests, with helper functions to resolve or reject. + let loader: () => Promise; + let resolveLoader: () => void; + let failLoader: () => void; - expect(() => plugin.addGlobalEventListener('document', 'swipe', () => {})).not.toThrowError(); - }); + // Arbitrary element and listener for testing. + let someElement: HTMLDivElement; + let someListener: () => void; - it('should warn user and do nothing when Hammer.js not loaeded', () => { - spyOn(mockConsole, 'warn'); + // Keep track of whatever value is in `window.Hammer` before the test so it can be + // restored afterwards so that this test doesn't care whether Hammer is actually loaded. + let originalHammerGlobal: any; - expect(plugin.supports('swipe')).toBe(false); - expect(mockConsole.warn) - .toHaveBeenCalledWith(`Hammer.js is not loaded, can not bind 'swipe' event.`); + // Fake Hammer instance ("mc") used to test the underlying event registration. + let fakeHammerInstance: {on: () => void, off: () => void}; + + // Inject the NgZone so that we can make it available to the plugin through a fake + // EventManager. + let ngZone: NgZone; + beforeEach(inject([NgZone], (z: NgZone) => { ngZone = z; })); + + beforeEach(() => { + originalHammerGlobal = (window as any).Hammer; + (window as any).Hammer = undefined; + + fakeHammerInstance = { + on: jasmine.createSpy('mc.on'), + off: jasmine.createSpy('mc.off'), + }; + + loader = () => new Promise((resolve, reject) => { + resolveLoader = resolve; + failLoader = reject; + }); + + // Make the hammer config return a fake hammer instance + const hammerConfig = new HammerGestureConfig(); + spyOn(hammerConfig, 'buildHammer').and.returnValue(fakeHammerInstance); + + plugin = new HammerGesturesPlugin(document, hammerConfig, fakeConsole, loader); + + // Use a fake EventManager that has access to the NgZone. + plugin.manager = { getZone: () => ngZone } as EventManager; + + someElement = document.createElement('div'); + someListener = () => {}; + }); + + afterEach(() => { (window as any).Hammer = originalHammerGlobal; }); + + it('should not log a warning when HammerJS is not loaded', () => { + plugin.addEventListener(someElement, 'swipe', () => {}); + expect(fakeConsole.warn).not.toHaveBeenCalled(); + }); + + it('should defer registering an event until Hammer is loaded', fakeAsync(() => { + plugin.addEventListener(someElement, 'swipe', someListener); + expect(fakeHammerInstance.on).not.toHaveBeenCalled(); + + (window as any).Hammer = {}; + resolveLoader(); + tick(); + + expect(fakeHammerInstance.on).toHaveBeenCalledWith('swipe', jasmine.any(Function)); + })); + + it('should cancel registration if an event is removed before being added', fakeAsync(() => { + const deregister = plugin.addEventListener(someElement, 'swipe', someListener); + deregister(); + + (window as any).Hammer = {}; + resolveLoader(); + tick(); + + expect(fakeHammerInstance.on).not.toHaveBeenCalled(); + })); + + it('should remove a listener after Hammer is loaded', fakeAsync(() => { + const removeListener = plugin.addEventListener(someElement, 'swipe', someListener); + + (window as any).Hammer = {}; + resolveLoader(); + tick(); + + removeListener(); + expect(fakeHammerInstance.off).toHaveBeenCalledWith('swipe', jasmine.any(Function)); + })); + + it('should log a warning when the loader fails', fakeAsync(() => { + plugin.addEventListener(someElement, 'swipe', () => {}); + failLoader(); + tick(); + + expect(fakeConsole.warn) + .toHaveBeenCalledWith( + `The "swipe" event cannot be bound because the custom Hammer.JS loader failed.`); + })); + + it('should load a warning if the loader resolves and Hammer is not present', fakeAsync(() => { + plugin.addEventListener(someElement, 'swipe', () => {}); + resolveLoader(); + tick(); + + expect(fakeConsole.warn) + .toHaveBeenCalledWith( + `The custom HAMMER_LOADER completed, but Hammer.JS is not present.`); + })); }); }); } From b7c417f618f1aa27fe1613129711d1380ce71dce Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Fri, 27 Apr 2018 20:47:23 -0700 Subject: [PATCH 059/582] feat(aio): add brand and concept icons, img style class more flexible (#23589) PR Close #23589 --- aio/content/marketing/presskit.html | 434 ++++++++++++++++++ .../images/logos/concept-icons/animations.png | Bin 0 -> 4660 bytes .../images/logos/concept-icons/animations.svg | 1 + .../images/logos/concept-icons/augury.png | Bin 0 -> 5317 bytes .../images/logos/concept-icons/augury.svg | 1 + .../assets/images/logos/concept-icons/cdk.png | Bin 0 -> 2966 bytes .../assets/images/logos/concept-icons/cdk.svg | 1 + .../assets/images/logos/concept-icons/cli.png | Bin 0 -> 3312 bytes .../assets/images/logos/concept-icons/cli.svg | 1 + .../images/logos/concept-icons/compiler.png | Bin 0 -> 6288 bytes .../images/logos/concept-icons/compiler.svg | 1 + .../images/logos/concept-icons/components.png | Bin 0 -> 4763 bytes .../images/logos/concept-icons/components.svg | 1 + .../concept-icons/dependency-injection.png | Bin 0 -> 4366 bytes .../concept-icons/dependency-injection.svg | 1 + .../images/logos/concept-icons/forms.png | Bin 0 -> 3512 bytes .../images/logos/concept-icons/forms.svg | 1 + .../images/logos/concept-icons/http.png | Bin 0 -> 4835 bytes .../images/logos/concept-icons/http.svg | 1 + .../images/logos/concept-icons/i18n.png | Bin 0 -> 7242 bytes .../images/logos/concept-icons/i18n.svg | 1 + .../images/logos/concept-icons/karma.png | Bin 0 -> 5096 bytes .../images/logos/concept-icons/karma.svg | 1 + .../images/logos/concept-icons/labs.png | Bin 0 -> 5775 bytes .../images/logos/concept-icons/labs.svg | 1 + .../logos/concept-icons/language-services.png | Bin 0 -> 5360 bytes .../logos/concept-icons/language-services.svg | 1 + .../logos/concept-icons/lazy-loading.png | Bin 0 -> 5839 bytes .../logos/concept-icons/lazy-loading.svg | 1 + .../images/logos/concept-icons/libraries.png | Bin 0 -> 4064 bytes .../images/logos/concept-icons/libraries.svg | 1 + .../images/logos/concept-icons/material.png | Bin 0 -> 5278 bytes .../images/logos/concept-icons/material.svg | 1 + .../logos/concept-icons/performance.png | Bin 0 -> 5560 bytes .../logos/concept-icons/performance.svg | 1 + .../images/logos/concept-icons/protractor.png | Bin 0 -> 4909 bytes .../images/logos/concept-icons/protractor.svg | 1 + .../assets/images/logos/concept-icons/pwa.png | Bin 0 -> 5000 bytes .../assets/images/logos/concept-icons/pwa.svg | 1 + .../images/logos/concept-icons/router.png | Bin 0 -> 3546 bytes .../images/logos/concept-icons/router.svg | 1 + .../images/logos/concept-icons/templates.png | Bin 0 -> 3046 bytes .../images/logos/concept-icons/templates.svg | 1 + .../images/logos/concept-icons/universal.png | Bin 0 -> 3717 bytes .../images/logos/concept-icons/universal.svg | 1 + aio/src/styles/2-modules/_presskit.scss | 2 +- 46 files changed, 457 insertions(+), 1 deletion(-) create mode 100755 aio/src/assets/images/logos/concept-icons/animations.png create mode 100644 aio/src/assets/images/logos/concept-icons/animations.svg create mode 100755 aio/src/assets/images/logos/concept-icons/augury.png create mode 100644 aio/src/assets/images/logos/concept-icons/augury.svg create mode 100755 aio/src/assets/images/logos/concept-icons/cdk.png create mode 100644 aio/src/assets/images/logos/concept-icons/cdk.svg create mode 100755 aio/src/assets/images/logos/concept-icons/cli.png create mode 100644 aio/src/assets/images/logos/concept-icons/cli.svg create mode 100755 aio/src/assets/images/logos/concept-icons/compiler.png create mode 100644 aio/src/assets/images/logos/concept-icons/compiler.svg create mode 100755 aio/src/assets/images/logos/concept-icons/components.png create mode 100644 aio/src/assets/images/logos/concept-icons/components.svg create mode 100755 aio/src/assets/images/logos/concept-icons/dependency-injection.png create mode 100644 aio/src/assets/images/logos/concept-icons/dependency-injection.svg create mode 100755 aio/src/assets/images/logos/concept-icons/forms.png create mode 100644 aio/src/assets/images/logos/concept-icons/forms.svg create mode 100755 aio/src/assets/images/logos/concept-icons/http.png create mode 100644 aio/src/assets/images/logos/concept-icons/http.svg create mode 100755 aio/src/assets/images/logos/concept-icons/i18n.png create mode 100644 aio/src/assets/images/logos/concept-icons/i18n.svg create mode 100755 aio/src/assets/images/logos/concept-icons/karma.png create mode 100644 aio/src/assets/images/logos/concept-icons/karma.svg create mode 100755 aio/src/assets/images/logos/concept-icons/labs.png create mode 100644 aio/src/assets/images/logos/concept-icons/labs.svg create mode 100755 aio/src/assets/images/logos/concept-icons/language-services.png create mode 100644 aio/src/assets/images/logos/concept-icons/language-services.svg create mode 100755 aio/src/assets/images/logos/concept-icons/lazy-loading.png create mode 100644 aio/src/assets/images/logos/concept-icons/lazy-loading.svg create mode 100755 aio/src/assets/images/logos/concept-icons/libraries.png create mode 100644 aio/src/assets/images/logos/concept-icons/libraries.svg create mode 100755 aio/src/assets/images/logos/concept-icons/material.png create mode 100644 aio/src/assets/images/logos/concept-icons/material.svg create mode 100755 aio/src/assets/images/logos/concept-icons/performance.png create mode 100644 aio/src/assets/images/logos/concept-icons/performance.svg create mode 100755 aio/src/assets/images/logos/concept-icons/protractor.png create mode 100644 aio/src/assets/images/logos/concept-icons/protractor.svg create mode 100755 aio/src/assets/images/logos/concept-icons/pwa.png create mode 100644 aio/src/assets/images/logos/concept-icons/pwa.svg create mode 100755 aio/src/assets/images/logos/concept-icons/router.png create mode 100644 aio/src/assets/images/logos/concept-icons/router.svg create mode 100755 aio/src/assets/images/logos/concept-icons/templates.png create mode 100644 aio/src/assets/images/logos/concept-icons/templates.svg create mode 100755 aio/src/assets/images/logos/concept-icons/universal.png create mode 100644 aio/src/assets/images/logos/concept-icons/universal.svg diff --git a/aio/content/marketing/presskit.html b/aio/content/marketing/presskit.html index 0fb8b79500..5d20411d0e 100644 --- a/aio/content/marketing/presskit.html +++ b/aio/content/marketing/presskit.html @@ -78,6 +78,440 @@ +
+
+
+

BRAND ICONS

+
+
+
+ +
+
+
+ Animations Icon +
+
+

ANIMATIONS

+
    +
  • + Animations Icon (png) - Download +
  • +
  • + Animations Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Augury Icon +
+
+

AUGURY

+ +
+
+
+ +
+
+
+ CDK Icon +
+
+

COMPONENT DEV KIT (CDK)

+ +
+
+
+ +
+
+
+ CLI Icon +
+
+

CLI

+ +
+
+
+ +
+
+
+ Compiler Icon +
+
+

COMPILER

+ +
+
+
+ +
+
+
+ Components Icon +
+
+

WEB COMPONENTS

+
    +
  • + Web Components Icon (png) - Download +
  • +
  • + Web Components Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Forms Icon +
+
+

FORMS

+ +
+
+
+ +
+
+
+ HTTP Icon +
+
+

HTTP

+ +
+
+
+ +
+
+
+ i18n Icon +
+
+

i18n

+ +
+
+
+ +
+
+
+ Karma Icon +
+
+

KARMA

+ +
+
+
+ +
+
+
+ Labs Icon +
+
+

LABS

+ +
+
+
+ +
+
+
+ Language Services Icon +
+
+

LANGUAGE SERVICES

+
    +
  • + Language Services Icon (png) - Download +
  • +
  • + Language Services Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Material Icon +
+
+

MATERIAL

+ +
+
+
+ +
+
+
+ Protractor Icon +
+
+

PROTRACTOR

+
    +
  • + Protractor Icon (png) - Download +
  • +
  • + Protractor Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ PWA Icon +
+
+

PWA

+ +
+
+
+ +
+
+
+ Router Icon +
+
+

ROUTER

+ +
+
+
+ +
+
+
+ Universal Icon +
+
+

UNIVERSAL

+ +
+
+
+ +
+
+
+

CONCEPT & FEATURE ICONS

+
+
+
+ +
+
+
+ Dependency Injection Icon +
+
+

DEPENDENCY INJECTION

+
    +
  • + Dependency Injection Icon (png) - Download +
  • +
  • + Dependency Injection Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Lazy Loading Icon +
+
+

LAZY LOADING

+
    +
  • + Lazy Loading Icon (png) - Download +
  • +
  • + Lazy Loading Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Libraries Icon +
+
+

LIBRARIES

+ +
+
+
+ +
+
+
+ Performance Icon +
+
+

PERFORMANCE

+
    +
  • + Performance Icon (png) - Download +
  • +
  • + Performance Icon (svg) - Download +
  • +
+
+
+
+ +
+
+
+ Templates Icon +
+
+

TEMPLATES

+ +
+
+
+
diff --git a/aio/src/assets/images/logos/concept-icons/animations.png b/aio/src/assets/images/logos/concept-icons/animations.png new file mode 100755 index 0000000000000000000000000000000000000000..85bec902259b83744af6b5b8c7d0b9eaf1df4d8e GIT binary patch literal 4660 zcmXX~c|4R~)E`@sL?cU)u}jJnvhU4c48~53u}iXL&DctoW+;qZhK!K1MY3n?*&DL` zWQ#IcvXgDRPw)Hwai9D7oO91P-+S)&Irlja&e%wo?c9}fAP|TRp$9Vs<_2JVVLk&q z?H7+!fQiLd@18#h#0vg5=!T~wjzOS{zYs7j^T#=BM6c&p$3wbLm-_pO&5cu|eAxU? z51CZNzLj024x?_xQQN#w~CW zDBG&{b7f0s*Pp$8;(ec+Jiq(1P}fTOv}kByFGQe*_Bn9td+>^Y4X1->gH{8v9n>*? z0c35DY1=yPY!$k}9IBCCM&>rGVM_fuoog@Btq8;FVYnQ-BKV!MU_w;Efb4YG;r z^hpdQdCdR*ti1AItQILltJa?bH=&y(6rBBtRLt^YFZ2eU7SA+4o4nI0L<#&I6eOG+ z%1xGVMWt}rkrmgsAOASD<~^|uTORefo|ppCB)w1Rn63s1j4Ih#?8xDEQahG4Nqt|} z_}kpRHy3;<#6QoCC0O6l+c8-bO>0OM!(P#M+7_aWc#5Z0gZ{`qV&x zRL>98*)olgSg+xx_5ICwV-YLufwRt6u1qVa4SP+HMfzNY`N)V-|B#B40NsS-h zm}WGMuQ^Ae$G)Dn$geaquA~*|9JAWvr6DPQt zw*gU_L%mZ&JYdFQ1g;}i-6=QSRkkmJo5NfP6J;dZv7lcfq7C6!Q9ca#g~chjF$>HY3&E`{M{(C z(U^_wf5hX?XT5plBBm{>@+@e+tYzqFu4TZLPwPR9EvJt_QvF}U3k+$0tI000=-TPDu(C4Is+LXvxx=57aoiL+ zA4*Mh-luyF>tQlXA_6SfJgKBIg7}*xY`5f0&hsBPFRuKe-x!0YWYN0X#ufx3&?+vs z#p++PRI%#q-0*HhuKn%F5>>5Vdg>Asx!?&Ng4A)=tzepO^QNQcYF4#2@PE@`5OMq{ z998WP^*y}{isYTxp2Cj5)$@B=Q6*ccWhZZkdE;^kq#l4$8qb<6;HfgdQBUq07FmK` z_#4NI#)G5=$RNg0jMVhgl zq@Z9l8yp(3rkZGXL|1+**!p@s+4Y}+!{6HB@4bDkQKRX(7kM8P z!(FdOB9F}do+7Jijffh_v?EJOVqWfhzgw2ZEIx|KR!S+Z(T zqxvj!+wp>GGTNbW^>-|*)|k(4e$NQ$a5n|t{c!SUmEQW9)L)HJ3!l&f-(`VasWFS+ zzIv~t|IcC%xzb{AS25#Na&;2RJ=HX{!~4}6%uBHvedp{3c_BDy$U*y-=-DUfZg->m zWm=X9te)qcd7Y=)d};;gzJ^$P@GGCXw_Q8PbLD3P!V_0Zfbi+m0c*K)A~ip;t>rn9 zo@^;-hm`-v)o?jpN0}8C(s#@T#CoxsaN_y5;LH#e?}(g{iEJ0+nGg(JS**{9)0mRR za$x@MU=xWs@N6K%sRM;uJ6VkFxMW9hgo@|P#AGE(`|rSg>WB} z(Li9%6}dvZ>p0{jbgb@eK`;PJWZ}y!wIhY~1yZsnSh=YmMc`|VUcJZ0^DOU$()w{` zUVWUwgv|fS3wyJ|bEvfby`d41jL;tGJ2ojDPtv*+Wk}=x z8;4&r%ma*g_xW}UIx-~=H)Ku^vnMw>M1I>hWJZ2flmQ4HfwGf9(%Sou`S{4ag!a$5#H0FI z*~2=~0JOsCf9HIETZx7;?f67I`Pp*f@7c85r#Io0uS%J=lk2tJ8d~`!G5NwQ zeUVHB2$M_wV~Ob!%ddL1m$KS*&CJgspgl$q6`&vmUGP|PeDmpnMh`~W6)F8JPZm3I zrFpT%xdKbfae=a#5vm1`X?1x^CtXXueEKm7Hd~2K2Zl#+LX)g!-3HPdsDMG;xSPrq zH&A!09y;4>nXvKDpK52w1Wye_xmXnqW}$Ckb{2}KkviQLw&^~p{_F(1?CYXSCQ`wY zQx!}Fg8VIuv$hQ0+_r84npI1tlLmtm`ZGc@r2F0w2QeAcbqw(=cX_(M1|eM3B35eQ zUp4`HY#y|6Inao(Z=|Hvskh4Fka1t3q>Ixp6xBRSM!Ux?!IKsC($bPW>aWu~QnnTR z*^}!M_|kYrulg~?k3U7OUy78=aH%M3Xyc8%xz4NM7654CewWc@KO=;yVbf1vHr-?bxyiQDA7+flB{RCU)wFPa9k{YaUQa>Nu zi+%CJsZcM`wN~r@G*S&GEF}nhs<2w>GnxoR&5F5M>>yKUOY^i`B)CeB+RlP~lD3qUqQ#5C+d zwT3K~yLnW!!@4Q`c+&PxFm%-P4MWwGbiF})=bUv(0XlLN+0t^k8;U`?g_&(O=}Qhf zO?qYUKQV8Q(G!4R1zECntv`MP*%vI(_9s5hA%Ylmu^#`~G_`cM zfUCXuCAg&D{BlrIOL@mOD!zo|n;0qxp z_$K+5T~&svM(O$s@ABgtE3hR5;mzY0F>fx_r($>NV3Xm=IdRE^_;;j{(sI0!YRd;Y z^ufTGsS8WhkuYFG8p`5@iG0QVi@Z5@Y^oVFE1Ka|$B{%0d<`D*@f2l$jx&7|yZRYX z&JZIVD8e^Xj7*k74p)Ur)w=+h46JyLMJ6B>c4oB}>6=Fvk*gM*HJ{e+Rr`vCxal-W z1kPR+QLx&z^!o?=yUne`yu2~BD334kK)|hG6~y_x+tvFS+|6m|!g+1-;k&+xX(MeyAG+&OvXIKpSS;;i+&wX%7B+bijTwd=Gk2b>l%LF_f}i=5(@(v<{zF zq1qE7@S~ECr9RKl#ub7}au#x_FRMQ{MgI)ZU@FDGRts_b5p(+4_9s8_YbVjFnFB)_ z2JQeU9SLTT!)o@jq%vuw-40fEOW>L=momxhCh;v#48Zi4RR1eW9Iyv(feT$PfOOHA zs;$W6(`iF10p2G$v4UuSJ& zq&^E67^s`NktFZ#l31@~SIS!i2-KuU;d)wbC%g9==TS99ILC@`7&uAt&Py9Ok0{Fu zLQXKk2T!#S&o*87pdFw^sLTcV+7F1_*5QOzC{t2fr727T5~b?#7%P(GGa`vWI6{Md z2y6*~SEvA54221|Apugw>>KxzB`BAdP8|FWCmT2oFR$|0%^}*BpHV-FWy{owG>|u= zM*}I(0Netd)$(2Q3%;m;rv)tU-;-wbr7J}pYqSEoLNwe_C%VVb zitzHWr{;S8FrIHYm18%tib^L0A6(J-0Zuz(vo@Oys=7qBjGWa!Rxl#;%n-+z8mjod z{v#IfRXvGP)T_@H79<5pO6pS>%1o=x!)8ZZJX44pILD8ASbA literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/animations.svg b/aio/src/assets/images/logos/concept-icons/animations.svg new file mode 100644 index 0000000000..6e73ef3110 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/animations.svg @@ -0,0 +1 @@ +animations \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/augury.png b/aio/src/assets/images/logos/concept-icons/augury.png new file mode 100755 index 0000000000000000000000000000000000000000..d65dd38c3b1e06f058beee773ca9a15715083e1d GIT binary patch literal 5317 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 Kp>;YgvHt^lTpf}C literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/augury.svg b/aio/src/assets/images/logos/concept-icons/augury.svg new file mode 100644 index 0000000000..6080bea35c --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/augury.svg @@ -0,0 +1 @@ +augury \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/cdk.png b/aio/src/assets/images/logos/concept-icons/cdk.png new file mode 100755 index 0000000000000000000000000000000000000000..03003c5f553111d91fb7a21cc64ecdba1450e4b3 GIT binary patch literal 2966 zcmX9=dmxkV7vDFz%$O0m&n1k~rl_l+dZU8KuoxkkPl*>5f>Q7OzVx5y=z z#)L?2nG|x5EpP5JUxw=UuJ0eu`=0kX=Q-zd&hvTR^Zd_&M3k0Pk%T}X($-cMPT-pb zj`tE`;M0q{c?5hx$yRQG5C}|8IR5%L8o31uR@N5yQz6ghNA2&a5tYT3@{7XNTGYH< zr{{Q$2NKt}DFujz9xj`lT-`m{C|a%v|HsO7viUEa6;X%DVYnPr9DcSLy`K?n+HxPG z4^6X5y$VytJUAluahd$M@XOejh3`cfr865VTRMB{j7R%)c>%A-N=ps)J?%ag#7p*8 zZG=aO$hThT$}m5k`cu{k`EZ~Hr(7FqjgVrg3vz2E6s}Kn{7A@#;4tpu9Lj$g#%3`)I`3+fO|sadT7 z@He3=i5INJ!*sAq-bS8za!`z&(^|U;Ss{D%;~Y_-KJ|U&n`0dVowM%2Qcta6QmlV$ z9ADp?sU42?;}qxm$_~rY#HloixL>go!eKGPR91Ci7OidLLj-`c~V> zT(i%io9*Cp|2Uh!qVH`Pv`$fd))f$o56*XLVazQRYS`b(qvC@xog^;jnq~!w-X?d9 z+?h>}mk99M_82}2*RE?J$~Crgx-~0oCNj1!{o>4PR-8O~Zl6u>p0#SYZd6W|D#2GZ zoWgKVO^2*71LH}($-ITf!5;daPGh2^SoAZK>;higZ&FjfLQcRkwWV%+sJYV%d6>O0 zFJsswT)uUyAPDjLl?s1qD2Gm{bR}n%5%XQmihfn7P!jtL;Blgg9em$5GF0_$LZ zp(&;rnLg?4MlQeY$6G6WDh&3ka--u3^HJSYVP!Sqi+1aH5BpT$;2ZTn@QN{?MAJnL zVoE9|2w$URb+ga_u~ZfRDgWl;$CA$jgE>TCVX9}T=?yM0a&%ZDa%L-y^&@S-^n}bv z`|pe?|2Lb{oNlFe9aLCeDRDYi)-o0>Kbk3(fJvrN zaa99y+QrDv>_K@7+)Lkxaoc>S{^3dAXq)k9#EBkEtwX(9j+fv81$h%P zINZM(Ztl8ivbJm+l-Zjms@PdGBfNkKi@a>E20&S)zb58WtvWaLUG?(>c0K znRvcny@*KL4AHCVx!g&Oib2;W42<>b25Z*WC8x_{70}wCn)vBo3~Y+=#!|kX#&Ds_joiz9%t{v&20()lXt)DM zGYamA!O-$z>CHZ66W*bWughvIha_9t2lsfBqco5-&R1X2-XA(4Je0;+uf9PcmsPJ=e zs`y;pPVBgZu#|ubr8w9VED4*traTYQtas=VYfSp)+VrP_py1CAQx!!KyFVKp)1mVfI!(Gf_?upCl zvLmam21&7yS|_;zcV5ED@kQR$h6L+@JtLw^^Cw~#lQRKhvp`X;wR6ezlx^5T!zmX;I7eWW*{ zVxGe>G`JzG|0yKWgzUVb;KLC|gLbMET^35mN0NmCEf#5|7S3hw5R@KCJN2gR9?)OV z_9Pm*MM?l1YEVeSQejxcdbBegzgExrBnu1W9o880JW-zp4>?=5v6C+W z#_SdISYCiyXHt>YS%tecn0v--O$7-4dr;NB-9vc`+O9>zVNCA0EqH`odn_h}dd=KJ zGt)Y3!I_>~8VAX;kvI(7-1X&U0y?;@269zoeYMf9+}{NIE(YdheVBW!*(@xj52fR= z3P8bpl=zY;(c*dLk*(5)Nk+hd7dS1yJpfcus1fVed$s|`!tC}+g~g3!Ce0ZGIlTdM z#%U4fIR|*c%a+KSFYD}k%p>bg9|nzpy9Xl)Hcdk \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/cli.png b/aio/src/assets/images/logos/concept-icons/cli.png new file mode 100755 index 0000000000000000000000000000000000000000..e4d2b584521f80a04a1950587bc3e8c4fa7112f9 GIT binary patch literal 3312 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 Hxl;ZQ)rlR4 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/cli.svg b/aio/src/assets/images/logos/concept-icons/cli.svg new file mode 100644 index 0000000000..1d00bd1f08 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/cli.svg @@ -0,0 +1 @@ +cli \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/compiler.png b/aio/src/assets/images/logos/concept-icons/compiler.png new file mode 100755 index 0000000000000000000000000000000000000000..285031201a6581e842b41fc62cb2b99a066243d0 GIT binary patch literal 6288 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 Is!w452kX5b+W-In literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/compiler.svg b/aio/src/assets/images/logos/concept-icons/compiler.svg new file mode 100644 index 0000000000..b1fdbce869 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/compiler.svg @@ -0,0 +1 @@ +compiler \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/components.png b/aio/src/assets/images/logos/concept-icons/components.png new file mode 100755 index 0000000000000000000000000000000000000000..2e74e9e719ceb73f8489acbe4b4a671e81d1ff6f GIT binary patch literal 4763 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/components.svg b/aio/src/assets/images/logos/concept-icons/components.svg new file mode 100644 index 0000000000..0c58f21013 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/components.svg @@ -0,0 +1 @@ +components \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/dependency-injection.png b/aio/src/assets/images/logos/concept-icons/dependency-injection.png new file mode 100755 index 0000000000000000000000000000000000000000..f1871cd3e91f57c27d1b74acdce22803f3ace180 GIT binary patch literal 4366 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 Xd|FP?DZY{52L}l6yx-}%Ln!%wuUMXJ literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/dependency-injection.svg b/aio/src/assets/images/logos/concept-icons/dependency-injection.svg new file mode 100644 index 0000000000..82cdf98dce --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/dependency-injection.svg @@ -0,0 +1 @@ +dependency-injection \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/forms.png b/aio/src/assets/images/logos/concept-icons/forms.png new file mode 100755 index 0000000000000000000000000000000000000000..9324696aaf0b4e2532d4b374f30052821fe55c28 GIT binary patch literal 3512 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/forms.svg b/aio/src/assets/images/logos/concept-icons/forms.svg new file mode 100644 index 0000000000..b0bf02adb1 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/forms.svg @@ -0,0 +1 @@ +forms \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/http.png b/aio/src/assets/images/logos/concept-icons/http.png new file mode 100755 index 0000000000000000000000000000000000000000..ebbf122d6da39c23b9ac37bcf159ceab1e2fb244 GIT binary patch literal 4835 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/http.svg b/aio/src/assets/images/logos/concept-icons/http.svg new file mode 100644 index 0000000000..fd099fe52a --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/http.svg @@ -0,0 +1 @@ +http \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/i18n.png b/aio/src/assets/images/logos/concept-icons/i18n.png new file mode 100755 index 0000000000000000000000000000000000000000..4a6172420aff7770e80c8096aede4f30e8a60752 GIT binary patch literal 7242 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 \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/karma.png b/aio/src/assets/images/logos/concept-icons/karma.png new file mode 100755 index 0000000000000000000000000000000000000000..385a964297c234a47db2a20f19124de26e3bb611 GIT binary patch literal 5096 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/karma.svg b/aio/src/assets/images/logos/concept-icons/karma.svg new file mode 100644 index 0000000000..e668f91049 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/karma.svg @@ -0,0 +1 @@ +karma \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/labs.png b/aio/src/assets/images/logos/concept-icons/labs.png new file mode 100755 index 0000000000000000000000000000000000000000..256cd5de4c1c5219d76fe9d4de7fbab1c24e5bcd GIT binary patch literal 5775 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 aDi7K-Jlv|ItbqzM=#d6oy&m>F`u_kUuLn#3 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/labs.svg b/aio/src/assets/images/logos/concept-icons/labs.svg new file mode 100644 index 0000000000..2c0229b705 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/labs.svg @@ -0,0 +1 @@ +labs \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/language-services.png b/aio/src/assets/images/logos/concept-icons/language-services.png new file mode 100755 index 0000000000000000000000000000000000000000..be46eb8fd5244300d961d40f67299fc6bd6b5455 GIT binary patch literal 5360 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 \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/lazy-loading.png b/aio/src/assets/images/logos/concept-icons/lazy-loading.png new file mode 100755 index 0000000000000000000000000000000000000000..b232549430ac9ffd9f14ddd3bf8a80be8fe4f19b GIT binary patch literal 5839 zcma)AXH*kWw?zf1LWm~P2@q-!5Tr^AgbvcXARxU-4^4qk5;{acih%Uqq$r>uy^1uY z_aa5vw()wP1)EJXe6$W8++DxRRRKNhzbg+=lk||W{`T)M0!u_+;5Fk^yvzZdQoR| zt`P|cIbtPPu4*bwr*n77Ky<><>v?X9i0H!&=H}0)gY%zfNE)YH`AclSZW%b}R1N2U z=2djcbbL#wRhJN=N3F%5BXPN*At7|ce-gjnm4<#LAhatEd>((}bnuE;da3#KBF!_) zGwbl}{VeI1=l2MaovXVj(O{T$1R)EKFabuGNB|Clfn_lu)Ji_)EY`VzKmGJBix8zYx%oZY3#-mK)0XH zOJMRNvfQr9NiA)in1fL(y()8}A~x*iaRntQp78m~-H+3A*!d9ToK$CEH54`QzU!=* z>p}ba@%Qm&_zhApF%YlP=cH|Jtj8?M&6gY;CnQRoG0|M@_fq(n*Byp@^s)KEt-gqV z^If{IA{qFJpGvXe)Tep&)vNb%7=k@wyOGV7kf9e+gU_S%sM&E*W;Hpk1B%WIP1I9S ztaT3r6Gkwng?4T&+w>m;&QYQ^lp~n1T8B*XfUOTUM|%i1qC{;9NE)GbpS8B2Dk#Ve zY9}F4a8F)A5veCq;kh)Nl}Rgn)K7yPLYpPBcBfLM5#7k@;cDLg5a}AlVq^~5-QzNu z;iHkzFC(5J(so~8f49FVJy7()$ly9yh0Fcn#{%#sZf;8`QtxS*Vea)+%4S|}!%rH{C9waVX!mi((jX{X~ zW|;r$!j3yBks7=#y=Rq()5ES8DA7<-+xsXR^jtZV{l>}ru0xPz1TMHI0CH6h6w zTA0yQ;5cER4igirxp-RQS(w(M0Y#AB3@Z%HpcG9Nsh>~6r23^eN|$qxSnL?{jgc z1|*lg3!AuL^7`u{_kV3{BVTc{bja~ec|>76sBejiaO9}R;PM^`&b6Oml*Fp5Q=ZkE zN%Kk0W_(3%#zX8q%-h{2tCA#dIp)9e=z_En`B##;~Me_4Bw4txj#oygDG|= zMs~_Y_@atoz+!T7aV{?dky(8U%u-=ew?YdmrR8WsfBs<1*=Ca?Bu)OAb4BupmjZTny1_+gn-Dc9Mi*q#>dP*Vw+Ev>Rmx6q{%k-&~5p!zhJvjq=Q zRJ3aw!1eS`f#7d`?4D3kw9U<7vqF$&zlWPG`|E8XB30}R+)o6YKf+n6hYC_ zv%iWR+b*srt`QN-TeN^Q^FxHuc6HNES^m=9iXb^1AMxFbL*__ zuim>}r(~|7S%$ZNH{|7a9g1Oszq!%8PlM6u?|EKlI(!qmj5KmiGvP^C^Aa+A1(#Tz z9j8deiY`S?&<0<+=2iS7dH%eUI(iPAj94@}PFRgPK(B&Jp`+5DtD!tqhM7KFcJ~YD z(}|*hh&1(a!ec1kmr(^jt^gZub2Dwa4=4K?|8OfsLzd2gteXu5tjbIHz(c z`ZVJ?ixwOGh!^g>jK82_Qj}W#A6U#08lNbSnYlQ{$%{E4Pz?JfqP3BcMJ-+D z!TS`X8~5YebQ-_WdG;}M`k!4_N_gG{db8!KrsIyb$J3S_yZ*B z4J>_Fwr{{$Q%^$VtsS74z%ow9t}&LH0A{$M5ATW^4C+^NQ9W*GN&F*>o_G;W_h+w? z!ecX?Qx&gOF&DJ#^UeBfJUGd>5(8{%?E&$nkOJ+t+UBD8G6o(1g!3X&fUKtOsIqpK z@X-2T^Sm>Fjf#2cwWiF(_ipQtUSC58CfHWu4)Cl?UB&#*PTVw+OFYU&7q`C+aCXf% zP2(njjm*6;Z!968r(ayx*?6+%_&BMmzUY-1 z^>vb61qr@%y=&~+Q)o-CPo}OS#wvMht`*7`T_}#XhBvrG4l~g%QV})=d3G;1=j;Nj zB)Tj$(nAX*W#!n=EMX&$*q`c|_j_AWc-)D^oRxo_QH_y?J{iUt7f8Zywj%Q<4*Q3G zrry6`kL?Ga*@|e=PgdLue??#Fi1%v)tmed++9n~FQd8ljk?{0nGat}u{)6r7!%dWnIBbUZ2t+r+K4W^$ z_xCiScafKEi9_jWFsRjCUxon$D|b}mIGX)8W{U8Mi{T8Srs z)oLGTZmHo(`+v%NK_7sm+}~yXf%L_iI-{ioA*jb{Y+x6>1y2d%r|KEj&?#%%A9cqL znQ^mX*^elJ2cd9JRQ(5=F_!Me;IdWq{cdbR;<*O%GbX z)uf}^#!L(n($Z&xvH~D+4a6gbnK;=DpQ_C7QQ?1P8b9UZ95P08{57n{QRk4O;R~LktMqw77E{^MsX7%i_W9+yuWFlS>LW0OS~&wf#`Thv{da( zHO7SEuzFzBk|KBefs>;X>s?A+m6s!hKK7zdX!`(mMP23hV0bgcCf?Q<8N8A7X8H~v zOPl}HL4kHh+5A1*OV`;?cLDa8udK`qz_hkUzt$2eI!T_FmT07M3(>$k{zI?l?2$~} z#qi59nZh5Tv!592Grsk1VU|wDcN|yK9JBb?sMznK3a854tn~H#oCdZdDkD32xX@S2 z$w^gtQngi|@_UDK3;=|7UI249X(ua;hUhc2qd>Ia8!JFv3OV}xo(#CSL+c3P?eCcX zofxp()6F@`L*ouW#8$@s;!b8ne!3lk%w%2$yq_fldX#U0YN`gmiG)oH6Q=>pFR!Hr zVhT+cQ_}%(re}VJ`M4!Z;%N<2Z^}u$TQb~Kd-$NayEy0j)F1xvZ01y4kFF;aTS?gRiX7!&Cq766^_WU#L$!lVMUE?f7iJsb6zCB z&YhY;Szu=?jts*n_}Pn}yN=U^GdVgvwU<73o$cQj)=##dgf#p5NRGeQ_jOqF>59<2 z+N)B34xUKY7u&~D?Q7n3de@2>~DQ|b-~X&-OhMXo17r| z`|&b1227~TOA7LL3%J1I(^1TM z>GX_D*k|v0_mhY>-9|U;VtE`s!g<#SpBJ+)LUJ1e=m)~-mj@?@0CeTx`>EDuRJPmM*0{3e7#8<^VgfzYRf3r@JJO; zS<2v;Yi9-tOb9-wm;q?lzG98Mgh42s)J3R@Mp z4Gt5MgtLgpgc3d^NWmvCbjx9A2+38HDOW}w;3$Zu27}XE3%2Q^G@?2j2X98dt|}&e z7hZ2H^(;4XRl@MFvNW}G&!O{ZMr~m!0^txo8^YF!EA zT?2+c>e#B*rL)=$#sZ^7O6SvA-N2bj^px%;$%n&Ls8S%7siZm$lAd^IY{K;!#uU=b zLBhTyh&S8y?K*kYvU13N^O{!-ta8^g^fSs@=&d8B)=wKIL0v!XAl0^%khWB`#gwlgmr|I+?wAW); z>3;TyjBP)Jbk?|q*!+;UP5~woX+5;^*oh5<$ ztM&EW5SR6kwCk5F=WPD$>t*^N5w}s2-Lz#25Vy}u?DFGYn%^-tRW6EUp zlrY2QMiQ$A%M{3}R-c}VFGUr>6O0&?PvEPxH=-{G?|@HwbMImHXPu=6&cDKUDQe5P zu&Nv??PVtfVq`kC4LV!1yoiAuJSlz>LO&tmQQ@f5j2sG@RZo!b+QN^!O!Xe02NIPm zO&k}farEU_*{C$U5Uzpwn5O9Iwxn@kqaf^4bwgeGSFWrd@5jy59DC1yP)-L}>5#Ix zUO7i!*15~6afGFL%a02IB> zuE_EPVCc;c)Owq)#X{kuD0U+=%49Eg2XQ~TH=|Ld7 z()Kc9;V`^NGh15tys$?zQ-1eL!pMirJm)hVz2rAT6_Efz-JqpGz4!;aY%{j}eKWXK zLz>$EUliWz+Gwc!j`%r$NQ~J7l$z#DYv=QYqy(QvgDTY6((R621nSxd*ky21l-=v# zV#_5WKoizPTa6I;Zz1)Z3c{jPJ>_C-KdH~Z`~yYS3nM0A5UE>L(jmDS z_Qx@`evzS#(#|z5;W*A$nGHu5uQi<22#j#gT}f;d=+d7udak_gvHC@tZ%zIfmJE}% zf0@4|3Rfu{0X%}?>$^BfK&2%LLZ)dQJ58eDyIL{WBn-hh`X+eYVoc)ABO!yD1Ot%l zKXVT`*K3mnx0rlzmS6%3hS%`eCfpJTff1-!2@kRo!|Y|0n*f=Pk$HBh=q2=#b?g<0 z=~rcd?pCUNbP3@N0yLW0M8*ja>aKtzLA&Kp&>x*kpTHyRgvjACws&zVVvOWytDJO0 zje5tO37Apk0_-#d5Rh%6fAcm91-cxiaOtM(Gh}}6Qw>0{x(|N=XM_}yoO%aAs$;GL zH*?i5O*H{Q5JbBM)Sb?4zLLBwBm}-#CHKp5@v$Cuz;gN8n`~KomzCJAwdw*4odL4kWkx0i zrx~I#b>a*Kkew?f9o0^)dO0=K0C}!Q%Ql%r>#?%TFT5G3*-9SB`IOWg)rc6t)Vb3P z2=(?kwim}fX+%KDh6767#77Jg9Ro&niAseYnk^`EN5x>AO@6imMH|5wz;TzB8y6Lm z+Y49bh|a-}V2ZCZ+iM|#RgwAuj_$ehg5xi3JHS}8cSD~nIcjECU-D^V0JyO{Ja-%` z$UBT6j0}C@f7`J6VsU3v$SZ1683{nx%yu3`(Hf|Cbf}pE_MJ ZOSuVl5C8q^3pmsSDhf|fm2wuC{{kiX>G=Qv literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/lazy-loading.svg b/aio/src/assets/images/logos/concept-icons/lazy-loading.svg new file mode 100644 index 0000000000..f71a6484ff --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/lazy-loading.svg @@ -0,0 +1 @@ +lazy-loading \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/libraries.png b/aio/src/assets/images/logos/concept-icons/libraries.png new file mode 100755 index 0000000000000000000000000000000000000000..036ed3ae863e04aa36d89d9d8a10dde31f012e34 GIT binary patch literal 4064 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/libraries.svg b/aio/src/assets/images/logos/concept-icons/libraries.svg new file mode 100644 index 0000000000..9f92fa7210 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/libraries.svg @@ -0,0 +1 @@ +libraries \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/material.png b/aio/src/assets/images/logos/concept-icons/material.png new file mode 100755 index 0000000000000000000000000000000000000000..608ca6984b5e65b7930bcd42538580132853313f GIT binary patch literal 5278 zcmW+)bzIY57ajuA!Wbn=NQZPuBaBX^5#~gYMubr#1qK_VOFBj?f`N37kWgSCp@b63 zU{XIu*E`=o?)QG~^S$?;bMAT0jf1i<(`TUNqy+!~42A~ahoriVREDUplfIAF&QwVi zjh}&i002N2`LB?qVBNn6;H$HUoixlzr{|k-b{6>tZ6aX$FF|{r1*b29+xo4F?h;I`h){ffmC`K&tJ1>RRt{c zoykCOZoKiPvJ5;Rxr_0^@;>u021^|rCJgaskqhwSB7ET^-V9lWwkvVRh2lNtk>5Mo zKNUB;HiikeArIo%I++y`jk+m>+Q^;yXY|n9E=@Im0q+!(9>v{h13C4#mFt#Bh&A`P zEo~v*>6YAQe?ObV1si*dH~;a;U&c>T=n3%BI%8( z?>se-{@0?Xe_hO|SkmTE4>78SdAb?C_w*+7gstv6PXmkCvbmff<)nAfSX~Y*&IQE~itH=1by#S-Pi+B_5o@36`M!|Au zwoZ+6w)f!}x*z|aqj$Ly`Sxxb#r)U`l1-iBUg8`<_m%g&{cRj3!34pVGuLj@5G^Uj zuY>018vB3K_`_@Qo9?&)DDs9@IqDjDbR+t=LcSJnyboXqm7-Hl19Gm?rw8<5$Z_u2 zLBNE#3U#uO)fbZzNj(Kr55P|g#>8own`Y(N%#Bk(X|3b)k9Cri=)?E<7XxwqoFwMm z%xS|L@|&uWmKqvFqh;nOz-b!7#IJDO9jEZ(vjM|32bk%C%tP{?I(!@N0rrC?Kyimk z@q*s20|JxJ_hgc#0Q@1QwhavF>_7aquL;OmQoY#!YITUdqP%^kdTO3*#ng9?`b%Pc z?LfY8bkmj={Bq%lt)Uv5T2hWZ>gJ9J&!j}91jH!oQ7SQ}h9fU!K@eRN(@%h$2= zghk?yr6L*eM)`<~Lv(wjiN2>)2DyA?D?wjA^>M30;jw(NU*eMRUE1$nY4eRk=jbAcEnOkQ6DL^3Z`~FP%)_37>%)}f2BKJZ$ z57X>J#XTpdy2nrU4_v!;_Vhy{Zh%nxtNZK%iv9}7174Pq^EP|4aHafUT20Q=%;A#d z95>{*@vgsY!W*bPWxO31SVMOxOa)HB-1h!JZGepf5>VnI%78T2Bi@7L3wYeo2r-^c zuN62iO(J(-a zN9Xa3F4=^&)+38sL>hOe^OpQ%_Md*A_Y8svY@p~~(p(+CEA|PL?Ocv2yF^MpF?Xc) zZnBn=0!X)$kZokW771|(v0dPvnU)gckYw3L(UnPGJNxGQ1I_all_Dfts1EDd+C$5l zbG}`m4Tq%uihQ5iox!m@svGPjl>%m=)082g(0rl{6Ba_}0^egX_fj*H=@mt6QHMru}q$eNxx|i?LF%y%bl%F~)Htc?QQlmv#7p2p-gV znUQZ~Fsh>Lk(;maNawz!-Jjdm@TTcjlDc{++wqpiPc?B^Dr);io2}oJDGcoNO1SAf z`D@X`58!rbY#Jd}PUbpES(deq+gTe}kRto%t3U zm!b8DlB2X3DiY8mSEkD!lVjbpl}e^`Iks@rxNH4B@<2`Eo~yB?8q*a}U7gXcGkK>j zpn7-XSx+H*WH|Fbo_^}K?27qFJIJrI=-b_pQ~qY$ZKtX{WYu&@!#2NE>SWbFEVCk2%a$@hAIvp&=iRbgC1W0?7DX|GTZfjMRpy3h_xtowmVdxUt925k)Wps6SJ;7A^OOnLNYkGVR@G(H zlY*b3)_2obY6=0w3kK=Zk;CXIR%rP(?MLT13z5KC12iXy!<3|0;J7H{gf=vhb&Wv7$w%=s7^x_kXRR zm@+)h(1wcF~i4tHaVK*sHxT>1})#BqYPgdJ*{0nCR$N7B}>b zW0CH3OtEyw{u9ED;PIN~@$CXLOTE5SVL6_`oj@`Ea6wPj@>_Q-r55R$7xaZ+SrOV_ ztvd2~n@&DZ`&)0SAk=_8-7SGe5d-FIsXQO^0H@)2Eg#%0+3`;F)5M&6G|ySR9<$#$ zY`8)kt_U)9Y2e60g_1Xae+-_|g}yddGmMY8q&5oUVdf;4k(+&J&X)h$iGNr-iTaqx z6tUQnld|h@eA)tMtJG0_vcAO3MWba>|ko!@+r~P1z)vUM_BsdA{WX!Q&u!7$;jJ(&ziu=jr&d7 zsP42gwH(}Ods5CkBOrZ9lh#dg46jrEO8otl>9)9Fur26gdX>niTHV+bmK^Z6_X-i{ zUd=fg@`mKe{$TKPd&-4V(<_V$;(5!s(~sBg4HHwFrF^H4W#F*{^9{k zJl>dc*)_i{l?j4l)XD)CRSQef4kMa1@bL$lQnl9JGo>@6?kfCkC8Tq)5bMT1nb{7O zRk)E9ac#1LqaslGuQ5+zvOWOf4}0-#9>h)w(oj#WPe^qLX$P-gtJ3%T;Lh;y=$z@$ zK=W^A*puH&D8*uF-oaaHul9-wd_j{nfvj9OYgbQt4t0hk(j{kBB)5g0{-VSyU)#YJ z+p=ny`#2Ifm>5=$5*A3}iggRAU)?H~0{l9)YafEvj%NIPRStNYAgzB~`+_&>h6rT! z%H)!-4%D7?YdKWW@r&cApbGDJ1rQX8E847-{hgcK$jH;*o#2EqACUYe!~I(dJJLx` zSX;th{;4Hj8s_UMz$*hrBlKCXqqi%1!mRU zJl)v@2SyA}^j(QT{|vG47co_$k>=~K5o_4Y!4oRWD8;t)bqA>}-B4Q@2)v}rioGiw z_g)_0Y5quKzB@z z5nk@xQmYlrj3yz?lrEX&@}@MauzI7407xe25PY4K0$a*hswLM_7werG+Md+ zu4Fg)qkxr1CAMOwcb*OQ)n{yshj8Lv9v@5+be}B01+{-OupHU_p+iMPLNv#Kk;C!s zW^`{rXOl9d-7DMB@(;A)dS_&RDt-_C&DcVE*%hLd(~W$%gElIDAHtFW z0~9OL`IEta#hS!8n^NrO7n>R)uuVSduybcW>}MCm9Y%qogWn}b-zX0eHgLa+qRyFCmf^)(ckreNL@{UI!3?m+dWb|fgZT~ z-yfpXmCQf>^@7B<2K;8p9_9h_lCa~Klfv<{4M>|1{NXd)jy~ZgF8H*wR&O;g z%U+%{zvSTTMHEOcskn)ilw$Edx)L15ti+`cayidcl$~gXFrGIYD+Hp(N(-vmOI2kQ zaF~4^&OZkkuxIF>9p`O3v|!kEr|)x=--`nUfDXhTX(p@H0`td#q}O5l%Lunpj2!$& z8vu6Ao#AqHS`Q7mnK5=F(|gryHN9-Rr(2Z;M|S8vDG4x-4-@86jLD`t_C_&M|~plt=m#2Fy3@ZuN81=>BQRoIy}9@ zyRIaPwH*{NqHRLp89dA?G-TSZf~n{}-HdPlhO(inzxJa1tP4XU$qvf7cuSEo5!P3-fd@whcpW#8x+gDkd$*YRAs7 zqx*lQO?Y}l6H%1EO&cM^xW`n4n`*itAN8sUnhk+blC)b9REcG)lhVPSJ3Yf$OUQ>J ztN)o6q=Pwk(!*Ixk$`4=G>r0%6~??I+gVNJyb*7K6}rU835eQNG8J(sdK5MbAIuHe z_Fre_8U_~KX@RmzsCX1cN&Au{4ugvB)IeDU;e{kw8R9DZD=`Y5xIQ&;UBO0t^haof z(BDV8Lhu+`Q|?B*8J~!q4!inG(l`g0lD1l`kO?l+fn&i6l#-{@6$(fQ>Mtd7h*=kMcfu9F#3k(k6t+qMa^<{xerB>pyQ}(R2oR@ Tw{M@55{>{vT{CdKHZ literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/material.svg b/aio/src/assets/images/logos/concept-icons/material.svg new file mode 100644 index 0000000000..9ac2836270 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/material.svg @@ -0,0 +1 @@ +material \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/performance.png b/aio/src/assets/images/logos/concept-icons/performance.png new file mode 100755 index 0000000000000000000000000000000000000000..01a9622b0cc6d3101a88f200acdf1339048c5de3 GIT binary patch literal 5560 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 literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/performance.svg b/aio/src/assets/images/logos/concept-icons/performance.svg new file mode 100644 index 0000000000..057e1b23cb --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/performance.svg @@ -0,0 +1 @@ +performance \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/protractor.png b/aio/src/assets/images/logos/concept-icons/protractor.png new file mode 100755 index 0000000000000000000000000000000000000000..79c32c91a30d620942480e9e3e075b3666bd3551 GIT binary patch literal 4909 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 UdNR8V{{Mn#z!9)=Wvk%-0qos|CIA2c literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/protractor.svg b/aio/src/assets/images/logos/concept-icons/protractor.svg new file mode 100644 index 0000000000..545ee9a132 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/protractor.svg @@ -0,0 +1 @@ +protractor \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/pwa.png b/aio/src/assets/images/logos/concept-icons/pwa.png new file mode 100755 index 0000000000000000000000000000000000000000..c0e42c3feeb13407329dfe4d09582be6b9240a19 GIT binary patch literal 5000 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 \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/router.png b/aio/src/assets/images/logos/concept-icons/router.png new file mode 100755 index 0000000000000000000000000000000000000000..759c500261dee7bd358a3d1a6321b9e87c6e5860 GIT binary patch literal 3546 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 WCg@294T3*wkh5p(u_frMQU3?jy5&Ux literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/router.svg b/aio/src/assets/images/logos/concept-icons/router.svg new file mode 100644 index 0000000000..a2ef8b334e --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/router.svg @@ -0,0 +1 @@ +router \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/templates.png b/aio/src/assets/images/logos/concept-icons/templates.png new file mode 100755 index 0000000000000000000000000000000000000000..ba462715959bb98921d5fc2b4d105a248934c77e GIT binary patch literal 3046 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 ROeEZaV>Ty<71rKy{{<&VfTjQd literal 0 HcmV?d00001 diff --git a/aio/src/assets/images/logos/concept-icons/templates.svg b/aio/src/assets/images/logos/concept-icons/templates.svg new file mode 100644 index 0000000000..fe789d48d6 --- /dev/null +++ b/aio/src/assets/images/logos/concept-icons/templates.svg @@ -0,0 +1 @@ +templates \ No newline at end of file diff --git a/aio/src/assets/images/logos/concept-icons/universal.png b/aio/src/assets/images/logos/concept-icons/universal.png new file mode 100755 index 0000000000000000000000000000000000000000..7450d9720eb31e6e7017b48d7183bcc357a70df6 GIT binary patch literal 3717 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 \ No newline at end of file diff --git a/aio/src/styles/2-modules/_presskit.scss b/aio/src/styles/2-modules/_presskit.scss index 75aeea1097..88921249ce 100644 --- a/aio/src/styles/2-modules/_presskit.scss +++ b/aio/src/styles/2-modules/_presskit.scss @@ -67,8 +67,8 @@ } img { - width: 128px; height: 128px; + width: auto; margin-bottom: 8px * 2; } } From 6a24c02d73bf303a5db09b84c6501f0183b970e3 Mon Sep 17 00:00:00 2001 From: Stephen Fluin Date: Mon, 19 Mar 2018 09:27:24 -0700 Subject: [PATCH 060/582] docs(aio): Remove Intertech with no courses scheduled (#22867) PR Close #22867 --- aio/content/marketing/resources.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index fcb84a5d9a..aa8e079662 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -597,12 +597,6 @@ "Workshops & Onsite Training": { "order": 2, "resources": { - "-KLIBo3ANF3-1B9wxsoB": { - "desc": "Angular Classes from Intertech in Minnesota", - "rev": true, - "title": "Intertech", - "url": "http://www.intertech.com/Training/Web-Dev/AngularJS/AngularJS/Angular-2-Training" - }, "-KLIBoFWStce29UCwkvY": { "desc": "Private Angular Training and Mentoring", "rev": true, From 4042a84ad6b96678c6c7660dd180e3b66b4cad00 Mon Sep 17 00:00:00 2001 From: davidstanke Date: Thu, 22 Mar 2018 16:26:29 -0400 Subject: [PATCH 061/582] docs(bazel): add a link to the Bazel doc (#22940) The developer doc mentions but doesn't link to BAZEL.md. Add link and fix capitalization. PR Close #22940 --- docs/DEVELOPER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index e0120e27c4..395c620e9f 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -29,7 +29,7 @@ following products on your development machine: * [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used to execute the selenium standalone server for e2e testing. -* (Optional for now) [Bazel](https://bazel.build/), please follow instructions in [Bazel.md] +* (Optional for now) [Bazel](https://bazel.build/), please follow instructions in [BAZEL.md](https://github.com/angular/angular/blob/master/docs/BAZEL.md) ## Getting the Sources From 091b11a4ab1ebfd816bb476e7ef50d05d566237b Mon Sep 17 00:00:00 2001 From: Jonathan Sharpe Date: Sat, 17 Mar 2018 12:50:26 +0000 Subject: [PATCH 062/582] docs(aio): update HTTP error test example (#22844) Update the example to match the description preceding it, which refers to the use of the error method and ErrorEvent rather than the flush method with a non-2xx status as shown previously. PR Close #22844 --- aio/content/guide/http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/http.md b/aio/content/guide/http.md index 10dd3b6d43..0c6d903f31 100644 --- a/aio/content/guide/http.md +++ b/aio/content/guide/http.md @@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example. From 547efb5f4d5b115c38173b288d2b25b67d30dac1 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sat, 12 May 2018 01:40:41 +0900 Subject: [PATCH 063/582] docs(aio): fix path to observables guide (#23858) PR Close #23858 --- aio/content/guide/glossary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/glossary.md b/aio/content/guide/glossary.md index 93a93d32ae..0bf407fccc 100644 --- a/aio/content/guide/glossary.md +++ b/aio/content/guide/glossary.md @@ -439,7 +439,7 @@ Observables can deliver single or multiple values of any type to subscribers, ei Angular uses a third-party library called [Reactive Extensions (RxJS)](http://reactivex.io/rxjs/). -To learn more, see the [Observables](guide/glossary#observable) guide. +To learn more, see the [Observables](guide/observables) guide. {@a observer} From 55103419e9363a09be37827152c0996e8ffaa287 Mon Sep 17 00:00:00 2001 From: Alexander Burakevych Date: Thu, 22 Mar 2018 22:16:07 +1100 Subject: [PATCH 064/582] docs(aio): add Angular Conf Australia to events (#22929) Angular Conf Australia 2018 will be held at June 22 in Melbourne, Australia! https://www.angularconf.com.au/ PR Close #22929 --- aio/content/marketing/events.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aio/content/marketing/events.html b/aio/content/marketing/events.html index 60bd069a16..7948a31104 100755 --- a/aio/content/marketing/events.html +++ b/aio/content/marketing/events.html @@ -55,6 +55,12 @@
Tokyo, Japan Jun 16, 2018
Angular Conf AustraliaMelbourne, AustraliaJun 22, 2018
AngularConnect
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VersionStatusRelease DateLTS Start DateLTS End Date
^4.0.0LTSMarch 23, 2017September 23, 2017September 23, 2018
^5.0.0LTSNovember 1, 2017May 1, 2018May 1, 2019
^6.0.0ActiveMay 3, 2018November 3, 2018November 3, 2019
@@ -127,8 +163,6 @@ Any changes to the public API surface will be done using the versioning, support {@a labs} ## Angular Labs -Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team. +Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team. -Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects. - -Angular Labs projects typically are in separate branches in the Angular repo, clearly separated from the main Angular codebase. +Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects. From 1eafd04eb33698b48fc3e9f2bfbfefe7d8005f92 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 21 May 2018 15:48:00 -0700 Subject: [PATCH 115/582] build(ivy): support alternate compilation modes to enable Ivy testing (#24056) Bazel has a restriction that a single output (eg. a compiled version of //packages/common) can only be produced by a single rule. This precludes the Angular repo from having multiple rules that build the same code. And the complexity of having a single rule produce multiple outputs (eg. an ngc-compiled version of //packages/common and an Ivy-enabled version) is too high. Additionally, the Angular repo has lots of existing tests which could be executed as-is under Ivy. Such testing is very valuable, and it would be nice to share not only the code, but the dependency graph / build config as well. Thus, this change introduces a --define flag 'compile' with three potential values. When --define=compile=X is set, the entire build system runs in a particular mode - the behavior of all existing targets is controlled by the flag. This allows us to reuse our entire build structure for testing in a variety of different manners. The flag has three possible settings: * legacy (the default): the traditional View Engine (ngc) build * local: runs the prototype ngtsc compiler, which does not rely on global analysis * jit: runs ngtsc in a mode which executes tsickle, but excludes the Angular related transforms, which approximates the behavior of plain tsc. This allows the main packages such as common to be tested with the JIT compiler. Additionally, the ivy_ng_module() rule still exists and runs ngc in a mode where Ivy-compiled output is produced from global analysis information, as a stopgap while ngtsc is being developed. PR Close #24056 --- .circleci/config.yml | 2 +- packages/bazel/src/ng_module.bzl | 158 ++++++++++++++---- packages/bazel/src/ng_rollup_bundle.bzl | 57 +++++++ packages/bazel/src/ngc-wrapped/index.ts | 15 ++ .../injector_def/ivy_build/app/BUILD.bazel | 2 +- packages/compiler-cli/src/main.ts | 4 +- packages/compiler-cli/src/transformers/api.ts | 10 +- .../src/transformers/compiler_host.ts | 2 +- .../compiler-cli/src/transformers/program.ts | 41 ++++- .../src/transformers/tsc_pass_through.ts | 107 ++++++++++++ packages/compiler/src/aot/compiler_options.ts | 2 +- packages/core/BUILD.bazel | 13 +- packages/core/src/ivy_switch.ts | 2 +- packages/core/src/ivy_switch_jit.ts | 9 + ...y_switch_false.ts => ivy_switch_legacy.ts} | 0 packages/core/src/ivy_switch_local.ts | 9 + .../{ivy_switch_true.ts => ivy_switch_on.ts} | 0 .../test/bundling/hello_world/BUILD.bazel | 5 +- .../bundling/hello_world/treeshaking_spec.ts | 3 +- .../test/bundling/hello_world_jit/BUILD.bazel | 58 ------- .../test/bundling/hello_world_jit/index.html | 31 ---- .../test/bundling/hello_world_jit/index.ts | 38 ----- .../hello_world_jit/integration_spec.ts | 23 --- packages/core/test/bundling/todo/BUILD.bazel | 2 +- tools/bazel.rc | 6 +- tools/defaults.bzl | 17 +- 26 files changed, 399 insertions(+), 217 deletions(-) create mode 100644 packages/compiler-cli/src/transformers/tsc_pass_through.ts create mode 100644 packages/core/src/ivy_switch_jit.ts rename packages/core/src/{ivy_switch_false.ts => ivy_switch_legacy.ts} (100%) create mode 100644 packages/core/src/ivy_switch_local.ts rename packages/core/src/{ivy_switch_true.ts => ivy_switch_on.ts} (100%) delete mode 100644 packages/core/test/bundling/hello_world_jit/BUILD.bazel delete mode 100644 packages/core/test/bundling/hello_world_jit/index.html delete mode 100644 packages/core/test/bundling/hello_world_jit/index.ts delete mode 100644 packages/core/test/bundling/hello_world_jit/integration_spec.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index c49a2cc944..4aabb53b68 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,7 +85,7 @@ jobs: # This avoids waiting for the slowest build target to finish before running the first test # See https://github.com/bazelbuild/bazel/issues/4257 # NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/... - - run: bazel query --output=label //... | xargs bazel test + - run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only # CircleCI will allow us to go back and view/download these artifacts from past builds. # Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts. diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index afeeccc845..0daa736273 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -14,6 +14,90 @@ load(":rules_typescript.bzl", "ts_providers_dict_to_struct", ) +def _compile_strategy(ctx): + """Detect which strategy should be used to implement ng_module. + + Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module + can be implemented in various ways. This function reads the configuration passed by the user and + determines which mode is active. + + Args: + ctx: skylark rule execution context + + Returns: + one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx + """ + + strategy = 'legacy' + if 'compile' in ctx.var: + strategy = ctx.var['compile'] + + if strategy not in ['legacy', 'local', 'jit']: + fail("Unknown --define=compile value '%s'" % strategy) + + if strategy == 'legacy' and hasattr(ctx.attr, '_global_mode') and ctx.attr._global_mode: + strategy = 'global' + + return strategy + +def _compiler_name(ctx): + """Selects a user-visible name depending on the current compilation strategy. + + Args: + ctx: skylark rule execution context + + Returns: + the name of the current compiler to be displayed in build output + """ + + strategy = _compile_strategy(ctx) + if strategy == 'legacy': + return 'ngc' + elif strategy == 'global': + return 'ngc.ivy' + elif strategy == 'local': + return 'ngtsc' + elif strategy == 'jit': + return 'tsc' + else: + fail('unreachable') + +def _enable_ivy_value(ctx): + """Determines the value of the enableIvy option in the generated tsconfig. + + Args: + ctx: skylark rule execution context + + Returns: + the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig + """ + + strategy = _compile_strategy(ctx) + if strategy == 'legacy': + return False + elif strategy == 'global': + return True + elif strategy == 'local': + return 'ngtsc' + elif strategy == 'jit': + return 'tsc' + else: + fail('unreachable') + +def _include_ng_files(ctx): + """Determines whether Angular outputs will be produced by the current compilation strategy. + + Args: + ctx: skylark rule execution context + + Returns: + true iff the current compilation strategy will produce View Engine compilation outputs (such as + factory files), false otherwise + """ + + strategy = _compile_strategy(ctx) + return strategy == 'legacy' or strategy == 'global' + def _basename_of(ctx, file): ext_len = len(".ts") if file.short_path.endswith(".ng.html"): @@ -61,6 +145,8 @@ def _should_produce_flat_module_outs(ctx): # in the library. Most of these will be produced as empty files but it is # unknown, without parsing, which will be empty. def _expected_outs(ctx): + include_ng_files = _include_ng_files(ctx) + devmode_js_files = [] closure_js_files = [] declaration_files = [] @@ -78,7 +164,7 @@ def _expected_outs(ctx): if short_path.endswith(".ts") and not short_path.endswith(".d.ts"): basename = short_path[len(package_prefix):-len(".ts")] - if len(factory_basename_set) == 0 or basename in factory_basename_set: + if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set): devmode_js = [ ".ngfactory.js", ".ngsummary.js", @@ -90,7 +176,7 @@ def _expected_outs(ctx): devmode_js = [".js"] summaries = [] metadata = [] - elif short_path.endswith(".css"): + elif include_ng_files and short_path.endswith(".css"): basename = short_path[len(package_prefix):-len(".css")] devmode_js = [ ".css.shim.ngstyle.js", @@ -113,7 +199,7 @@ def _expected_outs(ctx): metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata] # We do this just when producing a flat module index for a publishable ng_module - if _should_produce_flat_module_outs(ctx): + if include_ng_files and _should_produce_flat_module_outs(ctx): flat_module_out = _flat_module_out_file(ctx) devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out)) closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out)) @@ -123,7 +209,12 @@ def _expected_outs(ctx): else: bundle_index_typings = None - i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")] + # TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled + # when ngtsc can extract messages + if include_ng_files: + i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")] + else: + i18n_messages_files = [] return struct( closure_js = closure_js_files, @@ -135,14 +226,9 @@ def _expected_outs(ctx): i18n_messages = i18n_messages_files, ) -def _ivy_tsconfig(ctx, files, srcs, **kwargs): - return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs) - def _ngc_tsconfig(ctx, files, srcs, **kwargs): - return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs) - -def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs): outs = _expected_outs(ctx) + include_ng_files = _include_ng_files(ctx) if "devmode_manifest" in kwargs: expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata else: @@ -152,8 +238,9 @@ def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs): "enableResourceInlining": ctx.attr.inline_resources, "generateCodeForLibraries": False, "allowEmptyCodegenFiles": True, - "enableSummariesForJit": True, - "enableIvy": enable_ivy, + # Summaries are only enabled if Angular outputs are to be produced. + "enableSummariesForJit": include_ng_files, + "enableIvy": _enable_ivy_value(ctx), "fullTemplateTypeCheck": ctx.attr.type_check, # FIXME: wrong place to de-dupe "expectedOut": depset([o.path for o in expected_outs]).to_list() @@ -216,8 +303,10 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file, the parameters of the compilation which will be used to replay the ngc action for i18N. """ + include_ng_files = _include_ng_files(ctx) + mnemonic = "AngularTemplateCompile" - progress_message = "Compiling Angular templates (ngc) %s" % label + progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label) if locale: mnemonic = "AngularI18NMerging" @@ -251,7 +340,7 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file, }, ) - if messages_out != None: + if include_ng_files and messages_out != None: ctx.actions.run( inputs = list(inputs), outputs = messages_out, @@ -313,7 +402,7 @@ def _ts_expected_outs(ctx, label): _ignored = [label] return _expected_outs(ctx) -def ng_module_impl(ctx, ts_compile_actions, ivy = False): +def ng_module_impl(ctx, ts_compile_actions): """Implementation function for the ng_module rule. This is exposed so that google3 can have its own entry point that re-uses this @@ -322,29 +411,30 @@ def ng_module_impl(ctx, ts_compile_actions, ivy = False): Args: ctx: the skylark rule context ts_compile_actions: generates all the actions to run an ngc compilation - ivy: if True, run the compiler in Ivy mode (internal only) Returns: the result of the ng_module rule as a dict, suitable for conversion by ts_providers_dict_to_struct """ - tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig + include_ng_files = _include_ng_files(ctx) providers = ts_compile_actions( ctx, is_library=True, compile_action=_prodmode_compile_action, devmode_compile_action=_devmode_compile_action, - tsc_wrapped_tsconfig=tsconfig, + tsc_wrapped_tsconfig=_ngc_tsconfig, outputs = _ts_expected_outs) outs = _expected_outs(ctx) - providers["angular"] = { - "summaries": outs.summaries, - "metadata": outs.metadata - } - providers["ngc_messages"] = outs.i18n_messages - if _should_produce_flat_module_outs(ctx): + if include_ng_files: + providers["angular"] = { + "summaries": outs.summaries, + "metadata": outs.metadata + } + providers["ngc_messages"] = outs.i18n_messages + + if include_ng_files and _should_produce_flat_module_outs(ctx): if len(outs.metadata) > 1: fail("expecting exactly one metadata output for " + str(ctx.label)) @@ -360,9 +450,6 @@ def ng_module_impl(ctx, ts_compile_actions, ivy = False): def _ng_module_impl(ctx): return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts)) -def _ivy_module_impl(ctx): - return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True)) - NG_MODULE_ATTRIBUTES = { "srcs": attr.label_list(allow_files = [".ts"]), @@ -429,11 +516,16 @@ ng_module = rule( outputs = COMMON_OUTPUTS, ) -# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular, -# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be -# removed and its usages refactored to use ng_module() directly. -internal_ivy_ng_module = rule( - implementation = _ivy_module_impl, - attrs = NG_MODULE_RULE_ATTRS, + +# TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information. +# It to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used instead, and +# should be removed once ngtsc is capable of fulfilling the same requirements. +internal_global_ng_module = rule( + implementation = _ng_module_impl, + attrs = dict(NG_MODULE_RULE_ATTRS, **{ + "_global_mode": attr.bool( + default = True, + ), + }), outputs = COMMON_OUTPUTS, ) diff --git a/packages/bazel/src/ng_rollup_bundle.bzl b/packages/bazel/src/ng_rollup_bundle.bzl index 8a4b375d53..e4584d6133 100644 --- a/packages/bazel/src/ng_rollup_bundle.bzl +++ b/packages/bazel/src/ng_rollup_bundle.bzl @@ -27,6 +27,29 @@ PLUGIN_CONFIG="{sideEffectFreeModules: [\n%s]}" % ",\n".join( BO_ROLLUP="angular_devkit/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js" BO_PLUGIN="require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG) +def _use_plain_rollup(ctx): + """Determine whether to use the Angular or upstream versions of the rollup_bundle rule. + + In legacy mode, the Angular version of rollup is used. This runs build optimizer as part of its + processing, which affects decorators and annotations. + + In other modes, an emulation of the upstream rollup_bundle rule is used. This avoids running + build optimizer on code which isn't designed to be optimized by it. + + Args: + ctx: skylark rule execution context + + Returns: + true iff the Angular version of rollup with build optimizer should be used, false otherwise + """ + + if 'compile' not in ctx.var: + return False + + strategy = ctx.var['compile'] + return strategy != 'legacy' + + def run_brotli(ctx, input, output): ctx.actions.run( executable = ctx.executable._brotli, @@ -35,7 +58,41 @@ def run_brotli(ctx, input, output): arguments = ["--output=%s" % output.path, input.path], ) +# Borrowed from bazelbuild/rules_nodejs +def _run_tsc(ctx, input, output): + args = ctx.actions.args() + args.add(["--target", "es5"]) + args.add("--allowJS") + args.add(input.path) + args.add(["--outFile", output.path]) + + ctx.action( + executable = ctx.executable._tsc, + inputs = [input], + outputs = [output], + arguments = [args] + ) + +# Borrowed from bazelbuild/rules_nodejs, with the addition of brotli compression output +def _plain_rollup_bundle(ctx): + rollup_config = write_rollup_config(ctx) + run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es6) + _run_tsc(ctx, ctx.outputs.build_es6, ctx.outputs.build_es5) + source_map = run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min) + run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True) + umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd") + run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd) + run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html) + + run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed) + files = [ctx.outputs.build_es5_min, source_map] + return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files)) + def _ng_rollup_bundle(ctx): + # Escape and use the plain rollup rule if the compilation strategy requires it + if _use_plain_rollup(ctx): + return _plain_rollup_bundle(ctx) + # We don't expect anyone to make use of this bundle yet, but it makes this rule # compatible with rollup_bundle which allows them to be easily swapped back and # forth. diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 02b9b14b04..a787b4026c 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -119,6 +119,15 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, compilerOpts.annotationsAs = 'static fields'; } + // Detect from compilerOpts whether the entrypoint is being invoked in Ivy mode. + const isInIvyMode = compilerOpts.enableIvy === 'ngtsc' || compilerOpts.enableIvy === 'tsc'; + + // Disable downleveling and Closure annotation if in Ivy mode. + if (isInIvyMode) { + compilerOpts.annotateForClosureCompiler = false; + compilerOpts.annotationsAs = 'decorators'; + } + if (!compilerOpts.rootDirs) { throw new Error('rootDirs is not set!'); } @@ -172,6 +181,12 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, const bazelHost = new CompilerHost( files, compilerOpts, bazelOpts, tsHost, fileLoader, allowNonHermeticReads, generatedFileModuleResolver); + + // Also need to disable decorator downleveling in the BazelHost in Ivy mode. + if (isInIvyMode) { + bazelHost.transformDecorators = false; + } + // Prevent tsickle adding any types at all if we don't want closure compiler annotations. bazelHost.transformTypesToClosure = compilerOpts.annotateForClosureCompiler; const origBazelHostFileExist = bazelHost.fileExists; diff --git a/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/BUILD.bazel b/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/BUILD.bazel index 8cbf1b54a4..15a6b28c75 100644 --- a/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/BUILD.bazel +++ b/packages/compiler-cli/integrationtest/bazel/injector_def/ivy_build/app/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ivy_ng_module", "ts_library") +load("//tools:defaults.bzl", "ivy_ng_module") load("//packages/bazel/src:ng_rollup_bundle.bzl", "ng_rollup_bundle") ivy_ng_module( diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 25dcd54281..1e17fc9d62 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -40,8 +40,8 @@ export function main( function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined { - const transformDecorators = - options.enableIvy !== 'ngtsc' && options.annotationsAs !== 'decorators'; + const transformDecorators = options.enableIvy !== 'ngtsc' && options.enableIvy !== 'tsc' && + options.annotationsAs !== 'decorators'; const transformTypesToClosure = options.annotateForClosureCompiler; if (!transformDecorators && !transformTypesToClosure) { return undefined; diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index ce1cd4e233..b37033443d 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -182,9 +182,17 @@ export interface CompilerOptions extends ts.CompilerOptions { * Not all features are supported with this option enabled. It is only supported * for experimentation and testing of Render3 style code generation. * + * Acceptable values are as follows: + * + * `false` - run ngc normally + * `true` - run ngc with its usual global analysis, but compile decorators to Ivy fields instead + * of running the View Engine compilers + * `ngtsc` - run the ngtsc compiler instead of the normal ngc compiler + * `tsc` - behave like plain tsc as much as possible (used for testing JIT code) + * * @experimental */ - enableIvy?: boolean|'ngtsc'; + enableIvy?: boolean|'ngtsc'|'tsc'; /** @internal */ collectAllErrors?: boolean; diff --git a/packages/compiler-cli/src/transformers/compiler_host.ts b/packages/compiler-cli/src/transformers/compiler_host.ts index 972313ddd8..a91d056320 100644 --- a/packages/compiler-cli/src/transformers/compiler_host.ts +++ b/packages/compiler-cli/src/transformers/compiler_host.ts @@ -24,7 +24,7 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; export function createCompilerHost( {options, tsHost = ts.createCompilerHost(options, true)}: {options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost { - if (options.enableIvy) { + if (options.enableIvy === 'ngtsc' || options.enableIvy === 'tsc') { return new NgtscCompilerHost(tsHost); } return tsHost; diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 78764efca3..5b9f4c67fc 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -26,6 +26,7 @@ import {getAngularEmitterTransformFactory} from './node_emitter_transform'; import {PartialModuleMetadataTransformer} from './r3_metadata_transform'; import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators'; import {getAngularClassTransformerFactory} from './r3_transform'; +import {TscPassThroughProgram} from './tsc_pass_through'; import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util'; @@ -287,7 +288,7 @@ class AngularCompilerProgram implements Program { emitCallback?: TsEmitCallback, mergeEmitResultsCallback?: TsMergeEmitResultsCallback, } = {}): ts.EmitResult { - if (this.options.enableIvy === 'ngtsc') { + if (this.options.enableIvy === 'ngtsc' || this.options.enableIvy === 'tsc') { throw new Error('Cannot run legacy compiler in ngtsc mode'); } return this.options.enableIvy === true ? this._emitRender3(parameters) : @@ -337,14 +338,34 @@ class AngularCompilerProgram implements Program { /* genFiles */ undefined, /* partialModules */ modules, /* stripDecorators */ this.reifiedDecorators, customTransformers); - const emitResult = emitCallback({ - program: this.tsProgram, - host: this.host, - options: this.options, - writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTransformers - }); - return emitResult; + + // Restore the original references before we emit so TypeScript doesn't emit + // a reference to the .d.ts file. + const augmentedReferences = new Map>(); + for (const sourceFile of this.tsProgram.getSourceFiles()) { + const originalReferences = getOriginalReferences(sourceFile); + if (originalReferences) { + augmentedReferences.set(sourceFile, sourceFile.referencedFiles); + sourceFile.referencedFiles = originalReferences; + } + } + + try { + return emitCallback({ + program: this.tsProgram, + host: this.host, + options: this.options, + writeFile: writeTsFile, emitOnlyDtsFiles, + customTransformers: tsCustomTransformers + }); + } finally { + // Restore the references back to the augmented value to ensure that the + // checks that TypeScript makes for project structure reuse will succeed. + for (const [sourceFile, references] of Array.from(augmentedReferences)) { + // TODO(chuckj): Remove any cast after updating build to 2.6 + (sourceFile as any).referencedFiles = references; + } + } } private _emitRender2( @@ -909,6 +930,8 @@ export function createProgram({rootNames, options, host, oldProgram}: { }): Program { if (options.enableIvy === 'ngtsc') { return new NgtscProgram(rootNames, options, host, oldProgram); + } else if (options.enableIvy === 'tsc') { + return new TscPassThroughProgram(rootNames, options, host, oldProgram); } return new AngularCompilerProgram(rootNames, options, host, oldProgram); } diff --git a/packages/compiler-cli/src/transformers/tsc_pass_through.ts b/packages/compiler-cli/src/transformers/tsc_pass_through.ts new file mode 100644 index 0000000000..fde85062b4 --- /dev/null +++ b/packages/compiler-cli/src/transformers/tsc_pass_through.ts @@ -0,0 +1,107 @@ +/** + * @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 {GeneratedFile} from '@angular/compiler'; +import * as path from 'path'; +import * as ts from 'typescript'; + +import * as api from '../transformers/api'; + +/** + * An implementation of the `Program` API which behaves like plain `tsc` and does not include any + * Angular-specific behavior whatsoever. + * + * This allows `ngc` to behave like `tsc` in cases where JIT code needs to be tested. + */ +export class TscPassThroughProgram implements api.Program { + private tsProgram: ts.Program; + + constructor( + rootNames: ReadonlyArray, private options: api.CompilerOptions, + private host: api.CompilerHost, oldProgram?: api.Program) { + this.tsProgram = + ts.createProgram(rootNames, options, host, oldProgram && oldProgram.getTsProgram()); + } + + getTsProgram(): ts.Program { return this.tsProgram; } + + getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken| + undefined): ReadonlyArray { + return this.tsProgram.getOptionsDiagnostics(cancellationToken); + } + + getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken| + undefined): ReadonlyArray { + return []; + } + + getTsSyntacticDiagnostics( + sourceFile?: ts.SourceFile|undefined, + cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray { + return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken); + } + + getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken| + undefined): ReadonlyArray { + return []; + } + + getTsSemanticDiagnostics( + sourceFile?: ts.SourceFile|undefined, + cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray { + return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken); + } + + getNgSemanticDiagnostics( + fileName?: string|undefined, + cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray { + return []; + } + + loadNgStructureAsync(): Promise { return Promise.resolve(); } + + listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] { + throw new Error('Method not implemented.'); + } + + getLibrarySummaries(): Map { + throw new Error('Method not implemented.'); + } + + getEmittedGeneratedFiles(): Map { + throw new Error('Method not implemented.'); + } + + getEmittedSourceFiles(): Map { + throw new Error('Method not implemented.'); + } + + emit(opts?: { + emitFlags?: api.EmitFlags, + cancellationToken?: ts.CancellationToken, + customTransformers?: api.CustomTransformers, + emitCallback?: api.TsEmitCallback, + mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback + }): ts.EmitResult { + const emitCallback = opts && opts.emitCallback || defaultEmitCallback; + + const emitResult = emitCallback({ + program: this.tsProgram, + host: this.host, + options: this.options, + emitOnlyDtsFiles: false, + }); + return emitResult; + } +} + +const defaultEmitCallback: api.TsEmitCallback = + ({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, + customTransformers}) => + program.emit( + targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); diff --git a/packages/compiler/src/aot/compiler_options.ts b/packages/compiler/src/aot/compiler_options.ts index 10f87a333c..e4d7d1682e 100644 --- a/packages/compiler/src/aot/compiler_options.ts +++ b/packages/compiler/src/aot/compiler_options.ts @@ -18,5 +18,5 @@ export interface AotCompilerOptions { fullTemplateTypeCheck?: boolean; allowEmptyCodegenFiles?: boolean; strictInjectionParameters?: boolean; - enableIvy?: boolean|'ngtsc'; + enableIvy?: boolean|'ngtsc'|'tsc'; } diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 68a4cea600..6da82e9b34 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -18,6 +18,7 @@ ng_module( "//packages:types", "//packages/compiler", "@rxjs", + "@rxjs//operators", ], ) @@ -38,16 +39,18 @@ ng_package( ## Controls if Ivy is enabled. (Temporary target until we permanently switch over to Ivy) ## ## This file generates `src/ivy_switch.ts` file which reexports symbols for `ViewEngine` or `Ivy.` -## - append `--define=ivy=false` (default) to `bazel` command to reexport `./ivy_switch_false` -## and use `ViewEngine`; -## - append `--define=ivy=true` to `bazel` command to rexport `./ivy_switch_true` and use `Ivy`; +## - append `--define=compile=legacy` (default) to `bazel` command to reexport `./ivy_switch_legacy` +## and use `ViewEngine` +## - append `--define=compile=jit` to `bazel` command to rexport `./ivy_switch_jit` and use `Ivy` +## - append `--define=compile=local` to `bazel` command to rexport `./ivy_switch_jit` and use `Ivy` +## in the local analysis mode. (run as part of `ngtsc`) ## -## NOTE: `--define=ivy=true` works with any `bazel` command or target across the repo. +## NOTE: `--define=compile=jit` works with any `bazel` command or target across the repo. ## ## See: `//tools/bazel.rc` where `--define=ivy=false` is defined as default. ## See: `./src/ivy_switch.ts` for more details. genrule( name = "ivy_switch", outs = ["src/ivy_switch.ts"], - cmd = "echo export '*' from \"'./ivy_switch_$(ivy)';\" > $@", + cmd = "echo export '*' from \"'./ivy_switch_$(compile)';\" > $@", ) diff --git a/packages/core/src/ivy_switch.ts b/packages/core/src/ivy_switch.ts index e461fc900a..6a03ff8c7f 100644 --- a/packages/core/src/ivy_switch.ts +++ b/packages/core/src/ivy_switch.ts @@ -31,6 +31,6 @@ * 3) Import the symbol from `./ivy_switch`. The imported symbol will that point to either the * symbol in `./ivy_switch_false` and `./ivy_switch_false` depending on the compilation mode. */ -export * from './ivy_switch_false'; +export * from './ivy_switch_legacy'; // TODO(alxhub): debug why metadata doesn't properly propagate through this file. diff --git a/packages/core/src/ivy_switch_jit.ts b/packages/core/src/ivy_switch_jit.ts new file mode 100644 index 0000000000..60cf825342 --- /dev/null +++ b/packages/core/src/ivy_switch_jit.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export * from './ivy_switch_on'; \ No newline at end of file diff --git a/packages/core/src/ivy_switch_false.ts b/packages/core/src/ivy_switch_legacy.ts similarity index 100% rename from packages/core/src/ivy_switch_false.ts rename to packages/core/src/ivy_switch_legacy.ts diff --git a/packages/core/src/ivy_switch_local.ts b/packages/core/src/ivy_switch_local.ts new file mode 100644 index 0000000000..60cf825342 --- /dev/null +++ b/packages/core/src/ivy_switch_local.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export * from './ivy_switch_on'; \ No newline at end of file diff --git a/packages/core/src/ivy_switch_true.ts b/packages/core/src/ivy_switch_on.ts similarity index 100% rename from packages/core/src/ivy_switch_true.ts rename to packages/core/src/ivy_switch_on.ts diff --git a/packages/core/test/bundling/hello_world/BUILD.bazel b/packages/core/test/bundling/hello_world/BUILD.bazel index afe5cbd8a9..66549bedb6 100644 --- a/packages/core/test/bundling/hello_world/BUILD.bazel +++ b/packages/core/test/bundling/hello_world/BUILD.bazel @@ -44,9 +44,12 @@ jasmine_node_test( data = [ ":bundle", ":bundle.js", - ":bundle.min.js.br", + ":bundle.min.js", ":bundle.min_debug.js", ], + tags = [ + "ivy-jit", + ], deps = [":test_lib"], ) diff --git a/packages/core/test/bundling/hello_world/treeshaking_spec.ts b/packages/core/test/bundling/hello_world/treeshaking_spec.ts index fc7e758315..90b309c27b 100644 --- a/packages/core/test/bundling/hello_world/treeshaking_spec.ts +++ b/packages/core/test/bundling/hello_world/treeshaking_spec.ts @@ -29,7 +29,8 @@ describe('treeshaking with uglify', () => { expect(content).not.toContain('createCommonjsModule'); }); - it('should not contain zone.js', () => { expect(content).not.toContain('scheduleMicroTask'); }); + it('should not contain zone.js', + () => { expect(content).not.toContain('global[\'Zone\'] = Zone'); }); describe('functional test in domino', () => { it('should render hello world when not minified', diff --git a/packages/core/test/bundling/hello_world_jit/BUILD.bazel b/packages/core/test/bundling/hello_world_jit/BUILD.bazel deleted file mode 100644 index bd6acab2b3..0000000000 --- a/packages/core/test/bundling/hello_world_jit/BUILD.bazel +++ /dev/null @@ -1,58 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("//tools:defaults.bzl", "ts_library", "ivy_ng_module") -load("//tools/symbol-extractor:index.bzl", "js_expected_symbol_test") -load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test", "rollup_bundle") -load("//tools/http-server:http_server.bzl", "http_server") - -ts_library( - name = "hello_world_jit", - srcs = ["index.ts"], - deps = [ - "//packages/core", - ], -) - -rollup_bundle( - name = "bundle", - # TODO(alexeagle): This is inconsistent. - # We try to teach users to always have their workspace at the start of a - # path, to disambiguate from other workspaces. - # Here, the rule implementation is looking in an execroot where the layout - # has an "external" directory for external dependencies. - # This should probably start with "angular/" and let the rule deal with it. - entry_point = "packages/core/test/bundling/hello_world_jit/index.js", - deps = [ - ":hello_world_jit", - "//packages/core", - ], -) - -ts_library( - name = "test_lib", - testonly = 1, - srcs = glob(["*_spec.ts"]), - deps = [ - "//packages:types", - "//packages/core", - "//packages/core/testing", - ], -) - -jasmine_node_test( - name = "test", - data = [ - ":bundle", - ":bundle.js", - ], - deps = [":test_lib"], -) - -http_server( - name = "devserver", - data = [ - "index.html", - ":bundle.min.js", - ":bundle.min_debug.js", - ], -) diff --git a/packages/core/test/bundling/hello_world_jit/index.html b/packages/core/test/bundling/hello_world_jit/index.html deleted file mode 100644 index c5c7bb3e0b..0000000000 --- a/packages/core/test/bundling/hello_world_jit/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Angular Hello World Example - - - - - - - - - \ No newline at end of file diff --git a/packages/core/test/bundling/hello_world_jit/index.ts b/packages/core/test/bundling/hello_world_jit/index.ts deleted file mode 100644 index af6817a05e..0000000000 --- a/packages/core/test/bundling/hello_world_jit/index.ts +++ /dev/null @@ -1,38 +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 'reflect-metadata'; - -import {Component, NgModule, ɵrenderComponent as renderComponent} from '@angular/core'; - -@Component({ - selector: 'greeting-cmp', - template: 'Hello World!', -}) -export class Greeting { -} - -@NgModule({ - declarations: [Greeting], - exports: [Greeting], -}) -export class GreetingModule { -} - -@Component({selector: 'hello-world', template: ''}) -export class HelloWorld { -} - -@NgModule({ - declarations: [HelloWorld], - imports: [GreetingModule], -}) -export class HelloWorldModule { -} - -renderComponent(HelloWorld); diff --git a/packages/core/test/bundling/hello_world_jit/integration_spec.ts b/packages/core/test/bundling/hello_world_jit/integration_spec.ts deleted file mode 100644 index ee349da0a6..0000000000 --- a/packages/core/test/bundling/hello_world_jit/integration_spec.ts +++ /dev/null @@ -1,23 +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 {ɵivyEnabled as ivyEnabled} from '@angular/core'; -import {withBody} from '@angular/core/testing'; -import * as fs from 'fs'; -import * as path from 'path'; - -const PACKAGE = 'angular/packages/core/test/bundling/hello_world_jit'; - -ivyEnabled && describe('Ivy JIT hello world', () => { - it('should render hello world', withBody('', () => { - require(path.join(PACKAGE, 'bundle.js')); - expect(document.body.textContent).toEqual('Hello World!'); - })); -}); - -xit('ensure at least one spec exists', () => {}); diff --git a/packages/core/test/bundling/todo/BUILD.bazel b/packages/core/test/bundling/todo/BUILD.bazel index c979e1c49e..f8d5684cbb 100644 --- a/packages/core/test/bundling/todo/BUILD.bazel +++ b/packages/core/test/bundling/todo/BUILD.bazel @@ -47,7 +47,7 @@ jasmine_node_test( data = [ ":bundle", ":bundle.js", - ":bundle.min.js.br", + ":bundle.min.js", ":bundle.min_debug.js", ], deps = [":test_lib"], diff --git a/tools/bazel.rc b/tools/bazel.rc index 52af863bea..d9f63f2da3 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -57,6 +57,6 @@ test --experimental_ui ################################ # Temporary Settings for Ivy # ################################ -# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=ivy=true` on -# any bazel target. This is a temporary flag until codebase is permanently switched to ViewEngine. -build --define=ivy=false \ No newline at end of file +# to determine if the compiler used should be Ivy or ViewEngine one can use `--define=compile=local` on +# any bazel target. This is a temporary flag until codebase is permanently switched to Ivy. +build --define=compile=legacy \ No newline at end of file diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 9954668f3d..c58c92b0e9 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -2,7 +2,7 @@ load("@build_bazel_rules_nodejs//:defs.bzl", _npm_package = "npm_package") load("@build_bazel_rules_typescript//:defs.bzl", _ts_library = "ts_library", _ts_web_test_suite = "ts_web_test_suite") load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package") -load("//packages/bazel/src:ng_module.bzl", _ivy_ng_module = "internal_ivy_ng_module") +load("//packages/bazel/src:ng_module.bzl", _internal_global_ng_module = "internal_global_ng_module") DEFAULT_TSCONFIG = "//packages:tsconfig-build.json" @@ -49,6 +49,16 @@ def ng_module(name, tsconfig = None, entry_point = None, **kwargs): entry_point = "public_api.ts" _ng_module(name = name, flat_module_out_file = name, tsconfig = tsconfig, entry_point = entry_point, **kwargs) +# ivy_ng_module behaves like ng_module, and under --define=compile=legacy it runs ngc with global +# analysis but produces Ivy outputs. Under other compile modes, it behaves as ng_module. +# TODO(alxhub): remove when ngtsc supports the same use cases. +def ivy_ng_module(name, tsconfig = None, entry_point = None, **kwargs): + if not tsconfig: + tsconfig = DEFAULT_TSCONFIG + if not entry_point: + entry_point = "public_api.ts" + _internal_global_ng_module(name = name, flat_module_out_file = name, tsconfig = tsconfig, entry_point = entry_point, **kwargs) + def ng_package(name, readme_md = None, license_banner = None, **kwargs): if not readme_md: readme_md = "//packages:README.md" @@ -91,8 +101,3 @@ def ts_web_test_suite(bootstrap = [], deps = [], **kwargs): # TODO(alexeagle): add remote browsers on SauceLabs ], **kwargs) - -def ivy_ng_module(name, tsconfig = None, **kwargs): - if not tsconfig: - tsconfig = DEFAULT_TSCONFIG - _ivy_ng_module(name = name, tsconfig = tsconfig, **kwargs) From 3fd3c2ac4c12b052f3503ced07d8b34f3bb62c6a Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Fri, 25 May 2018 13:52:58 -0700 Subject: [PATCH 116/582] test(animations): fix Node.js detection in animation tests (#24139) PR Close #24139 --- packages/animations/browser/test/dsl/animation_spec.ts | 2 +- .../animations/browser/test/dsl/animation_trigger_spec.ts | 2 +- .../test/render/css_keyframes/css_keyframes_driver_spec.ts | 4 +--- .../test/render/css_keyframes/direct_style_player_spec.ts | 2 +- .../css_keyframes/element_animation_style_handler_spec.ts | 4 +--- .../browser/test/render/timeline_animation_engine_spec.ts | 2 +- .../browser/test/render/transition_animation_engine_spec.ts | 2 +- .../test/render/web_animations/web_animations_driver_spec.ts | 2 +- packages/core/test/animation/animation_integration_spec.ts | 2 +- .../core/test/animation/animation_query_integration_spec.ts | 2 +- .../core/test/animation/animation_router_integration_spec.ts | 2 +- ...imations_with_css_keyframes_animations_integration_spec.ts | 2 +- .../animations_with_web_animations_integration_spec.ts | 2 +- .../test/animation/animation_renderer_spec.ts | 2 +- 14 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/animations/browser/test/dsl/animation_spec.ts b/packages/animations/browser/test/dsl/animation_spec.ts index 2104968b8f..21071dec8f 100644 --- a/packages/animations/browser/test/dsl/animation_spec.ts +++ b/packages/animations/browser/test/dsl/animation_spec.ts @@ -20,7 +20,7 @@ function createDiv() { { describe('Animation', () => { // these tests are only mean't to be run within the DOM (for now) - if (typeof Element == 'undefined') return; + if (isNode) return; let rootElement: any; let subElement1: any; diff --git a/packages/animations/browser/test/dsl/animation_trigger_spec.ts b/packages/animations/browser/test/dsl/animation_trigger_spec.ts index a702a1ddb9..5482075fc6 100644 --- a/packages/animations/browser/test/dsl/animation_trigger_spec.ts +++ b/packages/animations/browser/test/dsl/animation_trigger_spec.ts @@ -17,7 +17,7 @@ import {makeTrigger} from '../shared'; { describe('AnimationTrigger', () => { // these tests are only mean't to be run within the DOM (for now) - if (typeof Element == 'undefined') return; + if (isNode) return; let element: any; beforeEach(() => { diff --git a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts index 00adf6e8ee..747a389575 100644 --- a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts +++ b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts @@ -16,9 +16,7 @@ import {assertElementExistsInDom, createElement, findKeyframeDefinition, forceRe const CSS_KEYFRAME_RULE_TYPE = 7; describe('CssKeyframesDriver tests', () => { - if (typeof Element == 'undefined' || typeof document == 'undefined' || - typeof(window as any)['AnimationEvent'] == 'undefined') - return; + if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return; describe('building keyframes', () => { it('should build CSS keyframe style object containing the keyframe styles', () => { diff --git a/packages/animations/browser/test/render/css_keyframes/direct_style_player_spec.ts b/packages/animations/browser/test/render/css_keyframes/direct_style_player_spec.ts index f11ad293d0..c04ae07783 100644 --- a/packages/animations/browser/test/render/css_keyframes/direct_style_player_spec.ts +++ b/packages/animations/browser/test/render/css_keyframes/direct_style_player_spec.ts @@ -14,7 +14,7 @@ import {assertStyle, createElement} from './shared'; const CSS_KEYFRAME_RULE_TYPE = 7; describe('DirectStylePlayer tests', () => { - if (typeof Element == 'undefined' || typeof document == 'undefined') return; + if (isNode) return; it('should apply the styling to the given element when the animation starts and remove when destroyed', () => { diff --git a/packages/animations/browser/test/render/css_keyframes/element_animation_style_handler_spec.ts b/packages/animations/browser/test/render/css_keyframes/element_animation_style_handler_spec.ts index 76d5a6e5d4..d5b64b5758 100644 --- a/packages/animations/browser/test/render/css_keyframes/element_animation_style_handler_spec.ts +++ b/packages/animations/browser/test/render/css_keyframes/element_animation_style_handler_spec.ts @@ -13,9 +13,7 @@ import {assertStyle, createElement, makeAnimationEvent, supportsAnimationEventCr const EMPTY_FN = () => {}; { describe('ElementAnimationStyleHandler', () => { - if (typeof Element == 'undefined' || typeof document == 'undefined' || - typeof(window as any)['AnimationEvent'] == 'undefined') - return; + if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return; it('should add and remove an animation on to an element\'s styling', () => { const element = createElement(); diff --git a/packages/animations/browser/test/render/timeline_animation_engine_spec.ts b/packages/animations/browser/test/render/timeline_animation_engine_spec.ts index 5842fae27f..a7c188133c 100644 --- a/packages/animations/browser/test/render/timeline_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/timeline_animation_engine_spec.ts @@ -21,7 +21,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_a } // these tests are only mean't to be run within the DOM - if (typeof Element == 'undefined') return; + if (isNode) return; describe('TimelineAnimationEngine', () => { let element: any; 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 5a122e9bd5..a76398d2e2 100644 --- a/packages/animations/browser/test/render/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/render/transition_animation_engine_spec.ts @@ -20,7 +20,7 @@ const DEFAULT_NAMESPACE_ID = 'id'; const driver = new MockAnimationDriver(); // these tests are only mean't to be run within the DOM - if (typeof Element == 'undefined') return; + if (isNode) return; describe('TransitionAnimationEngine', () => { let element: any; diff --git a/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts b/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts index 49bb118f13..f66043f58b 100644 --- a/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts +++ b/packages/animations/browser/test/render/web_animations/web_animations_driver_spec.ts @@ -12,7 +12,7 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat { describe('WebAnimationsDriver', () => { - if (typeof Element == 'undefined' || typeof document == 'undefined') return; + if (isNode) return; describe('when web-animations are not supported natively', () => { it('should return an instance of a CssKeyframePlayer if scrubbing is not requested', () => { diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 4980722a36..a1dcb50b60 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -20,7 +20,7 @@ const DEFAULT_COMPONENT_ID = '1'; (function() { // these tests are only mean't to be run within the DOM (for now) - if (typeof Element == 'undefined') return; + if (isNode) return; describe('animation tests', function() { function getLog(): MockAnimationPlayer[] { diff --git a/packages/core/test/animation/animation_query_integration_spec.ts b/packages/core/test/animation/animation_query_integration_spec.ts index 3f14f78b7a..14278b3f81 100644 --- a/packages/core/test/animation/animation_query_integration_spec.ts +++ b/packages/core/test/animation/animation_query_integration_spec.ts @@ -21,7 +21,7 @@ import {fakeAsync, flushMicrotasks} from '../../testing/src/fake_async'; (function() { // these tests are only mean't to be run within the DOM (for now) - if (typeof Element == 'undefined') return; + if (isNode) return; describe('animation query tests', function() { function getLog(): MockAnimationPlayer[] { diff --git a/packages/core/test/animation/animation_router_integration_spec.ts b/packages/core/test/animation/animation_router_integration_spec.ts index 012adb3a55..3225a8727f 100644 --- a/packages/core/test/animation/animation_router_integration_spec.ts +++ b/packages/core/test/animation/animation_router_integration_spec.ts @@ -16,7 +16,7 @@ import {RouterTestingModule} from '@angular/router/testing'; (function() { // these tests are only mean't to be run within the DOM (for now) - if (typeof Element == 'undefined') return; + if (isNode) return; describe('Animation Router Tests', function() { function getLog(): MockAnimationPlayer[] { diff --git a/packages/core/test/animation/animations_with_css_keyframes_animations_integration_spec.ts b/packages/core/test/animation/animations_with_css_keyframes_animations_integration_spec.ts index 4b9201d811..22c1c498c5 100644 --- a/packages/core/test/animation/animations_with_css_keyframes_animations_integration_spec.ts +++ b/packages/core/test/animation/animations_with_css_keyframes_animations_integration_spec.ts @@ -17,7 +17,7 @@ import {TestBed} from '../../testing'; (function() { // these tests are only mean't to be run within the DOM (for now) // Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793 - if (typeof Element == 'undefined') return; + if (isNode) return; describe('animation integration tests using css keyframe animations', function() { diff --git a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts index c5b00adce8..6067815255 100644 --- a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts +++ b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts @@ -18,7 +18,7 @@ import {TestBed} from '../../testing'; (function() { // these tests are only mean't to be run within the DOM (for now) // Buggy in Chromium 39, see https://github.com/angular/angular/issues/15793 - if (typeof Element == 'undefined' || !ɵsupportsWebAnimations()) return; + if (isNode || !ɵsupportsWebAnimations()) return; describe('animation integration tests using web animations', function() { diff --git a/packages/platform-browser/test/animation/animation_renderer_spec.ts b/packages/platform-browser/test/animation/animation_renderer_spec.ts index 05a0c0d8e9..8373cb1e93 100644 --- a/packages/platform-browser/test/animation/animation_renderer_spec.ts +++ b/packages/platform-browser/test/animation/animation_renderer_spec.ts @@ -117,7 +117,7 @@ import {el} from '../../testing/src/browser_util'; describe('flushing animations', () => { // these tests are only mean't to be run within the DOM - if (typeof Element == 'undefined') return; + if (isNode) return; it('should flush and fire callbacks when the zone becomes stable', (async) => { @Component({ From 41cd8f3efbd66696660dd9896afa5ad42e660144 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Thu, 24 May 2018 15:49:07 +0200 Subject: [PATCH 117/582] refactor(core): use Partial for MetadataOverride (#24103) Allows to write: const fixture = TestBed .overridePipe(DisplayNamePipe, { set: { pure: false } }) .createComponent(MenuComponent); when you only want to set the `pure` metadata, instead of currently: const fixture = TestBed .overridePipe(DisplayNamePipe, { set: { name: 'displayName', pure: false } }) .createComponent(MenuComponent); which forces you to redefine the name of the pipe even if it is useless. Fixes #24102 PR Close #24103 --- aio/content/guide/testing.md | 12 ++++++------ packages/core/testing/src/metadata_override.ts | 6 +++--- .../platform-browser/test/testing_public_spec.ts | 3 ++- tools/public_api_guard/core/testing.d.ts | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/aio/content/guide/testing.md b/aio/content/guide/testing.md index 41f6d9bcf8..0acff26bf1 100644 --- a/aio/content/guide/testing.md +++ b/aio/content/guide/testing.md @@ -2367,9 +2367,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a type MetadataOverride = { - add?: T; - remove?: T; - set?: T; + add?: Partial; + remove?: Partial; + set?: Partial; }; @@ -2725,9 +2725,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`, type MetadataOverride = { - add?: T; - remove?: T; - set?: T; + add?: Partial; + remove?: Partial; + set?: Partial; }; diff --git a/packages/core/testing/src/metadata_override.ts b/packages/core/testing/src/metadata_override.ts index 7e2d1d6467..7773cbbf25 100644 --- a/packages/core/testing/src/metadata_override.ts +++ b/packages/core/testing/src/metadata_override.ts @@ -12,7 +12,7 @@ * @experimental */ export type MetadataOverride = { - add?: T, - remove?: T, - set?: T + add?: Partial, + remove?: Partial, + set?: Partial }; diff --git a/packages/platform-browser/test/testing_public_spec.ts b/packages/platform-browser/test/testing_public_spec.ts index f20b78a0c7..cf33d392f0 100644 --- a/packages/platform-browser/test/testing_public_spec.ts +++ b/packages/platform-browser/test/testing_public_spec.ts @@ -376,7 +376,8 @@ class CompWithUrlTemplate { TestBed .overrideComponent( SomeComponent, {set: {selector: 'comp', template: `{{'hello' | somePipe}}`}}) - .overridePipe(SomePipe, {set: {name: 'somePipe'}}); + .overridePipe(SomePipe, {set: {name: 'somePipe'}}) + .overridePipe(SomePipe, {add: {pure: false}}); }); it('should work', () => { const compFixture = TestBed.createComponent(SomeComponent); diff --git a/tools/public_api_guard/core/testing.d.ts b/tools/public_api_guard/core/testing.d.ts index b25ae5069d..c7df004ea6 100644 --- a/tools/public_api_guard/core/testing.d.ts +++ b/tools/public_api_guard/core/testing.d.ts @@ -55,9 +55,9 @@ export declare class InjectSetupWrapper { /** @experimental */ export declare type MetadataOverride = { - add?: T; - remove?: T; - set?: T; + add?: Partial; + remove?: Partial; + set?: Partial; }; /** @experimental */ From 31795b620f8a574d729ea4caecaaa319585cecd5 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Sun, 6 May 2018 17:50:35 +0200 Subject: [PATCH 118/582] style(compiler): fix typo and formatting (#23729) PR Close #23729 --- packages/compiler/src/aot/static_symbol_resolver.ts | 5 ++--- packages/compiler/test/aot/static_symbol_resolver_spec.ts | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts index 9e2c1a1641..65f0e8a049 100644 --- a/packages/compiler/src/aot/static_symbol_resolver.ts +++ b/packages/compiler/src/aot/static_symbol_resolver.ts @@ -12,7 +12,6 @@ import {ValueTransformer, visitValue} from '../util'; import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {isGeneratedFile, stripSummaryForJitFileSuffix, stripSummaryForJitNameSuffix, summaryForJitFileName, summaryForJitName} from './util'; -const DTS = /\.d\.ts$/; const TS = /^(?!.*\.d\.ts$).*\.ts$/; export class ResolvedStaticSymbol { @@ -334,7 +333,7 @@ export class StaticSymbolResolver { } // handle the actual metadata. Has to be after the exports - // as there migth be collisions in the names, and we want the symbols + // as there might be collisions in the names, and we want the symbols // of the current module to win ofter reexports. if (metadata['metadata']) { // handle direct declarations of the symbol @@ -387,7 +386,7 @@ export class StaticSymbolResolver { let _originalFileMemo: string|undefined; const getOriginalName: () => string = () => { if (!_originalFileMemo) { - // Guess what hte original file name is from the reference. If it has a `.d.ts` extension + // Guess what the original file name is from the reference. If it has a `.d.ts` extension // replace it with `.ts`. If it already has `.ts` just leave it in place. If it doesn't have // .ts or .d.ts, append `.ts'. Also, if it is in `node_modules`, trim the `node_module` // location as it is not important to finding the file. diff --git a/packages/compiler/test/aot/static_symbol_resolver_spec.ts b/packages/compiler/test/aot/static_symbol_resolver_spec.ts index dd8dafbe22..1c30215e70 100644 --- a/packages/compiler/test/aot/static_symbol_resolver_spec.ts +++ b/packages/compiler/test/aot/static_symbol_resolver_spec.ts @@ -11,8 +11,6 @@ import {CollectorOptions, METADATA_VERSION} from '@angular/compiler-cli'; import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector'; import * as ts from 'typescript'; - - // This matches .ts files but not .d.ts files. const TS_EXT = /(^.|(?!\.d)..)\.ts$/; From e53179ef8c14f98b3cda01c553934f5e90516803 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 29 May 2018 15:08:30 -0700 Subject: [PATCH 119/582] refactor(ivy): move parent from LNode to TNode (#24189) PR Close #24189 --- packages/core/src/render3/di.ts | 22 ++--- packages/core/src/render3/instructions.ts | 41 +++++---- packages/core/src/render3/interfaces/node.ts | 85 +++++++++++++------ .../core/src/render3/node_manipulation.ts | 19 ++++- .../hello_world/bundle.golden_symbols.json | 3 + .../bundling/todo/bundle.golden_symbols.json | 3 + packages/core/test/render3/di_spec.ts | 2 +- .../render3/node_selector_matcher_spec.ts | 1 + 8 files changed, 117 insertions(+), 59 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 05e4205888..7d66fca500 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -27,7 +27,7 @@ import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {insertView, removeView} from './node_manipulation'; +import {getParentLNode, insertView, removeView} from './node_manipulation'; import {notImplemented, stringify} from './util'; import {EmbeddedViewRef, ViewRef, addDestroyable, createViewRef} from './view_ref'; @@ -102,7 +102,8 @@ export function getOrCreateNodeInjector(): LInjector { */ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNode): LInjector { const nodeInjector = node.nodeInjector; - const parentInjector = node.parent && node.parent.nodeInjector; + const parent = getParentLNode(node); + const parentInjector = parent && parent.nodeInjector; if (nodeInjector != parentInjector) { return nodeInjector !; } @@ -568,14 +569,15 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer const vcRefHost = di.node; ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element); - const lContainer = createLContainer(vcRefHost.parent !, vcRefHost.view); + const hostParent = getParentLNode(vcRefHost) !; + const lContainer = createLContainer(hostParent, vcRefHost.view); const lContainerNode: LContainerNode = createLNodeObject( - TNodeType.Container, vcRefHost.view, vcRefHost.parent !, undefined, lContainer, null); + TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null); const hostTNode = vcRefHost.tNode; if (!hostTNode.dynamicContainerNode) { hostTNode.dynamicContainerNode = - createTNode(TNodeType.Container, hostTNode.index, null, null, null); + createTNode(TNodeType.Container, null, null, null, null, null); } lContainerNode.tNode = hostTNode.dynamicContainerNode; @@ -640,8 +642,6 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { this._viewRefs.splice(adjustedIdx, 0, viewRef); - (lViewNode as{parent: LNode}).parent = this._lContainerNode; - // If the view is dynamic (has a template), it needs to be counted both at the container // level and at the node above the container. if (lViewNode.data.template !== null) { @@ -649,10 +649,10 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { this._lContainerNode.data.dynamicViewCount++; // Look for the parent node and increment its dynamic view count. - if (this._lContainerNode.parent !== null && this._lContainerNode.parent.data !== null) { - ngDevMode && assertNodeOfPossibleTypes( - this._lContainerNode.parent, TNodeType.View, TNodeType.Element); - this._lContainerNode.parent.data.dynamicViewCount++; + const containerParent = getParentLNode(this._lContainerNode) as LElementNode; + if (containerParent !== null && containerParent.data !== null) { + ngDevMode && assertNodeOfPossibleTypes(containerParent, TNodeType.View, TNodeType.Element); + containerParent.data.dynamicViewCount++; } } return viewRef; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 345e8730dd..cc90d6c24d 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,9 +15,9 @@ import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interface import {LQueries} from './interfaces/query'; import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; -import {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node'; import {assertNodeType} from './node_assert'; -import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode} from './node_manipulation'; +import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, PipeDefList, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; @@ -337,13 +337,12 @@ export function createLView( * (same properties assigned in the same order). */ export function createLNodeObject( - type: TNodeType, currentView: LView, parent: LNode, native: RText | RElement | null | undefined, - state: any, + type: TNodeType, currentView: LView, parent: LNode | null, + native: RText | RElement | null | undefined, state: any, queries: LQueries | null): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { return { native: native as any, view: currentView, - parent: parent as any, nodeInjector: parent ? parent.nodeInjector : null, data: state, queries: queries, @@ -382,7 +381,11 @@ export function createLNode( name: string | null, attrs: string[] | null, state?: null | LView | LContainer | LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : - previousOrParentNode && previousOrParentNode.parent as LNode; + previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode; + // Parents cannot cross component boundaries because components will be used in multiple places, + // so it's only set if the view is the same. + const tParent = + parent && parent.view === currentView ? parent.tNode as TElementNode | TContainerNode : null; let queries = (isParent ? currentQueries : previousOrParentNode && previousOrParentNode.queries) || parent && parent.queries && parent.queries.child(); @@ -393,7 +396,7 @@ export function createLNode( if (index === null || type === TNodeType.View) { // View nodes are not stored in data because they can be added / removed at runtime (which // would cause indices to change). Their TNodes are instead stored in TView.node. - node.tNode = (state as LView).tView.node || createTNode(type, index, null, null, null); + node.tNode = (state as LView).tView.node || createTNode(type, index, null, null, tParent, null); } else { // This is an element or container or projection node ngDevMode && assertDataNext(index); @@ -401,7 +404,7 @@ export function createLNode( // Every node adds a value to the static data array to avoid a sparse array if (index >= tData.length) { - const tNode = tData[index] = createTNode(type, index, name, attrs, null); + const tNode = tData[index] = createTNode(type, index, name, attrs, tParent, null); if (!isParent && previousOrParentNode) { const previousTNode = previousOrParentNode.tNode; previousTNode.next = tNode; @@ -596,7 +599,7 @@ export function elementStart( createLNode(index, TNodeType.Element, native !, name, attrs || null, null); if (attrs) setUpAttributes(native, attrs); - appendChild(node.parent !, native, currentView); + appendChild(getParentLNode(node), native, currentView); createDirectivesAndLocals(index, name, attrs, localRefs, false); return native; } @@ -954,7 +957,7 @@ export function elementEnd() { isParent = false; } else { ngDevMode && assertHasParent(); - previousOrParentNode = previousOrParentNode.parent !; + previousOrParentNode = getParentLNode(previousOrParentNode) as LElementNode; } ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Element); const queries = previousOrParentNode.queries; @@ -1038,12 +1041,13 @@ export function elementProperty( * @param index The index of the TNode in TView.data * @param tagName The tag name of the node * @param attrs The attributes defined on this node + * @param parent The parent of this node * @param tViews Any TViews attached to this node * @returns the TNode object */ export function createTNode( type: TNodeType, index: number | null, tagName: string | null, attrs: string[] | null, - tViews: TView[] | null): TNode { + parent: TElementNode | TContainerNode | null, tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { type: type, @@ -1058,6 +1062,7 @@ export function createTNode( tViews: tViews, next: null, child: null, + parent: parent, dynamicContainerNode: null }; } @@ -1257,7 +1262,7 @@ export function text(index: number, value?: any): void { // Text nodes are self closing. isParent = false; - appendChild(node.parent !, textNode, currentView); + appendChild(getParentLNode(node), textNode, currentView); } /** @@ -1485,7 +1490,7 @@ export function container( currentView.bindingStartIndex, -1, 'container nodes should be created before any bindings'); - const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent !; + const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !; const lContainer = createLContainer(currentParent, currentView, template); const node = createLNode( @@ -1542,7 +1547,7 @@ export function containerRefreshEnd(): void { } else { ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); ngDevMode && assertHasParent(); - previousOrParentNode = previousOrParentNode.parent !; + previousOrParentNode = getParentLNode(previousOrParentNode) !; } ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); const container = previousOrParentNode as LContainerNode; @@ -1609,7 +1614,7 @@ function scanForView( */ export function embeddedViewStart(viewBlockId: number): RenderFlags { const container = - (isParent ? previousOrParentNode : previousOrParentNode.parent !) as LContainerNode; + (isParent ? previousOrParentNode : getParentLNode(previousOrParentNode)) as LContainerNode; ngDevMode && assertNodeType(container, TNodeType.Container); const lContainer = container.data; let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex, viewBlockId); @@ -1662,7 +1667,7 @@ export function embeddedViewEnd(): void { refreshView(); isParent = false; const viewNode = previousOrParentNode = currentView.node as LViewNode; - const containerNode = previousOrParentNode.parent as LContainerNode; + const containerNode = getParentLNode(previousOrParentNode) as LContainerNode; if (containerNode) { ngDevMode && assertNodeType(viewNode, TNodeType.View); ngDevMode && assertNodeType(containerNode, TNodeType.Container); @@ -1834,7 +1839,6 @@ export function projection( // `` has no content isParent = false; - const currentParent = node.parent; // re-distribution of projectable nodes is memorized on a component's view level const componentNode = findComponentHost(currentView); @@ -1856,6 +1860,7 @@ export function projection( } } + const currentParent = getParentLNode(node); if (canInsertNativeNode(currentParent, currentView)) { ngDevMode && assertNodeType(currentParent, TNodeType.Element); // process each node in the list of projected nodes: @@ -2413,7 +2418,7 @@ export function assertPreviousIsParent() { } function assertHasParent() { - assertNotNull(previousOrParentNode.parent, 'previousOrParentNode should have a parent'); + assertNotNull(getParentLNode(previousOrParentNode), 'previousOrParentNode should have a parent'); } function assertDataInRange(index: number, arr?: any[]) { diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 0701f960eb..c7d316ab92 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -65,12 +65,6 @@ export interface LNode { */ readonly native: RElement|RText|null|undefined; - /** - * We need a reference to a node's parent so we can append the node to its parent's native - * element at the appropriate time. - */ - readonly parent: LNode|null; - /** * If regular LElementNode, then `data` will be null. * If LElementNode with component, then `data` contains LView. @@ -127,18 +121,12 @@ export interface LElementNode extends LNode { /** If Component then data has LView (light DOM) */ readonly data: LView|null; - - /** LElementNodes can be inside other LElementNodes or inside LViewNodes. */ - readonly parent: LElementNode|LViewNode; } /** LNode representing a #text node. */ export interface LTextNode extends LNode { /** The text node associated with this node. */ native: RText; - - /** LTextNodes can be inside LElementNodes or inside LViewNodes. */ - readonly parent: LElementNode|LViewNode; readonly data: null; dynamicLContainerNode: null; } @@ -146,9 +134,6 @@ export interface LTextNode extends LNode { /** Abstract node which contains root nodes of a view. */ export interface LViewNode extends LNode { readonly native: null; - - /** LViewNodes can only be added to LContainerNodes. */ - readonly parent: LContainerNode|null; readonly data: LView; dynamicLContainerNode: null; } @@ -164,19 +149,12 @@ export interface LContainerNode extends LNode { */ native: RElement|RText|null|undefined; readonly data: LContainer; - - /** Containers can be added to elements or views. */ - readonly parent: LElementNode|LViewNode|null; } export interface LProjectionNode extends LNode { readonly native: null; - readonly data: LProjection; - - /** Projections can be added to elements or views. */ - readonly parent: LElementNode|LViewNode; dynamicLContainerNode: null; } @@ -201,7 +179,7 @@ export interface TNode { * This is necessary to get from any TNode to its corresponding LNode when * traversing the node tree. * - * If null, this is a view node created from a dynamically created view. + * If null, this is a dynamically created container node or embedded view node. */ index: number|null; @@ -307,6 +285,22 @@ export interface TNode { */ child: TNode|null; + /** + * Parent node (in the same view only). + * + * We need a reference to a node's parent so we can append the node to its parent's native + * element at the appropriate time. + * + * If the parent would be in a different view (e.g. component host), this property will be null. + * It's important that we don't try to cross component boundaries when retrieving the parent + * because the parent will change (e.g. index, attrs) depending on where the component was + * used (and thus shouldn't be stored on TNode). In these cases, we retrieve the parent through + * LView.node instead (which will be instance-specific). + * + * If this is an inline view node (V), the parent will be its container. + */ + parent: TElementNode|TContainerNode|null; + /** * A pointer to a TContainerNode created by directives requesting ViewContainerRef */ @@ -315,31 +309,72 @@ export interface TNode { /** Static data for an LElementNode */ export interface TElementNode extends TNode { - child: TContainerNode|TElementNode|TProjectionNode|null; + /** Index in the data[] array */ + index: number; + child: TElementNode|TTextNode|TContainerNode|TProjectionNode|null; + /** + * Element nodes will have parents unless they are the first node of a component or + * embedded view (which means their parent is in a different view and must be + * retrieved using LView.node). + */ + parent: TElementNode|null; tViews: null; } /** Static data for an LTextNode */ export interface TTextNode extends TNode { + /** Index in the data[] array */ + index: number; child: null; + /** + * Text nodes will have parents unless they are the first node of a component or + * embedded view (which means their parent is in a different view and must be + * retrieved using LView.node). + */ + parent: TElementNode|null; tViews: null; } /** Static data for an LContainerNode */ export interface TContainerNode extends TNode { + /** + * If number, index in the data[] array. + * + * If null, this is a dynamically created container node that isn't stored in + * data[] (e.g. when you inject ViewContainerRef) . + */ + index: number|null; child: null; + + /** + * Container nodes will have parents unless: + * + * - They are the first node of a component or embedded view + * - They are dynamically created + */ + parent: TElementNode|null; tViews: TView|TView[]|null; } /** Static data for an LViewNode */ export interface TViewNode extends TNode { - child: TContainerNode|TElementNode|TProjectionNode|null; + /** If null, it's a dynamically created view*/ + index: number|null; + child: TElementNode|TTextNode|TContainerNode|TProjectionNode|null; + parent: TContainerNode|null; tViews: null; } /** Static data for an LProjectionNode */ export interface TProjectionNode extends TNode { + /** Index in the data[] array */ child: null; + /** + * Projection nodes will have parents unless they are the first node of a component + * or embedded view (which means their parent is in a different view and must be + * retrieved using LView.node). + */ + parent: TElementNode|null; tViews: null; } diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 3ae554dd92..cb0bc5bdee 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -52,7 +52,7 @@ function findNextRNodeSibling(node: LNode | null, stopNode: LNode | null): RElem } currentSibling = getNextLNode(currentSibling); } - const parentNode = currentNode.parent; + const parentNode = getParentLNode(currentNode); currentNode = null; if (parentNode) { const parentType = parentNode.tNode.type; @@ -84,6 +84,17 @@ export function getChildLNode(node: LNode): LNode|null { return null; } +/** Retrieves the parent LNode of a given node. */ +export function getParentLNode(node: LElementNode | LTextNode | LProjectionNode): LElementNode| + LViewNode; +export function getParentLNode(node: LViewNode): LContainerNode|null; +export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null; +export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNode|null { + if (node.tNode.index === null) return null; + const parent = node.tNode.parent; + return parent ? node.view.data[parent.index as number] : node.view.node; +} + /** * Get the next node in the LNode tree, taking into account the place where a node is * projected (in the shadow DOM) rather than where it comes from (in the light DOM). @@ -122,7 +133,7 @@ function getNextOrParentSiblingNode(initialNode: LNode, rootNode: LNode): LNode| while (node && !nextNode) { // if node.pNextOrParent is not null here, it is not the next node // (because, at this point, nextNode is null, so it is the parent) - node = node.pNextOrParent || node.parent; + node = node.pNextOrParent || getParentLNode(node); if (node === rootNode) { return null; } @@ -371,7 +382,7 @@ export function getParentState(state: LViewOrLContainer, rootView: LView): LView if ((node = (state as LView) !.node) && node.tNode.type === TNodeType.View) { // if it's an embedded view, the state needs to go up to the container, in case the // container has a next - return node.parent !.data as any; + return getParentLNode(node) !.data as any; } else { // otherwise, use parent view for containers or component views return state.parent === rootView ? null : state.parent; @@ -476,7 +487,7 @@ export function appendChild(parent: LNode, child: RNode | null, currentView: LVi * @param currentView Current LView */ export function insertChild(node: LNode, currentView: LView): void { - const parent = node.parent !; + const parent = getParentLNode(node) !; if (canInsertNativeNode(parent, currentView)) { let nativeSibling: RNode|null = findNextRNodeSibling(node, null); const renderer = currentView.renderer; diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 63f67c0bda..3a97e3864e 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -110,6 +110,9 @@ { "name": "getOrCreateTView" }, + { + "name": "getParentLNode" + }, { "name": "getRenderFlags" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 7cdd8fb784..34d33906bc 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -422,6 +422,9 @@ { "name": "getOrCreateTemplateRef" }, + { + "name": "getParentLNode" + }, { "name": "getParentState" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index cc3bded98b..73dc163330 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1372,7 +1372,7 @@ describe('di', () => { // Simulate the situation where the previous parent is not initialized. // This happens on first bootstrap because we don't init existing values // so that we have smaller HelloWorld. - (parent as{parent: any}).parent = undefined; + (parent.tNode as{parent: any}).parent = undefined; const injector = getOrCreateNodeInjector(); expect(injector).not.toBe(null); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index d98944e264..53b432ea56 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -23,6 +23,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { tViews: null, next: null, child: null, + parent: null, dynamicContainerNode: null }; } From b87d650da28333aacf5d04333b0ed80804693d5f Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 13 May 2018 18:29:45 +0200 Subject: [PATCH 120/582] refactor(ivy): rename `PipeDef.n` to `PipeDef.factory` (#23883) The original reason for this property to be short no longer holds true, as pipes always need to be defined using `definePipe`. PR Close #23883 --- packages/core/src/render3/definition.ts | 2 +- packages/core/src/render3/interfaces/definition.ts | 7 ++----- packages/core/src/render3/pipe.ts | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index da3c36e1c5..dbe6f53663 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -431,7 +431,7 @@ export function definePipe(pipeDef: { }): never { return (>{ name: pipeDef.name, - n: pipeDef.factory, + factory: pipeDef.factory, pure: pipeDef.pure !== false, onDestroy: pipeDef.type.prototype.ngOnDestroy || null }) as never; diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 4395efdca8..dc41b96f33 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -204,12 +204,9 @@ export interface PipeDef { name: string; /** - * factory function used to create a new directive instance. - * - * NOTE: this property is short (1 char) because it is used in - * component templates which is sensitive to size. + * Factory function used to create a new pipe instance. */ - n: () => T; + factory: () => T; /** * Whether or not the pipe is pure. diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index fd9031ca07..7f736721f7 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -33,7 +33,7 @@ export function pipe(index: number, pipeName: string): any { pipeDef = tView.data[index] as PipeDef; } - const pipeInstance = pipeDef.n(); + const pipeInstance = pipeDef.factory(); store(index, pipeInstance); return pipeInstance; } From 90bf5d8961979469e39ad27a9cdce4ec132ef11c Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Fri, 4 May 2018 15:58:42 +0200 Subject: [PATCH 121/582] feat(ivy): separate attributes for directive matching purposes (#23991) In ngIvy directives matching (determining which directives are active based on a CSS seletor) happens at runtime. This means that runtime needs to have enough context to match directives. This PR takes care of cases where a directive's selector should match bindings (ex. [foo]="exp") and event handlers (ex. (out)="do()"). In the mentioned cases we need to have binding / output "attributes" for directive's CSS selector matching purposes. At the same time those are not regular attributes and as such should not be reflected in the DOM. Closes #23706 PR Close #23991 --- packages/core/src/render3/di.ts | 10 +- packages/core/src/render3/index.ts | 4 + packages/core/src/render3/instructions.ts | 46 ++--- packages/core/src/render3/interfaces/node.ts | 41 ++++- .../core/src/render3/node_selector_matcher.ts | 22 ++- packages/core/test/render3/content_spec.ts | 44 ++++- packages/core/test/render3/di_spec.ts | 68 ++++++-- packages/core/test/render3/directive_spec.ts | 160 ++++++++++++++++-- .../render3/node_selector_matcher_spec.ts | 28 ++- 9 files changed, 349 insertions(+), 74 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 7d66fca500..b57926f254 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -22,7 +22,7 @@ import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; +import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView, TView} from './interfaces/view'; @@ -251,7 +251,7 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { * * @experimental */ -export function injectAttribute(attrName: string): string|undefined { +export function injectAttribute(attrNameToInject: string): string|undefined { ngDevMode && assertPreviousIsParent(); const lElement = getPreviousOrParentNode() as LElementNode; ngDevMode && assertNodeType(lElement, TNodeType.Element); @@ -260,8 +260,10 @@ export function injectAttribute(attrName: string): string|undefined { const attrs = tElement.attrs; if (attrs) { for (let i = 0; i < attrs.length; i = i + 2) { - if (attrs[i] == attrName) { - return attrs[i + 1]; + const attrName = attrs[i]; + if (attrName === AttributeMarker.SELECT_ONLY) break; + if (attrName == attrNameToInject) { + return attrs[i + 1] as string; } } } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 421b421364..0e8e091bc3 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -73,6 +73,10 @@ export { tick, } from './instructions'; +export { + AttributeMarker +} from './interfaces/node'; + export { pipe as Pp, pipeBind1 as pb1, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index cc90d6c24d..20752d436a 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,7 +15,7 @@ import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interface import {LQueries} from './interfaces/query'; import {CurrentMatchesList, LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; -import {LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node'; +import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue, TElementNode,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; @@ -366,19 +366,19 @@ export function createLNodeObject( */ export function createLNode( index: number | null, type: TNodeType.Element, native: RElement | RText | null, - name: string | null, attrs: string[] | null, lView?: LView | null): LElementNode; + name: string | null, attrs: TAttributes | null, lView?: LView | null): LElementNode; export function createLNode( index: number | null, type: TNodeType.View, native: null, name: null, attrs: null, lView: LView): LViewNode; export function createLNode( index: number, type: TNodeType.Container, native: undefined, name: string | null, - attrs: string[] | null, lContainer: LContainer): LContainerNode; + attrs: TAttributes | null, lContainer: LContainer): LContainerNode; export function createLNode( - index: number, type: TNodeType.Projection, native: null, name: null, attrs: string[] | null, + index: number, type: TNodeType.Projection, native: null, name: null, attrs: TAttributes | null, lProjection: LProjection): LProjectionNode; export function createLNode( index: number | null, type: TNodeType, native: RText | RElement | null | undefined, - name: string | null, attrs: string[] | null, state?: null | LView | LContainer | + name: string | null, attrs: TAttributes | null, state?: null | LView | LContainer | LProjection): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { const parent = isParent ? previousOrParentNode : previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode; @@ -586,7 +586,8 @@ function getRenderFlags(view: LView): RenderFlags { * ['id', 'warning5', 'class', 'alert'] */ export function elementStart( - index: number, name: string, attrs?: string[] | null, localRefs?: string[] | null): RElement { + index: number, name: string, attrs?: TAttributes | null, + localRefs?: string[] | null): RElement { ngDevMode && assertEqual( currentView.bindingStartIndex, -1, 'elements should be created before any bindings'); @@ -600,23 +601,18 @@ export function elementStart( if (attrs) setUpAttributes(native, attrs); appendChild(getParentLNode(node), native, currentView); - createDirectivesAndLocals(index, name, attrs, localRefs, false); + createDirectivesAndLocals(localRefs); return native; } /** * Creates directive instances and populates local refs. * - * @param index Index of the current node (to create TNode) - * @param name Tag name of the current node - * @param attrs Attrs of the current node * @param localRefs Local refs of the current node - * @param inlineViews Whether or not this node will create inline views */ -function createDirectivesAndLocals( - index: number, name: string | null, attrs: string[] | null | undefined, - localRefs: string[] | null | undefined, inlineViews: boolean) { +function createDirectivesAndLocals(localRefs?: string[] | null) { const node = previousOrParentNode; + if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null); @@ -822,17 +818,18 @@ export function createTView( }; } -function setUpAttributes(native: RElement, attrs: string[]): void { - ngDevMode && assertEqual(attrs.length % 2, 0, 'each attribute should have a key and a value'); - +function setUpAttributes(native: RElement, attrs: TAttributes): void { const isProc = isProceduralRenderer(renderer); for (let i = 0; i < attrs.length; i += 2) { const attrName = attrs[i]; + if (attrName === AttributeMarker.SELECT_ONLY) break; if (attrName !== NG_PROJECT_AS_ATTR_NAME) { const attrVal = attrs[i + 1]; ngDevMode && ngDevMode.rendererSetAttribute++; - isProc ? (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal) : - native.setAttribute(attrName, attrVal); + isProc ? + (renderer as ProceduralRenderer3) + .setAttribute(native, attrName as string, attrVal as string) : + native.setAttribute(attrName as string, attrVal as string); } } } @@ -1046,7 +1043,7 @@ export function elementProperty( * @returns the TNode object */ export function createTNode( - type: TNodeType, index: number | null, tagName: string | null, attrs: string[] | null, + type: TNodeType, index: number | null, tagName: string | null, attrs: TAttributes | null, parent: TElementNode | TContainerNode | null, tViews: TView[] | null): TNode { ngDevMode && ngDevMode.tNode++; return { @@ -1442,10 +1439,13 @@ function generateInitialInputs( for (let i = 0; i < attrs.length; i += 2) { const attrName = attrs[i]; const minifiedInputName = inputs[attrName]; + const attrValue = attrs[i + 1]; + + if (attrName === AttributeMarker.SELECT_ONLY) break; if (minifiedInputName !== undefined) { const inputsToStore: InitialInputs = initialInputData[directiveIndex] || (initialInputData[directiveIndex] = []); - inputsToStore.push(minifiedInputName, attrs[i + 1]); + inputsToStore.push(minifiedInputName, attrValue as string); } } return initialInputData; @@ -1484,7 +1484,7 @@ export function createLContainer( * @param localRefs A set of local reference bindings on the element. */ export function container( - index: number, template?: ComponentTemplate, tagName?: string | null, attrs?: string[], + index: number, template?: ComponentTemplate, tagName?: string | null, attrs?: TAttributes, localRefs?: string[] | null): void { ngDevMode && assertEqual( currentView.bindingStartIndex, -1, @@ -1501,7 +1501,7 @@ export function container( // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. addToViewTree(currentView, node.data); - createDirectivesAndLocals(index, tagName || null, attrs, localRefs, template == null); + createDirectivesAndLocals(localRefs); isParent = false; ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index c7d316ab92..80b04fecbe 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -158,6 +158,29 @@ export interface LProjectionNode extends LNode { dynamicLContainerNode: null; } +/** + * A set of marker values to be used in the attributes arrays. Those markers indicate that some + * items are not regular attributes and the processing should be adapted accordingly. + */ +export const enum AttributeMarker { + NS = 0, // namespace. Has to be repeated. + + /** + * This marker indicates that the following attribute names were extracted from bindings (ex.: + * [foo]="exp") and / or event handlers (ex. (bar)="doSth()"). + * Taking the above bindings and outputs as an example an attributes array could look as follows: + * ['class', 'fade in', AttributeMarker.SELECT_ONLY, 'foo', 'bar'] + */ + SELECT_ONLY = 1 +} + +/** + * A combination of: + * - attribute names and values + * - special markers acting as flags to alter attributes processing. + */ +export type TAttributes = (string | AttributeMarker)[]; + /** * LNode binding data (flyweight) for a particular node that is shared between all templates * of a specific type. @@ -198,18 +221,20 @@ export interface TNode { tagName: string|null; /** - * Static attributes associated with an element. We need to store - * static attributes to support content projection with selectors. - * Attributes are stored statically because reading them from the DOM - * would be way too slow for content projection and queries. + * Attributes associated with an element. We need to store attributes to support various use-cases + * (attribute injection, content projection with selectors, directives matching). + * Attributes are stored statically because reading them from the DOM would be way too slow for + * content projection and queries. * - * Since attrs will always be calculated first, they will never need - * to be marked undefined by other instructions. + * Since attrs will always be calculated first, they will never need to be marked undefined by + * other instructions. * - * The name of the attribute and its value alternate in the array. + * For regular attributes a name of an attribute and its value alternate in the array. * e.g. ['role', 'checkbox'] + * This array can contain flags that will indicate "special attributes" (attributes with + * namespaces, attributes extracted from bindings and outputs). */ - attrs: string[]|null; + attrs: TAttributes|null; /** * A set of local names under which a given element is exported in a template and diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index e92e3f287d..73f2ab55ec 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -9,7 +9,7 @@ import './ng_dev_mode'; import {assertNotNull} from './assert'; -import {TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; +import {AttributeMarker, TAttributes, TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; const unusedValueToPlacateAjd = unused1 + unused2; @@ -40,6 +40,7 @@ export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boo let mode: SelectorFlags = SelectorFlags.ELEMENT; const nodeAttrs = tNode.attrs !; + const selectOnlyMarkerIdx = nodeAttrs ? nodeAttrs.indexOf(AttributeMarker.SELECT_ONLY) : -1; // When processing ":not" selectors, we skip to the next ":not" if the // current one doesn't match @@ -80,9 +81,11 @@ export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boo const selectorAttrValue = mode & SelectorFlags.CLASS ? current : selector[++i]; if (selectorAttrValue !== '') { - const nodeAttrValue = nodeAttrs[attrIndexInNode + 1]; + const nodeAttrValue = selectOnlyMarkerIdx > -1 && attrIndexInNode > selectOnlyMarkerIdx ? + '' : + nodeAttrs[attrIndexInNode + 1]; if (mode & SelectorFlags.CLASS && - !isCssClassMatching(nodeAttrValue, selectorAttrValue as string) || + !isCssClassMatching(nodeAttrValue as string, selectorAttrValue as string) || mode & SelectorFlags.ATTRIBUTE && selectorAttrValue !== nodeAttrValue) { if (isPositive(mode)) return false; skipToNextSelector = true; @@ -98,10 +101,15 @@ function isPositive(mode: SelectorFlags): boolean { return (mode & SelectorFlags.NOT) === 0; } -function findAttrIndexInNode(name: string, attrs: string[] | null): number { +function findAttrIndexInNode(name: string, attrs: TAttributes | null): number { + let step = 2; if (attrs === null) return -1; - for (let i = 0; i < attrs.length; i += 2) { - if (attrs[i] === name) return i; + for (let i = 0; i < attrs.length; i += step) { + const attrName = attrs[i]; + if (attrName === name) return i; + if (attrName === AttributeMarker.SELECT_ONLY) { + step = 1; + } } return -1; } @@ -123,7 +131,7 @@ export function getProjectAsAttrValue(tNode: TNode): string|null { // only check for ngProjectAs in attribute names, don't accidentally match attribute's value // (attribute names are stored at even indexes) if ((ngProjectAsAttrIdx & 1) === 0) { - return nodeAttrs[ngProjectAsAttrIdx + 1]; + return nodeAttrs[ngProjectAsAttrIdx + 1] as string; } } return null; diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts index 45469996af..71d7dda90b 100644 --- a/packages/core/test/render3/content_spec.ts +++ b/packages/core/test/render3/content_spec.ts @@ -8,10 +8,11 @@ import {SelectorFlags} from '@angular/core/src/render3/interfaces/projection'; -import {detectChanges} from '../../src/render3/index'; -import {container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, projection, projectionDef, text} from '../../src/render3/instructions'; +import {AttributeMarker, detectChanges} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, loadDirective, projection, projectionDef, text} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {createComponent, renderComponent, toHtml} from './render_util'; + +import {ComponentFixture, createComponent, renderComponent, toHtml} from './render_util'; describe('content projection', () => { it('should project content', () => { @@ -583,6 +584,43 @@ describe('content projection', () => { '
1
2
'); }); + // https://stackblitz.com/edit/angular-psokum?file=src%2Fapp%2Fapp.module.ts + it('should project nodes where attribute selector matches a binding', () => { + /** + * + */ + const Child = createComponent('child', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + projectionDef(0, [[['', 'title', '']]], ['[title]']); + { projection(1, 0, 1); } + } + }); + + /** + * + * Has title + * + */ + const Parent = createComponent('parent', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'child'); + { + elementStart(1, 'span', [AttributeMarker.SELECT_ONLY, 'title']); + { text(2, 'Has title'); } + elementEnd(); + } + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'title', bind('Some title')); + } + }, [Child]); + + const fixture = new ComponentFixture(Parent); + expect(fixture.html).toEqual('Has title'); + + }); + it('should project nodes using class selectors', () => { /** *
diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 73dc163330..c5bceace20 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -12,9 +12,9 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; -import {TNodeType} from '../../src/render3/interfaces/node'; +import {AttributeMarker, TNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; import {ViewRef} from '../../src/render3/view_ref'; @@ -1199,21 +1199,61 @@ describe('di', () => { }); }); - it('should injectAttribute', () => { - let exist: string|undefined = 'wrong'; - let nonExist: string|undefined = 'wrong'; + describe('@Attribute', () => { - const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']); - exist = injectAttribute('exist'); - nonExist = injectAttribute('nonExist'); - } + it('should inject attribute', () => { + let exist: string|undefined = 'wrong'; + let nonExist: string|undefined = 'wrong'; + + const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']); + exist = injectAttribute('exist'); + nonExist = injectAttribute('nonExist'); + } + }); + + const fixture = new ComponentFixture(MyApp); + expect(exist).toEqual('existValue'); + expect(nonExist).toEqual(undefined); }); - const app = renderComponent(MyApp); - expect(exist).toEqual('existValue'); - expect(nonExist).toEqual(undefined); + // https://stackblitz.com/edit/angular-8ytqkp?file=src%2Fapp%2Fapp.component.ts + it('should not inject attributes representing bindings and outputs', () => { + let exist: string|undefined = 'wrong'; + let nonExist: string|undefined = 'wrong'; + + const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', ['exist', 'existValue', AttributeMarker.SELECT_ONLY, 'nonExist']); + exist = injectAttribute('exist'); + nonExist = injectAttribute('nonExist'); + } + }); + + const fixture = new ComponentFixture(MyApp); + expect(exist).toEqual('existValue'); + expect(nonExist).toEqual(undefined); + }); + + it('should not accidentally inject attributes representing bindings and outputs', () => { + let exist: string|undefined = 'wrong'; + let nonExist: string|undefined = 'wrong'; + + const MyApp = createComponent('my-app', function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', [ + 'exist', 'existValue', AttributeMarker.SELECT_ONLY, 'binding1', 'nonExist', 'binding2' + ]); + exist = injectAttribute('exist'); + nonExist = injectAttribute('nonExist'); + } + }); + + const fixture = new ComponentFixture(MyApp); + expect(exist).toEqual('existValue'); + expect(nonExist).toEqual(undefined); + }); }); describe('inject', () => { diff --git a/packages/core/test/render3/directive_spec.ts b/packages/core/test/render3/directive_spec.ts index d088989ea1..58f0c22349 100644 --- a/packages/core/test/render3/directive_spec.ts +++ b/packages/core/test/render3/directive_spec.ts @@ -6,10 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {defineDirective} from '../../src/render3/index'; -import {bind, elementEnd, elementProperty, elementStart, loadDirective} from '../../src/render3/instructions'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; -import {renderToHtml} from './render_util'; +import {EventEmitter} from '@angular/core'; + +import {AttributeMarker, defineDirective} from '../../src/render3/index'; +import {bind, elementEnd, elementProperty, elementStart, listener, loadDirective} from '../../src/render3/instructions'; + +import {TemplateFixture} from './render_util'; describe('directive', () => { @@ -31,17 +33,151 @@ describe('directive', () => { }); } - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - elementStart(0, 'span', ['dir', '']); - elementEnd(); + function Template() { + elementStart(0, 'span', [AttributeMarker.SELECT_ONLY, 'dir']); + elementEnd(); + } + + const fixture = new TemplateFixture(Template, () => {}, [Directive]); + expect(fixture.html).toEqual(''); + + directiveInstance !.klass = 'bar'; + fixture.update(); + expect(fixture.html).toEqual(''); + }); + + }); + + describe('selectors', () => { + + it('should match directives with attribute selectors on bindings', () => { + let directiveInstance: Directive; + + class Directive { + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'test', '']], + factory: () => directiveInstance = new Directive, + inputs: {test: 'test', other: 'other'} + }); + + testValue: boolean; + other: boolean; + + /** + * A setter to assert that a binding is not invoked with stringified attribute value + */ + set test(value: any) { + // if a binding is processed correctly we should only be invoked with a false Boolean + // and never with the "false" string literal + this.testValue = value; + if (value !== false) { + fail('Should only be called with a false Boolean value, got a non-falsy value'); + } } } - const defs = [Directive]; - expect(renderToHtml(Template, {}, defs)).toEqual(''); - directiveInstance !.klass = 'bar'; - expect(renderToHtml(Template, {}, defs)).toEqual(''); + /** + * + */ + function createTemplate() { + // using 2 bindings to show example shape of attributes array + elementStart(0, 'span', ['class', 'fade', AttributeMarker.SELECT_ONLY, 'test', 'other']); + elementEnd(); + } + + function updateTemplate() { elementProperty(0, 'test', bind(false)); } + + const fixture = new TemplateFixture(createTemplate, updateTemplate, [Directive]); + + // the "test" attribute should not be reflected in the DOM as it is here only for directive + // matching purposes + expect(fixture.html).toEqual(''); + expect(directiveInstance !.testValue).toBe(false); + }); + + it('should not accidentally set inputs from attributes extracted from bindings / outputs', + () => { + let directiveInstance: Directive; + + class Directive { + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'test', '']], + factory: () => directiveInstance = new Directive, + inputs: {test: 'test', prop1: 'prop1', prop2: 'prop2'} + }); + + prop1: boolean; + prop2: boolean; + testValue: boolean; + + + /** + * A setter to assert that a binding is not invoked with stringified attribute value + */ + set test(value: any) { + // if a binding is processed correctly we should only be invoked with a false Boolean + // and never with the "false" string literal + this.testValue = value; + if (value !== false) { + fail('Should only be called with a false Boolean value, got a non-falsy value'); + } + } + } + + /** + * + */ + function createTemplate() { + // putting name (test) in the "usual" value position + elementStart( + 0, 'span', ['class', 'fade', AttributeMarker.SELECT_ONLY, 'prop1', 'test', 'prop2']); + elementEnd(); + } + + function updateTemplate() { + elementProperty(0, 'prop1', bind(true)); + elementProperty(0, 'test', bind(false)); + elementProperty(0, 'prop2', bind(true)); + } + + const fixture = new TemplateFixture(createTemplate, updateTemplate, [Directive]); + + // the "test" attribute should not be reflected in the DOM as it is here only for directive + // matching purposes + expect(fixture.html).toEqual(''); + expect(directiveInstance !.testValue).toBe(false); + }); + + it('should match directives with attribute selectors on outputs', () => { + let directiveInstance: Directive; + + class Directive { + static ngDirectiveDef = defineDirective({ + type: Directive, + selectors: [['', 'out', '']], + factory: () => directiveInstance = new Directive, + outputs: {out: 'out'} + }); + + out = new EventEmitter(); + } + + /** + * + */ + function createTemplate() { + elementStart(0, 'span', [AttributeMarker.SELECT_ONLY, 'out']); + { listener('out', () => {}); } + elementEnd(); + } + + const fixture = new TemplateFixture(createTemplate, () => {}, [Directive]); + + // "out" should not be part of reflected attributes + expect(fixture.html).toEqual(''); + expect(directiveInstance !).not.toBeUndefined(); }); }); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index 53b432ea56..3a143bcadf 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {TNode, TNodeType} from '../../src/render3/interfaces/node'; +import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/interfaces/node'; import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags,} from '../../src/render3/interfaces/projection'; import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelector} from '../../src/render3/node_selector_matcher'; -function testLStaticData(tagName: string, attrs: string[] | null): TNode { +function testLStaticData(tagName: string, attrs: TAttributes | null): TNode { return { type: TNodeType.Element, index: 0, @@ -29,7 +29,7 @@ function testLStaticData(tagName: string, attrs: string[] | null): TNode { } describe('css selector matching', () => { - function isMatching(tagName: string, attrs: string[] | null, selector: CssSelector): boolean { + function isMatching(tagName: string, attrs: TAttributes | null, selector: CssSelector): boolean { return isNodeMatchingSelector(testLStaticData(tagName, attrs), selector); } @@ -177,6 +177,28 @@ describe('css selector matching', () => { '', 'class', 'foo' ])).toBeTruthy(`Selector '[class="foo"]' should match `); }); + + it('should take optional binding attribute names into account', () => { + expect(isMatching('span', [AttributeMarker.SELECT_ONLY, 'directive'], [ + '', 'directive', '' + ])).toBeTruthy(`Selector '[directive]' should match `); + }); + + it('should not match optional binding attribute names if attribute selector has value', + () => { + expect(isMatching('span', [AttributeMarker.SELECT_ONLY, 'directive'], [ + '', 'directive', 'value' + ])).toBeFalsy(`Selector '[directive=value]' should not match `); + }); + + it('should not match optional binding attribute names if attribute selector has value and next name equals to value', + () => { + expect(isMatching( + 'span', [AttributeMarker.SELECT_ONLY, 'directive', 'value'], + ['', 'directive', 'value'])) + .toBeFalsy( + `Selector '[directive=value]' should not match `); + }); }); describe('class matching', () => { From f6f44edcc03f3ccba1f132a8d691e3c499536a19 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Mon, 21 May 2018 20:50:29 +0200 Subject: [PATCH 122/582] docs: update ivy perf notes (#24035) PR Close #24035 --- packages/core/src/render3/PERF_NOTES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/src/render3/PERF_NOTES.md b/packages/core/src/render3/PERF_NOTES.md index d031899025..9a75641e00 100644 --- a/packages/core/src/render3/PERF_NOTES.md +++ b/packages/core/src/render3/PERF_NOTES.md @@ -64,3 +64,10 @@ for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) { const key = keys[i]; } ``` + +## Recursive functions +Avoid recursive functions when possible because they cannot be inlined. + +## Loops +Don't use foreach, it can cause megamorphic function calls (depending on the browser) and function allocations. +It is [a lot slower than regular `for` loops](https://jsperf.com/for-vs-foreach-misko) From 2e21690c664a6c038c0e26638e37b2a610513011 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Tue, 22 May 2018 17:40:59 +0200 Subject: [PATCH 123/582] feat(ivy): support renderer.destroy and renderer.destroyNode hooks (#24049) PR Close #24049 --- packages/core/src/render3/STATUS.md | 33 +++++- packages/core/src/render3/ng_dev_mode.ts | 4 + .../core/src/render3/node_manipulation.ts | 18 ++- packages/core/test/render3/render_util.ts | 8 +- .../test/render3/renderer_factory_spec.ts | 111 +++++++++++++++++- 5 files changed, 162 insertions(+), 12 deletions(-) diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index a96a211e99..3f492db4f0 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -228,10 +228,10 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S ### View Encapsulation | Feature | Runtime | Spec | Compiler | | ----------------------------------- | ------- | -------- | -------- | -| Render3.None | ✅ | ✅ | ✅ | -| Render2.None | ✅ | ✅ | ✅ | -| Render2.Emulated | ❌ | ❌ | ❌ | -| Render2.Native | ❌ | ❌ | ❌ | +| Renderer3.None | ✅ | ✅ | ✅ | +| Renderer2.None | ✅ | ✅ | ✅ | +| Renderer2.Emulated | ❌ | ❌ | ❌ | +| Renderer2.Native | ❌ | ❌ | ❌ | @@ -254,3 +254,28 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S | `checkNoChanges()` | n/a | n/a | ❌ | n/a | n/a | ✅ | | `reattach()` | n/a | n/a | ❌ | n/a | n/a | ✅ | | `nativeElement()` | n/a | n/a | n/a | n/a | ✅ | n/a | + +### Renderer2 +| Method | Runtime | +| ----------------------------------- | ------- | +| `data()` | n/a | +| `destroy()` | ✅ | +| `createElement()` | ✅ | +| `createComment()` | n/a | +| `createText()` | ✅ | +| `destroyNode()` | ✅ | +| `appendChild()` | ✅ | +| `insertBefore()` | ✅ | +| `removeChild()` | ✅ | +| `selectRootElement()` | ✅ | +| `parentNode()` | ❌ | +| `nextSibling()` | ❌ | +| `setAttribute()` | ✅ | +| `removeAttribute()` | ✅ | +| `addClass()` | ✅ | +| `removeClass()` | ✅ | +| `setStyle()` | ✅ | +| `removeStyle()` | ✅ | +| `setProperty()` | ✅ | +| `setValue()` | ✅ | +| `listen()` | ✅ | \ No newline at end of file diff --git a/packages/core/src/render3/ng_dev_mode.ts b/packages/core/src/render3/ng_dev_mode.ts index 3580605386..e3a2d7188b 100644 --- a/packages/core/src/render3/ng_dev_mode.ts +++ b/packages/core/src/render3/ng_dev_mode.ts @@ -25,6 +25,8 @@ declare global { rendererRemoveClass: number; rendererSetStyle: number; rendererRemoveStyle: number; + rendererDestroy: number; + rendererDestroyNode: number; } } @@ -50,6 +52,8 @@ export const ngDevModeResetPerfCounters: () => void = rendererRemoveClass: 0, rendererSetStyle: 0, rendererRemoveStyle: 0, + rendererDestroy: 0, + rendererDestroyNode: 0, }; } ngDevModeResetPerfCounters(); diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index cb0bc5bdee..1000197b31 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -9,7 +9,7 @@ import {assertNotNull} from './assert'; import {callHooks} from './hooks'; import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; @@ -215,8 +215,15 @@ export function addRemoveViewFromContainer( renderer.insertBefore(parent, node.native !, beforeNode as RNode | null) : parent.insertBefore(node.native !, beforeNode as RNode | null, true); } else { - isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, node.native !) : - parent.removeChild(node.native !); + if (isProceduralRenderer(renderer)) { + renderer.removeChild(parent as RElement, node.native !); + if (renderer.destroyNode) { + ngDevMode && ngDevMode.rendererDestroyNode++; + renderer.destroyNode(node.native !); + } + } else { + parent.removeChild(node.native !); + } } nextNode = getNextLNode(node); } else if (node.tNode.type === TNodeType.Container) { @@ -398,6 +405,11 @@ function cleanUpView(view: LView): void { removeListeners(view); executeOnDestroys(view); executePipeOnDestroys(view); + // For component views only, the local renderer is destroyed as clean up time. + if (view.id === -1 && isProceduralRenderer(view.renderer)) { + ngDevMode && ngDevMode.rendererDestroy++; + view.renderer.destroy(); + } } /** Removes listeners and unsubscribes from output subscriptions */ diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 655ff053d5..7bb954e2cd 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -53,6 +53,7 @@ export class TemplateFixture extends BaseFixture { private _directiveDefs: DirectiveDefList|null; private _pipeDefs: PipeDefList|null; private _sanitizer: Sanitizer|null; + private _rendererFactory: RendererFactory3; /** * @@ -64,11 +65,12 @@ export class TemplateFixture extends BaseFixture { constructor( private createBlock: () => void, private updateBlock: () => void = noop, directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null, - sanitizer?: Sanitizer) { + sanitizer?: Sanitizer|null, rendererFactory?: RendererFactory3) { super(); this._directiveDefs = toDefs(directives, extractDirectiveDef); this._pipeDefs = toDefs(pipes, extractPipeDef); this._sanitizer = sanitizer || null; + this._rendererFactory = rendererFactory || domRendererFactory3; this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => { if (rf & RenderFlags.Create) { this.createBlock(); @@ -76,7 +78,7 @@ export class TemplateFixture extends BaseFixture { if (rf & RenderFlags.Update) { this.updateBlock(); } - }, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs, sanitizer); + }, null !, this._rendererFactory, null, this._directiveDefs, this._pipeDefs, sanitizer); } /** @@ -86,7 +88,7 @@ export class TemplateFixture extends BaseFixture { */ update(updateBlock?: () => void): void { renderTemplate( - this.hostNode.native, updateBlock || this.updateBlock, null !, domRendererFactory3, + this.hostNode.native, updateBlock || this.updateBlock, null !, this._rendererFactory, this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer); } } diff --git a/packages/core/test/render3/renderer_factory_spec.ts b/packages/core/test/render3/renderer_factory_spec.ts index 68cbe30ba5..c2d8200a15 100644 --- a/packages/core/test/render3/renderer_factory_spec.ts +++ b/packages/core/test/render3/renderer_factory_spec.ts @@ -11,12 +11,12 @@ import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/brow import {RendererType2, ViewEncapsulation} from '../../src/core'; import {defineComponent, detectChanges} from '../../src/render3/index'; -import {bind, elementEnd, elementProperty, elementStart, listener, text, tick} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, text, tick} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {createRendererType2} from '../../src/view/index'; import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2'; -import {containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util'; +import {TemplateFixture, containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util'; describe('renderer factory lifecycle', () => { let logs: string[] = []; @@ -206,3 +206,110 @@ describe('animation renderer factory', () => { }); }); }); + +describe('Renderer2 destruction hooks', () => { + const rendererFactory = getRendererFactory2(document); + const origCreateRenderer = rendererFactory.createRenderer; + rendererFactory.createRenderer = function() { + const renderer = origCreateRenderer.apply(this, arguments); + renderer.destroyNode = () => {}; + return renderer; + }; + + it('should call renderer.destroyNode for each node destroyed', () => { + let condition = true; + + function createTemplate() { + elementStart(0, 'div'); + { container(1); } + elementEnd(); + } + + function updateTemplate() { + containerRefreshStart(1); + { + if (condition) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'span'); + elementEnd(); + elementStart(1, 'span'); + elementEnd(); + elementStart(2, 'span'); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + + const t = + new TemplateFixture(createTemplate, updateTemplate, null, null, null, rendererFactory); + + expect(t.html).toEqual('
'); + + condition = false; + t.update(); + expect(t.html).toEqual('
'); + expect(ngDevMode).toHaveProperties({rendererDestroy: 0, rendererDestroyNode: 3}); + }); + + it('should call renderer.destroy for each component destroyed', () => { + class SimpleComponent { + static ngComponentDef = defineComponent({ + type: SimpleComponent, + selectors: [['simple']], + template: function(rf: RenderFlags, ctx: SimpleComponent) { + if (rf & RenderFlags.Create) { + elementStart(0, 'span'); + elementEnd(); + } + }, + factory: () => new SimpleComponent, + }); + } + + let condition = true; + + function createTemplate() { + elementStart(0, 'div'); + { container(1); } + elementEnd(); + } + + function updateTemplate() { + containerRefreshStart(1); + { + if (condition) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'simple'); + elementEnd(); + elementStart(1, 'span'); + elementEnd(); + elementStart(2, 'simple'); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + + const t = new TemplateFixture( + createTemplate, updateTemplate, [SimpleComponent], null, null, rendererFactory); + + expect(t.html).toEqual( + '
'); + + condition = false; + t.update(); + expect(t.html).toEqual('
'); + expect(ngDevMode).toHaveProperties({rendererDestroy: 2, rendererDestroyNode: 3}); + }); +}); From 7c1bd7170e77ebea0a266615da31498151e20548 Mon Sep 17 00:00:00 2001 From: gjdev Date: Tue, 22 May 2018 13:44:36 +0200 Subject: [PATCH 124/582] docs(aio): add blox material library to resources (#20539) PR Close #20539 --- aio/content/marketing/resources.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index f8423a78ae..eac77391b0 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -377,6 +377,13 @@ "url": "http://www.amexio.tech/", "logo": "http://www.amexio.org/amexio-logo.png" }, + "bm": { + "desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web", + "logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg", + "rev": true, + "title": "Blox Material", + "url": "https://github.com/src-zone/material" + }, "essentialjs2": { "desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.", "rev": true, From 3ed7fc668641e62767eb3bcf61e0f5e08a1875cc Mon Sep 17 00:00:00 2001 From: Aaron Gussman Date: Tue, 13 Mar 2018 01:53:27 -0400 Subject: [PATCH 125/582] docs: update docs to use HttpClientModule instead of HttpModule (#22727) Updated most examples to use HttpClientModule instead of deprecated HttpModule fix #19280 PR Close #22727 --- .../examples/bootstrapping/src/app/app.module.ts | 4 ++-- .../src/app/app.module.ts | 4 ++-- .../examples/feature-modules/src/app/app.module.ts | 4 ++-- .../lazy-loading-ngmodules/src/app/app.module.ts | 4 ++-- aio/content/examples/ngmodules/src/app/app.module.ts | 2 +- aio/content/examples/styleguide/src/app/app.module.ts | 6 +++--- aio/content/guide/bootstrapping.md | 11 ++++------- aio/content/guide/entry-components.md | 2 +- 8 files changed, 17 insertions(+), 20 deletions(-) diff --git a/aio/content/examples/bootstrapping/src/app/app.module.ts b/aio/content/examples/bootstrapping/src/app/app.module.ts index 58c78b33ac..9b8a4fcaef 100644 --- a/aio/content/examples/bootstrapping/src/app/app.module.ts +++ b/aio/content/examples/bootstrapping/src/app/app.module.ts @@ -5,7 +5,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; // #docregion directive-import @@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive'; imports: [ BrowserModule, FormsModule, - HttpModule + HttpClientModule ], providers: [], bootstrap: [AppComponent] diff --git a/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts b/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts index a240e21f7c..490670a71c 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts @@ -1,7 +1,7 @@ // #docregion import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; // import { AppRoutingModule } from './app-routing.module'; import { LocationStrategy, @@ -54,7 +54,7 @@ const c_components = [ imports: [ BrowserModule, FormsModule, - HttpModule, + HttpClientModule, InMemoryWebApiModule.forRoot(HeroData) // AppRoutingModule TODO: add routes ], diff --git a/aio/content/examples/feature-modules/src/app/app.module.ts b/aio/content/examples/feature-modules/src/app/app.module.ts index 503d6a46a1..e74c19377a 100644 --- a/aio/content/examples/feature-modules/src/app/app.module.ts +++ b/aio/content/examples/feature-modules/src/app/app.module.ts @@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; // import the feature module here so you can add it to the imports array below @@ -17,7 +17,7 @@ import { CustomerDashboardModule } from './customer-dashboard/customer-dashboard imports: [ BrowserModule, FormsModule, - HttpModule, + HttpClientModule, CustomerDashboardModule // add the feature module here ], providers: [], diff --git a/aio/content/examples/lazy-loading-ngmodules/src/app/app.module.ts b/aio/content/examples/lazy-loading-ngmodules/src/app/app.module.ts index fa3ee4def3..d8d65b4404 100644 --- a/aio/content/examples/lazy-loading-ngmodules/src/app/app.module.ts +++ b/aio/content/examples/lazy-loading-ngmodules/src/app/app.module.ts @@ -1,7 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -13,7 +13,7 @@ import { AppComponent } from './app.component'; imports: [ BrowserModule, FormsModule, - HttpModule, + HttpClientModule, AppRoutingModule ], providers: [], diff --git a/aio/content/examples/ngmodules/src/app/app.module.ts b/aio/content/examples/ngmodules/src/app/app.module.ts index a19fbbae52..a2bae93468 100644 --- a/aio/content/examples/ngmodules/src/app/app.module.ts +++ b/aio/content/examples/ngmodules/src/app/app.module.ts @@ -1,7 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; /* App Root */ import { AppComponent } from './app.component'; diff --git a/aio/content/examples/styleguide/src/app/app.module.ts b/aio/content/examples/styleguide/src/app/app.module.ts index 1e974baf5f..2420170d4f 100644 --- a/aio/content/examples/styleguide/src/app/app.module.ts +++ b/aio/content/examples/styleguide/src/app/app.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule } from '@angular/platform-browser'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { RouterModule } from '@angular/router'; @@ -44,7 +44,7 @@ import * as s0901 from '../09-01/app/app.module'; @NgModule({ imports: [ BrowserModule, - HttpModule, + HttpClientModule, InMemoryWebApiModule.forRoot(HeroData), s0101.AppModule, diff --git a/aio/content/guide/bootstrapping.md b/aio/content/guide/bootstrapping.md index dc5b873d29..42c0ffe7ae 100644 --- a/aio/content/guide/bootstrapping.md +++ b/aio/content/guide/bootstrapping.md @@ -18,8 +18,7 @@ If you use the CLI to generate an app, the default `AppModule` is as follows: /* JavaScript imports */ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; + import { AppComponent } from './app.component'; @@ -29,9 +28,7 @@ import { AppComponent } from './app.component'; AppComponent ], imports: [ - BrowserModule, - FormsModule, - HttpModule + BrowserModule ], providers: [], bootstrap: [AppComponent] @@ -137,8 +134,8 @@ It tells Angular about other NgModules that this particular module needs to func This list of modules are those that export components, directives, or pipes that the component templates in this module reference. In this case, the component is -`AppComponent`, which references components, directives, or pipes in `BrowserModule`, -`FormsModule`, or `HttpModule`. +`AppComponent`, which references components, directives, or pipes in `BrowserModule`. +Other common components in the examples are `FormsModule` and `HttpClientModule`. A component template can reference another component, directive, or pipe when the referenced class is declared in this module or the class was imported from another module. diff --git a/aio/content/guide/entry-components.md b/aio/content/guide/entry-components.md index d57654218d..990c8ae84a 100644 --- a/aio/content/guide/entry-components.md +++ b/aio/content/guide/entry-components.md @@ -36,7 +36,7 @@ The following is an example of specifying a bootstrapped component, imports: [ BrowserModule, FormsModule, - HttpModule, + HttpClientModule, AppRoutingModule ], providers: [], From 5b25c07795b137fd01e7588f9d2ab5977ab1b2d4 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 24 Mar 2018 07:37:54 +1300 Subject: [PATCH 126/582] docs(bazel): improve error message for ng_package with no metadata (#22964) PR Close #22964 --- packages/bazel/src/ng_package/packager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bazel/src/ng_package/packager.ts b/packages/bazel/src/ng_package/packager.ts index 649f79aa9f..603d8c5ef9 100644 --- a/packages/bazel/src/ng_package/packager.ts +++ b/packages/bazel/src/ng_package/packager.ts @@ -274,6 +274,8 @@ function main(args: string[]): number { // So ignore package.json files when we are missing data. console.error('WARNING: no module metadata for package', packageName); console.error(' Not updating the package.json file to point to it'); + console.error( + ' The ng_module for this package is possibly missing the module_name attribute '); return JSON.stringify(parsedPackage, null, 2); } From d6595ebd39ecf7554abb20406c43e9b438b96cce Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Fri, 25 May 2018 07:22:05 -0700 Subject: [PATCH 127/582] feat(platform-server): use EventManagerPlugin on the server (#24132) Previously event handlers on the server were setup directly. This change makes it so that the event registration on the server go through EventManagerPlugin just like on client. This allows us to add custom event registration handlers on the server which allows us to hook up preboot event handlers cleanly. PR Close #24132 --- .../src/dom/events/dom_events.ts | 2 +- packages/platform-server/src/server.ts | 4 +- packages/platform-server/src/server_events.ts | 30 ++++++++++++ .../platform-server/src/server_renderer.ts | 49 ++++++++++++------- 4 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 packages/platform-server/src/server_events.ts diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts index 42d44d2acc..6c1138d30e 100644 --- a/packages/platform-browser/src/dom/events/dom_events.ts +++ b/packages/platform-browser/src/dom/events/dom_events.ts @@ -110,7 +110,7 @@ export class DomEventsPlugin extends EventManagerPlugin { } private patchEvent() { - if (!Event || !Event.prototype) { + if (typeof Event === 'undefined' || !Event || !Event.prototype) { return; } if ((Event.prototype as any)[stopMethodSymbol]) { diff --git a/packages/platform-server/src/server.ts b/packages/platform-server/src/server.ts index 46dbd9707a..1b0febf54c 100644 --- a/packages/platform-server/src/server.ts +++ b/packages/platform-server/src/server.ts @@ -11,7 +11,7 @@ import {PlatformLocation, ɵPLATFORM_SERVER_ID as PLATFORM_SERVER_ID} from '@ang import {HttpClientModule} from '@angular/common/http'; import {Injectable, InjectionToken, Injector, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS} from '@angular/core'; import {HttpModule} from '@angular/http'; -import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser'; +import {BrowserModule, DOCUMENT, EVENT_MANAGER_PLUGINS, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser'; import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic'; import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations'; @@ -19,6 +19,7 @@ import {DominoAdapter, parseDocument} from './domino_adapter'; import {SERVER_HTTP_PROVIDERS} from './http'; import {ServerPlatformLocation} from './location'; import {PlatformState} from './platform_state'; +import {ServerEventManagerPlugin} from './server_events'; import {ServerRendererFactory2} from './server_renderer'; import {ServerStylesHost} from './styles_host'; import {INITIAL_CONFIG, PlatformConfig} from './tokens'; @@ -58,6 +59,7 @@ export const SERVER_RENDER_PROVIDERS: Provider[] = [ }, ServerStylesHost, {provide: SharedStylesHost, useExisting: ServerStylesHost}, + {provide: EVENT_MANAGER_PLUGINS, multi: true, useClass: ServerEventManagerPlugin}, ]; /** diff --git a/packages/platform-server/src/server_events.ts b/packages/platform-server/src/server_events.ts new file mode 100644 index 0000000000..25598633a9 --- /dev/null +++ b/packages/platform-server/src/server_events.ts @@ -0,0 +1,30 @@ +/** + * @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 {Inject, Injectable} from '@angular/core'; +import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser'; + +@Injectable() +export class ServerEventManagerPlugin /* extends EventManagerPlugin which is private */ { + constructor(@Inject(DOCUMENT) private doc: any) {} + + // Handle all events on the server. + supports(eventName: string) { return true; } + + addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { + return getDOM().onAndCancel(element, eventName, handler); + } + + addGlobalEventListener(element: string, eventName: string, handler: Function): Function { + const target: HTMLElement = getDOM().getGlobalEventTarget(this.doc, element); + if (!target) { + throw new Error(`Unsupported event target ${target} for event ${eventName}`); + } + return this.addEventListener(target, eventName, handler); + } +} diff --git a/packages/platform-server/src/server_renderer.ts b/packages/platform-server/src/server_renderer.ts index 77bcc16ccf..67c48be90a 100644 --- a/packages/platform-server/src/server_renderer.ts +++ b/packages/platform-server/src/server_renderer.ts @@ -8,7 +8,7 @@ import {DomElementSchemaRegistry} from '@angular/compiler'; import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, RootRenderer, ViewEncapsulation, ɵstringify as stringify} from '@angular/core'; -import {DOCUMENT, ɵNAMESPACE_URIS as NAMESPACE_URIS, ɵSharedStylesHost as SharedStylesHost, ɵflattenStyles as flattenStyles, ɵgetDOM as getDOM, ɵshimContentAttribute as shimContentAttribute, ɵshimHostAttribute as shimHostAttribute} from '@angular/platform-browser'; +import {DOCUMENT, EventManager, ɵNAMESPACE_URIS as NAMESPACE_URIS, ɵSharedStylesHost as SharedStylesHost, ɵflattenStyles as flattenStyles, ɵgetDOM as getDOM, ɵshimContentAttribute as shimContentAttribute, ɵshimHostAttribute as shimHostAttribute} from '@angular/platform-browser'; const EMPTY_ARRAY: any[] = []; @@ -19,9 +19,9 @@ export class ServerRendererFactory2 implements RendererFactory2 { private schema = new DomElementSchemaRegistry(); constructor( - private ngZone: NgZone, @Inject(DOCUMENT) private document: any, - private sharedStylesHost: SharedStylesHost) { - this.defaultRenderer = new DefaultServerRenderer2(document, ngZone, this.schema); + private eventManager: EventManager, private ngZone: NgZone, + @Inject(DOCUMENT) private document: any, private sharedStylesHost: SharedStylesHost) { + this.defaultRenderer = new DefaultServerRenderer2(eventManager, document, ngZone, this.schema); } createRenderer(element: any, type: RendererType2|null): Renderer2 { @@ -34,7 +34,8 @@ export class ServerRendererFactory2 implements RendererFactory2 { let renderer = this.rendererByCompId.get(type.id); if (!renderer) { renderer = new EmulatedEncapsulationServerRenderer2( - this.document, this.ngZone, this.sharedStylesHost, this.schema, type); + this.eventManager, this.document, this.ngZone, this.sharedStylesHost, this.schema, + type); this.rendererByCompId.set(type.id, renderer); } (renderer).applyToHost(element); @@ -61,7 +62,8 @@ class DefaultServerRenderer2 implements Renderer2 { data: {[key: string]: any} = Object.create(null); constructor( - private document: any, private ngZone: NgZone, private schema: DomElementSchemaRegistry) {} + private eventManager: EventManager, protected document: any, private ngZone: NgZone, + private schema: DomElementSchemaRegistry) {} destroy(): void {} @@ -69,10 +71,10 @@ class DefaultServerRenderer2 implements Renderer2 { createElement(name: string, namespace?: string, debugInfo?: any): any { if (namespace) { - return getDOM().createElementNS(NAMESPACE_URIS[namespace], name); + return getDOM().createElementNS(NAMESPACE_URIS[namespace], name, this.document); } - return getDOM().createElement(name); + return getDOM().createElement(name, this.document); } createComment(value: string, debugInfo?: any): any { return getDOM().createComment(value); } @@ -166,14 +168,25 @@ class DefaultServerRenderer2 implements Renderer2 { listen( target: 'document'|'window'|'body'|any, eventName: string, callback: (event: any) => boolean): () => void { - // Note: We are not using the EventsPlugin here as this is not needed - // to run our tests. checkNoSyntheticProp(eventName, 'listener'); - const el = - typeof target === 'string' ? getDOM().getGlobalEventTarget(this.document, target) : target; - const outsideHandler = (event: any) => this.ngZone.runGuarded(() => callback(event)); - return this.ngZone.runOutsideAngular( - () => getDOM().onAndCancel(el, eventName, outsideHandler) as any); + if (typeof target === 'string') { + return <() => void>this.eventManager.addGlobalEventListener( + target, eventName, this.decoratePreventDefault(callback)); + } + return <() => void>this.eventManager.addEventListener( + target, eventName, this.decoratePreventDefault(callback)) as() => void; + } + + private decoratePreventDefault(eventHandler: Function): Function { + return (event: any) => { + // Run the event handler inside the ngZone because event handlers are not patched + // by Zone on the server. This is required only for tests. + const allowDefaultBehavior = this.ngZone.runGuarded(() => eventHandler(event)); + if (allowDefaultBehavior === false) { + event.preventDefault(); + event.returnValue = false; + } + }; } } @@ -190,9 +203,9 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 { private hostAttr: string; constructor( - document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost, + eventManager: EventManager, document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost, schema: DomElementSchemaRegistry, private component: RendererType2) { - super(document, ngZone, schema); + super(eventManager, document, ngZone, schema); const styles = flattenStyles(component.id, component.styles, []); sharedStylesHost.addStyles(styles); @@ -203,7 +216,7 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 { applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); } createElement(parent: any, name: string): Element { - const el = super.createElement(parent, name); + const el = super.createElement(parent, name, this.document); super.setAttribute(el, this.contentAttr, ''); return el; } From c73196eb5921d30667d53b37e3ec052f39be71e1 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Thu, 24 May 2018 16:04:04 -0700 Subject: [PATCH 128/582] fix(platform-server): provide Domino DOM types globally (#24116) Fixes #23280, #23133. This fix lets code access DOM types like Node, HTMLElement in the code. These are invariant across requests and the corresponding classes from Domino can be safely provided during platform initialization. This is needed for the current sanitizer to work properly on platform-server. Also allows HTML types in injection - Ex. `@inject(DOCUMENT) doc: Document`. PR Close #24116 --- .../platform-server/src/domino_adapter.ts | 11 ++++++- .../platform-server/test/integration_spec.ts | 29 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/platform-server/src/domino_adapter.ts b/packages/platform-server/src/domino_adapter.ts index 8ea4035c80..17bf3c4fc9 100644 --- a/packages/platform-server/src/domino_adapter.ts +++ b/packages/platform-server/src/domino_adapter.ts @@ -13,6 +13,12 @@ function _notImplemented(methodName: string) { return new Error('This method is not implemented in DominoAdapter: ' + methodName); } +function setDomTypes() { + // Make all Domino types available as types in the global env. + Object.assign(global, domino.impl); + (global as any)['KeyboardEvent'] = domino.impl.Event; +} + /** * Parses a document string to a Document object. */ @@ -33,7 +39,10 @@ export function serializeDocument(doc: Document): string { * DOM Adapter for the server platform based on https://github.com/fgnass/domino. */ export class DominoAdapter extends BrowserDomAdapter { - static makeCurrent() { setRootDomAdapter(new DominoAdapter()); } + static makeCurrent() { + setDomTypes(); + setRootDomAdapter(new DominoAdapter()); + } private static defaultDoc: Document; diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index 092e135153..bc27c9d856 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -10,7 +10,7 @@ import {AnimationBuilder, animate, style, transition, trigger} from '@angular/an import {APP_BASE_HREF, PlatformLocation, isPlatformServer} from '@angular/common'; import {HttpClient, HttpClientModule} from '@angular/common/http'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; -import {ApplicationRef, CompilerFactory, Component, HostListener, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core'; +import {ApplicationRef, CompilerFactory, Component, HostListener, Inject, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core'; import {TestBed, async, inject} from '@angular/core/testing'; import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http'; import {MockBackend, MockConnection} from '@angular/http/testing'; @@ -254,6 +254,20 @@ class MyInputComponent { class NameModule { } +@Component({selector: 'app', template: '
'}) +class HTMLTypesApp { + html = 'foo bar'; + constructor(@Inject(DOCUMENT) doc: Document) {} +} + +@NgModule({ + declarations: [HTMLTypesApp], + imports: [BrowserModule.withServerTransition({appId: 'inner-html'}), ServerModule], + bootstrap: [HTMLTypesApp] +}) +class HTMLTypesModule { +} + const TEST_KEY = makeStateKey('test'); const STRING_KEY = makeStateKey('testString'); @@ -552,6 +566,19 @@ class EscapedTransferStoreModule { }); })); + it('should work with sanitizer to handle "innerHTML"', async(() => { + // Clear out any global states. These should be set when platform-server + // is initialized. + (global as any).Node = undefined; + (global as any).Document = undefined; + renderModule(HTMLTypesModule, {document: doc}).then(output => { + expect(output).toBe( + '' + + '
foo bar
'); + called = true; + }); + })); + it('should call render hook', async(() => { renderModule(RenderHookModule, {document: doc}).then(output => { // title should be added by the render hook. From aafb46a8fe729b1d1aaa232bd827b6fd88196396 Mon Sep 17 00:00:00 2001 From: John Papa Date: Tue, 13 Mar 2018 15:56:33 -0400 Subject: [PATCH 129/582] style(compiler): fix up grammar in error message (#24201) closes #22746 PR Close #24201 --- packages/compiler/src/metadata_resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts index db4c7decf0..d43556f56d 100644 --- a/packages/compiler/src/metadata_resolver.ts +++ b/packages/compiler/src/metadata_resolver.ts @@ -993,7 +993,7 @@ export class CompileMetadataResolver { providerMeta = new cpl.ProviderMeta(provider, {useClass: provider}); } else if (provider === void 0) { this._reportError(syntaxError( - `Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using 'barrel' index.ts files.`)); + `Encountered undefined provider! Usually this means you have a circular dependencies. This might be caused by using 'barrel' index.ts files.`)); return; } else { const providersInfo = From 223882aeb63e77109b7306689b2bbc22161364bf Mon Sep 17 00:00:00 2001 From: Aditya Bhardwaj Date: Fri, 11 May 2018 17:57:49 +0530 Subject: [PATCH 130/582] docs: fix typo (#24201) closes #23853 PR Close #24201 --- aio/content/guide/testing.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aio/content/guide/testing.md b/aio/content/guide/testing.md index 0acff26bf1..1efa71d480 100644 --- a/aio/content/guide/testing.md +++ b/aio/content/guide/testing.md @@ -204,8 +204,7 @@ The test consumes that spy in the same way it did earlier. Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test and rely on the `TestBed` to create classes and inject services. -There's another school of testing that never calls `beforeEach()` and -and prefers to create classes explicitly rather than use the `TestBed`. +There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`. Here's how you might rewrite one of the `MasterService` tests in that style. @@ -3400,4 +3399,4 @@ accidental corruption of remote resources. It can even be hard to navigate to the component you want to test. Because of these many obstacles, you should test DOM interaction -with unit testing techniques as much as possible. \ No newline at end of file +with unit testing techniques as much as possible. From 7c392160835da85dfb500b0395ae7696a49c4a30 Mon Sep 17 00:00:00 2001 From: Kyle Liu Date: Thu, 17 May 2018 13:29:50 -0700 Subject: [PATCH 131/582] docs(aio): fix typo for @NgModuledecorator to @NgModule decorator (#24201) closes #23974 PR Close #24201 --- aio/content/tutorial/toh-pt1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/tutorial/toh-pt1.md b/aio/content/tutorial/toh-pt1.md index 0be72e9311..b581fc2fea 100644 --- a/aio/content/tutorial/toh-pt1.md +++ b/aio/content/tutorial/toh-pt1.md @@ -175,7 +175,7 @@ This information is called _metadata_ Some of the metadata is in the `@Component` decorators that you added to your component classes. Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators. -The most important `@NgModule`decorator annotates the top-level **AppModule** class. +The most important `@NgModule` decorator annotates the top-level **AppModule** class. The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project. This is where you _opt-in_ to the `FormsModule`. From 42a7295203f887e28f4c847b817467b2b3830205 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 17 May 2018 16:19:44 +0200 Subject: [PATCH 132/582] refactor(ivy): remove dynamicViewCount from LContainer (#23963) PR Close #23963 --- packages/core/src/render3/di.ts | 15 +------- packages/core/src/render3/instructions.ts | 34 +++++++++++++------ .../core/src/render3/interfaces/container.ts | 11 +++--- packages/core/src/render3/interfaces/view.ts | 8 ----- .../hello_world/bundle.golden_symbols.json | 3 ++ .../bundling/todo/bundle.golden_symbols.json | 3 ++ 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index b57926f254..f5a0f82c1e 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -572,7 +572,7 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer ngDevMode && assertNodeOfPossibleTypes(vcRefHost, TNodeType.Container, TNodeType.Element); const hostParent = getParentLNode(vcRefHost) !; - const lContainer = createLContainer(hostParent, vcRefHost.view); + const lContainer = createLContainer(hostParent, vcRefHost.view, undefined, true); const lContainerNode: LContainerNode = createLNodeObject( TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null); @@ -644,19 +644,6 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { this._viewRefs.splice(adjustedIdx, 0, viewRef); - // If the view is dynamic (has a template), it needs to be counted both at the container - // level and at the node above the container. - if (lViewNode.data.template !== null) { - // Increment the container view count. - this._lContainerNode.data.dynamicViewCount++; - - // Look for the parent node and increment its dynamic view count. - const containerParent = getParentLNode(this._lContainerNode) as LElementNode; - if (containerParent !== null && containerParent.data !== null) { - ngDevMode && assertNodeOfPossibleTypes(containerParent, TNodeType.View, TNodeType.Element); - containerParent.data.dynamicViewCount++; - } - } return viewRef; } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 20752d436a..cc178c2eb0 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -321,7 +321,6 @@ export function createLView( bindingIndex: -1, template: template, context: context, - dynamicViewCount: 0, lifecycleStage: LifecycleStage.Init, queries: null, injector: currentView && currentView.injector, @@ -1455,19 +1454,28 @@ function generateInitialInputs( //// ViewContainer & View ////////////////////////// +/** + * Creates a LContainer, either from a container instruction, or for a ViewContainerRef. + * + * @param parentLNode the LNode in which the container's content will be rendered + * @param currentView The parent view of the LContainer + * @param template Optional the inline template (ng-template instruction case) + * @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case + * @returns LContainer + */ export function createLContainer( - parentLNode: LNode, currentView: LView, template?: ComponentTemplate): LContainer { + parentLNode: LNode, currentView: LView, template?: ComponentTemplate, + isForViewContainerRef?: boolean): LContainer { ngDevMode && assertNotNull(parentLNode, 'containers should have a parent'); return { views: [], - nextIndex: 0, + nextIndex: isForViewContainerRef ? null : 0, // If the direct parent of the container is a view, its views will need to be added // through insertView() when its parent view is being inserted: renderParent: canInsertNativeNode(parentLNode, currentView) ? parentLNode : null, template: template == null ? null : template, next: null, parent: currentView, - dynamicViewCount: 0, queries: null }; } @@ -1553,7 +1561,7 @@ export function containerRefreshEnd(): void { const container = previousOrParentNode as LContainerNode; container.native = undefined; ngDevMode && assertNodeType(container, TNodeType.Container); - const nextIndex = container.data.nextIndex; + const nextIndex = container.data.nextIndex !; // remove extra views at the end of the container while (nextIndex < container.data.views.length) { @@ -1563,7 +1571,9 @@ export function containerRefreshEnd(): void { function refreshDynamicChildren() { for (let current = currentView.child; current !== null; current = current.next) { - if (current.dynamicViewCount !== 0 && (current as LContainer).views) { + // Note: current can be a LView or a LContainer, but here we are only interested in LContainer. + // The distinction is made because nextIndex and views do not exist on LView. + if (isLContainer(current)) { const container = current as LContainer; for (let i = 0; i < container.views.length; i++) { const lViewNode = container.views[i]; @@ -1577,6 +1587,10 @@ function refreshDynamicChildren() { } } +function isLContainer(node: LView | LContainer): node is LContainer { + return (node as LContainer).nextIndex == null && (node as LContainer).views != null; +} + /** * Looks for a view with a given view block id inside a provided LContainer. * Removes views that need to be deleted in the process. @@ -1617,7 +1631,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { (isParent ? previousOrParentNode : getParentLNode(previousOrParentNode)) as LContainerNode; ngDevMode && assertNodeType(container, TNodeType.Container); const lContainer = container.data; - let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex, viewBlockId); + let viewNode: LViewNode|null = scanForView(container, lContainer.nextIndex !, viewBlockId); if (viewNode) { previousOrParentNode = viewNode; @@ -1630,7 +1644,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null, LViewFlags.CheckAlways, getCurrentSanitizer()); if (lContainer.queries) { - newView.queries = lContainer.queries.enterView(lContainer.nextIndex); + newView.queries = lContainer.queries.enterView(lContainer.nextIndex !); } enterView( @@ -1678,10 +1692,10 @@ export function embeddedViewEnd(): void { // used by the ViewContainerRef must be set. setRenderParentInProjectedNodes(lContainer.renderParent, viewNode); // it is a new view, insert it into collection of views for a given container - insertView(containerNode, viewNode, lContainer.nextIndex); + insertView(containerNode, viewNode, lContainer.nextIndex !); } - lContainer.nextIndex++; + lContainer.nextIndex !++; } leaveView(currentView !.parent !); ngDevMode && assertEqual(isParent, false, 'isParent'); diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index be4fedb11b..54cb538d7d 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -18,8 +18,11 @@ export interface LContainer { /** * The next active index in the views array to read or write to. This helps us * keep track of where we are in the views array. + * In the case the LContainer is created for a ViewContainerRef, + * it is set to null to identify this scenario, as indices are "absolute" in that case, + * i.e. provided directly by the user of the ViewContainerRef API. */ - nextIndex: number; + nextIndex: number|null; /** * This allows us to jump from a container to a sibling container or @@ -69,12 +72,6 @@ export interface LContainer { */ readonly template: ComponentTemplate|null; - /** - * A count of dynamic views rendered into this container. If this is non-zero, the `views` array - * will be traversed when refreshing dynamic views on this container. - */ - dynamicViewCount: number; - /** * Queries active for this container - all the views inserted to / removed from * this container are reported to queries referenced here. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index a405cdde93..a234389f4a 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -181,14 +181,6 @@ export interface LView { */ context: {}|RootContext|null; - /** - * A count of dynamic views that are children of this view (indirectly via containers). - * - * This is used to decide whether to scan children of this view when refreshing dynamic views - * after refreshing the view itself. - */ - dynamicViewCount: number; - /** * Queries active for this view - nodes from a view are reported to those queries */ diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 3a97e3864e..77abaff7ae 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -125,6 +125,9 @@ { "name": "invertObject" }, + { + "name": "isLContainer" + }, { "name": "isProceduralRenderer" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 34d33906bc..11389750f7 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -494,6 +494,9 @@ { "name": "isJsObject" }, + { + "name": "isLContainer" + }, { "name": "isListLikeIterable" }, From 24e5c5b425e68baf5f26d4be8f5dc2aca95517d3 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Wed, 23 May 2018 10:46:18 -0700 Subject: [PATCH 133/582] refactor(platform-browser): make HAMMER_LOADER non-nullable (#24077) PR Close #24077 --- packages/platform-browser/src/dom/events/hammer_gestures.ts | 4 ++-- tools/public_api_guard/platform-browser/platform-browser.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/platform-browser/src/dom/events/hammer_gestures.ts b/packages/platform-browser/src/dom/events/hammer_gestures.ts index f4f4b9c09c..7e2a5db227 100644 --- a/packages/platform-browser/src/dom/events/hammer_gestures.ts +++ b/packages/platform-browser/src/dom/events/hammer_gestures.ts @@ -63,7 +63,7 @@ export const HAMMER_GESTURE_CONFIG = new InjectionToken('Ha /** Function that loads HammerJS, returning a promise that is resolved once HammerJs is loaded. */ -export type HammerLoader = (() => Promise) | null; +export type HammerLoader = () => Promise; /** Injection token used to provide a {@link HammerLoader} to Angular. */ export const HAMMER_LOADER = new InjectionToken('HammerLoader'); @@ -146,7 +146,7 @@ export class HammerGesturesPlugin extends EventManagerPlugin { constructor( @Inject(DOCUMENT) doc: any, @Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig, private console: Console, - @Optional() @Inject(HAMMER_LOADER) private loader?: HammerLoader) { + @Optional() @Inject(HAMMER_LOADER) private loader?: HammerLoader|null) { super(doc); } diff --git a/tools/public_api_guard/platform-browser/platform-browser.d.ts b/tools/public_api_guard/platform-browser/platform-browser.d.ts index d56b6164c4..e1c07cbe9b 100644 --- a/tools/public_api_guard/platform-browser/platform-browser.d.ts +++ b/tools/public_api_guard/platform-browser/platform-browser.d.ts @@ -67,7 +67,7 @@ export declare class HammerGestureConfig { buildHammer(element: HTMLElement): HammerInstance; } -export declare type HammerLoader = (() => Promise) | null; +export declare type HammerLoader = () => Promise; /** @experimental */ export declare function makeStateKey(key: string): StateKey; From 646b42a11353a12bd0bd08789f24bfb47f5785cc Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 21 May 2018 08:15:19 -0700 Subject: [PATCH 134/582] feat(ivy): JIT renders the TODO app (#24138) This commit builds out enough of the JIT compiler to render //packages/core/test/bundling/todo, and allows the tests to run in JIT mode. To play with the app, run: bazel run --define=compile=jit //packages/core/test/bundling/todo:prodserver PR Close #24138 --- packages/bazel/src/ng_module.bzl | 4 +- packages/compiler/src/compiler.ts | 4 +- .../src/ml_parser/html_whitespaces.ts | 2 +- packages/compiler/src/render3/r3_jit.ts | 18 +-- packages/compiler/src/render3/view/api.ts | 22 +-- .../compiler/src/render3/view/compiler.ts | 34 ++--- .../compiler/src/render3/view/template.ts | 13 +- .../render3/r3_compiler_compliance_spec.ts | 4 +- packages/core/src/ivy_switch_legacy.ts | 1 + packages/core/src/ivy_switch_on.ts | 3 +- packages/core/src/metadata/directives.ts | 7 +- packages/core/src/render3/jit/directive.ts | 144 +++++++++++++----- packages/core/src/render3/jit/environment.ts | 41 ++++- packages/core/src/render3/jit/injectable.ts | 109 +++++++------ packages/core/src/render3/jit/module.ts | 54 +++++-- packages/core/src/render3/jit/util.ts | 7 +- packages/core/src/util.ts | 5 +- .../test/bundling/hello_world/BUILD.bazel | 2 +- .../hello_world_r2/bundle.golden_symbols.json | 3 - packages/core/test/bundling/todo/BUILD.bazel | 2 + .../bundling/todo/bundle.golden_symbols.json | 3 - packages/core/test/bundling/todo/index.ts | 60 ++++---- 22 files changed, 348 insertions(+), 194 deletions(-) diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index 0daa736273..8defd2ff4e 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -518,8 +518,8 @@ ng_module = rule( # TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information. -# It to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used instead, and -# should be removed once ngtsc is capable of fulfilling the same requirements. +# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used +# instead, and should be removed once ngtsc is capable of fulfilling the same requirements. internal_global_ng_module = rule( implementation = _ng_module_impl, attrs = dict(NG_MODULE_RULE_ATTRS, **{ diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 88303c3d46..b64abc6716 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -81,9 +81,9 @@ export {getParseErrors, isSyntaxError, syntaxError, Version} from './util'; export {SourceMap} from './output/source_map'; export * from './injectable_compiler_2'; export * from './render3/view/api'; -export {jitPatchDefinition} from './render3/r3_jit'; +export {jitExpression} from './render3/r3_jit'; export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler'; export {makeBindingParser, parseTemplate} from './render3/view/template'; -export {compileComponent, compileDirective} from './render3/view/compiler'; +export {compileComponentFromMetadata, compileDirectiveFromMetadata} from './render3/view/compiler'; // This file only reexports content of the `src` folder. Keep it that way. \ No newline at end of file diff --git a/packages/compiler/src/ml_parser/html_whitespaces.ts b/packages/compiler/src/ml_parser/html_whitespaces.ts index c9302a7288..f948a8481d 100644 --- a/packages/compiler/src/ml_parser/html_whitespaces.ts +++ b/packages/compiler/src/ml_parser/html_whitespaces.ts @@ -49,7 +49,7 @@ export function replaceNgsp(value: string): string { * 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 { +export 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 diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts index 5d1a93e3d9..17ccd0e186 100644 --- a/packages/compiler/src/render3/r3_jit.ts +++ b/packages/compiler/src/render3/r3_jit.ts @@ -50,19 +50,17 @@ class R3JitReflector implements CompileReflector { } /** - * JIT compiles an expression and monkey-patches the result of executing the expression onto a given - * type. + * JIT compiles an expression and returns the result of executing that expression. * - * @param type the type which will receive the monkey-patched result - * @param field name of the field on the type to monkey-patch * @param def the definition which will be compiled and executed to get the value to patch * @param context an object map of @angular/core symbol names to symbols which will be available in * the context of the compiled expression + * @param sourceUrl a URL to use for the source map of the compiled expression * @param constantPool an optional `ConstantPool` which contains constants used in the expression */ -export function jitPatchDefinition( - type: any, field: string, def: o.Expression, context: {[key: string]: any}, - constantPool?: ConstantPool): void { +export function jitExpression( + def: o.Expression, context: {[key: string]: any}, sourceUrl: string, + constantPool?: ConstantPool): any { // The ConstantPool may contain Statements which declare variables used in the final expression. // Therefore, its statements need to precede the actual JIT operation. The final statement is a // declaration of $def which is set to the expression being compiled. @@ -71,8 +69,6 @@ export function jitPatchDefinition( new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]), ]; - // Monkey patch the field on the given type with the result of compilation. - // TODO(alxhub): consider a better source url. - type[field] = jitStatements( - `ng://${type && type.name}/${field}`, statements, new R3JitReflector(context), false)['$def']; + const res = jitStatements(sourceUrl, statements, new R3JitReflector(context), false); + return res['$def']; } diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 71f1624a69..495ccf8976 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -66,6 +66,17 @@ export interface R3DirectiveMetadata { properties: {[key: string]: string}; }; + /** + * Information about usage of specific lifecycle events which require special treatment in the + * code generator. + */ + lifecycle: { + /** + * Whether the directive uses NgOnChanges. + */ + usesOnChanges: boolean; + }; + /** * A mapping of input field names to the property names. */ @@ -101,17 +112,6 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata { ngContentSelectors: string[]; }; - /** - * Information about usage of specific lifecycle events which require special treatment in the - * code generator. - */ - lifecycle: { - /** - * Whether the component uses NgOnChanges. - */ - usesOnChanges: boolean; - }; - /** * Information about the view queries made by the component. */ diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 6075ef0591..a057d83826 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -65,13 +65,22 @@ function baseDirectiveFields( // e.g 'outputs: {a: 'a'}` definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); + // e.g. `features: [NgOnChangesFeature(MyComponent)]` + const features: o.Expression[] = []; + if (meta.lifecycle.usesOnChanges) { + features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); + } + if (features.length) { + definitionMap.set('features', o.literalArr(features)); + } + return definitionMap; } /** * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`. */ -export function compileDirective( +export function compileDirectiveFromMetadata( meta: R3DirectiveMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3DirectiveDef { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); @@ -84,7 +93,7 @@ export function compileDirective( /** * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. */ -export function compileComponent( +export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser): R3ComponentDef { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); @@ -143,15 +152,6 @@ export function compileComponent( definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); } - // e.g. `features: [NgOnChangesFeature(MyComponent)]` - const features: o.Expression[] = []; - if (meta.lifecycle.usesOnChanges) { - features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type])); - } - if (features.length) { - definitionMap.set('features', o.literalArr(features)); - } - const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); const type = new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)])); @@ -175,7 +175,7 @@ export function compileDirectiveFromRender2( const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector); - const res = compileDirective(meta, outputCtx.constantPool, bindingParser); + const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser); // Create the partial class to be merged with the actual class. outputCtx.statements.push(new o.ClassStmt( @@ -211,15 +211,11 @@ export function compileComponentFromRender2( hasNgContent: render3Ast.hasNgContent, ngContentSelectors: render3Ast.ngContentSelectors, }, - lifecycle: { - usesOnChanges: - component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), - }, directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx), pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx), viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx), }; - const res = compileComponent(meta, outputCtx.constantPool, bindingParser); + const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser); // Create the partial class to be merged with the actual class. outputCtx.statements.push(new o.ClassStmt( @@ -251,6 +247,10 @@ function directiveMetadataFromGlobalMetadata( listeners: summary.hostListeners, properties: summary.hostProperties, }, + lifecycle: { + usesOnChanges: + directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges), + }, inputs: directive.inputs, outputs: directive.outputs, }; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index fc1b2a8513..b0d21a9a11 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -16,6 +16,7 @@ import {Lexer} from '../../expression_parser/lexer'; import {Parser} from '../../expression_parser/parser'; import * as html from '../../ml_parser/ast'; import {HtmlParser} from '../../ml_parser/html_parser'; +import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; import {ParseError, ParseSourceSpan} from '../../parse_util'; @@ -777,16 +778,24 @@ function interpolate(args: o.Expression[]): o.Expression { * @param template text of the template to parse * @param templateUrl URL to use for source mapping of the parsed template */ -export function parseTemplate(template: string, templateUrl: string): +export function parseTemplate( + template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}): {errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} { const bindingParser = makeBindingParser(); const htmlParser = new HtmlParser(); const parseResult = htmlParser.parse(template, templateUrl); + if (parseResult.errors && parseResult.errors.length > 0) { return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []}; } + + let rootNodes: html.Node[] = parseResult.rootNodes; + if (!options.preserveWhitespace) { + rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes); + } + const {nodes, hasNgContent, ngContentSelectors, errors} = - htmlAstToRender3Ast(parseResult.rootNodes, bindingParser); + htmlAstToRender3Ast(rootNodes, bindingParser); if (errors && errors.length > 0) { return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []}; } diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 3bd371cb21..a60ccb687c 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -1078,8 +1078,8 @@ describe('compiler compliance', () => { selectors: [['lifecycle-comp']], factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, inputs: {nameMin: 'name'}, - template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}, - features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)] + features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)], + template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {} });`; const SimpleLayoutDefinition = ` diff --git a/packages/core/src/ivy_switch_legacy.ts b/packages/core/src/ivy_switch_legacy.ts index b3d9427670..0da653c104 100644 --- a/packages/core/src/ivy_switch_legacy.ts +++ b/packages/core/src/ivy_switch_legacy.ts @@ -8,5 +8,6 @@ export const ivyEnabled = false; export const R3_COMPILE_COMPONENT: ((type: any, meta: any) => void)|null = null; +export const R3_COMPILE_DIRECTIVE: ((type: any, meta: any) => void)|null = null; export const R3_COMPILE_INJECTABLE: ((type: any, meta: any) => void)|null = null; export const R3_COMPILE_NGMODULE: ((type: any, meta: any) => void)|null = null; diff --git a/packages/core/src/ivy_switch_on.ts b/packages/core/src/ivy_switch_on.ts index 0dfe7be6ee..7b2d79214a 100644 --- a/packages/core/src/ivy_switch_on.ts +++ b/packages/core/src/ivy_switch_on.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {compileComponentDecorator} from './render3/jit/directive'; +import {compileComponentDecorator, compileDirective} from './render3/jit/directive'; import {compileInjectable} from './render3/jit/injectable'; import {compileNgModule} from './render3/jit/module'; export const ivyEnabled = true; export const R3_COMPILE_COMPONENT = compileComponentDecorator; +export const R3_COMPILE_DIRECTIVE = compileDirective; export const R3_COMPILE_INJECTABLE = compileInjectable; export const R3_COMPILE_NGMODULE = compileNgModule; diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index 34bb977191..ddc1756b63 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -8,7 +8,7 @@ import {ChangeDetectionStrategy} from '../change_detection/constants'; import {Provider} from '../di'; -import {R3_COMPILE_COMPONENT} from '../ivy_switch'; +import {R3_COMPILE_COMPONENT, R3_COMPILE_DIRECTIVE} from '../ivy_switch'; import {Type} from '../type'; import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators'; import {ViewEncapsulation} from './view'; @@ -400,8 +400,9 @@ export interface Directive { * * @Annotation */ -export const Directive: DirectiveDecorator = - makeDecorator('Directive', (dir: Directive = {}) => dir); +export const Directive: DirectiveDecorator = makeDecorator( + 'Directive', (dir: Directive = {}) => dir, undefined, undefined, + (type: Type, meta: Directive) => (R3_COMPILE_DIRECTIVE || (() => {}))(type, meta)); /** * Type of the Component decorator / constructor function. diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 7cabe4f363..c3551ed9cc 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {compileComponent as compileIvyComponent, parseTemplate, ConstantPool, makeBindingParser, WrappedNodeExpr, jitPatchDefinition,} from '@angular/compiler'; +import {ConstantPool, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata as compileIvyComponent, compileDirectiveFromMetadata as compileIvyDirective, jitExpression, makeBindingParser, parseTemplate} from '@angular/compiler'; -import {Component} from '../../metadata/directives'; +import {Component, Directive, HostBinding, Input, Output} from '../../metadata/directives'; import {ReflectionCapabilities} from '../../reflection/reflection_capabilities'; import {Type} from '../../type'; import {angularCoreEnv} from './environment'; -import {reflectDependencies} from './util'; +import {getReflect, reflectDependencies} from './util'; let _pendingPromises: Promise[] = []; @@ -31,45 +31,65 @@ export function compileComponent(type: Type, metadata: Component): Promise< if (!metadata.template) { throw new Error('templateUrl not yet supported'); } + const templateStr = metadata.template; - // Parse the template and check for errors. - const template = parseTemplate(metadata.template !, `ng://${type.name}/template.html`); - if (template.errors !== undefined) { - const errors = template.errors.map(err => err.toString()).join(', '); - throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`); - } + let def: any = null; + Object.defineProperty(type, 'ngComponentDef', { + get: () => { + if (def === null) { + // The ConstantPool is a requirement of the JIT'er. + const constantPool = new ConstantPool(); - // The ConstantPool is a requirement of the JIT'er. - const constantPool = new ConstantPool(); + // Parse the template and check for errors. + const template = parseTemplate(templateStr, `ng://${type.name}/template.html`); + if (template.errors !== undefined) { + const errors = template.errors.map(err => err.toString()).join(', '); + throw new Error(`Errors during JIT compilation of template for ${type.name}: ${errors}`); + } - // Compile the component metadata, including template, into an expression. - // TODO(alxhub): implement inputs, outputs, queries, etc. - const res = compileIvyComponent( - { - name: type.name, - type: new WrappedNodeExpr(type), - selector: metadata.selector !, template, - deps: reflectDependencies(type), - directives: new Map(), - pipes: new Map(), - host: { - attributes: {}, - listeners: {}, - properties: {}, - }, - inputs: {}, - outputs: {}, - lifecycle: { - usesOnChanges: false, - }, - queries: [], - typeSourceSpan: null !, - viewQueries: [], - }, - constantPool, makeBindingParser()); + // Compile the component metadata, including template, into an expression. + // TODO(alxhub): implement inputs, outputs, queries, etc. + const res = compileIvyComponent( + { + ...directiveMetadata(type, metadata), + template, + directives: new Map(), + pipes: new Map(), + viewQueries: [], + }, + constantPool, makeBindingParser()); - // Patch the generated expression as ngComponentDef on the type. - jitPatchDefinition(type, 'ngComponentDef', res.expression, angularCoreEnv, constantPool); + def = jitExpression( + res.expression, angularCoreEnv, `ng://${type.name}/ngComponentDef.js`, constantPool); + } + return def; + }, + }); + + return null; +} + +/** + * Compile an Angular directive according to its decorator metadata, and patch the resulting + * ngDirectiveDef onto the component type. + * + * In the event that compilation is not immediate, `compileDirective` will return a `Promise` which + * will resolve when compilation completes and the directive becomes usable. + */ +export function compileDirective(type: Type, directive: Directive): Promise|null { + let def: any = null; + Object.defineProperty(type, 'ngDirectiveDef', { + get: () => { + if (def === null) { + const constantPool = new ConstantPool(); + const sourceMapUrl = `ng://${type && type.name}/ngDirectiveDef.js`; + const res = compileIvyDirective( + directiveMetadata(type, directive), constantPool, makeBindingParser()); + def = jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool); + } + return def; + }, + }); return null; } @@ -95,3 +115,51 @@ export function awaitCurrentlyCompilingComponents(): Promise { _pendingPromises = []; return res; } + +/** + * Extract the `R3DirectiveMetadata` for a particular directive (either a `Directive` or a + * `Component`). + */ +function directiveMetadata(type: Type, metadata: Directive): R3DirectiveMetadata { + // Reflect inputs and outputs. + const props = getReflect().propMetadata(type); + const inputs: {[key: string]: string} = {}; + const outputs: {[key: string]: string} = {}; + + for (let field in props) { + props[field].forEach(ann => { + if (isInput(ann)) { + inputs[field] = ann.bindingPropertyName || field; + } else if (isOutput(ann)) { + outputs[field] = ann.bindingPropertyName || field; + } + }); + } + + return { + name: type.name, + type: new WrappedNodeExpr(type), + selector: metadata.selector !, + deps: reflectDependencies(type), + host: { + attributes: {}, + listeners: {}, + properties: {}, + }, + inputs, + outputs, + queries: [], + lifecycle: { + usesOnChanges: type.prototype.ngOnChanges !== undefined, + }, + typeSourceSpan: null !, + }; +} + +function isInput(value: any): value is Input { + return value.ngMetadataName === 'Input'; +} + +function isOutput(value: any): value is Output { + return value.ngMetadataName === 'Output'; +} diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index a5068eee8c..4714697558 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -17,15 +17,37 @@ import * as r3 from '../index'; * * This should be kept up to date with the public exports of @angular/core. */ -export const angularCoreEnv = { +export const angularCoreEnv: {[name: string]: Function} = { 'ɵdefineComponent': r3.defineComponent, + 'ɵdefineDirective': r3.defineDirective, 'defineInjectable': defineInjectable, 'ɵdefineNgModule': defineNgModule, 'ɵdirectiveInject': r3.directiveInject, 'inject': inject, + 'ɵinjectAttribute': r3.injectAttribute, + 'ɵinjectChangeDetectorRef': r3.injectChangeDetectorRef, + 'ɵinjectElementRef': r3.injectElementRef, + 'ɵinjectTemplateRef': r3.injectTemplateRef, + 'ɵinjectViewContainerRef': r3.injectViewContainerRef, + 'ɵNgOnChangesFeature': r3.NgOnChangesFeature, + 'ɵa': r3.a, + 'ɵb': r3.b, 'ɵC': r3.C, + 'ɵcR': r3.cR, + 'ɵcr': r3.cr, + 'ɵd': r3.d, 'ɵE': r3.E, 'ɵe': r3.e, + 'ɵf0': r3.f0, + 'ɵf1': r3.f1, + 'ɵf2': r3.f2, + 'ɵf3': r3.f3, + 'ɵf4': r3.f4, + 'ɵf5': r3.f5, + 'ɵf6': r3.f6, + 'ɵf7': r3.f7, + 'ɵf8': r3.f8, + 'ɵfV': r3.fV, 'ɵi1': r3.i1, 'ɵi2': r3.i2, 'ɵi3': r3.i3, @@ -34,6 +56,23 @@ export const angularCoreEnv = { 'ɵi6': r3.i6, 'ɵi7': r3.i7, 'ɵi8': r3.i8, + 'ɵk': r3.k, + 'ɵkn': r3.kn, + 'ɵL': r3.L, + 'ɵld': r3.ld, + 'ɵp': r3.p, + 'ɵpb1': r3.pb1, + 'ɵpb2': r3.pb2, + 'ɵpb3': r3.pb3, + 'ɵpb4': r3.pb4, + 'ɵpbV': r3.pbV, + 'ɵQ': r3.Q, + 'ɵqR': r3.qR, + 'ɵs': r3.s, + 'ɵsn': r3.sn, + 'ɵst': r3.st, 'ɵT': r3.T, 'ɵt': r3.t, + 'ɵV': r3.V, + 'ɵv': r3.v, }; diff --git a/packages/core/src/render3/jit/injectable.ts b/packages/core/src/render3/jit/injectable.ts index e0b4bc1a26..eaf199c6a5 100644 --- a/packages/core/src/render3/jit/injectable.ts +++ b/packages/core/src/render3/jit/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitPatchDefinition} from '@angular/compiler'; +import {Expression, LiteralExpr, R3DependencyMetadata, WrappedNodeExpr, compileInjectable as compileIvyInjectable, jitExpression} from '@angular/compiler'; import {Injectable} from '../../di/injectable'; import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../../di/provider'; @@ -27,60 +27,69 @@ export function compileInjectable(type: Type, meta?: Injectable): void { return; } - // Check whether the injectable metadata includes a provider specification. - const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || - isUseValueProvider(meta) || isUseExistingProvider(meta); + let def: any = null; + Object.defineProperty(type, 'ngInjectableDef', { + get: () => { + if (def === null) { + // Check whether the injectable metadata includes a provider specification. + const hasAProvider = isUseClassProvider(meta) || isUseFactoryProvider(meta) || + isUseValueProvider(meta) || isUseExistingProvider(meta); - let deps: R3DependencyMetadata[]|undefined = undefined; - if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { - deps = reflectDependencies(type); - } else if (isUseClassProvider(meta)) { - deps = meta.deps && convertDependencies(meta.deps); - } else if (isUseFactoryProvider(meta)) { - deps = meta.deps && convertDependencies(meta.deps) || []; - } + let deps: R3DependencyMetadata[]|undefined = undefined; + if (!hasAProvider || (isUseClassProvider(meta) && type === meta.useClass)) { + deps = reflectDependencies(type); + } else if (isUseClassProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps); + } else if (isUseFactoryProvider(meta)) { + deps = meta.deps && convertDependencies(meta.deps) || []; + } - // Decide which flavor of factory to generate, based on the provider specified. - // Only one of the use* fields should be set. - let useClass: Expression|undefined = undefined; - let useFactory: Expression|undefined = undefined; - let useValue: Expression|undefined = undefined; - let useExisting: Expression|undefined = undefined; + // Decide which flavor of factory to generate, based on the provider specified. + // Only one of the use* fields should be set. + let useClass: Expression|undefined = undefined; + let useFactory: Expression|undefined = undefined; + let useValue: Expression|undefined = undefined; + let useExisting: Expression|undefined = undefined; - if (!hasAProvider) { - // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. - // The deps will have been reflected above, causing the factory to create the class by calling - // its constructor with injected deps. - useClass = new WrappedNodeExpr(type); - } else if (isUseClassProvider(meta)) { - // The user explicitly specified useClass, and may or may not have provided deps. - useClass = new WrappedNodeExpr(meta.useClass); - } else if (isUseValueProvider(meta)) { - // The user explicitly specified useValue. - useValue = new WrappedNodeExpr(meta.useValue); - } else if (isUseFactoryProvider(meta)) { - // The user explicitly specified useFactory. - useFactory = new WrappedNodeExpr(meta.useFactory); - } else if (isUseExistingProvider(meta)) { - // The user explicitly specified useExisting. - useExisting = new WrappedNodeExpr(meta.useExisting); - } else { - // Can't happen - either hasAProvider will be false, or one of the providers will be set. - throw new Error(`Unreachable state.`); - } + if (!hasAProvider) { + // In the case the user specifies a type provider, treat it as {provide: X, useClass: X}. + // The deps will have been reflected above, causing the factory to create the class by + // calling + // its constructor with injected deps. + useClass = new WrappedNodeExpr(type); + } else if (isUseClassProvider(meta)) { + // The user explicitly specified useClass, and may or may not have provided deps. + useClass = new WrappedNodeExpr(meta.useClass); + } else if (isUseValueProvider(meta)) { + // The user explicitly specified useValue. + useValue = new WrappedNodeExpr(meta.useValue); + } else if (isUseFactoryProvider(meta)) { + // The user explicitly specified useFactory. + useFactory = new WrappedNodeExpr(meta.useFactory); + } else if (isUseExistingProvider(meta)) { + // The user explicitly specified useExisting. + useExisting = new WrappedNodeExpr(meta.useExisting); + } else { + // Can't happen - either hasAProvider will be false, or one of the providers will be set. + throw new Error(`Unreachable state.`); + } - const {expression} = compileIvyInjectable({ - name: type.name, - type: new WrappedNodeExpr(type), - providedIn: computeProvidedIn(meta.providedIn), - useClass, - useFactory, - useValue, - useExisting, - deps, + const {expression} = compileIvyInjectable({ + name: type.name, + type: new WrappedNodeExpr(type), + providedIn: computeProvidedIn(meta.providedIn), + useClass, + useFactory, + useValue, + useExisting, + deps, + }); + + def = jitExpression(expression, angularCoreEnv, `ng://${type.name}/ngInjectableDef.js`); + } + return def; + }, }); - - jitPatchDefinition(type, 'ngInjectableDef', expression, angularCoreEnv); } function computeProvidedIn(providedIn: Type| string | null | undefined): Expression { diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 991faf2913..51df46a86b 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitPatchDefinition} from '@angular/compiler'; +import {Expression, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule as compileIvyNgModule, jitExpression} from '@angular/compiler'; import {ModuleWithProviders, NgModule, NgModuleDef} from '../../metadata/ng_module'; import {Type} from '../../type'; @@ -33,17 +33,6 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { directives: [] as any[], pipes: [] as any[], }; - flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => { - if (decl.ngPipeDef) { - transitiveCompileScope.pipes.push(decl); - } else if (decl.ngComponentDef) { - transitiveCompileScope.directives.push(decl); - patchComponentWithScope(decl, type as any); - } else { - transitiveCompileScope.directives.push(decl); - decl.ngSelectorScope = type; - } - }); function addExportsFrom(module: Type& {ngModuleDef: NgModuleDef}): void { module.ngModuleDef.exports.forEach((exp: any) => { @@ -60,16 +49,49 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { flatten([(ngModule.imports || EMPTY_ARRAY), (ngModule.exports || EMPTY_ARRAY)]) .filter(importExport => isNgModule(importExport)) .forEach(mod => addExportsFrom(mod)); - jitPatchDefinition(type, 'ngModuleDef', res.expression, angularCoreEnv); - ((type as any).ngModuleDef as NgModuleDef).transitiveCompileScope = transitiveCompileScope; + + flatten(ngModule.declarations || EMPTY_ARRAY).forEach(decl => { + if (decl.ngPipeDef) { + transitiveCompileScope.pipes.push(decl); + } else if (decl.ngComponentDef) { + transitiveCompileScope.directives.push(decl); + patchComponentWithScope(decl, type as any); + } else { + transitiveCompileScope.directives.push(decl); + decl.ngSelectorScope = type; + } + }); + + let def: any = null; + Object.defineProperty(type, 'ngModuleDef', { + get: () => { + if (def === null) { + const meta: R3NgModuleMetadata = { + type: wrap(type), + bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap), + declarations: flatten(ngModule.declarations || EMPTY_ARRAY).map(wrap), + imports: + flatten(ngModule.imports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + exports: + flatten(ngModule.exports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + emitInline: true, + }; + const res = compileIvyNgModule(meta); + def = jitExpression(res.expression, angularCoreEnv, `ng://${type.name}/ngModuleDef.js`); + def.transitiveCompileScope = transitiveCompileScope; + } + return def; + }, + }); } export function patchComponentWithScope( component: Type& {ngComponentDef: ComponentDef}, module: Type& {ngModuleDef: NgModuleDef}) { component.ngComponentDef.directiveDefs = () => - module.ngModuleDef.transitiveCompileScope !.directives.map( - dir => dir.ngDirectiveDef || dir.ngComponentDef); + module.ngModuleDef.transitiveCompileScope !.directives + .map(dir => dir.ngDirectiveDef || dir.ngComponentDef) + .filter(def => !!def); component.ngComponentDef.pipeDefs = () => module.ngModuleDef.transitiveCompileScope !.pipes.map(pipe => pipe.ngPipeDef); } diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index 3c07880c82..485ebd5dc9 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -19,9 +19,12 @@ import {Type} from '../../type'; let _reflect: ReflectionCapabilities|null = null; +export function getReflect(): ReflectionCapabilities { + return (_reflect = _reflect || new ReflectionCapabilities()); +} + export function reflectDependencies(type: Type): R3DependencyMetadata[] { - _reflect = _reflect || new ReflectionCapabilities(); - return convertDependencies(_reflect.parameters(type)); + return convertDependencies(getReflect().parameters(type)); } export function convertDependencies(deps: any[]): R3DependencyMetadata[] { diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts index 3ff5b2ae6a..df27d9a748 100644 --- a/packages/core/src/util.ts +++ b/packages/core/src/util.ts @@ -20,7 +20,10 @@ const __window = typeof window !== 'undefined' && window; const __self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope && self; const __global = typeof global !== 'undefined' && global; -const _global: {[name: string]: any} = __window || __global || __self; + +// Check __global first, because in Node tests both __global and __window may be defined and _global +// should be __global in that case. +const _global: {[name: string]: any} = __global || __window || __self; const promise: Promise = Promise.resolve(0); /** diff --git a/packages/core/test/bundling/hello_world/BUILD.bazel b/packages/core/test/bundling/hello_world/BUILD.bazel index 66549bedb6..786a215b92 100644 --- a/packages/core/test/bundling/hello_world/BUILD.bazel +++ b/packages/core/test/bundling/hello_world/BUILD.bazel @@ -44,7 +44,7 @@ jasmine_node_test( data = [ ":bundle", ":bundle.js", - ":bundle.min.js", + ":bundle.min.js.br", ":bundle.min_debug.js", ], tags = [ diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index fe5e7d7bbd..8d96a81a46 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -2072,9 +2072,6 @@ { "name": "__extends$7" }, - { - "name": "__global" - }, { "name": "__read" }, diff --git a/packages/core/test/bundling/todo/BUILD.bazel b/packages/core/test/bundling/todo/BUILD.bazel index f8d5684cbb..e3fd43ba1a 100644 --- a/packages/core/test/bundling/todo/BUILD.bazel +++ b/packages/core/test/bundling/todo/BUILD.bazel @@ -27,6 +27,7 @@ ng_rollup_bundle( entry_point = "packages/core/test/bundling/todo/index.js", deps = [ ":todo", + "//packages/common", "//packages/core", ], ) @@ -50,6 +51,7 @@ jasmine_node_test( ":bundle.min.js", ":bundle.min_debug.js", ], + tags = ["ivy-jit"], deps = [":test_lib"], ) diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 11389750f7..6b487a0142 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -119,9 +119,6 @@ { "name": "__extends" }, - { - "name": "__global" - }, { "name": "__read" }, diff --git a/packages/core/test/bundling/todo/index.ts b/packages/core/test/bundling/todo/index.ts index 86e355f953..d5694a3407 100644 --- a/packages/core/test/bundling/todo/index.ts +++ b/packages/core/test/bundling/todo/index.ts @@ -6,6 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ +import 'reflect-metadata'; + import {CommonModule, NgForOf, NgIf} from '@angular/common'; import {Component, Injectable, IterableDiffers, NgModule, defineInjector, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefineDirective as defineDirective, ɵdirectiveInject as directiveInject, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵrenderComponent as renderComponent} from '@angular/core'; @@ -145,35 +147,39 @@ export class ToDoAppComponent { } } -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule}); +// In JIT mode the @Directive decorators in //packages/common will compile the Ivy fields. When +// running under --define=compile=legacy, //packages/common is not compiled with Ivy fields, so they +// must be monkey-patched on. +if (!(NgIf as any).ngDirectiveDef) { + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule}); -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(NgForOf as any).ngDirectiveDef = defineDirective({ - type: NgForOf, - selectors: [['', 'ngFor', '', 'ngForOf', '']], - factory: () => new NgForOf( - injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)), - features: [NgOnChangesFeature({ - ngForOf: 'ngForOf', - ngForTrackBy: 'ngForTrackBy', - ngForTemplate: 'ngForTemplate', - })], - inputs: { - ngForOf: 'ngForOf', - ngForTrackBy: 'ngForTrackBy', - ngForTemplate: 'ngForTemplate', - } -}); - -// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. -(NgIf as any).ngDirectiveDef = defineDirective({ - type: NgIf, - selectors: [['', 'ngIf', '']], - factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()), - inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} -}); + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (NgForOf as any).ngDirectiveDef = defineDirective({ + type: NgForOf, + selectors: [['', 'ngFor', '', 'ngForOf', '']], + factory: () => new NgForOf( + injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers)), + features: [NgOnChangesFeature({ + ngForOf: 'ngForOf', + ngForTrackBy: 'ngForTrackBy', + ngForTemplate: 'ngForTemplate', + })], + inputs: { + ngForOf: 'ngForOf', + ngForTrackBy: 'ngForTrackBy', + ngForTemplate: 'ngForTemplate', + } + }); + // TODO(misko): This hack is here because common is not compiled with Ivy flag turned on. + (NgIf as any).ngDirectiveDef = defineDirective({ + type: NgIf, + selectors: [['', 'ngIf', '']], + factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()), + inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} + }); +} @NgModule({declarations: [ToDoAppComponent, ToDoAppComponent], imports: [CommonModule]}) export class ToDoAppModule { From 62f751cd8777b13f30484dcd428d001762d2edad Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Wed, 30 May 2018 11:04:26 +0300 Subject: [PATCH 135/582] build: update brotli version in WORKSPACE (#24194) The updated version includes the fix for google/brotli#671. PR Close #24194 --- WORKSPACE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 9d0d875b2b..99988ed805 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -66,9 +66,9 @@ http_archive( http_archive( name = "org_brotli", - url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip", - strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e", - sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc", + url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip", + strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c", + sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828", ) # From 49d97f1ba0168952c869d98cf217b59e116cac46 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 30 May 2018 08:48:45 -0700 Subject: [PATCH 136/582] build: update rules_webtesting (#24198) this includes a fix for spammy browser installs that makes our CI logs hard to read PR Close #24198 --- WORKSPACE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 99988ed805..579ba2935c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -13,9 +13,9 @@ http_archive( http_archive( name = "io_bazel_rules_webtesting", - url = "https://github.com/bazelbuild/rules_webtesting/archive/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip", - strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683", - sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3", + url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip", + strip_prefix = "rules_webtesting-0.2.0", + sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c", ) http_archive( From 855d9c00e09c5e6df1cdca2ae337734d9bf034b6 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 30 May 2018 09:27:15 -0700 Subject: [PATCH 137/582] build: replace hard-coded master branch with the variable (#24199) PR Close #24199 --- scripts/github/merge-pr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/github/merge-pr b/scripts/github/merge-pr index 0204d6e56f..faa8aa4528 100755 --- a/scripts/github/merge-pr +++ b/scripts/github/merge-pr @@ -72,7 +72,7 @@ fi if [[ "$STATUS" != "All checks passed!" ]]; then echo PR $PR_NUMBER is failing with: $STATUS if [[ $FORCE == 1 ]]; then - echo FORCING: --force flag used to ignor PR status. + echo FORCING: --force flag used to ignore PR status. else echo Exting... exit 1 @@ -100,11 +100,11 @@ CHECKOUT_MASTER="git checkout merge_pr_master" CHECKOUT_PATCH="git checkout merge_pr_patch" RESTORE_BRANCH="git checkout $CURRENT_BRANCH" -FETCH_PR="git fetch git@github.com:angular/angular.git pull/$PR_NUMBER/head:merge_pr heads/master:merge_pr_master heads/$PATCH_BRANCH:merge_pr_patch -f" +FETCH_PR="git fetch git@github.com:angular/angular.git pull/$PR_NUMBER/head:merge_pr heads/$MASTER_BRANCH:merge_pr_master heads/$PATCH_BRANCH:merge_pr_patch -f" BASE_PR="git checkout merge_pr~$PR_SHA_COUNT -B merge_pr_base" SQUASH_PR="git rebase --autosquash --interactive merge_pr_base merge_pr" REWRITE_MESSAGE="git filter-branch -f --msg-filter \"$BASEDIR/utils/github_closes.js $PR_NUMBER\" merge_pr_base..merge_pr" -PUSH_BRANCHES="git push git@github.com:angular/angular.git merge_pr_master:master merge_pr_patch:$PATCH_BRANCH" +PUSH_BRANCHES="git push git@github.com:angular/angular.git merge_pr_master:$MASTER_BRANCH merge_pr_patch:$PATCH_BRANCH" CHERRY_PICK_PR="git cherry-pick merge_pr_base..merge_pr" echo "======================" From 1cd9e6c2ebc2cfadb65d28846c7b56548529b495 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Mon, 28 May 2018 11:57:36 +0200 Subject: [PATCH 138/582] feat(ivy): support queries with views inserted through ViewContainerRef (#24179) This PR tackles a simple case where ViewRef definition point () is the same as the insertion point (ViewContainerRef requested on the said ). For this particular case we can assume that we know a container into which a given view will be inserted when a view is created. This is not true fall all the possible cases so follow-up PR will be needed to extend this basic implementation. PR Close #24179 --- packages/core/src/render3/di.ts | 15 +- packages/core/src/render3/instructions.ts | 10 +- packages/core/src/render3/interfaces/query.ts | 18 +- .../core/src/render3/node_manipulation.ts | 13 +- packages/core/src/render3/query.ts | 47 +- packages/core/test/render3/query_spec.ts | 660 ++++++++++-------- 6 files changed, 460 insertions(+), 303 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index f5a0f82c1e..450fe9afcd 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,10 +20,10 @@ import {Type} from '../type'; import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; -import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; +import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; -import {QueryReadType} from './interfaces/query'; +import {LQueries, QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; @@ -576,6 +576,11 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer const lContainerNode: LContainerNode = createLNodeObject( TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null); + + if (vcRefHost.queries) { + lContainerNode.queries = vcRefHost.queries.container(); + } + const hostTNode = vcRefHost.tNode; if (!hostTNode.dynamicContainerNode) { hostTNode.dynamicContainerNode = @@ -701,7 +706,7 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated'); di.templateRef = new TemplateRef( getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !, - getRenderer(), hostTView.directiveRegistry, hostTView.pipeRegistry); + getRenderer(), hostNode.queries); } return di.templateRef; } @@ -712,13 +717,13 @@ class TemplateRef implements viewEngine_TemplateRef { constructor( elementRef: viewEngine_ElementRef, private _tView: TView, private _template: ComponentTemplate, private _renderer: Renderer3, - private _directives: DirectiveDefList|null, private _pipes: PipeDefList|null) { + private _queries: LQueries|null) { this.elementRef = elementRef; } createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { const viewNode = renderEmbeddedTemplate( - null, this._tView, this._template, context, this._renderer, this._directives, this._pipes); + null, this._tView, this._template, context, this._renderer, this._queries); return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context)); } } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index cc178c2eb0..ba586dfad2 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -493,8 +493,7 @@ export function renderTemplate( */ export function renderEmbeddedTemplate( viewNode: LViewNode | null, tView: TView, template: ComponentTemplate, context: T, - renderer: Renderer3, directives?: DirectiveDefList | null, - pipes?: PipeDefList | null): LViewNode { + renderer: Renderer3, queries?: LQueries | null): LViewNode { const _isParent = isParent; const _previousOrParentNode = previousOrParentNode; let oldView: LView; @@ -507,6 +506,10 @@ export function renderEmbeddedTemplate( const lView = createLView( -1, renderer, tView, template, context, LViewFlags.CheckAlways, getCurrentSanitizer()); + if (queries) { + lView.queries = queries.createView(); + } + viewNode = createLNode(null, TNodeType.View, null, null, null, lView); rf = RenderFlags.Create; } @@ -1643,8 +1646,9 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { const newView = createLView( viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null, LViewFlags.CheckAlways, getCurrentSanitizer()); + if (lContainer.queries) { - newView.queries = lContainer.queries.enterView(lContainer.nextIndex !); + newView.queries = lContainer.queries.createView(); } enterView( diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index 2ab94de136..57822a0815 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -28,19 +28,25 @@ export interface LQueries { addNode(node: LNode): void; /** - * Notify `LQueries` that a `LNode` has been created and needs to be added to query results - * if matching query predicate. + * Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need + * to prepare room for views that might be inserted into this container. */ container(): LQueries|null; /** - * Notify `LQueries` that a new view was created and is being entered in the creation mode. - * This allow queries to prepare space for matching nodes from views. + * Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room + * and collect nodes that match query predicate. */ - enterView(newViewIndex: number): LQueries|null; + createView(): LQueries|null; /** - * Notify `LQueries` that an `LViewNode` has been removed from `LContainerNode`. As a result all + * Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all + * the matching nodes from this view should be added to container's queries. + */ + insertView(newViewIndex: number): void; + + /** + * Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all * the matching nodes from this view should be removed from container's queries. */ removeView(removeIndex: number): void; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 1000197b31..95633efbea 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -328,6 +328,12 @@ export function insertView( viewNode.data.next = null; } + // Notify query that a new view has been added + const lView = viewNode.data; + if (lView.queries) { + lView.queries.insertView(index); + } + // If the container's renderParent is null, we know that it is a root node of its own parent view // and we should wait until that parent processes its nodes (otherwise, we will insert this view's // nodes twice - once now and once when its parent inserts its views). @@ -367,8 +373,13 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie views.splice(removeIndex, 1); destroyViewTree(viewNode.data); addRemoveViewFromContainer(container, viewNode, false); + // Notify query that view has been removed - container.data.queries && container.data.queries.removeView(removeIndex); + const removedLview = viewNode.data; + if (removedLview.queries) { + removedLview.queries.removeView(removeIndex); + } + return viewNode; } diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 4502b47b9c..21a69f8ff3 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -76,6 +76,12 @@ export interface LQuery { * This is what builds up the `QueryList._valuesTree`. */ values: any[]; + + /** + * A pointer to an array that stores collected values from views. This is necessary so we know a + * container into which to insert nodes collected from views. + */ + containerValues: any[]|null; } export class LQueries_ implements LQueries { @@ -118,8 +124,13 @@ export class LQueries_ implements LQueries { while (query) { const containerValues: any[] = []; // prepare room for views query.values.push(containerValues); - const clonedQuery: LQuery = - {next: null, list: query.list, predicate: query.predicate, values: containerValues}; + const clonedQuery: LQuery = { + next: null, + list: query.list, + predicate: query.predicate, + values: containerValues, + containerValues: null + }; clonedQuery.next = result; result = clonedQuery; query = query.next; @@ -128,15 +139,18 @@ export class LQueries_ implements LQueries { return result ? new LQueries_(result) : null; } - enterView(index: number): LQueries|null { + createView(): LQueries|null { let result: LQuery|null = null; let query = this.deep; while (query) { - const viewValues: any[] = []; // prepare room for view nodes - query.values.splice(index, 0, viewValues); - const clonedQuery: LQuery = - {next: null, list: query.list, predicate: query.predicate, values: viewValues}; + const clonedQuery: LQuery = { + next: null, + list: query.list, + predicate: query.predicate, + values: [], + containerValues: query.values + }; clonedQuery.next = result; result = clonedQuery; query = query.next; @@ -145,6 +159,17 @@ export class LQueries_ implements LQueries { return result ? new LQueries_(result) : null; } + insertView(index: number): void { + let query = this.deep; + while (query) { + ngDevMode && + assertNotNull( + query.containerValues, 'View queries need to have a pointer to container values.'); + query.containerValues !.splice(index, 0, query.values); + query = query.next; + } + } + addNode(node: LNode): void { add(this.shallow, node); add(this.deep, node); @@ -153,7 +178,10 @@ export class LQueries_ implements LQueries { removeView(index: number): void { let query = this.deep; while (query) { - const removed = query.values.splice(index, 1); + ngDevMode && + assertNotNull( + query.containerValues, 'View queries need to have a pointer to container values.'); + const removed = query.containerValues !.splice(index, 1); // mark a query as dirty only when removed view had matching modes ngDevMode && assertEqual(removed.length, 1, 'removed.length'); @@ -279,7 +307,8 @@ function createQuery( next: previous, list: queryList, predicate: createPredicate(predicate, read), - values: (queryList as any as QueryList_)._valuesTree + values: (queryList as any as QueryList_)._valuesTree, + containerValues: null }; } diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index e31fbd02d7..9309ec5022 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -5,14 +5,17 @@ * 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 {NgForOfContext} from '@angular/common'; + import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di'; import {QueryList, defineComponent, detectChanges} from '../../src/render3/index'; -import {container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; -import {createComponent, createDirective, renderComponent} from './render_util'; - +import {NgForOf, NgIf} from './common_with_def'; +import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util'; /** @@ -685,307 +688,406 @@ describe('query', () => { describe('view boundaries', () => { - it('should report results in embedded views', () => { - let firstEl; - /** - * - *
- *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } + describe('ViewContainerRef', () => { + + it('should report results in views inserted / removed by ngIf', () => { + + /** + * + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1, (rf1: RenderFlags, ctx1: any) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); } - embeddedViewEnd(); - } + }, null, ['ngIf', '']); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngIf', bind(ctx.value)); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }, [NgIf]); + + const fixture = new ComponentFixture(Cmpt); + const qList = fixture.component.query; + expect(qList.length).toBe(0); + + fixture.component.value = true; + fixture.update(); + expect(qList.length).toBe(1); + + fixture.component.value = false; + fixture.update(); + expect(qList.length).toBe(0); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); + it('should report results in views inserted / removed by ngFor', () => { - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toBe(firstEl); + /** + * + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1, (rf1: RenderFlags, row: NgForOfContext) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(0, 'id', bind(row.$implicit)); + } + }, null, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(ctx.value)); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }, [NgForOf]); + + const fixture = new ComponentFixture(Cmpt); + const qList = fixture.component.query; + expect(qList.length).toBe(0); + + fixture.component.value = ['a', 'b', 'c']; + fixture.update(); + fixture + .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 + expect(qList.length).toBe(3); + + fixture.component.value.splice(1, 1); // remove "b" + fixture.update(); + fixture + .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 + expect(qList.length).toBe(2); + + // make sure that a proper element was removed from query results + expect(qList.first.nativeElement.id).toBe('a'); + expect(qList.last.nativeElement.id).toBe('c'); + + }); - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(qList.length).toBe(0); }); - it('should add results from embedded views in the correct order - views and elements mix', - () => { - let firstEl, lastEl, viewEl; - /** - * - * - *
- *
- * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - firstEl = elementStart(1, 'span', null, ['foo', '']); - elementEnd(); - container(3); - lastEl = elementStart(4, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(3); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - viewEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + describe('JS blocks', () => { + + it('should report results in embedded views', () => { + let firstEl; + /** + * % if (exp) { + *
+ * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toBe(firstEl); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(qList.length).toBe(0); + }); + + it('should add results from embedded views in the correct order - views and elements mix', + () => { + let firstEl, lastEl, viewEl; + /** + * + * % if (exp) { + *
+ * % } + * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + firstEl = elementStart(1, 'span', null, ['foo', '']); + elementEnd(); + container(3); + lastEl = elementStart(4, 'span', null, ['foo', '']); + elementEnd(); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(3); + { + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + viewEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); + + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(3); + expect(qList.toArray()[0].nativeElement).toBe(firstEl); + expect(qList.toArray()[1].nativeElement).toBe(viewEl); + expect(qList.toArray()[2].nativeElement).toBe(lastEl); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(3); - expect(qList.toArray()[0].nativeElement).toBe(firstEl); - expect(qList.toArray()[1].nativeElement).toBe(viewEl); - expect(qList.toArray()[2].nativeElement).toBe(lastEl); - - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should add results from embedded views in the correct order - views side by side', () => { - let firstEl, lastEl; - /** - * - *
- *
- * - * - * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } - if (ctx.exp2) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + it('should add results from embedded views in the correct order - views side by side', () => { + let firstEl, lastEl; + /** + * % if (exp1) { + *
+ * % } if (exp2) { + * + * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); - - cmptInstance.exp2 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.last.nativeElement).toBe(lastEl); - - cmptInstance.exp1 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should add results from embedded views in the correct order - nested views', () => { - let firstEl, lastEl; - /** - * - *
- * - * - * - *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - container(2); - } - if (rf0 & RenderFlags.Update) { - containerRefreshStart(2); - { - if (ctx.exp2) { - let rf2 = embeddedViewStart(0); - { - if (rf2) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); } - containerRefreshEnd(); } + embeddedViewEnd(); + } + if (ctx.exp2) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } - embeddedViewEnd(); } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp2 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.last.nativeElement).toBe(lastEl); + + cmptInstance.exp1 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); - - cmptInstance.exp1 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toBe(firstEl); - - cmptInstance.exp2 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should support combination of deep and shallow queries', () => { - /** - * - *
- *
- * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - query(1, ['foo'], false, QUERY_READ_FROM_NODE); - container(2); - elementStart(3, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(2); - { - if (ctx.exp) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + it('should add results from embedded views in the correct order - nested views', () => { + let firstEl, lastEl; + /** + * % if (exp1) { + *
+ * % if (exp2) { + * + * } + * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.shallow = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + container(2); + } + if (rf0 & RenderFlags.Update) { + containerRefreshStart(2); + { + if (ctx.exp2) { + let rf2 = embeddedViewStart(0); + { + if (rf2) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp1 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toBe(firstEl); + + cmptInstance.exp2 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const deep = (cmptInstance.deep as any); - const shallow = (cmptInstance.shallow as any); - expect(deep.length).toBe(1); - expect(shallow.length).toBe(1); + it('should support combination of deep and shallow queries', () => { + /** + * + *
+ *
+ * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + query(1, ['foo'], false, QUERY_READ_FROM_NODE); + container(2); + elementStart(3, 'span', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(2); + { + if (ctx.exp) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); + queryRefresh(tmp = load>(1)) && (ctx.shallow = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const deep = (cmptInstance.deep as any); + const shallow = (cmptInstance.shallow as any); + expect(deep.length).toBe(1); + expect(shallow.length).toBe(1); - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(deep.length).toBe(2); - expect(shallow.length).toBe(1); + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(deep.length).toBe(2); + expect(shallow.length).toBe(1); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(deep.length).toBe(1); + expect(shallow.length).toBe(1); + }); - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(deep.length).toBe(1); - expect(shallow.length).toBe(1); }); }); From 95074ca3033bb4a45a0d9e4e16a7b4b4cabfe0d6 Mon Sep 17 00:00:00 2001 From: JoostK Date: Tue, 15 May 2018 22:07:20 +0200 Subject: [PATCH 139/582] fix(ivy): fix performance counter for textBinding instruction (#23924) PR Close #23924 --- packages/core/src/render3/instructions.ts | 17 +++++++++-------- packages/core/test/render3/integration_spec.ts | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ba586dfad2..c577aa5200 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1272,14 +1272,15 @@ export function text(index: number, value?: any): void { * @param value Stringified value to write. */ export function textBinding(index: number, value: T | NO_CHANGE): void { - ngDevMode && assertDataInRange(index); - let existingNode = data[index] as LTextNode; - ngDevMode && assertNotNull(existingNode, 'LNode should exist'); - ngDevMode && assertNotNull(existingNode.native, 'native element should exist'); - ngDevMode && ngDevMode.rendererSetText++; - value !== NO_CHANGE && - (isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) : - existingNode.native.textContent = stringify(value)); + if (value !== NO_CHANGE) { + ngDevMode && assertDataInRange(index); + const existingNode = data[index] as LTextNode; + ngDevMode && assertNotNull(existingNode, 'LNode should exist'); + ngDevMode && assertNotNull(existingNode.native, 'native element should exist'); + ngDevMode && ngDevMode.rendererSetText++; + isProceduralRenderer(renderer) ? renderer.setValue(existingNode.native, stringify(value)) : + existingNode.native.textContent = stringify(value); + } } ////////////////////////// diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 7fea1a24f3..257abc1938 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -68,6 +68,12 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, 'benoit')).toEqual('benoit'); expect(renderToHtml(Template, undefined)).toEqual(''); + expect(ngDevMode).toHaveProperties({ + firstTemplatePass: 0, + tNode: 0, + tView: 1, + rendererSetText: 2, + }); }); it('should render "null" as "" when used with `bind()`', () => { @@ -82,6 +88,12 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, 'benoit')).toEqual('benoit'); expect(renderToHtml(Template, null)).toEqual(''); + expect(ngDevMode).toHaveProperties({ + firstTemplatePass: 0, + tNode: 0, + tView: 1, + rendererSetText: 2, + }); }); it('should support creation-time values in text nodes', () => { @@ -95,6 +107,12 @@ describe('render3 integration test', () => { } expect(renderToHtml(Template, 'once')).toEqual('once'); expect(renderToHtml(Template, 'twice')).toEqual('once'); + expect(ngDevMode).toHaveProperties({ + firstTemplatePass: 0, + tNode: 0, + tView: 1, + rendererSetText: 1, + }); }); }); From 2a78d5e6fee26a6a43804f39da578cd9d79172ea Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Fri, 18 May 2018 12:50:52 +0200 Subject: [PATCH 140/582] refactor(core): clean up dupe'd imports in reflector (#24203) Closure Compiler in some configurations complains about duplicate imports. This change replaces the export-with-import with an export of the imported symbol. closes #23993 PR Close #24203 --- packages/core/src/reflection/reflector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/reflection/reflector.ts b/packages/core/src/reflection/reflector.ts index 8115a14e33..678941f442 100644 --- a/packages/core/src/reflection/reflector.ts +++ b/packages/core/src/reflection/reflector.ts @@ -10,8 +10,8 @@ import {Type} from '../type'; import {PlatformReflectionCapabilities} from './platform_reflection_capabilities'; import {GetterFn, MethodFn, SetterFn} from './types'; -export {PlatformReflectionCapabilities} from './platform_reflection_capabilities'; -export {GetterFn, MethodFn, SetterFn} from './types'; +export {PlatformReflectionCapabilities}; +export {GetterFn, MethodFn, SetterFn}; /** * Provides access to reflection data about symbols. Used internally by Angular From c917e5b5bb84edebdb79980d8cdc913c397856b3 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 30 May 2018 13:53:48 -0700 Subject: [PATCH 141/582] test(ivy): update TNode counts to reflect changes in #24113 (#24208) After #24113 there is 2 `TNode` in those tests: - 1 for the host, - 1 for the text node. The PR #23924 status was green because it branched off master before #24113 was merged in. PR Close #24208 --- packages/core/test/render3/integration_spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 257abc1938..fce716580d 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -70,7 +70,7 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, undefined)).toEqual(''); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 0, - tNode: 0, + tNode: 2, tView: 1, rendererSetText: 2, }); @@ -90,7 +90,7 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, null)).toEqual(''); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 0, - tNode: 0, + tNode: 2, tView: 1, rendererSetText: 2, }); @@ -109,7 +109,7 @@ describe('render3 integration test', () => { expect(renderToHtml(Template, 'twice')).toEqual('once'); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 0, - tNode: 0, + tNode: 2, tView: 1, rendererSetText: 1, }); From b96a3c8def377ce393341ecea8bfd7bbd61d3e47 Mon Sep 17 00:00:00 2001 From: Vikram Subramanian Date: Mon, 28 May 2018 00:20:53 -0700 Subject: [PATCH 142/582] fix(platform-server): avoid clash between server and client style encapsulation attributes (#24158) Previously the style encapsulation attributes(_nghost-* and _ngcontent-*) created on the server could overlap with the attributes and styles created by the client side app when it botstraps. In case the client is bootstrapping a lazy route, the client side styles are added before the server-side styles are removed. If the components on the client are bootstrapped in a different order than on the server, the styles generated by the client will cause the elements on the server to have the wrong styles. The fix puts the styles and attributes generated on the server in a completely differemt space so that they are not affected by the client generated styles. The client generated styles will only affect elements bootstrapped on the client. PR Close #24158 --- .../platform-browser/src/dom/events/dom_events.ts | 13 ++++++++++--- .../test/dom/events/event_manager_spec.ts | 2 +- packages/platform-server/src/server_renderer.ts | 8 +++++--- packages/platform-server/test/integration_spec.ts | 15 ++++++++++++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts index 6c1138d30e..5b4c6acaac 100644 --- a/packages/platform-browser/src/dom/events/dom_events.ts +++ b/packages/platform-browser/src/dom/events/dom_events.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, NgZone} from '@angular/core'; +import {isPlatformServer} from '@angular/common'; +import {Inject, Injectable, NgZone, Optional, PLATFORM_ID} from '@angular/core'; + + // Import zero symbols from zone.js. This causes the zone ambient type to be // added to the type-checker, without emitting any runtime module load statement import {} from 'zone.js'; @@ -103,10 +106,14 @@ const globalListener = function(event: Event) { @Injectable() export class DomEventsPlugin extends EventManagerPlugin { - constructor(@Inject(DOCUMENT) doc: any, private ngZone: NgZone) { + constructor( + @Inject(DOCUMENT) doc: any, private ngZone: NgZone, + @Optional() @Inject(PLATFORM_ID) platformId: {}|null) { super(doc); - this.patchEvent(); + if (!platformId || !isPlatformServer(platformId)) { + this.patchEvent(); + } } private patchEvent() { diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts index 185bf98822..b294e4e5c0 100644 --- a/packages/platform-browser/test/dom/events/event_manager_spec.ts +++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts @@ -24,7 +24,7 @@ import {el} from '../../../testing/src/browser_util'; beforeEach(() => { doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument(); zone = new NgZone({}); - domEventPlugin = new DomEventsPlugin(doc, zone); + domEventPlugin = new DomEventsPlugin(doc, zone, null); }); it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one', diff --git a/packages/platform-server/src/server_renderer.ts b/packages/platform-server/src/server_renderer.ts index 67c48be90a..6b8d1dfc34 100644 --- a/packages/platform-server/src/server_renderer.ts +++ b/packages/platform-server/src/server_renderer.ts @@ -206,11 +206,13 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 { eventManager: EventManager, document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost, schema: DomElementSchemaRegistry, private component: RendererType2) { super(eventManager, document, ngZone, schema); - const styles = flattenStyles(component.id, component.styles, []); + // Add a 's' prefix to style attributes to indicate server. + const componentId = 's' + component.id; + const styles = flattenStyles(componentId, component.styles, []); sharedStylesHost.addStyles(styles); - this.contentAttr = shimContentAttribute(component.id); - this.hostAttr = shimHostAttribute(component.id); + this.contentAttr = shimContentAttribute(componentId); + this.hostAttr = shimHostAttribute(componentId); } applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); } diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index bc27c9d856..435f2a5765 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -154,7 +154,11 @@ class MyAnimationApp { class AnimationServerModule { } -@Component({selector: 'app', template: `Works!`, styles: [':host { color: red; }']}) +@Component({ + selector: 'app', + template: `
Works!
`, + styles: ['div {color: blue; } :host { color: red; }'] +}) class MyStylesApp { } @@ -548,6 +552,15 @@ class EscapedTransferStoreModule { }); })); + + it('sets a prefix for the _nghost and _ngcontent attributes', async(() => { + renderModule(ExampleStylesModule, {document: doc}).then(output => { + expect(output).toMatch( + / - - - -[**Webpack**](https://webpack.github.io/) is a popular module bundler, -a tool for bundling application source code in convenient _chunks_ -and for loading that code from a server into a browser. - -It's an excellent alternative to the *SystemJS* approach used elsewhere in the documentation. -This guide offers a taste of Webpack and explains how to use it with Angular applications. - - -{@a top} - - - -You can also download the final result. - -{@a what-is-webpack} - -## What is Webpack? - -Webpack is a powerful module bundler. -A _bundle_ is a JavaScript file that incorporates _assets_ that *belong* together and -should be served to the client in a response to a single file request. -A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file. - -Webpack roams over your application source code, -looking for `import` statements, building a dependency graph, and emitting one or more _bundles_. -With plugins and rules, Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files. - -You determine what Webpack does and how it does it with a JavaScript configuration file, `webpack.config.js`. - - -{@a entries-outputs} - - - -### Entries and outputs - -You supply Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries. -The one entry point file in this example is the application's root file, `src/main.ts`: - - - - - - - - -Webpack inspects that file and traverses its `import` dependencies recursively. - - - - - - - - -It sees that you're importing `@angular/core` so it adds that to its dependency list for potential inclusion in the bundle. -It opens the `@angular/core` file and follows _its_ network of `import` statements until it has built the complete dependency graph from `main.ts` down. - -Then it **outputs** these files to the `app.js` _bundle file_ designated in configuration: - - - output: { - filename: 'app.js' - } - - - -This `app.js` output bundle is a single JavaScript file that contains the application source and its dependencies. -You'll load it later with a ` + + diff --git a/packages/bazel/test/protractor-2/on-prepare.js b/packages/bazel/test/protractor-2/on-prepare.js new file mode 100644 index 0000000000..1ce5b2d426 --- /dev/null +++ b/packages/bazel/test/protractor-2/on-prepare.js @@ -0,0 +1,22 @@ +/** + * @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 + */ + +const protractorUtils = require('@angular/bazel/protractor-utils'); +const protractor = require('protractor'); + +module.exports = function(config) { + if (!global.userOnPrepareGotCalled) { + throw new Error('Expecting user configuration onPrepare to have been called'); + } + const portFlag = config.server.endsWith('prodserver') ? '-p' : '-port'; + return protractorUtils.runServer(config.workspace, config.server, portFlag, []) + .then(serverSpec => { + const serverUrl = `http://localhost:${serverSpec.port}`; + protractor.browser.baseUrl = serverUrl; + }); +}; diff --git a/packages/bazel/test/protractor-2/test.spec.ts b/packages/bazel/test/protractor-2/test.spec.ts new file mode 100644 index 0000000000..cf8b5e9564 --- /dev/null +++ b/packages/bazel/test/protractor-2/test.spec.ts @@ -0,0 +1,28 @@ +/** + * @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 + * + * @fileoverview A small demo of how to run a protractor test. + */ + +import {ExpectedConditions, browser, by, element} from 'protractor'; + + +// This test uses Protractor without Angular, so disable Angular features +browser.waitForAngularEnabled(false); + +describe('app', () => { + beforeAll(() => { + browser.get(''); + browser.wait(ExpectedConditions.presenceOf(element(by.css('div.ts1'))), 100000); + }); + + it('should display: Hello, Protractor', (done) => { + const div = element(by.css('div.ts1')); + div.getText().then(t => expect(t).toEqual(`Hello, Protractor`)); + done(); + }); +}); diff --git a/packages/bazel/test/protractor-2/tsconfig.json b/packages/bazel/test/protractor-2/tsconfig.json new file mode 100644 index 0000000000..8f3c656796 --- /dev/null +++ b/packages/bazel/test/protractor-2/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["es2015"] + } +} diff --git a/packages/bazel/test/protractor/BUILD.bazel b/packages/bazel/test/protractor/BUILD.bazel index fe9ef9e9ec..ffd48d6ccb 100644 --- a/packages/bazel/test/protractor/BUILD.bazel +++ b/packages/bazel/test/protractor/BUILD.bazel @@ -7,8 +7,17 @@ ts_library( srcs = ["test.spec.ts"], ) +ts_library( + name = "ts_conf", + testonly = True, + srcs = ["conf.ts"], + tsconfig = ":tsconfig.json", + deps = ["//packages/bazel/src/protractor/utils"], +) + protractor_web_test_suite( - name = "demo", - conf = "conf.js", + name = "test", + configuration = ":ts_conf", + data = ["//packages/bazel/src/protractor/utils"], deps = [":ts_spec"], ) diff --git a/packages/bazel/test/protractor/conf.js b/packages/bazel/test/protractor/conf.js deleted file mode 100644 index 2ac5a3ce80..0000000000 --- a/packages/bazel/test/protractor/conf.js +++ /dev/null @@ -1,29 +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 - */ - -/** - * @fileoverview A demo of what a user's conf.js file might look like. - */ -const http = require('http'); - -const PORT = 3000; - -exports.config = { - baseUrl: `http://localhost:${PORT}`, - onPrepare() { - const app = new http.Server(); - - app.on('request', (req, res) => { - res.writeHead(200, {'Content-Type': 'text/plain'}); - res.write('Hello World'); - res.end('\n'); - }); - - return new Promise(resolve => { app.listen(PORT, () => { resolve(); }); }); - } -}; diff --git a/packages/bazel/test/protractor/conf.ts b/packages/bazel/test/protractor/conf.ts new file mode 100644 index 0000000000..2a3e21f4be --- /dev/null +++ b/packages/bazel/test/protractor/conf.ts @@ -0,0 +1,30 @@ +/** + * @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 protractorUtils from '@angular/bazel/protractor-utils'; +import {browser} from 'protractor'; + +const http = require('http'); + +exports.config = { + onPrepare() { + return protractorUtils.findFreeTcpPort().then(port => { + const app = new http.Server(); + + app.on('request', (req, res) => { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.write('Hello World'); + res.end('\n'); + }); + + browser.baseUrl = `http://localhost:${port}`; + + return new Promise(resolve => { app.listen(port, () => { resolve(); }); }); + }); + } +}; diff --git a/packages/bazel/test/protractor/tsconfig.json b/packages/bazel/test/protractor/tsconfig.json new file mode 100644 index 0000000000..8f3c656796 --- /dev/null +++ b/packages/bazel/test/protractor/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["es2015"] + } +} diff --git a/tools/http-server/http_server.bzl b/tools/http-server/http_server.bzl index e835fd5ffc..a8ef4422ef 100644 --- a/tools/http-server/http_server.bzl +++ b/tools/http-server/http_server.bzl @@ -4,15 +4,15 @@ See https://www.npmjs.com/package/http-server """ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") -def http_server(args = [], **kwargs): +def http_server(templated_args = [], **kwargs): # By default, we pass an argument pointing the http server to the # package of the caller. # This assumes there is an index.html in the package directory. - if not args: - args = [native.package_name()] + if not templated_args: + templated_args = [native.package_name()] nodejs_binary( node_modules = "@http-server_runtime_deps//:node_modules", entry_point = "http-server/bin/http-server", - args = args, + templated_args = templated_args, **kwargs) From ee50ee493d11d4810aa9d3ab2a778a3d469a3461 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Wed, 11 Jul 2018 10:34:24 -0700 Subject: [PATCH 467/582] build(bazel): try removing gazelle (#24787) PR Close #24787 --- WORKSPACE | 13 ------------- integration/bazel/WORKSPACE | 9 --------- 2 files changed, 22 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index a6c5b1ecf2..290420c7f5 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -11,15 +11,6 @@ http_archive( sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c", ) -http_archive( - name = "bazel_gazelle", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz", - "https://github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz", - ], - sha256 = "ddedc7aaeb61f2654d7d7d4fd7940052ea992ccdb031b8f9797ed143ac7e8d43", -) - http_archive( name = "io_bazel_rules_webtesting", url = "https://github.com/bazelbuild/rules_webtesting/archive/7ffe970bbf380891754487f66c3d680c087d67f2.zip", @@ -94,10 +85,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_too go_rules_dependencies() go_register_toolchains() -load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") - -gazelle_dependencies() - load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories") web_test_repositories() diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index 3f872be725..9c8f1e4c14 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -11,15 +11,6 @@ http_archive( sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c", ) -http_archive( - name = "bazel_gazelle", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz", - "https://github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz", - ], - sha256 = "ddedc7aaeb61f2654d7d7d4fd7940052ea992ccdb031b8f9797ed143ac7e8d43", -) - http_archive( name = "io_bazel_rules_webtesting", url = "https://github.com/bazelbuild/rules_webtesting/archive/8fd9ce0fd9254bde251da0bc373d6cd08e811434.zip", From d05d28629df95c583fd2a93e2836e002d0998f9c Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 11:55:55 -0700 Subject: [PATCH 468/582] test(common): run common/http tests with Bazel (#24738) @angular/common/http had tests which were not executed in Bazel. This commit adds a BUILD.bazel file and ensures the tests pass. PR Close #24738 --- packages/common/http/test/BUILD.bazel | 35 ++++++++++++++++++++++++++ packages/common/http/test/xsrf_spec.ts | 8 ++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 packages/common/http/test/BUILD.bazel diff --git a/packages/common/http/test/BUILD.bazel b/packages/common/http/test/BUILD.bazel new file mode 100644 index 0000000000..3f01fc2c37 --- /dev/null +++ b/packages/common/http/test/BUILD.bazel @@ -0,0 +1,35 @@ +load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite") +load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test") + +ts_library( + name = "test_lib", + testonly = 1, + srcs = glob( + ["**/*.ts"], + exclude = ["**/*_node_only_spec.ts"], + ), + deps = [ + "//packages/common/http", + "//packages/common/http/testing", + "//packages/core", + "//packages/core/testing", + "@rxjs", + "@rxjs//operators", + ], +) + +jasmine_node_test( + name = "test", + bootstrap = ["angular/tools/testing/init_node_spec.js"], + deps = [ + ":test_lib", + "//tools/testing:node", + ], +) + +ts_web_test_suite( + name = "test_web", + deps = [ + ":test_lib", + ], +) diff --git a/packages/common/http/test/xsrf_spec.ts b/packages/common/http/test/xsrf_spec.ts index 20a3ac8250..c6ee4b10a7 100644 --- a/packages/common/http/test/xsrf_spec.ts +++ b/packages/common/http/test/xsrf_spec.ts @@ -76,13 +76,17 @@ class SampleTokenExtractor { it('does not re-parse if document.cookie has not changed', () => { expect(extractor.getToken()).toEqual('test'); expect(extractor.getToken()).toEqual('test'); - expect(extractor.parseCount).toEqual(1); + expect(getParseCount(extractor)).toEqual(1); }); it('re-parses if document.cookie changes', () => { expect(extractor.getToken()).toEqual('test'); document['cookie'] = 'XSRF-TOKEN=blah'; expect(extractor.getToken()).toEqual('blah'); - expect(extractor.parseCount).toEqual(2); + expect(getParseCount(extractor)).toEqual(2); }); }); } + +function getParseCount(extractor: HttpXsrfCookieExtractor): number { + return (extractor as any).parseCount; +} \ No newline at end of file From a1b630ee8ffca94cffea1c091122e692a19f87f6 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 14:03:18 -0700 Subject: [PATCH 469/582] fix(ivy): generate a type parameter for InjectorDef (#24738) InjectorDef is parameterized on the type of the injector configuration class (e.g. the @NgModule decorated type). Previously this parameter was not included when generating .d.ts files that contained InjectorDefs. PR Close #24738 --- packages/compiler/src/render3/r3_module_compiler.ts | 3 ++- tools/public_api_guard/core/core.d.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index 8b83f25931..921bae7ecf 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -107,7 +107,8 @@ export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { providers: meta.providers, imports: meta.imports, })]); - const type = new o.ExpressionType(o.importExpr(R3.InjectorDef)); + const type = + new o.ExpressionType(o.importExpr(R3.InjectorDef, [new o.ExpressionType(meta.type)])); return {expression, type}; } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 04893d869f..09052c005c 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -408,6 +408,12 @@ export declare abstract class Injector { /** @experimental */ export declare const INJECTOR: InjectionToken; +export interface InjectorDef { + factory: () => T; + imports: (InjectorType | InjectorTypeWithProviders)[]; + providers: (Type | ValueProvider | ExistingProvider | FactoryProvider | ConstructorProvider | StaticClassProvider | ClassProvider | any[])[]; +} + /** @experimental */ export interface InjectorType extends Type { ngInjectorDef: never; From 9f20dd937aa1a1a8fe3a3eace4238f9783db6395 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 14:03:46 -0700 Subject: [PATCH 470/582] feat(ivy): give ngtsc a basic understanding of ModuleWithProviders (#24738) This commit changes the @NgModule provider to understand that sometimes an import will resolve to an object instead of a type, and that object could be of the ModuleWithProviders type. In that case, the 'ngModule' property is read, and its value used instead. This still will not handle ModuleWithProviders references across compilation units; that work is coming in a future PR. PR Close #24738 --- .../compiler-cli/src/ngtsc/annotations/src/ng_module.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index dd5c304194..5a7df1c339 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -132,6 +132,12 @@ function resolveTypeList(resolvedList: ResolvedValue, name: string): Reference[] } resolvedList.forEach((entry, idx) => { + // Unwrap ModuleWithProviders for modules that are locally declared (and thus static resolution + // was able to descend into the function and return an object literal, a Map). + if (entry instanceof Map && entry.has('ngModule')) { + entry = entry.get('ngModule') !; + } + if (Array.isArray(entry)) { // Recurse into nested arrays. refList.push(...resolveTypeList(entry, name)); @@ -144,7 +150,7 @@ function resolveTypeList(resolvedList: ResolvedValue, name: string): Reference[] refList.push(entry); } else { // TODO(alxhub): expand ModuleWithProviders. - throw new Error(`Value at position ${idx} in ${name} array is not a reference`); + throw new Error(`Value at position ${idx} in ${name} array is not a reference: ${entry}`); } }); From cde0b4b3614656ef619be54e95a9c654dd588d51 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 14:17:42 -0700 Subject: [PATCH 471/582] =?UTF-8?q?fix(ivy):=20*Def=20types=20are=20privat?= =?UTF-8?q?e=20(=C9=B5)=20symbols=20(#24738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On accident a few of the definition types were emitted as public API symbols. Much of the Ivy API surface is still prefixed with ɵ, indicating it's a private API. The definition types should be private for now. PR Close #24738 --- .../src/ngtsc/transform/src/translator.ts | 4 ++-- packages/compiler-cli/test/ngtsc/ngtsc_spec.ts | 12 ++++++------ packages/compiler/src/render3/r3_identifiers.ts | 8 ++++---- packages/core/src/di.ts | 2 +- packages/core/src/r3_symbols.ts | 4 ++-- packages/core/test/render3/jit_environment_spec.ts | 8 ++++---- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts index b6b3122bad..c5e8f967cd 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts @@ -35,8 +35,8 @@ const CORE_SUPPORTED_SYMBOLS = new Set([ 'ɵdefineNgModule', 'inject', 'InjectableDef', - 'InjectorDef', - 'NgModuleDef', + 'ɵInjectorDef', + 'ɵNgModuleDef', ]); export class ImportManager { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 1ea38268bc..e690129325 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -144,7 +144,7 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).not.toContain('__decorate'); const dtsContents = getContents('test.d.ts'); - expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef'); + expect(dtsContents).toContain('static ngComponentDef: i0.ɵComponentDef'); }); it('should compile Components without errors', () => { @@ -196,9 +196,9 @@ describe('ngtsc behavioral tests', () => { 'declarations: [TestCmp], imports: [], exports: [] })'); const dtsContents = getContents('test.d.ts'); - expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef'); + expect(dtsContents).toContain('static ngComponentDef: i0.ɵComponentDef'); expect(dtsContents) - .toContain('static ngModuleDef: i0.NgModuleDef'); + .toContain('static ngModuleDef: i0.ɵNgModuleDef'); expect(dtsContents).not.toContain('__decorate'); }); @@ -240,8 +240,8 @@ describe('ngtsc behavioral tests', () => { const dtsContents = getContents('test.d.ts'); expect(dtsContents) - .toContain('static ngModuleDef: i0.NgModuleDef'); - expect(dtsContents).toContain('static ngInjectorDef: i0.InjectorDef'); + .toContain('static ngModuleDef: i0.ɵNgModuleDef'); + expect(dtsContents).toContain('static ngInjectorDef: i0.ɵInjectorDef'); }); it('should compile Pipes without errors', () => { @@ -342,6 +342,6 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain('pipes: [TestPipe]'); const dtsContents = getContents('test.d.ts'); - expect(dtsContents).toContain('i0.NgModuleDef'); + expect(dtsContents).toContain('i0.ɵNgModuleDef'); }); }); diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index a2fb852395..12f6375229 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -104,7 +104,7 @@ export class Identifiers { static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE}; static ComponentDef: o.ExternalReference = { - name: 'ComponentDef', + name: 'ɵComponentDef', moduleName: CORE, }; @@ -114,12 +114,12 @@ export class Identifiers { }; static DirectiveDef: o.ExternalReference = { - name: 'DirectiveDef', + name: 'ɵDirectiveDef', moduleName: CORE, }; static InjectorDef: o.ExternalReference = { - name: 'InjectorDef', + name: 'ɵInjectorDef', moduleName: CORE, }; @@ -129,7 +129,7 @@ export class Identifiers { }; static NgModuleDef: o.ExternalReference = { - name: 'NgModuleDef', + name: 'ɵNgModuleDef', moduleName: CORE, }; diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts index 1e6b201b84..0a776ced01 100644 --- a/packages/core/src/di.ts +++ b/packages/core/src/di.ts @@ -13,7 +13,7 @@ */ export * from './di/metadata'; -export {InjectableType, InjectorType, defineInjectable, defineInjector} from './di/defs'; +export {InjectableType, InjectorDef, InjectorType, defineInjectable, defineInjector} from './di/defs'; export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref'; export {Injectable, InjectableDecorator, InjectableProvider} from './di/injectable'; export {inject, InjectFlags, INJECTOR, Injector} from './di/injector'; diff --git a/packages/core/src/r3_symbols.ts b/packages/core/src/r3_symbols.ts index e76e2d02e9..ab344a1f06 100644 --- a/packages/core/src/r3_symbols.ts +++ b/packages/core/src/r3_symbols.ts @@ -19,9 +19,9 @@ * The below symbols are used for @Injectable and @NgModule compilation. */ -export {InjectableDef, InjectorDef, defineInjectable, defineInjector} from './di/defs'; +export {InjectableDef, InjectorDef as ɵInjectorDef, defineInjectable, defineInjector} from './di/defs'; export {inject} from './di/injector'; -export {NgModuleDef} from './metadata/ng_module'; +export {NgModuleDef as ɵNgModuleDef} from './metadata/ng_module'; export {defineNgModule as ɵdefineNgModule} from './render3/definition'; diff --git a/packages/core/test/render3/jit_environment_spec.ts b/packages/core/test/render3/jit_environment_spec.ts index ea7e22b898..134970bdd6 100644 --- a/packages/core/test/render3/jit_environment_spec.ts +++ b/packages/core/test/render3/jit_environment_spec.ts @@ -12,10 +12,10 @@ import {Identifiers} from '@angular/compiler/src/render3/r3_identifiers'; import {angularCoreEnv} from '../../src/render3/jit/environment'; const INTERFACE_EXCEPTIONS = new Set([ - 'ComponentDef', - 'DirectiveDef', - 'InjectorDef', - 'NgModuleDef', + 'ɵComponentDef', + 'ɵDirectiveDef', + 'ɵInjectorDef', + 'ɵNgModuleDef', 'ɵPipeDef', ]); From 48394c64aea83eb0cfad131873d3e032d504ec5f Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 14:25:46 -0700 Subject: [PATCH 472/582] fix(ivy): remove spurious comma in ngtsc-built .d.ts files (#24738) On accident a comma was emitted between imports when generating .d.ts files. This commit removes it. PR Close #24738 --- packages/compiler-cli/src/ngtsc/transform/src/declaration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts b/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts index 3c24a6be95..fbb4cee703 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/declaration.ts @@ -59,7 +59,7 @@ export class DtsFileTransformer { const imports = this.imports.getAllImports(tsPath, this.coreImportsFrom); if (imports.length !== 0) { - dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join() + dts; + dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join('') + dts; } return dts; From 02b50876858964de7b7f9560c92fa17825ad4926 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 29 Jun 2018 14:34:26 -0700 Subject: [PATCH 473/582] build(ivy): enable ngtsc AOT builds for a few packages (#24738) Turn on AOT builds using ngtsc for: * animations * common * compiler * compiler-cli * forms * platform-browser PR Close #24738 --- packages/animations/BUILD.bazel | 1 + packages/common/BUILD.bazel | 1 + packages/compiler-cli/BUILD.bazel | 1 + packages/compiler/BUILD.bazel | 1 + packages/forms/BUILD.bazel | 1 + packages/platform-browser/BUILD.bazel | 1 + 6 files changed, 6 insertions(+) diff --git a/packages/animations/BUILD.bazel b/packages/animations/BUILD.bazel index 8cf0a12c17..d32879fa70 100644 --- a/packages/animations/BUILD.bazel +++ b/packages/animations/BUILD.bazel @@ -26,6 +26,7 @@ ng_package( entry_point = "packages/animations/index.js", tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [ diff --git a/packages/common/BUILD.bazel b/packages/common/BUILD.bazel index bcf015c9d5..0b06fa465f 100644 --- a/packages/common/BUILD.bazel +++ b/packages/common/BUILD.bazel @@ -29,6 +29,7 @@ ng_package( packages = ["//packages/common/locales:package"], tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [ diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index cd99c5f7df..4cf8888f12 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -38,6 +38,7 @@ npm_package( ], tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [":compiler-cli"], diff --git a/packages/compiler/BUILD.bazel b/packages/compiler/BUILD.bazel index db8920628e..08269e2b51 100644 --- a/packages/compiler/BUILD.bazel +++ b/packages/compiler/BUILD.bazel @@ -23,6 +23,7 @@ ng_package( include_devmode_srcs = True, tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [ diff --git a/packages/forms/BUILD.bazel b/packages/forms/BUILD.bazel index 24f377d99e..668f0b47eb 100644 --- a/packages/forms/BUILD.bazel +++ b/packages/forms/BUILD.bazel @@ -24,6 +24,7 @@ ng_package( entry_point = "packages/forms/index.js", tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [ diff --git a/packages/platform-browser/BUILD.bazel b/packages/platform-browser/BUILD.bazel index b9ad4a7331..e1c7384f9b 100644 --- a/packages/platform-browser/BUILD.bazel +++ b/packages/platform-browser/BUILD.bazel @@ -28,6 +28,7 @@ ng_package( entry_point = "packages/platform-browser/index.js", tags = [ "ivy-jit", + "ivy-local", "release-with-framework", ], deps = [ From d98b1c3bc49eaa0fcc800b572fb210a164bfdc84 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 2 Jul 2018 11:24:58 -0700 Subject: [PATCH 474/582] fix(ivy): strip newlines from selectors in .d.ts files (#24738) When writing selectors as string literal types in .d.ts files, strip newlines to avoid generating invalid code. Newlines carry no meaning in selectors anyway. PR Close #24738 --- packages/compiler/src/render3/view/compiler.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 375c9e134b..96d7167adc 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -88,9 +88,14 @@ export function compileDirectiveFromMetadata( bindingParser: BindingParser): R3DirectiveDef { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]); + + // On the type side, remove newlines from the selector as it will need to fit into a TypeScript + // string literal, which must be on one line. + const selectorForType = (meta.selector || '').replace(/\n/g, ''); + const type = new o.ExpressionType(o.importExpr( R3.DirectiveDef, - [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(meta.selector || ''))])); + [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(selectorForType))])); return {expression, type}; } @@ -157,10 +162,14 @@ export function compileComponentFromMetadata( definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed))); } + // On the type side, remove newlines from the selector as it will need to fit into a TypeScript + // string literal, which must be on one line. + const selectorForType = (meta.selector || '').replace(/\n/g, ''); + const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]); const type = new o.ExpressionType(o.importExpr( R3.ComponentDef, - [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(meta.selector || ''))])); + [new o.ExpressionType(meta.type), new o.ExpressionType(o.literal(selectorForType))])); return {expression, type}; } From d723a69b31554db4f5fc48c5ae48e52bd3f4f6c9 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 2 Jul 2018 11:26:16 -0700 Subject: [PATCH 475/582] fix(ivy): animations should not be a hard error yet (#24738) Previously the Ivy template compiler would throw on encountering an animation binding (e.g. [@anim]). This is unneccessary and precludes testing existing code. This commit changes the error to a warning. PR Close #24738 --- packages/compiler/src/render3/view/template.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 3704c2b7f2..ed55319205 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -489,7 +489,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Generate element input bindings allOtherInputs.forEach((input: t.BoundAttribute) => { if (input.type === BindingType.Animation) { - this._unsupported('animations'); + console.error('warning: animation bindings not yet supported'); + return; } const convertedBinding = this.convertPropertyBinding(implicit, input.value); From 9a6f27c34ce32af346e20f9cae8ebaacff89cd56 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Mon, 2 Jul 2018 14:23:46 -0700 Subject: [PATCH 476/582] fix(ivy): support zero-argument @NgModule() invocations (#24738) It's possible to declare an argument-less NgModule: @NgModule() export class Foo {} Update the @NgModule compiler to support this usage. PR Close #24738 --- .../compiler-cli/src/ngtsc/annotations/src/ng_module.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 5a7df1c339..1a91a0e238 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -37,10 +37,13 @@ export class NgModuleDecoratorHandler implements DecoratorHandler { - if (decorator.args === null || decorator.args.length !== 1) { + if (decorator.args === null || decorator.args.length > 1) { throw new Error(`Incorrect number of arguments to @NgModule decorator`); } - const meta = decorator.args[0]; + + // @NgModule can be invoked without arguments. In case it is, pretend as if a blank object + // literal was specified. This simplifies the code below. + const meta = decorator.args.length === 1 ? decorator.args[0] : ts.createObjectLiteral([]); if (!ts.isObjectLiteralExpression(meta)) { throw new Error(`Decorator argument must be literal.`); } From 80a74b450a5954bb5e3fbf32449fc475b95804a2 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Wed, 27 Jun 2018 14:33:16 -0500 Subject: [PATCH 477/582] docs(forms): update form builder API reference (#24693) PR Close #24693 --- .../ts/formBuilder/form_builder_example.ts | 21 ++++++- packages/forms/src/form_builder.ts | 63 ++++++++++++------- packages/forms/src/model.ts | 15 ++--- 3 files changed, 67 insertions(+), 32 deletions(-) diff --git a/packages/examples/forms/ts/formBuilder/form_builder_example.ts b/packages/examples/forms/ts/formBuilder/form_builder_example.ts index 01a9690977..e6fbd9c3eb 100644 --- a/packages/examples/forms/ts/formBuilder/form_builder_example.ts +++ b/packages/examples/forms/ts/formBuilder/form_builder_example.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -// #docregion Component +// #docregion Component, disabled-control import {Component, Inject} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; +// #enddocregion disabled-control @Component({ selector: 'example-app', @@ -40,3 +41,19 @@ export class FormBuilderComp { } } // #enddocregion + +// #docregion disabled-control +@Component({ + selector: 'app-disabled-form-control', + template: ` + + ` +}) +export class DisabledFormControlComponent { + control: FormControl; + + constructor(private fb: FormBuilder) { + this.control = fb.control({value: 'my val', disabled: true}); + } +} +// #enddocregion disabled-control \ No newline at end of file diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index ccb1fd9563..9b3c4570ec 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -13,31 +13,28 @@ import {AbstractControl, FormArray, FormControl, FormGroup} from './model'; /** * @description - * * Creates an `AbstractControl` from a user-specified configuration. * - * This is essentially syntactic sugar that shortens the `new FormGroup()`, - * `new FormControl()`, and `new FormArray()` boilerplate that can build up in larger + * The `FormBuilder` provides syntactic sugar that shortens creating instances of a `FormControl`, + * `FormGroup`, or `FormArray`. It reduces the amount of boilerplate needed to build complex * forms. * - * To use, inject `FormBuilder` into your component class. You can then call its methods - * directly. - * - * {@example forms/ts/formBuilder/form_builder_example.ts region='Component'} - * - * * **npm package**: `@angular/forms` - * - * * **NgModule**: `ReactiveFormsModule` - * + * @see [Reactive Forms Guide](/guide/reactive-forms) * */ @Injectable() export class FormBuilder { /** - * Construct a new `FormGroup` with the given map of configuration. - * Valid keys for the `extra` parameter map are `validator` and `asyncValidator`. + * @description + * Construct a new `FormGroup` instance. + * + * @param controlsConfig A collection of child controls. The key for each child is the name + * under which it is registered. + * + * @param extra An object of configuration options for the `FormGroup`. + * * `validator`: A synchronous validator function, or an array of validator functions + * * `asyncValidator`: A single async validator or array of async validator functions * - * See the `FormGroup` constructor for more details. */ group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any}|null = null): FormGroup { const controls = this._reduceControls(controlsConfig); @@ -45,12 +42,28 @@ export class FormBuilder { const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; return new FormGroup(controls, validator, asyncValidator); } + /** - * Construct a new `FormControl` with the given `formState`,`validator`, and - * `asyncValidator`. + * @description + * Construct a new `FormControl` instance. * - * `formState` can either be a standalone value for the form control or an object - * that contains both a value and a disabled status. + * @param formState Initializes the control with an initial value, + * or an object that defines the initial value and disabled state. + * + * @param validator A synchronous validator function, or an array of synchronous validator + * functions. + * + * @param asyncValidator A single async validator or array of async validator functions + * + * @usageNotes + * + * ### Initialize a control as disabled + * + * The following example returns a control with an initial value in a disabled state. + * + * + * * */ control( @@ -60,8 +73,16 @@ export class FormBuilder { } /** - * Construct a `FormArray` from the given `controlsConfig` array of - * configuration, with the given optional `validator` and `asyncValidator`. + * @description + * Construct a new `FormArray` instance. + * + * @param controlsConfig An array of child controls. The key for each child control is its index + * in the array. + * + * @param validator A synchronous validator function, or an array of synchronous validator + * functions. + * + * @param asyncValidator A single async validator or array of async validator functions */ array( controlsConfig: any[], validator?: ValidatorFn|ValidatorFn[]|null, diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index af833c8e39..834b48c64a 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -880,9 +880,8 @@ export class FormControl extends AbstractControl { /** * Creates a new `FormControl` instance. * - * @param formState Initializes the control with an initial state value, - * or with an object that defines the initial value, status, and options - * for handling updates and validation. + * @param formState Initializes the control with an initial value, + * or an object that defines the initial value and disabled state. * * @param validatorOrOpts A synchronous validator function, or an array of * such functions, or an `AbstractControlOptions` object that contains validation functions @@ -963,9 +962,8 @@ export class FormControl extends AbstractControl { * Resets the form control, marking it `pristine` and `untouched`, and setting * the value to null. * - * @param formState Initializes the control with an initial state value, - * or with an object that defines the initial value, status, and options - * for handling updates and validation. + * @param formState Resets the control with an initial value, + * or an object that defines the initial value and disabled state. * * @param options Configuration options that determine how the control propagates changes * and emits events after the value changes. @@ -1319,9 +1317,8 @@ export class FormGroup extends AbstractControl { * is a standalone value or a form state object with both a value and a disabled * status. * - * @param value Initializes the control with an initial state value, - * or with an object that defines the initial value, status, - * and options for handling updates and validation. + * @param formState Resets the control with an initial value, + * or an object that defines the initial value and disabled state. * * @param options Configuration options that determine how the control propagates changes * and emits events when the group is reset. From 85d9c20b1d10019750b87074c3e9dffc9bc4b286 Mon Sep 17 00:00:00 2001 From: Alain Chautard Date: Mon, 14 May 2018 19:04:45 -0700 Subject: [PATCH 478/582] docs(aio): Add Angular Training to list of training companies (#23907) PR Close #23907 --- aio/content/marketing/resources.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aio/content/marketing/resources.json b/aio/content/marketing/resources.json index 8e14beefd0..4e704c349c 100644 --- a/aio/content/marketing/resources.json +++ b/aio/content/marketing/resources.json @@ -641,6 +641,12 @@ "title": "Angular Academy (Canada)", "url": "http://www.angularacademy.ca" }, + "at": { + "desc": "Angular Training teaches Angular on-site all over the world. Also provides consulting and mentoring.", + "rev": true, + "title": "Angular Training", + "url": "http://www.angulartraining.com" + }, "-KLIBo_lm-WrK1Sjtt-2": { "desc": "Basic and Advanced training across Europe in German", "rev": true, From a663565403559731344e8a55fa06f4633465942b Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 2 Apr 2018 10:57:38 +0300 Subject: [PATCH 479/582] build: fix windows scripts (#23121) The `packages/core/src/animation/dsl.ts` symlink ws removed in #22692, so `create-/remove-symlinks.sh` scripts for Windows should not try to "fix" it. PR Close #23121 --- scripts/windows/create-symlinks.sh | 3 --- scripts/windows/remove-symlinks.sh | 3 --- 2 files changed, 6 deletions(-) diff --git a/scripts/windows/create-symlinks.sh b/scripts/windows/create-symlinks.sh index ef2ca70f5a..09b2b7015c 100644 --- a/scripts/windows/create-symlinks.sh +++ b/scripts/windows/create-symlinks.sh @@ -2,9 +2,6 @@ cd `dirname $0` -CORE_SRC_ANIMATION_DIR=./../../packages/core/src/animation UPGRADE_STATIC_DIR=./../../packages/upgrade/static -mv ${CORE_SRC_ANIMATION_DIR}/dsl.ts ${CORE_SRC_ANIMATION_DIR}/dsl.ts.old mv ${UPGRADE_STATIC_DIR}/src ${UPGRADE_STATIC_DIR}/src.old -cmd <<< "mklink \"..\\..\\packages\\core\\src\\animation\\dsl.ts\" \"..\\..\\..\\animations\\src\\animation_metadata.ts\"" cmd <<< "mklink /d \"..\\..\\packages\\upgrade\\static\\src\" \"..\\src\"" diff --git a/scripts/windows/remove-symlinks.sh b/scripts/windows/remove-symlinks.sh index 93ae4ea3dd..4d8f3d025e 100644 --- a/scripts/windows/remove-symlinks.sh +++ b/scripts/windows/remove-symlinks.sh @@ -2,9 +2,6 @@ cd `dirname $0` -CORE_SRC_ANIMATION_DIR=./../../packages/core/src/animation UPGRADE_STATIC_DIR=./../../packages/upgrade/static -rm ${CORE_SRC_ANIMATION_DIR}/dsl.ts rm ${UPGRADE_STATIC_DIR}/src -mv ${CORE_SRC_ANIMATION_DIR}/dsl.ts.old ${CORE_SRC_ANIMATION_DIR}/dsl.ts mv ${UPGRADE_STATIC_DIR}/src.old ${UPGRADE_STATIC_DIR}/src From 82004c76ac46c5c03a4462ebddd739be8f56f73c Mon Sep 17 00:00:00 2001 From: Dario Braun Date: Thu, 12 Jul 2018 10:00:17 +0200 Subject: [PATCH 480/582] docs: update component styles doc regarding relative URL (#24471) Update the documentation to match the CLI mechanics regarding relative URL in link tags. docs: update info on stylesheet location for CLI PR Close #24471 --- aio/content/guide/component-styles.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aio/content/guide/component-styles.md b/aio/content/guide/component-styles.md index 444da23b60..1d4eaca201 100644 --- a/aio/content/guide/component-styles.md +++ b/aio/content/guide/component-styles.md @@ -216,11 +216,10 @@ You can also write `` tags into the component's HTML template.
-The link tag's `href` URL must be relative to the -_**application root**_, not relative to the component file. - When building with the CLI, be sure to include the linked style file among the assets to be copied to the server as described in the [CLI documentation](https://github.com/angular/angular-cli/wiki/stories-asset-configuration). +Once included, the CLI will include the stylesheet, whether the link tag's href URL is relative to the application root or the component file. +
### CSS @imports From 1821b7553038ed8059bade676d30ca45358cbed7 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Fri, 13 Jul 2018 10:51:54 +0200 Subject: [PATCH 481/582] test(ivy): run render3 tests with test.sh (#24866) PR Close #24866 --- karma-js.conf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/karma-js.conf.js b/karma-js.conf.js index 96f5642e8b..48b8a555fd 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -77,7 +77,7 @@ module.exports = function(config) { 'dist/all/@angular/compiler/test/aot/**', 'dist/all/@angular/compiler/test/render3/**', 'dist/all/@angular/core/test/bundling/**', - 'dist/all/@angular/core/test/render3/**', + 'dist/all/@angular/core/test/render3/ivy/**', 'dist/all/@angular/elements/schematics/**', 'dist/all/@angular/examples/**/e2e_test/*', 'dist/all/@angular/language-service/**', From 97277bc9fb429a7122ffa2a1507418c9b76cc5f8 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 11 Jul 2018 07:39:41 -0700 Subject: [PATCH 482/582] build: update to Bazel 0.15 (#24841) PR Close #24841 --- .circleci/config.yml | 4 ++-- WORKSPACE | 2 +- integration/bazel/WORKSPACE | 2 +- tools/ngcontainer/Dockerfile | 2 +- tools/ngcontainer/README.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ef587e0d90..4ad0d76d94 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,8 +12,8 @@ ## IMPORTANT # If you change the `docker_image` version, also change the `cache_key` suffix and the version of # `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file. -var_1: &docker_image angular/ngcontainer:0.3.2 -var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.2 +var_1: &docker_image angular/ngcontainer:0.3.3 +var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.3 # Define common ENV vars var_3: &define_env_vars diff --git a/WORKSPACE b/WORKSPACE index 290420c7f5..8b909046f9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -77,7 +77,7 @@ http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install") -check_bazel_version("0.14.0") +check_bazel_version("0.15.0") node_repositories(package_json = ["//:package.json"]) load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index 9c8f1e4c14..1eb968725c 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -44,7 +44,7 @@ http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories") -check_bazel_version("0.14.0") +check_bazel_version("0.15.0") node_repositories(package_json = ["//:package.json"]) load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains") diff --git a/tools/ngcontainer/Dockerfile b/tools/ngcontainer/Dockerfile index eec67f69be..273d28f485 100644 --- a/tools/ngcontainer/Dockerfile +++ b/tools/ngcontainer/Dockerfile @@ -19,7 +19,7 @@ RUN JAVA_DEBIAN_VERSION="8u131-b11-1~bpo8+1" \ ### # Bazel install # See https://bazel.build/versions/master/docs/install-ubuntu.html#using-bazel-custom-apt-repository-recommended -RUN BAZEL_VERSION="0.14.1" \ +RUN BAZEL_VERSION="0.15.0" \ && wget -q -O - https://bazel.build/bazel-release.pub.gpg | apt-key add - \ && echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" > /etc/apt/sources.list.d/bazel.list \ && apt-get update \ diff --git a/tools/ngcontainer/README.md b/tools/ngcontainer/README.md index 105900a86c..1fef76f256 100644 --- a/tools/ngcontainer/README.md +++ b/tools/ngcontainer/README.md @@ -6,7 +6,7 @@ This docker container provides everything needed to build and test Angular appli - npm 5.5.1 - yarn 1.3.2 - Java 8 (for Closure Compiler and Bazel) -- Bazel build tool v0.14.1 - http://bazel.build +- Bazel build tool v0.15.0 - http://bazel.build - Google Chrome 63.0.3239.84 - Mozilla Firefox 47.0.1 - xvfb (virtual framebuffer) for headless testing From d8c828c9b1b671a2e9a90145d53f5a230afc6a08 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 22 Jun 2018 16:58:29 +0100 Subject: [PATCH 483/582] build(docs-infra): implement the 'package' API template (#24631) PR Close #24631 --- .../api/api-list.component.html | 2 +- .../api/api-list.component.spec.ts | 2 + .../custom-elements/api/api.service.spec.ts | 8 +- .../app/custom-elements/api/api.service.ts | 1 + aio/src/styles/0-base/_typography.scss | 8 +- aio/src/styles/1-layouts/_api-pages.scss | 1 + aio/src/styles/2-modules/_api-pages.scss | 2 +- aio/src/styles/_constants.scss | 2 +- .../transforms/angular-api-package/index.js | 18 +- .../processors/addMetadataAliases.spec.js | 2 +- .../processors/computeApiBreadCrumbs.spec.js | 4 +- .../processors/computeSearchTitle.js | 2 +- .../processors/computeSearchTitle.spec.js | 4 +- .../processors/generateApiListDoc.js | 15 +- .../processors/generateApiListDoc.spec.js | 26 +-- .../processors/processPackages.js | 63 ++++++ .../processors/processPackages.spec.js | 180 ++++++++++++++++++ .../readers/package-content.js | 25 +++ .../processors/createOverviewDump.js | 2 +- .../templates/api/lib/githubLinks.html | 6 +- .../templates/api/module.template.html | 16 -- .../templates/api/package.template.html | 40 ++++ packages/animations/PACKAGE.md | 29 +++ 23 files changed, 399 insertions(+), 59 deletions(-) create mode 100644 aio/tools/transforms/angular-api-package/processors/processPackages.js create mode 100644 aio/tools/transforms/angular-api-package/processors/processPackages.spec.js create mode 100644 aio/tools/transforms/angular-api-package/readers/package-content.js delete mode 100644 aio/tools/transforms/templates/api/module.template.html create mode 100644 aio/tools/transforms/templates/api/package.template.html create mode 100644 packages/animations/PACKAGE.md diff --git a/aio/src/app/custom-elements/api/api-list.component.html b/aio/src/app/custom-elements/api/api-list.component.html index 893947fcaf..aa65d70291 100644 --- a/aio/src/app/custom-elements/api/api-list.component.html +++ b/aio/src/app/custom-elements/api/api-list.component.html @@ -21,7 +21,7 @@
-

{{section.title}}

+

{{section.title}}

  • diff --git a/aio/src/app/custom-elements/api/api-list.component.spec.ts b/aio/src/app/custom-elements/api/api-list.component.spec.ts index 9c4c3d2ad4..035a07f134 100644 --- a/aio/src/app/custom-elements/api/api-list.component.spec.ts +++ b/aio/src/app/custom-elements/api/api-list.component.spec.ts @@ -218,6 +218,7 @@ const apiSections: ApiSection[] = [ { "name": "common", "title": "common", + "path": "api/common", "items": [ { "name": "class_1", @@ -256,6 +257,7 @@ const apiSections: ApiSection[] = [ { "name": "core", "title": "core", + "path": "api/core", "items": [ { "name": "class_3", diff --git a/aio/src/app/custom-elements/api/api.service.spec.ts b/aio/src/app/custom-elements/api/api.service.spec.ts index c5ae75c6a8..253acd57de 100644 --- a/aio/src/app/custom-elements/api/api.service.spec.ts +++ b/aio/src/app/custom-elements/api/api.service.spec.ts @@ -50,7 +50,7 @@ describe('ApiService', () => { describe('#sections', () => { it('first subscriber should fetch sections', done => { - const data = [{name: 'a', title: 'A', items: []}, {name: 'b', title: 'B', items: []}]; + const data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; service.sections.subscribe(sections => { expect(sections).toEqual(data); @@ -61,7 +61,7 @@ describe('ApiService', () => { }); it('second subscriber should get previous sections and NOT trigger refetch', done => { - const data = [{name: 'a', title: 'A', items: []}, {name: 'b', title: 'B', items: []}]; + const data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; let subscriptions = 0; service.sections.subscribe(sections => { @@ -91,7 +91,7 @@ describe('ApiService', () => { let call = 0; - let data = [{name: 'a', title: 'A', items: []}, {name: 'b', title: 'B', items: []}]; + let data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; service.sections.subscribe(sections => { // called twice during this test @@ -103,7 +103,7 @@ describe('ApiService', () => { httpMock.expectOne({}).flush(data); // refresh/refetch - data = [{name: 'c', title: 'C', items: []}]; + data = [{name: 'c', title: 'C', path: '', items: []}]; service.fetchSections(); httpMock.expectOne({}).flush(data); diff --git a/aio/src/app/custom-elements/api/api.service.ts b/aio/src/app/custom-elements/api/api.service.ts index 9d42350b2e..c2398e7d91 100644 --- a/aio/src/app/custom-elements/api/api.service.ts +++ b/aio/src/app/custom-elements/api/api.service.ts @@ -19,6 +19,7 @@ export interface ApiItem { } export interface ApiSection { + path: string; name: string; title: string; items: ApiItem[]; diff --git a/aio/src/styles/0-base/_typography.scss b/aio/src/styles/0-base/_typography.scss index 8d0f38f361..b83a0604b0 100755 --- a/aio/src/styles/0-base/_typography.scss +++ b/aio/src/styles/0-base/_typography.scss @@ -28,7 +28,7 @@ h2 { h3 { font-size: 20px; font-weight: 400; - margin: 24px 0px; + margin: 24px 0px 12px; clear: both; } @@ -55,6 +55,10 @@ h6 { } h2, h3, h4, h5, h6 { + a { + font-size: inherit; + } + @media screen and (max-width: 600px) { margin: 8px 0; } @@ -105,7 +109,7 @@ table { border-collapse: collapse; border-radius: 2px; border-spacing: 0; - margin: 0 0 32px 0; + margin: 12px 0 32px; } table tbody th { diff --git a/aio/src/styles/1-layouts/_api-pages.scss b/aio/src/styles/1-layouts/_api-pages.scss index 81e2f548a0..a819b1192f 100644 --- a/aio/src/styles/1-layouts/_api-pages.scss +++ b/aio/src/styles/1-layouts/_api-pages.scss @@ -3,6 +3,7 @@ max-width: 1200px; table { + margin: 12px 0 24px; th { text-transform: none; diff --git a/aio/src/styles/2-modules/_api-pages.scss b/aio/src/styles/2-modules/_api-pages.scss index dcd25bab04..41768a0bd9 100644 --- a/aio/src/styles/2-modules/_api-pages.scss +++ b/aio/src/styles/2-modules/_api-pages.scss @@ -30,7 +30,7 @@ } } - .method-table, .option-table { + .method-table, .option-table, .list-table { th { display: flex; align-items: center; diff --git a/aio/src/styles/_constants.scss b/aio/src/styles/_constants.scss index b46e0815b8..c719602f65 100755 --- a/aio/src/styles/_constants.scss +++ b/aio/src/styles/_constants.scss @@ -105,7 +105,7 @@ $api-symbols: ( content: 'T', background: $light-green-600 ), - module: ( + package: ( content: 'Pk', background: $purple-600 ) diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index d6838c892f..a40e3f7ed1 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -33,6 +33,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) .processor(require('./processors/simplifyMemberAnchors')) .processor(require('./processors/computeStability')) .processor(require('./processors/removeInjectableConstructors')) + .processor(require('./processors/processPackages')) /** * These are the API doc types that will be rendered to actual files. @@ -40,7 +41,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) * more Angular specific API types, such as decorators and directives. */ .factory(function API_DOC_TYPES_TO_RENDER(EXPORT_DOC_TYPES) { - return EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'pipe', 'module']); + return EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'pipe', 'package']); }) /** @@ -58,8 +59,10 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) return API_DOC_TYPES_TO_RENDER.concat(API_CONTAINED_DOC_TYPES); }) + .factory(require('./readers/package-content')) + // Where do we get the source files? - .config(function(readTypeScriptModules, readFilesProcessor, collectExamples, tsParser) { + .config(function(readTypeScriptModules, readFilesProcessor, collectExamples, tsParser, packageContentFileReader) { // Tell TypeScript how to load modules that start with with `@angular` tsParser.options.paths = { '@angular/*': [API_SOURCE_PATH + '/*'] }; @@ -102,12 +105,19 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) 'upgrade/static/index.ts', ]; + readFilesProcessor.fileReaders.push(packageContentFileReader); + // API Examples readFilesProcessor.sourceFiles = [ { basePath: API_SOURCE_PATH, include: API_SOURCE_PATH + '/examples/**/*', fileReader: 'exampleFileReader' + }, + { + basePath: API_SOURCE_PATH, + include: API_SOURCE_PATH + '/**/PACKAGE.md', + fileReader: 'packageContentFileReader' } ]; collectExamples.exampleFolders.push('examples'); @@ -123,7 +133,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) .config(function(computeStability, splitDescription, addNotYetDocumentedProperty, API_DOC_TYPES_TO_RENDER, API_DOC_TYPES) { computeStability.docTypes = API_DOC_TYPES_TO_RENDER; // Only split the description on the API docs - splitDescription.docTypes = API_DOC_TYPES; + splitDescription.docTypes = API_DOC_TYPES.concat(['package-content']); addNotYetDocumentedProperty.docTypes = API_DOC_TYPES; }) @@ -180,7 +190,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) generateApiListDoc.outputFolder = API_SEGMENT; computePathsProcessor.pathTemplates.push({ - docTypes: ['module'], + docTypes: ['package'], getPath: function computeModulePath(doc) { doc.moduleFolder = `${API_SEGMENT}/${doc.id.replace(/\/index$/, '')}`; return doc.moduleFolder; diff --git a/aio/tools/transforms/angular-api-package/processors/addMetadataAliases.spec.js b/aio/tools/transforms/angular-api-package/processors/addMetadataAliases.spec.js index 154f935fe7..33faa8ae73 100644 --- a/aio/tools/transforms/angular-api-package/processors/addMetadataAliases.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/addMetadataAliases.spec.js @@ -33,7 +33,7 @@ describe('addSelectorsAsAliases processor', () => { { docType: 'directive', name: 'NgModel', aliases: ['NgModel'], directiveOptions: { selector: '\'[ngModel]:not([formControlName]):not([formControl])\'' } }, { docType: 'component', name: 'MyComponent', aliases: ['MyComponent'], componentOptions: { selector: '\'my-component\'' } }, { docType: 'decorator', name: 'MyDecorator', aliases: ['MyDecorator'] }, - { docType: 'module', name: 'myModule', aliases: ['myModule'], id: 'some/myModule' }, + { docType: 'package', name: 'myPackage', aliases: ['myPackage'], id: 'some/myPackage' }, { docType: 'var', name: 'myVar', aliases: ['myVar'] }, { docType: 'let', name: 'myLet', aliases: ['myLet'] }, { docType: 'const', name: 'myConst', aliases: ['myConst'] }, diff --git a/aio/tools/transforms/angular-api-package/processors/computeApiBreadCrumbs.spec.js b/aio/tools/transforms/angular-api-package/processors/computeApiBreadCrumbs.spec.js index 03bc24e426..256d7c44d2 100644 --- a/aio/tools/transforms/angular-api-package/processors/computeApiBreadCrumbs.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/computeApiBreadCrumbs.spec.js @@ -14,14 +14,14 @@ describe('angular-api-package: computeApiBreadCrumbs processor', () => { }); it('should attach a breadCrumbs property to each of the API_DOC_TYPES_TO_RENDER docs', () => { - const API_DOC_TYPES_TO_RENDER = ['class', 'interface', 'module']; + const API_DOC_TYPES_TO_RENDER = ['class', 'interface', 'package']; const processor = processorFactory(API_DOC_TYPES_TO_RENDER); const docs = [ { docType: 'class', name: 'ClassA', path: 'module-1/class-a', moduleDoc: { id: 'moduleOne', path: 'module-1' } }, { docType: 'interface', name: 'InterfaceB', path: 'module-2/interface-b', moduleDoc: { id: 'moduleTwo', path: 'module-2' } }, { docType: 'guide', name: 'Guide One', path: 'guide/guide-1' }, - { docType: 'module', name: 'testing', id: 'http/testing', path: 'http/testing' }, + { docType: 'package', name: 'testing', id: 'http/testing', path: 'http/testing' }, ]; processor.$process(docs); diff --git a/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.js b/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.js index 6ea06a0229..295a2839fa 100644 --- a/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.js +++ b/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.js @@ -8,7 +8,7 @@ module.exports = function computeSearchTitleProcessor() { case 'function': doc.searchTitle = `${doc.name}()`; break; - case 'module': + case 'package': doc.searchTitle = `${doc.id} package`; break; } diff --git a/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.spec.js b/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.spec.js index d90b707b50..61b205d956 100644 --- a/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/computeSearchTitle.spec.js @@ -31,7 +31,7 @@ describe('computeSearchTitle processor', () => { { docType: 'pipe', name: 'MyPipe', pipeOptions: { name: 'myPipe' } }, { docType: 'directive', name: 'MyDirective', directiveOptions: {} }, { docType: 'decorator', name: 'MyDecorator' }, - { docType: 'module', name: 'myModule', id: 'some/myModule' }, + { docType: 'package', name: 'myPackage', id: 'some/myPackage' }, { docType: 'var', name: 'myVar' }, { docType: 'let', name: 'myLet' }, { docType: 'const', name: 'myConst' }, @@ -45,7 +45,7 @@ describe('computeSearchTitle processor', () => { expect(docs[4].searchTitle).toBeUndefined(); expect(docs[5].searchTitle).toBeUndefined(); expect(docs[6].searchTitle).toBeUndefined(); - expect(docs[7].searchTitle).toEqual('some/myModule package'); + expect(docs[7].searchTitle).toEqual('some/myPackage package'); expect(docs[8].searchTitle).toBeUndefined(); expect(docs[9].searchTitle).toBeUndefined(); expect(docs[10].searchTitle).toBeUndefined(); diff --git a/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.js b/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.js index b36b446904..4a8fd31a11 100644 --- a/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.js +++ b/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.js @@ -12,19 +12,20 @@ module.exports = function generateApiListDoc() { path: this.outputFolder + '/api-list.json', outputPath: this.outputFolder + '/api-list.json', data: docs - .filter(doc => doc.docType === 'module') - .map(getModuleInfo) + .filter(doc => doc.docType === 'package') + .map(getPackageInfo) }); } }; }; -function getModuleInfo(moduleDoc) { - const moduleName = moduleDoc.id.replace(/\/index$/, ''); +function getPackageInfo(packageDoc) { + const packageName = packageDoc.id.replace(/\/index$/, ''); return { - name: moduleName.toLowerCase(), - title: moduleName, - items: moduleDoc.exports + name: packageName.toLowerCase(), + title: packageName, + path: packageDoc.path, + items: packageDoc.exports // Ignore internals and private exports (indicated by the ɵ prefix) .filter(doc => !doc.internal && !doc.privateExport) .map(getExportInfo) diff --git a/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.spec.js b/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.spec.js index 35d9ad000c..14eda0482f 100644 --- a/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.spec.js +++ b/aio/tools/transforms/angular-api-package/processors/generateApiListDoc.spec.js @@ -38,28 +38,28 @@ describe('generateApiListDoc processor', () => { it('should add an info object to the doc for each module doc', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [] }, - { docType: 'module', id: '@angular/core/index', exports: [] }, - { docType: 'module', id: '@angular/http/index', exports: [] }, + { docType: 'package', id: '@angular/common/index', exports: [], path: 'common' }, + { docType: 'package', id: '@angular/core/index', exports: [], path: 'core' }, + { docType: 'package', id: '@angular/http/index', exports: [], path: 'http' }, ]; processor.$process(docs); expect(docs[3].data).toEqual([ - { name: '@angular/common', title: '@angular/common', items: [] }, - { name: '@angular/core', title: '@angular/core', items: [] }, - { name: '@angular/http', title: '@angular/http', items: [] }, + { name: '@angular/common', title: '@angular/common', items: [], path: 'common' }, + { name: '@angular/core', title: '@angular/core', items: [], path: 'core' }, + { name: '@angular/http', title: '@angular/http', items: [], path: 'http' }, ]); }); it('should add info about each export on each module', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'directive', name: 'AaaAaa', path: 'aaa' }, { docType: 'pipe', name: 'BbbBbb', path: 'bbb' }, { docType: 'decorator', name: 'CccCcc', path: 'ccc' }, { docType: 'class', name: 'DddDdd', path: 'ddd' } ] }, - { docType: 'module', id: '@angular/core/index', exports: [ + { docType: 'package', id: '@angular/core/index', exports: [ { docType: 'interface', name: 'EeeEee', path: 'eee' }, { docType: 'function', name: 'FffFff', path: 'fff' }, { docType: 'enum', name: 'GggGgg', path: 'ggg' }, @@ -86,7 +86,7 @@ describe('generateApiListDoc processor', () => { it('should ignore internal and private exports', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'directive', name: 'AaaAaa', path: 'aaa', internal: true }, { docType: 'class', name: 'XxxXxx', path: 'xxx', privateExport: true }, { docType: 'pipe', name: 'BbbBbb', path: 'bbb' } @@ -101,7 +101,7 @@ describe('generateApiListDoc processor', () => { it('should convert `let` and `var` docTypes to `const`', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'var', name: 'AaaAaa', path: 'aaa' }, { docType: 'let', name: 'BbbBbb', path: 'bbb' }, ]} @@ -116,7 +116,7 @@ describe('generateApiListDoc processor', () => { it('should convert security to a boolean securityRisk', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'class', name: 'AaaAaa', path: 'aaa', security: 'This is a security risk' }, { docType: 'class', name: 'BbbBbb', path: 'bbb', security: '' }, ]} @@ -131,7 +131,7 @@ describe('generateApiListDoc processor', () => { it('should convert stability tags to the stable string property', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'class', name: 'AaaAaa', path: 'aaa', stable: undefined }, { docType: 'class', name: 'BbbBbb', path: 'bbb', experimental: 'Some message' }, { docType: 'class', name: 'CccCcc', path: 'ccc', deprecated: null }, @@ -150,7 +150,7 @@ describe('generateApiListDoc processor', () => { it('should sort items in each group alphabetically', () => { const processor = processorFactory(); const docs = [ - { docType: 'module', id: '@angular/common/index', exports: [ + { docType: 'package', id: '@angular/common/index', exports: [ { docType: 'class', name: 'DddDdd', path: 'uuu' }, { docType: 'class', name: 'BbbBbb', path: 'vvv' }, { docType: 'class', name: 'AaaAaa', path: 'xxx' }, diff --git a/aio/tools/transforms/angular-api-package/processors/processPackages.js b/aio/tools/transforms/angular-api-package/processors/processPackages.js new file mode 100644 index 0000000000..3d9fc3f202 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/processPackages.js @@ -0,0 +1,63 @@ +const { dirname } = require('canonical-path'); + +module.exports = function processPackages() { + return { + $runAfter: ['extractDecoratedClassesProcessor'], + $runBefore: ['computing-ids'], + $process(docs) { + const packageContentFiles = {}; + const packageMap = {}; + + docs = docs.filter(doc => { + if (doc.docType === 'package-content') { + packageContentFiles[dirname(doc.fileInfo.filePath)] = doc; + return false; + } else { + return true; + } + }); + + docs.forEach(doc => { + if (doc.docType === 'module') { + // Convert the doc type from "module" to "package" + doc.docType = 'package'; + // The name is actually the full id + doc.name = `@angular/${doc.id}`; + + // Partition the exports into groups by type + if (doc.exports) { + doc.classes = doc.exports.filter(doc => doc.docType === 'class'); + doc.decorators = doc.exports.filter(doc => doc.docType === 'decorator'); + doc.functions = doc.exports.filter(doc => doc.docType === 'function'); + doc.structures = doc.exports.filter(doc => doc.docType === 'enum' || doc.docType === 'interface'); + doc.directives = doc.exports.filter(doc => doc.docType === 'directive'); + doc.pipes = doc.exports.filter(doc => doc.docType === 'pipe'); + doc.types = doc.exports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const'); + } + + // Copy over docs from the PACKAGE.md file that is used to document packages + const readmeDoc = packageContentFiles[dirname(doc.fileInfo.filePath)]; + if (readmeDoc) { + doc.shortDescription = readmeDoc.shortDescription; + doc.description = readmeDoc.description; + doc.see = readmeDoc.see; + doc.fileInfo = readmeDoc.fileInfo; + } + + // Compute the primary/secondary entry point relationships + const packageParts = doc.id.split('/'); + const primaryPackageName = packageParts[0]; + doc.isPrimaryPackage = packageParts.length === 1; + doc.packageInfo = packageMap[primaryPackageName] = packageMap[primaryPackageName] || { primary: undefined, secondary: [] }; + if (doc.isPrimaryPackage) { + doc.packageInfo.primary = doc; + } else { + doc.packageInfo.secondary.push(doc); + } + } + }); + + return docs; + } + }; +}; diff --git a/aio/tools/transforms/angular-api-package/processors/processPackages.spec.js b/aio/tools/transforms/angular-api-package/processors/processPackages.spec.js new file mode 100644 index 0000000000..3692ab1b4c --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/processPackages.spec.js @@ -0,0 +1,180 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./processPackages'); +const Dgeni = require('dgeni'); + +describe('processPackages processor', () => { + + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('processPackages'); + expect(processor.$process).toBeDefined(); + expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor']); + expect(processor.$runBefore).toEqual(['computing-ids']); + }); + + it('should filter out any `package-content` docs from the collection', () => { + const docs = [ + { fileInfo: { filePath: 'some/a' }, docType: 'a', id: 'a' }, + { fileInfo: { filePath: 'some/x' }, docType: 'package-content', id: 'x' }, + { fileInfo: { filePath: 'some/b' }, docType: 'b', id: 'b' }, + { fileInfo: { filePath: 'some/y' }, docType: 'package-content', id: 'y' }, + { fileInfo: { filePath: 'some/z' }, docType: 'package-content', id: 'z' }, + ]; + const processor = processorFactory(); + const newDocs = processor.$process(docs); + expect(newDocs).toEqual([ + { fileInfo: { filePath: 'some/a' }, docType: 'a', id: 'a' }, + { fileInfo: { filePath: 'some/b' }, docType: 'b', id: 'b' }, + ]); + }); + + + it('should change `module` docs to `package` docs', () => { + const processor = processorFactory(); + const docs = [ + { fileInfo: { filePath: 'some/a' }, docType: 'module', id: 'a' }, + { fileInfo: { filePath: 'some/b' }, docType: 'module', id: 'b' }, + { docType: 'other', id: 'c' }, + ]; + const newDocs = processor.$process(docs); + expect(newDocs).toEqual([ + jasmine.objectContaining({ docType: 'package', id: 'a' }), + jasmine.objectContaining({ docType: 'package', id: 'b' }), + jasmine.objectContaining({ docType: 'other', id: 'c' }), + ]); + }); + + it('should attach the relevant package contents to the package doc', () => { + const docs = [ + { + fileInfo: { filePath: 'some/package-1/index' }, + docType: 'module', + id: 'package-1', + someProp: 'foo', + }, + { + fileInfo: { filePath: 'some/package-1/PACKAGE.md' }, + docType: 'package-content', + id: 'package-1/PACKAGE.md', + shortDescription: 'some short description', + description: 'some description', + see: [ 'a', 'b' ], + }, + { + fileInfo: { filePath: 'some/package-2/index' }, + docType: 'module', + id: 'package-2', + }, + ]; + const processor = processorFactory(); + const newDocs = processor.$process(docs); + + const package1 = jasmine.objectContaining({ + fileInfo: { filePath: 'some/package-1/PACKAGE.md' }, + docType: 'package', + name: '@angular/package-1', + id: 'package-1', + someProp: 'foo', + shortDescription: 'some short description', + description: 'some description', + see: [ 'a', 'b' ], + isPrimaryPackage: true, + }); + + const package2 = jasmine.objectContaining({ + fileInfo: { filePath: 'some/package-2/index' }, + docType: 'package', + name: '@angular/package-2', + id: 'package-2', + isPrimaryPackage: true, + }); + + expect(newDocs).toEqual([package1, package2]); + }); + + it('should compute primary and second package info', () => { + const docs = [ + { + fileInfo: { filePath: 'some/package-1/index' }, + docType: 'module', + id: 'package-1', + }, + { + fileInfo: { filePath: 'some/package-1/sub-1index' }, + docType: 'module', + id: 'package-1/sub-1', + }, + { + fileInfo: { filePath: 'some/package-1/sub-2index' }, + docType: 'module', + id: 'package-1/sub-2', + }, + ]; + const processor = processorFactory(); + const newDocs = processor.$process(docs); + + expect(newDocs[0].isPrimaryPackage).toBe(true); + expect(newDocs[1].isPrimaryPackage).toBe(false); + expect(newDocs[2].isPrimaryPackage).toBe(false); + + expect(newDocs[0].packageInfo.primary).toBe(newDocs[0]); + expect(newDocs[1].packageInfo.primary).toBe(newDocs[0]); + expect(newDocs[2].packageInfo.primary).toBe(newDocs[0]); + + expect(newDocs[0].packageInfo.secondary).toEqual([newDocs[1], newDocs[2]]); + expect(newDocs[1].packageInfo.secondary).toEqual([newDocs[1], newDocs[2]]); + expect(newDocs[2].packageInfo.secondary).toEqual([newDocs[1], newDocs[2]]); + }); + + it('should partition the exports of packages into groups', () => { + const docs = [ + { + fileInfo: { filePath: 'some/x' }, + docType: 'module', + id: 'x', + exports: [ + { docType: 'directive', id: 'directive-1' }, + { docType: 'function', id: 'function-1' }, + { docType: 'directive', id: 'directive-2' }, + { docType: 'decorator', id: 'decorator-1' }, + { docType: 'class', id: 'class-1' }, + { docType: 'type-alias', id: 'type-alias-1' }, + { docType: 'class', id: 'class-2' }, + { docType: 'pipe', id: 'pipe-1' }, + { docType: 'const', id: 'const-1' }, + { docType: 'const', id: 'const-2' }, + { docType: 'enum', id: 'enum-1' }, + { docType: 'interface', id: 'interface-1' }, + { docType: 'interface', id: 'interface-2' }, + ] + }, + ]; + const processor = processorFactory(); + const newDocs = processor.$process(docs); + + expect(newDocs[0].decorators).toEqual([ + { docType: 'decorator', id: 'decorator-1' }, + ]); + expect(newDocs[0].functions).toEqual([ + { docType: 'function', id: 'function-1' }, + ]); + expect(newDocs[0].structures).toEqual([ + { docType: 'enum', id: 'enum-1' }, + { docType: 'interface', id: 'interface-1' }, + { docType: 'interface', id: 'interface-2' }, + ]); + expect(newDocs[0].directives).toEqual([ + { docType: 'directive', id: 'directive-1' }, + { docType: 'directive', id: 'directive-2' }, + ]); + expect(newDocs[0].pipes).toEqual([ + { docType: 'pipe', id: 'pipe-1' }, + ]); + expect(newDocs[0].types).toEqual([ + { docType: 'type-alias', id: 'type-alias-1' }, + { docType: 'const', id: 'const-1' }, + { docType: 'const', id: 'const-2' }, + ]); + }); +}); diff --git a/aio/tools/transforms/angular-api-package/readers/package-content.js b/aio/tools/transforms/angular-api-package/readers/package-content.js new file mode 100644 index 0000000000..941b9c74e7 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/readers/package-content.js @@ -0,0 +1,25 @@ +/** + * @dgService + * @description + * This file reader will pull the contents from a text file that will be used + * as the description of a package. + * + * The doc will initially have the form: + * ``` + * { + * content: 'the content of the file', + * startingLine: 1 + * } + * ``` + */ +module.exports = function packageContentFileReader() { + return { + name: 'packageContentFileReader', + defaultPattern: /PACKAGE\.md$/, + getDocs: function(fileInfo) { + + // We return a single element array because content files only contain one document + return [{docType: 'package-content', content: fileInfo.content, startingLine: 1}]; + } + }; +}; \ No newline at end of file diff --git a/aio/tools/transforms/angular.io-package/processors/createOverviewDump.js b/aio/tools/transforms/angular.io-package/processors/createOverviewDump.js index 6032e94f64..ab590315b0 100644 --- a/aio/tools/transforms/angular.io-package/processors/createOverviewDump.js +++ b/aio/tools/transforms/angular.io-package/processors/createOverviewDump.js @@ -14,7 +14,7 @@ module.exports = function createOverviewDump() { modules: [] }; _.forEach(docs, function(doc) { - if (doc.docType === 'module') { + if (doc.docType === 'package') { overviewDoc.modules.push(doc); } }); diff --git a/aio/tools/transforms/templates/api/lib/githubLinks.html b/aio/tools/transforms/templates/api/lib/githubLinks.html index 349e135b45..f99b37543b 100644 --- a/aio/tools/transforms/templates/api/lib/githubLinks.html +++ b/aio/tools/transforms/templates/api/lib/githubLinks.html @@ -1,12 +1,12 @@ {% macro githubViewHref(doc, versionInfo) -%} -https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/tree/{$ versionInfo.currentVersion.isSnapshot and versionInfo.currentVersion.SHA or versionInfo.currentVersion.raw $}/packages/{$ doc.fileInfo.realProjectRelativePath $}#L{$ doc.startingLine + 1 $}-L{$ doc.endingLine + 1 $} +https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/tree/{$ versionInfo.currentVersion.isSnapshot and versionInfo.currentVersion.SHA or versionInfo.currentVersion.raw $}/packages/{$ doc.fileInfo.realProjectRelativePath or doc.fileInfo.relativePath $}#L{$ doc.startingLine + 1 $}-L{$ doc.endingLine + 1 $} {%- endmacro -%} {% macro githubEditHref(doc, versionInfo) -%} -https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/edit/master/packages/{$ doc.fileInfo.realProjectRelativePath $}?message=docs( +https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/edit/master/packages/{$ doc.fileInfo.realProjectRelativePath or doc.fileInfo.relativePath $}?message=docs( {%- if doc.moduleDoc %}{$ doc.moduleDoc.id.split('/')[0] $} - {%- elseif doc.docType === 'module' %}{$ doc.id.split('/')[0] $} + {%- elseif doc.docType === 'package' %}{$ doc.id.split('/')[0] $} {%- else %}...{%- endif -%} )%3A%20describe%20your%20change...#L{$ doc.startingLine + 1 $}-L{$ doc.endingLine + 1 $} {%- endmacro -%} diff --git a/aio/tools/transforms/templates/api/module.template.html b/aio/tools/transforms/templates/api/module.template.html deleted file mode 100644 index 0704998b13..0000000000 --- a/aio/tools/transforms/templates/api/module.template.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'base.template.html' -%} - -{% block body -%} - -{% include "includes/deprecation.html" %} -{% include "includes/description.html" %} - -
    - -
    - -{%- endblock %} diff --git a/aio/tools/transforms/templates/api/package.template.html b/aio/tools/transforms/templates/api/package.template.html new file mode 100644 index 0000000000..3583ee6a0b --- /dev/null +++ b/aio/tools/transforms/templates/api/package.template.html @@ -0,0 +1,40 @@ +{% extends 'base.template.html' -%} + +{% macro listItems(items, title) %} + {% if items.length %} +
    +

    {$ title $}

    + + {% for item in items %} + + + + + {% endfor %} +
    + {$ item.name $}{% if item.shortDescription %}{$ item.shortDescription | marked $}{% endif %}
    +
    + {% endif %} +{% endmacro %} + +{% block body -%} + {% include "includes/deprecation.html" %} + {$ doc.shortDescription | marked $} + {% if doc.description %}{$ doc.description | marked $}{% endif %} + + {% include "includes/see-also.html" %} + +

    Entry points

    + {$ listItems([doc.packageInfo.primary], 'Primary') $} + {$ listItems(doc.packageInfo.secondary, 'Secondary') $} + + +

    Exports

    + {$ listItems(doc.classes, 'Classes') $} + {$ listItems(doc.decorators, 'Decorators') $} + {$ listItems(doc.functions, 'Functions') $} + {$ listItems(doc.structures, 'Structures') $} + {$ listItems(doc.directives, 'Directives') $} + {$ listItems(doc.pipes, 'Pipes') $} + {$ listItems(doc.types, 'Types') $} +{%- endblock %} diff --git a/packages/animations/PACKAGE.md b/packages/animations/PACKAGE.md new file mode 100644 index 0000000000..412b03243b --- /dev/null +++ b/packages/animations/PACKAGE.md @@ -0,0 +1,29 @@ +Implements a domain-specific language (DSL) for defining web animation sequences for HTML elements as +multiple transformations over time. + +Use this API to define how an HTML element can move, change color, grow or shrink, fade, or slide off +the page. These changes can occur simultaneously or sequentially. You can control the timing of each +of these transformations. The function calls generate the data structures and metadata that enable Angular +to integrate animations into templates and run them based on application states. + +Animation definitions are linked to components through the `{@link Component.animations animations}` +property in the `@Component` metadata, typically in the component file of the HTML element to be animated. +The `trigger()` function encapsulates a named animation, with all other function calls nested within. Use +the trigger name to bind the named animation to a specific triggering element in the HTML template. + +Angular animations are based on CSS web transition functionality, so anything that can be styled or +transformed in CSS can be animated the same way in Angular. Angular animations allow you to: + +* Set animation timings, styles, keyframes, and transitions. +* Animate HTML elements in complex sequences and choreographies. +* Animate HTML elements as they are inserted and removed from the DOM, including responsive real-time + filtering. +* Create reusable animations. +* Animate parent and child elements. + +Additional animation functionality is provided in other Angular modules for animation testing, for +route-based animations, and for programmatic animation controls that allow an end user to fast forward +and reverse an animation sequence. + +@see Find out more in the [animations guide](guide/animations). +@see See what polyfills you might need in the [browser support guide](guide/browser-support). From 74b250b1465661292dfcdd67444261cacb78bf3f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 1 Jul 2018 21:23:45 +0100 Subject: [PATCH 484/582] feat(docs-infra): enable filtering by package on API list page (#24631) PR Close #24631 --- .../api/api-list.component.html | 6 +- .../api/api-list.component.spec.ts | 60 +++++++++++-------- .../custom-elements/api/api-list.component.ts | 19 +++--- .../app/custom-elements/api/api.service.ts | 4 +- 4 files changed, 46 insertions(+), 43 deletions(-) diff --git a/aio/src/app/custom-elements/api/api-list.component.html b/aio/src/app/custom-elements/api/api-list.component.html index aa65d70291..3011853b14 100644 --- a/aio/src/app/custom-elements/api/api-list.component.html +++ b/aio/src/app/custom-elements/api/api-list.component.html @@ -21,10 +21,10 @@
    -

    {{section.title}}

    -
      +

      {{section.title}}

      +
        -
      • +
      • {{item.title}}
      • diff --git a/aio/src/app/custom-elements/api/api-list.component.spec.ts b/aio/src/app/custom-elements/api/api-list.component.spec.ts index 035a07f134..ac10868bf5 100644 --- a/aio/src/app/custom-elements/api/api-list.component.spec.ts +++ b/aio/src/app/custom-elements/api/api-list.component.spec.ts @@ -36,18 +36,13 @@ describe('ApiListComponent', () => { */ function expectFilteredResult(label: string, itemTest: (item: ApiItem) => boolean) { component.filteredSections.subscribe(filtered => { - let badItem: ApiItem|undefined; + filtered = filtered.filter(section => section.items); expect(filtered.length).toBeGreaterThan(0, 'expected something'); - expect(filtered.every(section => section.items.every( - item => { - const ok = item.show === itemTest(item); - if (!ok) { badItem = item; } - return ok; - } - ))).toBe(true, `${label} fail: ${JSON.stringify(badItem, null, 2)}`); + expect(filtered.every(section => section.items!.every(itemTest))).toBe(true, label); }); } + describe('#filteredSections', () => { beforeEach(() => { @@ -65,34 +60,45 @@ describe('ApiListComponent', () => { expectFilteredResult('query: class', item => /class/.test(item.name)); }); - it('item.show should be true for every item in section when query matches section name', () => { + it('items should be an array for every item in section when query matches section name', () => { component.setQuery('core'); component.filteredSections.subscribe(filtered => { + filtered = filtered.filter(section => Array.isArray(section.items)); expect(filtered.length).toBe(1, 'only one section'); expect(filtered[0].name).toBe('core'); - expect(filtered[0].items.every(item => !!item.show)).toBe(true, 'all core items shown'); + expect(filtered[0].items).toEqual(sections.find(section => section.name === 'core')!.items); }); }); - it('item.show should be true for items with selected status', () => { - component.setStatus({value: 'stable', title: 'Stable'}); - expectFilteredResult('status: stable', item => item.stability === 'stable'); + describe('section.items', () => { + it('should null if there are no matching items and the section itself does not match', () => { + component.setQuery('core'); + component.filteredSections.subscribe(filtered => { + const commonSection = filtered.find(section => section.name === 'common')!; + expect(commonSection.items).toBe(null); + }); + }); + + it('should be visible if they have the selected stability status', () => { + component.setStatus({value: 'stable', title: 'Stable'}); + expectFilteredResult('status: stable', item => item.stability === 'stable'); + }); + + it('should be visible if they have the selected security status', () => { + component.setStatus({value: 'security-risk', title: 'Security Risk'}); + expectFilteredResult('status: security-risk', item => item.securityRisk); + }); + + it('should be visible if they match the selected API type', () => { + component.setType({value: 'class', title: 'Class'}); + expectFilteredResult('type: class', item => item.docType === 'class'); + }); }); - it('item.show should be true for items with "security-risk" status when selected', () => { - component.setStatus({value: 'security-risk', title: 'Security Risk'}); - expectFilteredResult('status: security-risk', item => item.securityRisk); - }); - - it('item.show should be true for items of selected type', () => { - component.setType({value: 'class', title: 'Class'}); - expectFilteredResult('type: class', item => item.docType === 'class'); - }); - - it('should have no sections and no items when no match', () => { + it('should have no sections and no items visible when there is no match', () => { component.setQuery('fizbuzz'); component.filteredSections.subscribe(filtered => { - expect(filtered.length).toBe(0, 'expected no sections'); + expect(filtered.some(section => !!section.items)).toBeFalsy(); }); }); }); @@ -108,9 +114,11 @@ describe('ApiListComponent', () => { fixture.detectChanges(); component.filteredSections.subscribe(filtered => { + filtered = filtered.filter(s => s.items); + console.log(filtered); expect(filtered.length).toBe(1, 'sections'); expect(filtered[0].name).toBe(section, 'section name'); - const items = filtered[0].items.filter(i => i.show); + const items = filtered[0].items!; expect(items.length).toBe(1, 'items'); const item = items[0]; diff --git a/aio/src/app/custom-elements/api/api-list.component.ts b/aio/src/app/custom-elements/api/api-list.component.ts index a343bfb949..666b05aa49 100644 --- a/aio/src/app/custom-elements/api/api-list.component.ts +++ b/aio/src/app/custom-elements/api/api-list.component.ts @@ -49,7 +49,8 @@ export class ApiListComponent implements OnInit { { value: 'function', title: 'Function' }, { value: 'interface', title: 'Interface' }, { value: 'pipe', title: 'Pipe'}, - { value: 'type-alias', title: 'Type Alias' } + { value: 'type-alias', title: 'Type alias' }, + { value: 'package', title: 'Package'} ]; statuses: Option[] = [ @@ -71,7 +72,8 @@ export class ApiListComponent implements OnInit { this.apiService.sections, this.criteriaSubject, (sections, criteria) => { - return sections.filter(section => this.filterSection(section, criteria)); + return sections + .map(section => ({ ...section, items: this.filterSection(section, criteria) })); } ); @@ -107,13 +109,8 @@ export class ApiListComponent implements OnInit { //////// Private ////////// private filterSection(section: ApiSection, { query, status, type }: SearchCriteria) { - let showSection = false; - - section.items.forEach(item => { - item.show = matchesType() && matchesStatus() && matchesQuery(); - - // show section if any of its items will be shown - showSection = showSection || item.show; + const items = section.items!.filter(item => { + return matchesType() && matchesStatus() && matchesQuery(); function matchesQuery() { return !query || @@ -132,8 +129,8 @@ export class ApiListComponent implements OnInit { } }); - return showSection; - + // If there are no items we still return an empty array if the section name matches and the type is 'package' + return items.length ? items : (type === 'package' && (!query || section.name.indexOf(query) !== -1)) ? [] : null; } // Get initial search criteria from URL search params diff --git a/aio/src/app/custom-elements/api/api.service.ts b/aio/src/app/custom-elements/api/api.service.ts index c2398e7d91..f90599c8af 100644 --- a/aio/src/app/custom-elements/api/api.service.ts +++ b/aio/src/app/custom-elements/api/api.service.ts @@ -14,15 +14,13 @@ export interface ApiItem { docType: string; stability: string; securityRisk: boolean; - - show?: boolean; } export interface ApiSection { path: string; name: string; title: string; - items: ApiItem[]; + items: ApiItem[]|null; } @Injectable() From 9be8abd0126b76a13d511eb393257e4c5c665d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 16 Jul 2018 11:41:02 -0700 Subject: [PATCH 485/582] build: disable IE web worker tests (#24908) Travis (saucelabs) has been super flaky when running IE web worker tests lately. This patch temporarily disables these tests on IE (not edge) until things get more stable. PR Close #24908 --- .../worker/renderer_v2_integration_spec.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts b/packages/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts index 4b1320c72f..3a3b5e209f 100644 --- a/packages/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts +++ b/packages/platform-webworker/test/web_workers/worker/renderer_v2_integration_spec.ts @@ -81,6 +81,10 @@ let lastCreatedRenderer: Renderer2; } it('should update text nodes', () => { + // IE (v11 to be exact) has been problematic lately with saucelabs for this specific test + // TODO (matsko): revisit this once things become more stable in the saucelabs world + if (isOldIE()) return; + const fixture = TestBed.overrideTemplate(MyComp2, '
        {{ctxProp}}
        ').createComponent(MyComp2); const renderEl = getRenderElement(fixture.nativeElement); @@ -93,6 +97,10 @@ let lastCreatedRenderer: Renderer2; it('should update any element property/attributes/class/style(s) independent of the compilation on the root element and other elements', () => { + // IE (v11 to be exact) has been problematic lately with saucelabs for this specific test + // TODO (matsko): revisit this once things become more stable in the saucelabs world + if (isOldIE()) return; + const fixture = TestBed.overrideTemplate(MyComp2, '') .createComponent(MyComp2); @@ -127,6 +135,10 @@ let lastCreatedRenderer: Renderer2; }); it('should update any template comment property/attributes', () => { + // IE (v11 to be exact) has been problematic lately with saucelabs for this specific test + // TODO (matsko): revisit this once things become more stable in the saucelabs world + if (isOldIE()) return; + const fixture = TestBed.overrideTemplate(MyComp2, '') .createComponent(MyComp2); @@ -137,6 +149,10 @@ let lastCreatedRenderer: Renderer2; }); it('should add and remove fragments', () => { + // IE (v11 to be exact) has been problematic lately with saucelabs for this specific test + // TODO (matsko): revisit this once things become more stable in the saucelabs world + if (isOldIE()) return; + const fixture = TestBed .overrideTemplate(MyComp2, 'hello') @@ -156,6 +172,10 @@ let lastCreatedRenderer: Renderer2; if (getDOM().supportsDOMEvents()) { it('should listen to events', () => { + // IE (v11 to be exact) has been problematic lately with saucelabs for this specific test + // TODO (matsko): revisit this once things become more stable in the saucelabs world + if (isOldIE()) return; + const fixture = TestBed.overrideTemplate(MyComp2, '') .createComponent(MyComp2); @@ -214,3 +234,8 @@ class RenderFactory extends WebWorkerRendererFactory2 { return lastCreatedRenderer; } } + +function isOldIE() { + // note that this only applies to older IEs (not edge) + return (window as any).document['documentMode'] ? true : false; +} From c8ad9657c9423cc2d07d6cc6a230ef28e2b60f3c Mon Sep 17 00:00:00 2001 From: Carlos Ortiz Garcia Date: Fri, 13 Jul 2018 16:14:08 -0700 Subject: [PATCH 486/582] fix(compiler): i18n_extractor now outputs the correct source file name (#24885) for non-inline templates - Non-inline templates used to ouput the path to the component TS file instead of the path to the original HTML file. - Inline templates keep the same behavior. Fixes #24884 PR Close #24885 --- .../compiler-cli/test/extract_i18n_spec.ts | 97 +++++++++++++++---- packages/compiler/src/aot/compiler.ts | 6 +- packages/compiler/src/i18n/extractor.ts | 6 +- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/packages/compiler-cli/test/extract_i18n_spec.ts b/packages/compiler-cli/test/extract_i18n_spec.ts index 4373fef338..8e44da3e22 100644 --- a/packages/compiler-cli/test/extract_i18n_spec.ts +++ b/packages/compiler-cli/test/extract_i18n_spec.ts @@ -43,8 +43,9 @@ const EXPECTED_XMB = ` ]> - src/module.ts:1translate me - src/module.ts:2Welcome + src/basic.html:1src/comp2.ts:1src/basic.html:1translate me + src/basic.html:3,4src/comp2.ts:3,4src/comp2.ts:2,3src/basic.html:3,4 + Welcome `; @@ -55,18 +56,39 @@ const EXPECTED_XLIFF = ` translate me - src/module.ts + src/basic.html + 1 + + + src/comp2.ts + 1 + + + src/basic.html 1 desc meaning - - Welcome + + + Welcome - src/module.ts + src/basic.html + 3 + + + src/comp2.ts + 3 + + + src/comp2.ts 2 + + src/basic.html + 3 + @@ -80,18 +102,24 @@ const EXPECTED_XLIFF2 = ` desc meaning - src/module.ts:1 + src/basic.html:1 + src/comp2.ts:1 + src/basic.html:1 translate me - + - src/module.ts:2 + src/basic.html:3,4 + src/comp2.ts:3,4 + src/comp2.ts:2,3 + src/basic.html:3,4 - Welcome + + Welcome @@ -155,21 +183,54 @@ describe('extract_i18n command line', () => { }); function writeSources() { - write('src/basic.html', [ - `
        `, - `

        Welcome

        `, - ].join('\n')); - write('src/module.ts', ` - import {Component, NgModule} from '@angular/core'; + const welcomeMessage = ` + + Welcome + `; + write('src/basic.html', `
        +

        ${welcomeMessage}

        `); + + write('src/comp1.ts', ` + import {Component} from '@angular/core'; @Component({ selector: 'basic', templateUrl: './basic.html', }) - export class BasicCmp {} + export class BasicCmp1 {}`); + + write('src/comp2.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'basic2', + template: \`
        +

        ${welcomeMessage}

        \`, + }) + export class BasicCmp2 {} + @Component({ + selector: 'basic4', + template: \`

        ${welcomeMessage}

        \`, + }) + export class BasicCmp4 {}`); + + write('src/comp3.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'basic3', + templateUrl: './basic.html', + }) + export class BasicCmp3 {}`); + + write('src/module.ts', ` + import {NgModule} from '@angular/core'; + import {BasicCmp1} from './comp1'; + import {BasicCmp2, BasicCmp4} from './comp2'; + import {BasicCmp3} from './comp3'; @NgModule({ - declarations: [BasicCmp] + declarations: [BasicCmp1, BasicCmp2, BasicCmp3, BasicCmp4] }) export class I18nModule {} `); diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 660c1eeb26..f76e0fe06c 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -318,10 +318,12 @@ export class AotCompiler { }); compMetas.forEach(compMeta => { const html = compMeta.template !.template !; + // Template URL points to either an HTML or TS file depending on whether + // the file is used with `templateUrl:` or `template:`, respectively. + const templateUrl = compMeta.template !.templateUrl !; const interpolationConfig = InterpolationConfig.fromArray(compMeta.template !.interpolation); - errors.push( - ...messageBundle.updateFromTemplate(html, file.fileName, interpolationConfig) !); + errors.push(...messageBundle.updateFromTemplate(html, templateUrl, interpolationConfig) !); }); }); diff --git a/packages/compiler/src/i18n/extractor.ts b/packages/compiler/src/i18n/extractor.ts index 80015d3fa0..8b950feb6d 100644 --- a/packages/compiler/src/i18n/extractor.ts +++ b/packages/compiler/src/i18n/extractor.ts @@ -75,10 +75,14 @@ export class Extractor { }); compMetas.forEach(compMeta => { const html = compMeta.template !.template !; + // Template URL points to either an HTML or TS file depending on + // whether the file is used with `templateUrl:` or `template:`, + // respectively. + const templateUrl = compMeta.template !.templateUrl !; const interpolationConfig = InterpolationConfig.fromArray(compMeta.template !.interpolation); errors.push(...this.messageBundle.updateFromTemplate( - html, file.fileName, interpolationConfig) !); + html, templateUrl, interpolationConfig) !); }); }); From ba3eb8b654d77b504988f8f98eaa9e4b7c256b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 11 Jul 2018 09:56:47 -0700 Subject: [PATCH 487/582] feat(ivy): properly apply class="", [class], [class.foo] and [attr.class] bindings (#24822) PR Close #24822 --- .../src/largetable/render3/table.ts | 8 +- modules/benchmarks/src/tree/render3/tree.ts | 28 +- packages/compiler/src/core.ts | 4 +- .../compiler/src/render3/r3_identifiers.ts | 6 +- .../compiler/src/render3/view/template.ts | 188 ++- .../render3/r3_compiler_compliance_spec.ts | 28 +- .../render3/r3_view_compiler_styling_spec.ts | 136 +- .../core/src/core_render3_private_export.ts | 3 +- packages/core/src/render3/assert.ts | 3 +- packages/core/src/render3/index.ts | 5 +- packages/core/src/render3/instructions.ts | 127 +- .../core/src/render3/interfaces/definition.ts | 4 +- packages/core/src/render3/jit/environment.ts | 3 +- .../core/src/render3/node_manipulation.ts | 8 +- packages/core/src/render3/styling.ts | 522 ++++-- packages/core/src/render3/util.ts | 28 + .../hello_world/bundle.golden_symbols.json | 3 + .../bundling/todo/bundle.golden_symbols.json | 104 +- .../compiler_canonical/elements_spec.ts | 33 +- .../compiler_canonical/sanitize_spec.ts | 12 +- packages/core/test/render3/exports_spec.ts | 9 +- .../core/test/render3/instructions_spec.ts | 32 +- .../core/test/render3/integration_spec.ts | 26 +- packages/core/test/render3/styling_spec.ts | 1414 ++++++++++------- 24 files changed, 1806 insertions(+), 928 deletions(-) diff --git a/modules/benchmarks/src/largetable/render3/table.ts b/modules/benchmarks/src/largetable/render3/table.ts index bbd822dcc1..852538c4b2 100644 --- a/modules/benchmarks/src/largetable/render3/table.ts +++ b/modules/benchmarks/src/largetable/render3/table.ts @@ -48,13 +48,13 @@ export class LargeTableComponent { { if (rf2 & RenderFlags.Create) { E(0, 'td'); - s(1, c0); - { T(2); } + s(c0); + { T(1); } e(); } if (rf2 & RenderFlags.Update) { - sp(1, 0, cell.row % 2 ? '' : 'grey'); - t(2, b(cell.value)); + sp(0, 0, cell.row % 2 ? '' : 'grey'); + t(1, b(cell.value)); } } v(); diff --git a/modules/benchmarks/src/tree/render3/tree.ts b/modules/benchmarks/src/tree/render3/tree.ts index 17ef84672b..3fc03b2f81 100644 --- a/modules/benchmarks/src/tree/render3/tree.ts +++ b/modules/benchmarks/src/tree/render3/tree.ts @@ -41,16 +41,16 @@ export class TreeComponent { template: function(rf: RenderFlags, ctx: TreeComponent) { if (rf & RenderFlags.Create) { E(0, 'span'); - s(1, c0); - { T(2); } + s(c0); + { T(1); } e(); + C(2); C(3); - C(4); } if (rf & RenderFlags.Update) { - sp(1, 0, ctx.data.depth % 2 ? '' : 'grey'); - t(2, i1(' ', ctx.data.value, ' ')); - cR(3); + sp(0, 0, ctx.data.depth % 2 ? '' : 'grey'); + t(1, i1(' ', ctx.data.value, ' ')); + cR(2); { if (ctx.data.left != null) { let rf0 = V(0); @@ -67,7 +67,7 @@ export class TreeComponent { } } cr(); - cR(4); + cR(3); { if (ctx.data.right != null) { let rf0 = V(0); @@ -114,18 +114,18 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) { E(0, 'tree'); { E(1, 'span'); - s(2, c1); - { T(3); } + s(c1); + { T(2); } e(); + C(3); C(4); - C(5); } e(); } if (rf & RenderFlags.Update) { - sp(2, 0, ctx.depth % 2 ? '' : 'grey'); - t(3, i1(' ', ctx.value, ' ')); - cR(4); + sp(1, 0, ctx.depth % 2 ? '' : 'grey'); + t(2, i1(' ', ctx.value, ' ')); + cR(3); { if (ctx.left != null) { let rf0 = V(0); @@ -134,7 +134,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) { } } cr(); - cR(5); + cR(4); { if (ctx.right != null) { let rf0 = V(0); diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 353645a8e6..e34aa4c1c0 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -380,8 +380,6 @@ export const enum RenderFlags { Update = 0b10 } -// Note this will expand once `class` is introduced to styling export const enum InitialStylingFlags { - /** Mode for matching initial style values */ - INITIAL_STYLES = 0b00, + VALUES_MODE = 0b1, } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 12f6375229..698326b6a8 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -33,13 +33,11 @@ export class Identifiers { static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE}; - static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE}; - - static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE}; + static elementClassProp: o.ExternalReference = {name: 'ɵcp', moduleName: CORE}; static elementStyling: o.ExternalReference = {name: 'ɵs', moduleName: CORE}; - static elementStyle: o.ExternalReference = {name: 'ɵsm', moduleName: CORE}; + static elementStylingMap: o.ExternalReference = {name: 'ɵsm', moduleName: CORE}; static elementStyleProp: o.ExternalReference = {name: 'ɵsp', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index ed55319205..e50f2a5ce9 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -40,19 +40,12 @@ function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefin case BindingType.Attribute: return R3.elementAttribute; case BindingType.Class: - return R3.elementClassNamed; + return R3.elementClassProp; default: return undefined; } } -// `className` is used below instead of `class` because the interception -// code (where this map is used) deals with DOM element property values -// (like elm.propName) and not component bindining properties (like [propName]). -const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP: {[index: string]: o.ExternalReference} = { - 'className': R3.elementClass -}; - export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _dataIndex = 0; private _bindingContext = 0; @@ -310,33 +303,59 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const i18nMessages: o.Statement[] = []; const attributes: o.Expression[] = []; const initialStyleDeclarations: o.Expression[] = []; + const initialClassDeclarations: o.Expression[] = []; const styleInputs: t.BoundAttribute[] = []; + const classInputs: t.BoundAttribute[] = []; const allOtherInputs: t.BoundAttribute[] = []; element.inputs.forEach((input: t.BoundAttribute) => { - // [attr.style] should not be treated as a styling-based - // binding since it is intended to write directly to the attr - // and therefore will skip all style resolution that is present - // with style="", [style]="" and [style.prop]="" assignments - if (input.name == 'style' && input.type == BindingType.Property) { - // this should always go first in the compilation (for [style]) - styleInputs.splice(0, 0, input); - } else if (input.type == BindingType.Style) { - styleInputs.push(input); - } else { - allOtherInputs.push(input); + switch (input.type) { + // [attr.style] or [attr.class] should not be treated as styling-based + // bindings since they are intended to be written directly to the attr + // and therefore will skip all style/class resolution that is present + // with style="", [style]="" and [style.prop]="", class="", + // [class.prop]="". [class]="" assignments + case BindingType.Property: + if (input.name == 'style') { + // this should always go first in the compilation (for [style]) + styleInputs.splice(0, 0, input); + } else if (isClassBinding(input)) { + // this should always go first in the compilation (for [class]) + classInputs.splice(0, 0, input); + } else { + allOtherInputs.push(input); + } + break; + case BindingType.Style: + styleInputs.push(input); + break; + case BindingType.Class: + classInputs.push(input); + break; + default: + allOtherInputs.push(input); + break; } }); let currStyleIndex = 0; + let currClassIndex = 0; let staticStylesMap: {[key: string]: any}|null = null; + let staticClassesMap: {[key: string]: boolean}|null = null; const stylesIndexMap: {[key: string]: number} = {}; + const classesIndexMap: {[key: string]: number} = {}; Object.getOwnPropertyNames(outputAttrs).forEach(name => { const value = outputAttrs[name]; if (name == 'style') { staticStylesMap = parseStyle(value); Object.keys(staticStylesMap).forEach(prop => { stylesIndexMap[prop] = currStyleIndex++; }); + } else if (name == 'class') { + staticClassesMap = {}; + value.split(/\s+/g).forEach(className => { + classesIndexMap[className] = currClassIndex++; + staticClassesMap ![className] = true; + }); } else { attributes.push(o.literal(name)); if (attrI18nMetas.hasOwnProperty(name)) { @@ -357,6 +376,14 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } + for (let i = 0; i < classInputs.length; i++) { + const input = classInputs[i]; + const isMapBasedClassBinding = i === 0 && isClassBinding(input); + if (!isMapBasedClassBinding && !stylesIndexMap.hasOwnProperty(input.name)) { + classesIndexMap[input.name] = currClassIndex++; + } + } + // this will build the instructions so that they fall into the following syntax // => [prop1, prop2, prop3, 0, prop1, value1, prop2, value2] Object.keys(stylesIndexMap).forEach(prop => { @@ -364,7 +391,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver }); if (staticStylesMap) { - initialStyleDeclarations.push(o.literal(core.InitialStylingFlags.INITIAL_STYLES)); + initialStyleDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE)); Object.keys(staticStylesMap).forEach(prop => { initialStyleDeclarations.push(o.literal(prop)); @@ -373,6 +400,22 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver }); } + Object.keys(classesIndexMap).forEach(prop => { + initialClassDeclarations.push(o.literal(prop)); + }); + + if (staticClassesMap) { + initialClassDeclarations.push(o.literal(core.InitialStylingFlags.VALUES_MODE)); + + Object.keys(staticClassesMap).forEach(className => { + initialClassDeclarations.push(o.literal(className)); + initialClassDeclarations.push(o.literal(true)); + }); + } + + const hasStylingInstructions = initialStyleDeclarations.length || styleInputs.length || + initialClassDeclarations.length || classInputs.length; + const attrArg: o.Expression = attributes.length > 0 ? this.constantPool.getConstLiteral(o.literalArr(attributes), true) : o.TYPED_NULL_EXPR; @@ -411,10 +454,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const implicit = o.variable(CONTEXT_NAME); - const elementStyleIndex = - (initialStyleDeclarations.length || styleInputs.length) ? this.allocateDataSlot() : 0; const createSelfClosingInstruction = - elementStyleIndex === 0 && element.children.length === 0 && element.outputs.length === 0; + !hasStylingInstructions && element.children.length === 0 && element.outputs.length === 0; if (createSelfClosingInstruction) { this.instruction( @@ -429,16 +470,30 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver ...trimTrailingNulls(parameters)); // initial styling for static style="..." attributes - if (elementStyleIndex > 0) { - let paramsList: (o.Expression)[] = [o.literal(elementStyleIndex)]; + if (hasStylingInstructions) { + const paramsList: (o.Expression)[] = []; + if (initialStyleDeclarations.length) { - // the template compiler handles initial styling (e.g. style="foo") values + // the template compiler handles initial style (e.g. style="foo") values // in a special command called `elementStyle` so that the initial styles // can be processed during runtime. These initial styles values are bound to // a constant because the inital style values do not change (since they're static). paramsList.push( this.constantPool.getConstLiteral(o.literalArr(initialStyleDeclarations), true)); + } else if (initialClassDeclarations.length) { + // no point in having an extra `null` value unless there are follow-up params + paramsList.push(o.NULL_EXPR); } + + if (initialClassDeclarations.length) { + // the template compiler handles initial class styling (e.g. class="foo") values + // in a special command called `elementClass` so that the initial class + // can be processed during runtime. These initial class values are bound to + // a constant because the inital class values do not change (since they're static). + paramsList.push( + this.constantPool.getConstLiteral(o.literalArr(initialClassDeclarations), true)); + } + this._creationCode.push(o.importExpr(R3.elementStyling).callFn(paramsList).toStmt()); } @@ -465,25 +520,64 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver }); } - if (styleInputs.length && elementStyleIndex > 0) { - const indexLiteral = o.literal(elementStyleIndex); - styleInputs.forEach((input, i) => { - const isMapBasedStyleBinding = i == 0 && input.name == 'style'; - const convertedBinding = this.convertPropertyBinding(implicit, input.value, true); - if (isMapBasedStyleBinding) { - this.instruction( - this._bindingCode, input.sourceSpan, R3.elementStyle, indexLiteral, convertedBinding); - } else { + if ((styleInputs.length || classInputs.length) && hasStylingInstructions) { + const indexLiteral = o.literal(elementIndex); + + const firstStyle = styleInputs[0]; + const mapBasedStyleInput = firstStyle && firstStyle.name == 'style' ? firstStyle : null; + + const firstClass = classInputs[0]; + const mapBasedClassInput = firstClass && isClassBinding(firstClass) ? firstClass : null; + + const stylingInput = mapBasedStyleInput || mapBasedClassInput; + if (stylingInput) { + const params: o.Expression[] = []; + if (mapBasedStyleInput) { + params.push(this.convertPropertyBinding(implicit, mapBasedStyleInput.value, true)); + } else if (mapBasedClassInput) { + params.push(o.NULL_EXPR); + } + if (mapBasedClassInput) { + params.push(this.convertPropertyBinding(implicit, mapBasedClassInput.value, true)); + } + this.instruction( + this._bindingCode, stylingInput.sourceSpan, R3.elementStylingMap, indexLiteral, + ...params); + } + + let lastInputCommand: t.BoundAttribute|null = null; + if (styleInputs.length) { + let i = mapBasedStyleInput ? 1 : 0; + for (i; i < styleInputs.length; i++) { + const input = styleInputs[i]; + const convertedBinding = this.convertPropertyBinding(implicit, input.value, true); const key = input.name; - let styleIndex: number = stylesIndexMap[key] !; + const styleIndex: number = stylesIndexMap[key] !; this.instruction( this._bindingCode, input.sourceSpan, R3.elementStyleProp, indexLiteral, o.literal(styleIndex), convertedBinding); } - }); - const spanEnd = styleInputs[styleInputs.length - 1].sourceSpan; - this.instruction(this._bindingCode, spanEnd, R3.elementStylingApply, indexLiteral); + lastInputCommand = styleInputs[styleInputs.length - 1]; + } + + if (classInputs.length) { + let i = mapBasedClassInput ? 1 : 0; + for (i; i < classInputs.length; i++) { + const input = classInputs[i]; + const convertedBinding = this.convertPropertyBinding(implicit, input.value, true); + const key = input.name; + const classIndex: number = classesIndexMap[key] !; + this.instruction( + this._bindingCode, input.sourceSpan, R3.elementClassProp, indexLiteral, + o.literal(classIndex), convertedBinding); + } + + lastInputCommand = classInputs[classInputs.length - 1]; + } + + this.instruction( + this._bindingCode, lastInputCommand !.sourceSpan, R3.elementStylingApply, indexLiteral); } // Generate element input bindings @@ -494,18 +588,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } const convertedBinding = this.convertPropertyBinding(implicit, input.value); - const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name]; - if (specialInstruction) { - // special case for [style] and [class] bindings since they are not handled as - // standard properties within this implementation. Instead they are - // handed off to special cased instruction handlers which will then - // delegate them as animation sequences (or input bindings for dirs/cmps) - this.instruction( - this._bindingCode, input.sourceSpan, specialInstruction, o.literal(elementIndex), - convertedBinding); - return; - } - const instruction = mapBindingToInstruction(input.type); if (instruction) { // TODO(chuckj): runtime: security context? @@ -975,3 +1057,7 @@ export function makeBindingParser(): BindingParser { new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), null, []); } + +function isClassBinding(input: t.BoundAttribute): boolean { + return input.name == 'className' || input.name == 'class'; +} diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 47a041a224..007c103bc4 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -6,9 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ +import {InitialStylingFlags} from '../../src/core'; import {MockDirectory, setup} from '../aot/test_util'; + import {compile, expectEmit} from './mock_compile'; + /* These tests are codified version of the tests in compiler_canonical_spec.ts. Every * test in compiler_canonical_spec.ts should have a corresponding test here. */ @@ -44,15 +47,17 @@ describe('compiler compliance', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c1$ = ['class', 'my-app', 'title', 'Hello']; - const $c2$ = ['cx', '20', 'cy', '30', 'r', '50']; + const $c1$ = ['title', 'Hello']; + const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true]; + const $c3$ = ['cx', '20', 'cy', '30', 'r', '50']; … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div', $c1$); + $r3$.ɵs((null as any), $c2$); $r3$.ɵNS(); $r3$.ɵE(1, 'svg'); - $r3$.ɵEe(2, 'circle', $c2$); + $r3$.ɵEe(2, 'circle', $c3$); $r3$.ɵe(); $r3$.ɵNH(); $r3$.ɵE(3, 'p'); @@ -93,11 +98,13 @@ describe('compiler compliance', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c1$ = ['class', 'my-app', 'title', 'Hello']; + const $c1$ = ['title', 'Hello']; + const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true]; … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div', $c1$); + $r3$.ɵs((null as any), $c2$); $r3$.ɵNM(); $r3$.ɵE(1, 'math'); $r3$.ɵEe(2, 'infinity'); @@ -141,11 +148,13 @@ describe('compiler compliance', () => { // The template should look like this (where IDENT is a wild card for an identifier): const template = ` - const $c1$ = ['class', 'my-app', 'title', 'Hello']; + const $c1$ = ['title', 'Hello']; + const $c2$ = ['my-app', ${InitialStylingFlags.VALUES_MODE}, 'my-app', true]; … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { $r3$.ɵE(0, 'div', $c1$); + $r3$.ɵs((null as any), $c2$); $r3$.ɵT(1, 'Hello '); $r3$.ɵE(2, 'b'); $r3$.ɵT(3, 'World'); @@ -322,6 +331,7 @@ describe('compiler compliance', () => { const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }'; const template = ` const _c0 = ['background-color']; + const _c1 = ['error']; class MyComponent { static ngComponentDef = i0.ɵdefineComponent({type:MyComponent,selectors:[['my-component']], factory:function MyComponent_Factory(){ @@ -329,13 +339,13 @@ describe('compiler compliance', () => { },template:function MyComponent_Template(rf:number,ctx:any){ if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1, _c0); + $r3$.ɵs(_c0, _c1); $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵsp(1, 0, ctx.color); - $r3$.ɵsa(1); - $r3$.ɵkn(0, 'error', $r3$.ɵb(ctx.error)); + $r3$.ɵsp(0, 0, ctx.color); + $r3$.ɵcp(0, 0, ctx.error); + $r3$.ɵsa(0); } } `; diff --git a/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts b/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts index eae327c642..63b29ae47a 100644 --- a/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts @@ -43,12 +43,12 @@ describe('compiler compliance: styling', () => { template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1); + $r3$.ɵs(); $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵsm(1, $ctx$.myStyleExp); - $r3$.ɵsa(1); + $r3$.ɵsm(0, $ctx$.myStyleExp); + $r3$.ɵsa(0); } } `; @@ -57,7 +57,7 @@ describe('compiler compliance: styling', () => { expectEmit(result.source, template, 'Incorrect template'); }); - it('should place initial, multi, singular and application followed by attribute styling instructions in the template code in that order', + it('should place initial, multi, singular and application followed by attribute style instructions in the template code in that order', () => { const files = { app: { @@ -85,7 +85,7 @@ describe('compiler compliance: styling', () => { }; const template = ` - const _c0 = ['opacity','width','height',${InitialStylingFlags.INITIAL_STYLES},'opacity','1']; + const _c0 = ['opacity','width','height',${InitialStylingFlags.VALUES_MODE},'opacity','1']; class MyComponent { static ngComponentDef = i0.ɵdefineComponent({ type: MyComponent, @@ -96,14 +96,14 @@ describe('compiler compliance: styling', () => { template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1, _c0); + $r3$.ɵs(_c0); $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵsm(1, $ctx$.myStyleExp); - $r3$.ɵsp(1, 1, $ctx$.myWidth); - $r3$.ɵsp(1, 2, $ctx$.myHeight); - $r3$.ɵsa(1); + $r3$.ɵsm(0, $ctx$.myStyleExp); + $r3$.ɵsp(0, 1, $ctx$.myWidth); + $r3$.ɵsp(0, 2, $ctx$.myHeight); + $r3$.ɵsa(0); $r3$.ɵa(0, 'style', $r3$.ɵb('border-width: 10px')); } } @@ -127,7 +127,7 @@ describe('compiler compliance: styling', () => { template: \`
        \` }) export class MyComponent { - myClassExp = [{color:'orange'}, {color:'green', duration:1000}] + myClassExp = {'foo':true} } @NgModule({declarations: [MyComponent]}) @@ -139,10 +139,13 @@ describe('compiler compliance: styling', () => { const template = ` template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { if (rf & 1) { - $r3$.ɵEe(0, 'div'); + $r3$.ɵE(0, 'div'); + $r3$.ɵs(); + $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵk(0,$r3$.ɵb($ctx$.myClassExp)); + $r3$.ɵsm(0,(null as any),$ctx$.myClassExp); + $r3$.ɵsa(0); } } `; @@ -150,5 +153,112 @@ describe('compiler compliance: styling', () => { const result = compile(files, angularFiles); expectEmit(result.source, template, 'Incorrect template'); }); + + it('should place initial, multi, singular and application followed by attribute class instructions in the template code in that order', + () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \`
        \` + }) + export class MyComponent { + myClassExp = {a:true, b:true}; + yesToApple = true; + yesToOrange = true; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + const _c0 = ['grape','apple','orange',${InitialStylingFlags.VALUES_MODE},'grape',true]; + class MyComponent { + static ngComponentDef = i0.ɵdefineComponent({ + type: MyComponent, + selectors:[['my-component']], + factory:function MyComponent_Factory(){ + return new MyComponent(); + }, + template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { + if (rf & 1) { + $r3$.ɵE(0, 'div'); + $r3$.ɵs((null as any), _c0); + $r3$.ɵe(); + } + if (rf & 2) { + $r3$.ɵsm(0, (null as any), $ctx$.myClassExp); + $r3$.ɵcp(0, 1, $ctx$.yesToApple); + $r3$.ɵcp(0, 2, $ctx$.yesToOrange); + $r3$.ɵsa(0); + $r3$.ɵa(0, 'class', $r3$.ɵb('banana')); + } + } + }); + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + + it('should not generate the styling apply instruction if there are only static style/class attributes', + () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \`
        \` + }) + export class MyComponent {} + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + const _c0 = ['width',${InitialStylingFlags.VALUES_MODE},'width','100px']; + const _c1 = ['foo',${InitialStylingFlags.VALUES_MODE},'foo',true]; + class MyComponent { + static ngComponentDef = i0.ɵdefineComponent({ + type: MyComponent, + selectors:[['my-component']], + factory:function MyComponent_Factory(){ + return new MyComponent(); + }, + template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { + if (rf & 1) { + $r3$.ɵE(0, 'div'); + $r3$.ɵs(_c0, _c1); + $r3$.ɵe(); + } + if (rf & 2) { + $r3$.ɵa(0, 'class', $r3$.ɵb('round')); + $r3$.ɵa(0, 'style', $r3$.ɵb('height:100px')); + } + } + }); + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); }); }); diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index a6853410d9..ee98d506bd 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -80,8 +80,7 @@ export { sm as ɵsm, sp as ɵsp, sa as ɵsa, - k as ɵk, - kn as ɵkn, + cp as ɵcp, t as ɵt, v as ɵv, st as ɵst, diff --git a/packages/core/src/render3/assert.ts b/packages/core/src/render3/assert.ts index 121217cb58..dc3d97311d 100644 --- a/packages/core/src/render3/assert.ts +++ b/packages/core/src/render3/assert.ts @@ -63,6 +63,7 @@ export function assertComponentType( msg: string = 'Type passed in is not ComponentType, it does not have \'ngComponentDef\' property.') { if (!actual.ngComponentDef) { + debugger; throwError(msg); } } @@ -70,4 +71,4 @@ export function assertComponentType( function throwError(msg: string): never { debugger; // Left intentionally for better debugger experience. throw new Error(`ASSERTION ERROR: ${msg}`); -} \ No newline at end of file +} diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 5734e884ed..e022bc4144 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -51,14 +51,13 @@ export { element as Ee, elementAttribute as a, - elementClass as k, - elementClassNamed as kn, + elementClassProp as cp, elementEnd as e, elementProperty as p, elementStart as E, elementStyling as s, - elementStyle as sm, + elementStylingMap as sm, elementStyleProp as sp, elementStylingApply as sa, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index d5f0e2c4f4..a226cbb499 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,7 +15,7 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; -import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; @@ -25,8 +25,8 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, Curre import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyles as renderElementStyles, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp} from './styling'; -import {isDifferent, stringify} from './util'; +import {StylingContext, StylingIndex, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling'; +import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util'; import {ViewRef} from './view_ref'; @@ -96,6 +96,7 @@ export const CIRCULAR = '__CIRCULAR__'; */ let renderer: Renderer3; let rendererFactory: RendererFactory3; +let currentElementNode: LElementNode|null = null; export function getRenderer(): Renderer3 { // top level variables should not be exported for performance reasons (PERF_NOTES.md) @@ -668,6 +669,7 @@ export function elementStart( const node: LElementNode = createLNode(index, TNodeType.Element, native !, name, attrs || null, null); + currentElementNode = node; if (attrs) { setUpAttributes(native, attrs); @@ -1104,6 +1106,7 @@ export function elementEnd() { const queries = previousOrParentNode.queries; queries && queries.addNode(previousOrParentNode); queueLifecycleHooks(previousOrParentNode.tNode.flags, tView); + currentElementNode = null; } /** @@ -1118,7 +1121,7 @@ export function elementEnd() { export function elementAttribute( index: number, name: string, value: any, sanitizer?: SanitizerFn): void { if (value !== NO_CHANGE) { - const element: LElementNode = load(index); + const element = loadElement(index); if (value == null) { ngDevMode && ngDevMode.rendererRemoveAttribute++; isProceduralRenderer(renderer) ? renderer.removeAttribute(element.native, name) : @@ -1149,7 +1152,7 @@ export function elementAttribute( export function elementProperty( index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void { if (value === NO_CHANGE) return; - const node = load(index) as LElementNode; + const node = loadElement(index) as LElementNode; const tNode = node.tNode; // if tNode.inputs is undefined, a listener has created outputs, but inputs haven't // yet been checked @@ -1268,44 +1271,9 @@ function generatePropertyAliases( * renaming as part of minification. * @param value A value indicating if a given class should be added or removed. */ -export function elementClassNamed(index: number, className: string, value: T | NO_CHANGE): void { - if (value !== NO_CHANGE) { - const lElement = load(index) as LElementNode; - if (value) { - ngDevMode && ngDevMode.rendererAddClass++; - isProceduralRenderer(renderer) ? renderer.addClass(lElement.native, className) : - lElement.native.classList.add(className); - - } else { - ngDevMode && ngDevMode.rendererRemoveClass++; - isProceduralRenderer(renderer) ? renderer.removeClass(lElement.native, className) : - lElement.native.classList.remove(className); - } - } -} - -/** - * Set the `className` property on a DOM element. - * - * This instruction is meant to handle the `[class]="exp"` usage. - * - * `elementClass` instruction writes the value to the "element's" `className` property. - * - * @param index The index of the element to update in the data array - * @param value A value indicating a set of classes which should be applied. The method overrides - * any existing classes. The value is stringified (`toString`) before it is applied to the - * element. - */ -export function elementClass(index: number, value: T | NO_CHANGE): void { - if (value !== NO_CHANGE) { - // TODO: This is a naive implementation which simply writes value to the `className`. In the - // future - // we will add logic here which would work with the animation code. - const lElement: LElementNode = load(index); - ngDevMode && ngDevMode.rendererSetClassName++; - isProceduralRenderer(renderer) ? renderer.setProperty(lElement.native, 'className', value) : - lElement.native['className'] = stringify(value); - } +export function elementClassProp( + index: number, stylingIndex: number, value: T | NO_CHANGE): void { + updateElementClassProp(getStylingContext(index), stylingIndex, value ? true : false); } /** @@ -1323,22 +1291,29 @@ export function elementClass(index: number, value: T | NO_CHANGE): void { * (Note that this is not the element index, but rather an index value allocated * specifically for element styling--the index must be the next index after the element * index.) - * @param styles A key/value map of CSS styles that will be registered on the element. + * @param styleDeclarations A key/value array of CSS styles that will be registered on the element. * Each individual style will be used on the element as long as it is not overridden * by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`) * bindings. If a style binding changes its value to null then the initial styling * values that are passed in here will be applied to the element (if matched). + * @param classDeclarations A key/value array of CSS classes that will be registered on the element. + * Each individual style will be used on the element as long as it is not overridden + * by any classes placed on the element by multiple (`[class]`) or singular (`[class.named]`) + * bindings. If a class binding changes its value to a falsy value then the matching initial + * class value that are passed in here will be applied to the element (if matched). */ -export function elementStyling(index: number, styles?: (string | number)[] | null): void { - const tNode = load(index - 1).tNode; +export function elementStyling( + styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, + classDeclarations?: (string | boolean | InitialStylingFlags)[] | null): void { + const lElement = currentElementNode !; + const tNode = lElement.tNode; if (!tNode.stylingTemplate) { // initialize the styling template. - tNode.stylingTemplate = createStylingContextTemplate(styles); + tNode.stylingTemplate = createStylingContextTemplate(styleDeclarations, classDeclarations); } - // Allocate space but leave null for lazy creation. - viewData[index + HEADER_OFFSET] = null; - if (styles && styles.length) { - elementStylingApply(index); + if (styleDeclarations && styleDeclarations.length || + classDeclarations && classDeclarations.length) { + elementStylingApply(tNode.index - HEADER_OFFSET); } } @@ -1354,12 +1329,13 @@ export function elementStyling(index: number, styles?: (string | number)[] | */ function getStylingContext(index: number): StylingContext { let stylingContext = load(index); - if (!stylingContext) { - const lElement: LElementNode = load(index - 1); + if (!Array.isArray(stylingContext)) { + const lElement = stylingContext as any as LElementNode; const tNode = lElement.tNode; ngDevMode && assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()'); - stylingContext = viewData[index + HEADER_OFFSET] = allocStylingContext(tNode.stylingTemplate !); + stylingContext = viewData[index + HEADER_OFFSET] = + allocStylingContext(lElement, tNode.stylingTemplate !); } return stylingContext; } @@ -1379,7 +1355,7 @@ function getStylingContext(index: number): StylingContext { * index.) */ export function elementStylingApply(index: number): void { - renderElementStyles(load(index - 1), getStylingContext(index), renderer); + renderElementStyles(getStylingContext(index), renderer); } /** @@ -1436,12 +1412,17 @@ export function elementStyleProp( * (Note that this is not the element index, but rather an index value allocated * specifically for element styling--the index must be the next index after the element * index.) - * @param value A value indicating if a given style should be added or removed. - * The expected shape of `value` is an object where keys are style names and the values - * are their corresponding values to set. If value is null, then the style is removed. + * @param styles A key/value style map of the styles that will be applied to the given element. + * Any missing styles (that have already been applied to the element beforehand) will be + * removed (unset) from the element's styling. + * @param classes A key/value style map of CSS classes that will be added to the given element. + * Any missing classes (that have already been applied to the element beforehand) will be + * removed (unset) from the element's list of CSS classes. */ -export function elementStyle(index: number, value: {[styleName: string]: any} | null): void { - updateElementStyleMap(getStylingContext(index), value); +export function elementStylingMap( + index: number, styles: {[styleName: string]: any} | null, + classes?: {[key: string]: any} | string | null): void { + updateStylingMap(getStylingContext(index), styles, classes); } ////////////////////////// @@ -1476,7 +1457,7 @@ export function text(index: number, value?: any): void { export function textBinding(index: number, value: T | NO_CHANGE): void { if (value !== NO_CHANGE) { ngDevMode && assertDataInRange(index + HEADER_OFFSET); - const existingNode = load(index) as LTextNode; + const existingNode = loadElement(index) as any as LTextNode; ngDevMode && assertDefined(existingNode, 'LNode should exist'); ngDevMode && assertDefined(existingNode.native, 'native element should exist'); ngDevMode && ngDevMode.rendererSetText++; @@ -1758,7 +1739,7 @@ export function container( * @param index The index of the container in the data array */ export function containerRefreshStart(index: number): void { - previousOrParentNode = load(index) as LNode; + previousOrParentNode = loadElement(index) as LNode; ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); isParent = true; (previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0; @@ -2541,17 +2522,6 @@ export function store(index: number, value: T): void { viewData[adjustedIndex] = value; } -/** Retrieves a value from current `viewData`. */ -export function load(index: number): T { - return loadInternal(index, viewData); -} - -/** Retrieves a value from any `LViewData`. */ -export function loadInternal(index: number, arr: LViewData): T { - ngDevMode && assertDataInRange(index + HEADER_OFFSET, arr); - return arr[index + HEADER_OFFSET]; -} - /** Retrieves a value from the `directives` array. */ export function loadDirective(index: number): T { ngDevMode && assertDefined(directives, 'Directives array should be defined if reading a dir.'); @@ -2568,6 +2538,15 @@ export function loadQueryList(queryListIdx: number): QueryList { return viewData[CONTENT_QUERIES] ![queryListIdx]; } +/** Retrieves a value from current `viewData`. */ +export function load(index: number): T { + return loadInternal(index, viewData); +} + +export function loadElement(index: number): LElementNode { + return loadElementInternal(index, viewData); +} + /** Gets the current binding value and increments the binding index. */ export function consumeBinding(): any { ngDevMode && assertDataInRange(viewData[BINDING_INDEX]); @@ -2645,7 +2624,7 @@ function assertHasParent() { function assertDataInRange(index: number, arr?: any[]) { if (arr == null) arr = viewData; - assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index'); + assertDataInRangeInternal(index, arr || viewData); } function assertDataNext(index: number, arr?: any[]) { diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 20ddafad12..27cedbdefb 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -299,8 +299,6 @@ export type PipeTypeList = // failure based on types. export const unusedValueExportToPlacateAjd = 1; -// Note this will expand once `class` is introduced to styling export const enum InitialStylingFlags { - /** Mode for matching initial style values */ - INITIAL_STYLES = 0b00, + VALUES_MODE = 0b1, } diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index b0d3f7b8ae..b5808dd6ad 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -63,8 +63,7 @@ export const angularCoreEnv: {[name: string]: Function} = { 'ɵi7': r3.i7, 'ɵi8': r3.i8, 'ɵiV': r3.iV, - 'ɵk': r3.k, - 'ɵkn': r3.kn, + 'ɵcp': r3.cp, 'ɵL': r3.L, 'ɵld': r3.ld, 'ɵP': r3.P, diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 41c9f14cc5..56f99d8e78 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -14,7 +14,7 @@ import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection' import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {stringify} from './util'; +import {readElementValue, stringify} from './util'; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; @@ -32,7 +32,7 @@ export function getNextLNode(node: LNode): LNode|null { export function getChildLNode(node: LNode): LNode|null { if (node.tNode.child) { const viewData = node.tNode.type === TNodeType.View ? node.data as LViewData : node.view; - return viewData[node.tNode.child.index]; + return readElementValue(viewData[node.tNode.child.index]); } return null; } @@ -50,7 +50,7 @@ export function getParentLNode(node: LNode): LElementNode|LContainerNode|LViewNo return containerHostIndex === -1 ? null : node.view[containerHostIndex].dynamicLContainerNode; } const parent = node.tNode.parent; - return parent ? node.view[parent.index] : node.view[HOST_NODE]; + return readElementValue(parent ? node.view[parent.index] : node.view[HOST_NODE]); } const enum WalkLNodeTreeAction { @@ -463,7 +463,7 @@ function removeListeners(viewData: LViewData): void { for (let i = 0; i < cleanup.length - 1; i += 2) { if (typeof cleanup[i] === 'string') { // This is a listener with the native renderer - const native = viewData[cleanup[i + 1]].native; + const native = readElementValue(viewData[cleanup[i + 1]]).native; const listener = viewData[CLEANUP] ![cleanup[i + 2]]; native.removeEventListener(cleanup[i], listener, cleanup[i + 3]); i += 2; diff --git a/packages/core/src/render3/styling.ts b/packages/core/src/render3/styling.ts index ddae3e798a..d4e2d049da 100644 --- a/packages/core/src/render3/styling.ts +++ b/packages/core/src/render3/styling.ts @@ -12,66 +12,81 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces /** * The styling context acts as a styling manifest (shaped as an array) for determining which - * styling properties have been assigned via the provided `updateStyleMap` and `updateStyleProp` - * functions. There are also two initialization functions `allocStylingContext` and - * `createStylingContextTemplate` which are used to initialize and/or clone the context. + * styling properties have been assigned via the provided `updateStylingMap`, `updateStyleProp` + * and `updateClassProp` functions. There are also two initialization functions + * `allocStylingContext` and `createStylingContextTemplate` which are used to initialize + * and/or clone the context. * * The context is an array where the first two cells are used for static data (initial styling) * and dirty flags / index offsets). The remaining set of cells is used for multi (map) and single * (prop) style values. * * each value from here onwards is mapped as so: - * [i] = mutation/type flag for the style value + * [i] = mutation/type flag for the style/class value * [i + 1] = prop string (or null incase it has been removed) * [i + 2] = value string (or null incase it has been removed) * * There are three types of styling types stored in this context: * initial: any styles that are passed in once the context is created * (these are stored in the first cell of the array and the first - * value of this array is always `null` even if no initial styles exist. + * value of this array is always `null` even if no initial styling exists. * the `null` value is there so that any new styles have a parent to point * to. This way we can always assume that there is a parent.) * - * single: any styles that are updated using `updateStyleProp` (fixed set) + * single: any styles that are updated using `updateStyleProp` or `updateClassProp` (fixed set) * - * multi: any styles that are updated using `updateStyleMap` (dynamic set) + * multi: any styles that are updated using `updateStylingMap` (dynamic set) * - * Note that context is only used to collect style information. Only when `renderStyles` + * Note that context is only used to collect style information. Only when `renderStyling` * is called is when the styling payload will be rendered (or built as a key/value map). * - * When the context is created, depending on what initial styles are passed in, the context itself - * will be pre-filled with slots based on the initial style properties. Say for example we have a - * series of initial styles that look like so: + * When the context is created, depending on what initial styling values are passed in, the + * context itself will be pre-filled with slots based on the initial style properties. Say + * for example we have a series of initial styles that look like so: * * style="width:100px; height:200px;" + * class="foo" * * Then the initial state of the context (once initialized) will look like so: * * ``` * context = [ - * [null, '100px', '200px'], // property names are not needed since they have already been + * [null, '100px', '200px', true], // property names are not needed since they have already been * written to DOM. * + * 1, // this instructs how many `style` values there are so that class index values can be + * offsetted + * * configMasterVal, * - * // 2 + * // 3 * 'width', - * pointers(1, 8); // Point to static `width`: `100px` and multi `width`. + * pointers(1, 12); // Point to static `width`: `100px` and multi `width`. * null, * - * // 5 + * // 6 * 'height', - * pointers(2, 11); // Point to static `height`: `200px` and multi `height`. + * pointers(2, 15); // Point to static `height`: `200px` and multi `height`. * null, * - * // 8 + * // 9 + * 'foo', + * pointers(1, 18); // Point to static `foo`: `true` and multi `foo`. + * null, + * + * // 12 * 'width', - * pointers(1, 2); // Point to static `width`: `100px` and single `width`. + * pointers(1, 3); // Point to static `width`: `100px` and single `width`. * null, * - * // 11 + * // 15 * 'height', - * pointers(2, 5); // Point to static `height`: `200px` and single `height`. + * pointers(2, 6); // Point to static `height`: `200px` and single `height`. + * null, + * + * // 18 + * 'foo', + * pointers(3, 9); // Point to static `foo`: `true` and single `foo`. * null, * ] * @@ -82,30 +97,50 @@ import {Renderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces * } * ``` * - * The values are duplicated so that space is set aside for both multi ([style]) - * and single ([style.prop]) values. The respective config values (configValA, configValB, etc...) - * are a combination of the StylingFlags with two index values: the `initialIndex` (which points to - * the index location of the style value in the initial styles array in slot 0) and the - * `dynamicIndex` (which points to the matching single/multi index position in the context array - * for the same prop). + * The values are duplicated so that space is set aside for both multi ([style] and [class]) + * and single ([style.prop] or [class.named]) values. The respective config values + * (configValA, configValB, etc...) are a combination of the StylingFlags with two index + * values: the `initialIndex` (which points to the index location of the style value in + * the initial styles array in slot 0) and the `dynamicIndex` (which points to the + * matching single/multi index position in the context array for the same prop). * - * This means that every time `updateStyleProp` is called it must be called using an index value - * (not a property string) which references the index value of the initial style when the context - * was created. This also means that `updateStyleProp` cannot be called with a new property - * (only `updateStyleMap` can include new CSS properties that will be added to the context). + * This means that every time `updateStyleProp` or `updateClassProp` are called then they + * must be called using an index value (not a property string) which references the index + * value of the initial style prop/class when the context was created. This also means that + * `updateStyleProp` or `updateClassProp` cannot be called with a new property (only + * `updateStylingMap` can include new CSS properties that will be added to the context). */ -export interface StylingContext extends Array { +export interface StylingContext extends + Array { + /** + * Location of element that is used as a target for this context. + */ + [0]: LElementNode|null; + /** * Location of initial data shared by all instances of this style. */ - [0]: InitialStyles; + [1]: InitialStyles; /** * A numeric value representing the configuration status (whether the context is dirty or not) * mixed together (using bit shifting) with a index value which tells the starting index value * of where the multi style entries begin. */ - [1]: number; + [2]: number; + + /** + * A numeric value representing the class index offset value. Whenever a single class is + * applied (using `elementClassProp`) it should have an styling index value that doesn't + * need to take into account any style values that exist in the context. + */ + [3]: number; + + /** + * The last CLASS STRING VALUE that was interpreted by elementStylingMap. This is cached + * So that the algorithm can exit early incase the string has not changed. + */ + [4]: string|null; } /** @@ -116,7 +151,7 @@ export interface StylingContext extends Array * All other entries in this array are of `string` value and correspond to the values that * were extracted from the `style=""` attribute in the HTML code for the provided template. */ -export interface InitialStyles extends Array { [0]: null; } +export interface InitialStyles extends Array { [0]: null; } /** * Used to set the context to be dirty or not both on the master flag (position 1) @@ -124,21 +159,31 @@ export interface InitialStyles extends Array { [0]: null; } */ export const enum StylingFlags { // Implies no configurations - None = 0b0, + None = 0b00, // Whether or not the entry or context itself is dirty - Dirty = 0b1, + Dirty = 0b01, + // Whether or not this is a class-based assignment + Class = 0b10, // The max amount of bits used to represent these configuration values - BitCountSize = 1, + BitCountSize = 2, + // There are only two bits here + BitMask = 0b11 } /** Used as numeric pointer values to determine what cells to update in the `StylingContext` */ export const enum StylingIndex { // Position of where the initial styles are stored in the styling context - InitialStylesPosition = 0, + ElementPosition = 0, + // Position of where the initial styles are stored in the styling context + InitialStylesPosition = 1, // Index of location where the start of single properties are stored. (`updateStyleProp`) - MasterFlagPosition = 1, + MasterFlagPosition = 2, + // Index of location where the class index offset value is located + ClassOffsetPosition = 3, + // Position of where the last string-based CSS class value was stored + CachedCssClassString = 4, // Location of single (prop) value entries are stored within the context - SingleStylesStartPosition = 2, + SingleStylesStartPosition = 5, // Multi and single entries are stored in `StylingContext` as: Flag; PropertyName; PropertyValue FlagsOffset = 0, PropertyOffset = 1, @@ -157,9 +202,12 @@ export const enum StylingIndex { * A pre-computed template is designed to be computed once for a given element * (instructions.ts has logic for caching this). */ -export function allocStylingContext(templateStyleContext: StylingContext): StylingContext { +export function allocStylingContext( + lElement: LElementNode | null, templateStyleContext: StylingContext): StylingContext { // each instance gets a copy - return templateStyleContext.slice() as any as StylingContext; + const context = templateStyleContext.slice() as any as StylingContext; + context[StylingIndex.ElementPosition] = lElement; + return context; } /** @@ -176,38 +224,74 @@ export function allocStylingContext(templateStyleContext: StylingContext): Styli * -> ['width', 'height', SPECIAL_ENUM_VAL, 'width', '100px'] * This implies that `width` and `height` will be later styled and that the `width` * property has an initial value of `100px`. + * + * @param initialClassDeclarations a list of class declarations and initial class values + * that are used later within the styling context. + * + * -> ['foo', 'bar', SPECIAL_ENUM_VAL, 'foo', true] + * This implies that `foo` and `bar` will be later styled and that the `foo` + * class will be applied to the element as an initial class since it's true */ export function createStylingContextTemplate( - initialStyleDeclarations?: (string | InitialStylingFlags)[] | null): StylingContext { - const initialStyles: InitialStyles = [null]; - const context: StylingContext = [initialStyles, 0]; + initialStyleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, + initialClassDeclarations?: (string | boolean | InitialStylingFlags)[] | null): StylingContext { + const initialStylingValues: InitialStyles = [null]; + const context: StylingContext = [null, initialStylingValues, 0, 0, null]; - const indexLookup: {[key: string]: number} = {}; + // we use two maps since a class name might collide with a CSS style prop + const stylesLookup: {[key: string]: number} = {}; + const classesLookup: {[key: string]: number} = {}; + + let totalStyleDeclarations = 0; if (initialStyleDeclarations) { let hasPassedDeclarations = false; for (let i = 0; i < initialStyleDeclarations.length; i++) { const v = initialStyleDeclarations[i] as string | InitialStylingFlags; // this flag value marks where the declarations end the initial values begin - if (v === InitialStylingFlags.INITIAL_STYLES) { + if (v === InitialStylingFlags.VALUES_MODE) { hasPassedDeclarations = true; } else { const prop = v as string; if (hasPassedDeclarations) { const value = initialStyleDeclarations[++i] as string; - initialStyles.push(value); - indexLookup[prop] = initialStyles.length - 1; + initialStylingValues.push(value); + stylesLookup[prop] = initialStylingValues.length - 1; } else { - // it's safe to use `0` since the default initial value for - // each property will always be null (which is at position 0) - indexLookup[prop] = 0; + totalStyleDeclarations++; + stylesLookup[prop] = 0; } } } } - const allProps = Object.keys(indexLookup); - const totalProps = allProps.length; + // make where the class offsets begin + context[StylingIndex.ClassOffsetPosition] = totalStyleDeclarations; + + if (initialClassDeclarations) { + let hasPassedDeclarations = false; + for (let i = 0; i < initialClassDeclarations.length; i++) { + const v = initialClassDeclarations[i] as string | boolean | InitialStylingFlags; + // this flag value marks where the declarations end the initial values begin + if (v === InitialStylingFlags.VALUES_MODE) { + hasPassedDeclarations = true; + } else { + const className = v as string; + if (hasPassedDeclarations) { + const value = initialClassDeclarations[++i] as boolean; + initialStylingValues.push(value); + classesLookup[className] = initialStylingValues.length - 1; + } else { + classesLookup[className] = 0; + } + } + } + } + + const styleProps = Object.keys(stylesLookup); + const classNames = Object.keys(classesLookup); + const classNamesIndexStart = styleProps.length; + const totalProps = styleProps.length + classNames.length; // *2 because we are filling for both single and multi style spaces const maxLength = totalProps * StylingIndex.Size * 2 + StylingIndex.SingleStylesStartPosition; @@ -222,86 +306,140 @@ export function createStylingContextTemplate( const multiStart = totalProps * StylingIndex.Size + StylingIndex.SingleStylesStartPosition; // fill single and multi-level styles - for (let i = 0; i < allProps.length; i++) { - const prop = allProps[i]; + for (let i = 0; i < totalProps; i++) { + const isClassBased = i >= classNamesIndexStart; + const prop = isClassBased ? classNames[i - classNamesIndexStart] : styleProps[i]; + const indexForInitial = isClassBased ? classesLookup[prop] : stylesLookup[prop]; + const initialValue = initialStylingValues[indexForInitial]; - const indexForInitial = indexLookup[prop]; const indexForMulti = i * StylingIndex.Size + multiStart; const indexForSingle = i * StylingIndex.Size + singleStart; + const initialFlag = isClassBased ? StylingFlags.Class : StylingFlags.None; - setFlag(context, indexForSingle, pointers(StylingFlags.None, indexForInitial, indexForMulti)); + setFlag(context, indexForSingle, pointers(initialFlag, indexForInitial, indexForMulti)); setProp(context, indexForSingle, prop); setValue(context, indexForSingle, null); - setFlag(context, indexForMulti, pointers(StylingFlags.Dirty, indexForInitial, indexForSingle)); + const flagForMulti = + initialFlag | (initialValue !== null ? StylingFlags.Dirty : StylingFlags.None); + setFlag(context, indexForMulti, pointers(flagForMulti, indexForInitial, indexForSingle)); setProp(context, indexForMulti, prop); setValue(context, indexForMulti, null); } - // there is no initial value flag for the master index since it doesn't reference an initial style - // value + // there is no initial value flag for the master index since it doesn't + // reference an initial style value setFlag(context, StylingIndex.MasterFlagPosition, pointers(0, 0, multiStart)); - setContextDirty(context, initialStyles.length > 1); + setContextDirty(context, initialStylingValues.length > 1); return context; } const EMPTY_ARR: any[] = []; +const EMPTY_OBJ: {[key: string]: any} = {}; /** - * Sets and resolves all `multi` styles on an `StylingContext` so that they can be - * applied to the element once `renderStyles` is called. + * Sets and resolves all `multi` styling on an `StylingContext` so that they can be + * applied to the element once `renderStyling` is called. * - * All missing styles (any values that are not provided in the new `styles` param) - * will resolve to `null` within their respective positions in the context. + * All missing styles/class (any values that are not provided in the new `styles` + * or `classes` params) will resolve to `null` within their respective positions + * in the context. * * @param context The styling context that will be updated with the * newly provided style values. * @param styles The key/value map of CSS styles that will be used for the update. + * @param classes The key/value map of CSS class names that will be used for the update. */ -export function updateStyleMap(context: StylingContext, styles: {[key: string]: any} | null): void { - const propsToApply = styles ? Object.keys(styles) : EMPTY_ARR; +export function updateStylingMap( + context: StylingContext, styles: {[key: string]: any} | null, + classes?: {[key: string]: any} | string | null): void { + let classNames: string[] = EMPTY_ARR; + let applyAllClasses = false; + let ignoreAllClassUpdates = false; + + // each time a string-based value pops up then it shouldn't require a deep + // check of what's changed. + if (typeof classes == 'string') { + const cachedClassString = context[StylingIndex.CachedCssClassString] as string | null; + if (cachedClassString && cachedClassString === classes) { + ignoreAllClassUpdates = true; + } else { + context[StylingIndex.CachedCssClassString] = classes; + classNames = classes.split(/\s+/); + // this boolean is used to avoid having to create a key/value map of `true` values + // since a classname string implies that all those classes are added + applyAllClasses = true; + } + } else { + classNames = classes ? Object.keys(classes) : EMPTY_ARR; + context[StylingIndex.CachedCssClassString] = null; + } + + classes = (classes || EMPTY_OBJ) as{[key: string]: any}; + + const styleProps = styles ? Object.keys(styles) : EMPTY_ARR; + styles = styles || EMPTY_OBJ; + + const classesStartIndex = styleProps.length; const multiStartIndex = getMultiStartIndex(context); let dirty = false; let ctxIndex = multiStartIndex; + let propIndex = 0; + const propLimit = styleProps.length + classNames.length; // the main loop here will try and figure out how the shape of the provided - // styles differ with respect to the context. Later if the context/styles are - // off-balance then they will be dealt in another loop after this one - while (ctxIndex < context.length && propIndex < propsToApply.length) { - const flag = getPointers(context, ctxIndex); - const prop = getProp(context, ctxIndex); - const value = getValue(context, ctxIndex); + // styles differ with respect to the context. Later if the context/styles/classes + // are off-balance then they will be dealt in another loop after this one + while (ctxIndex < context.length && propIndex < propLimit) { + const isClassBased = propIndex >= classesStartIndex; - const newProp = propsToApply[propIndex]; - const newValue = styles ![newProp]; - if (prop === newProp) { - if (value !== newValue) { - setValue(context, ctxIndex, newValue); - const initialValue = getInitialValue(context, flag); + // when there is a cache-hit for a string-based class then we should + // avoid doing any work diffing any of the changes + if (!ignoreAllClassUpdates || !isClassBased) { + const adjustedPropIndex = isClassBased ? propIndex - classesStartIndex : propIndex; + const newProp: string = + isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex]; + const newValue: string|boolean = + isClassBased ? (applyAllClasses ? true : classes[newProp]) : styles[newProp]; - // there is no point in setting this to dirty if the previously - // rendered value was being referenced by the initial style (or null) - if (initialValue !== newValue) { - setDirty(context, ctxIndex, true); - dirty = true; - } - } - } else { - const indexOfEntry = findEntryPositionByProp(context, newProp, ctxIndex); - if (indexOfEntry > 0) { - // it was found at a later point ... just swap the values - swapMultiContextEntries(context, ctxIndex, indexOfEntry); + const prop = getProp(context, ctxIndex); + if (prop === newProp) { + const value = getValue(context, ctxIndex); if (value !== newValue) { setValue(context, ctxIndex, newValue); - dirty = true; + + const flag = getPointers(context, ctxIndex); + const initialValue = getInitialValue(context, flag); + + // there is no point in setting this to dirty if the previously + // rendered value was being referenced by the initial style (or null) + if (initialValue !== newValue) { + setDirty(context, ctxIndex, true); + dirty = true; + } } } else { - // we only care to do this if the insertion is in the middle - const doShift = ctxIndex < context.length; - insertNewMultiProperty(context, ctxIndex, newProp, newValue); - dirty = true; + const indexOfEntry = findEntryPositionByProp(context, newProp, ctxIndex); + if (indexOfEntry > 0) { + // it was found at a later point ... just swap the values + const valueToCompare = getValue(context, indexOfEntry); + const flagToCompare = getPointers(context, indexOfEntry); + swapMultiContextEntries(context, ctxIndex, indexOfEntry); + if (valueToCompare !== newValue) { + const initialValue = getInitialValue(context, flagToCompare); + setValue(context, ctxIndex, newValue); + if (initialValue !== newValue) { + setDirty(context, ctxIndex, true); + dirty = true; + } + } + } else { + // we only care to do this if the insertion is in the middle + insertNewMultiProperty(context, ctxIndex, isClassBased, newProp, newValue); + dirty = true; + } } } @@ -310,11 +448,16 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]: } // this means that there are left-over values in the context that - // were not included in the provided styles and in this case the - // goal is to "remove" them from the context (by nullifying) + // were not included in the provided styles/classes and in this + // case the goal is to "remove" them from the context (by nullifying) while (ctxIndex < context.length) { - const value = context[ctxIndex + StylingIndex.ValueOffset]; - if (value !== null) { + const flag = getPointers(context, ctxIndex); + const isClassBased = (flag & StylingFlags.Class) === StylingFlags.Class; + if (ignoreAllClassUpdates && isClassBased) break; + + const value = getValue(context, ctxIndex); + const doRemoveValue = valueExists(value, isClassBased); + if (doRemoveValue) { setDirty(context, ctxIndex, true); setValue(context, ctxIndex, null); dirty = true; @@ -322,13 +465,19 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]: ctxIndex += StylingIndex.Size; } - // this means that there are left-over property in the context that + // this means that there are left-over properties in the context that // were not detected in the context during the loop above. In that // case we want to add the new entries into the list - while (propIndex < propsToApply.length) { - const prop = propsToApply[propIndex]; - const value = styles ![prop]; - context.push(StylingFlags.Dirty, prop, value); + while (propIndex < propLimit) { + const isClassBased = propIndex >= classesStartIndex; + if (ignoreAllClassUpdates && isClassBased) break; + + const adjustedPropIndex = isClassBased ? propIndex - classesStartIndex : propIndex; + const prop = isClassBased ? classNames[adjustedPropIndex] : styleProps[adjustedPropIndex]; + const value: string|boolean = + isClassBased ? (applyAllClasses ? true : classes[prop]) : styles[prop]; + const flag = StylingFlags.Dirty | (isClassBased ? StylingFlags.Class : StylingFlags.None); + context.push(flag, prop, value); propIndex++; dirty = true; } @@ -339,13 +488,13 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]: } /** - * Sets and resolves a single CSS style on a property on an `StylingContext` so that they - * can be applied to the element once `renderElementStyles` is called. + * Sets and resolves a single styling property/value on the provided `StylingContext` so + * that they can be applied to the element once `renderStyling` is called. * - * Note that prop-level styles are considered higher priority than styles that are applied - * using `updateStyleMap`, therefore, when styles are rendered then any styles that - * have been applied using this function will be considered first (then multi values second - * and then initial values as a backup). + * Note that prop-level styling values are considered higher priority than any styling that + * has been applied using `updateStylingMap`, therefore, when styling values are rendered + * then any styles/classes that have been applied using this function will be considered first + * (then multi values second and then initial values as a backup). * * @param context The styling context that will be updated with the * newly provided style value. @@ -353,7 +502,7 @@ export function updateStyleMap(context: StylingContext, styles: {[key: string]: * @param value The CSS style value that will be assigned */ export function updateStyleProp( - context: StylingContext, index: number, value: string | null): void { + context: StylingContext, index: number, value: string | boolean | null): void { const singleIndex = StylingIndex.SingleStylesStartPosition + index * StylingIndex.Size; const currValue = getValue(context, singleIndex); const currFlag = getPointers(context, singleIndex); @@ -370,8 +519,10 @@ export function updateStyleProp( let multiDirty = false; let singleDirty = true; + const isClassBased = (currFlag & StylingFlags.Class) === StylingFlags.Class; + // only when the value is set to `null` should the multi-value get flagged - if (value == null && valueForMulti) { + if (!valueExists(value, isClassBased) && valueExists(valueForMulti, isClassBased)) { multiDirty = true; singleDirty = false; } @@ -384,12 +535,28 @@ export function updateStyleProp( } /** - * Renders all queued styles using a renderer onto the given element. + * This method will toggle the referenced CSS class (by the provided index) + * within the given context. + * + * @param context The styling context that will be updated with the + * newly provided class value. + * @param index The index of the CSS class which is being updated. + * @param addOrRemove Whether or not to add or remove the CSS class + */ +export function updateClassProp( + context: StylingContext, index: number, addOrRemove: boolean): void { + const adjustedIndex = index + context[StylingIndex.ClassOffsetPosition]; + updateStyleProp(context, adjustedIndex, addOrRemove); +} + +/** + * Renders all queued styling using a renderer onto the given element. * * This function works by rendering any styles (that have been applied - * using `updateStyleMap` and `updateStyleProp`) onto the - * provided element using the provided renderer. Just before the styles - * are rendered a final key/value style map will be assembled. + * using `updateStylingMap`) and any classes (that have been applied using + * `updateStyleProp`) onto the provided element using the provided renderer. + * Just before the styles/classes are rendered a final key/value style map + * will be assembled (if `styleStore` or `classStore` are provided). * * @param lElement the element that the styles will be rendered on * @param context The styling context that will be used to determine @@ -397,13 +564,14 @@ export function updateStyleProp( * @param renderer the renderer that will be used to apply the styling * @param styleStore if provided, the updated style values will be applied * to this key/value map instead of being renderered via the renderer. - * @returns an object literal. `{ color: 'red', height: 'auto'}`. + * @param classStore if provided, the updated class values will be applied + * to this key/value map instead of being renderered via the renderer. */ -export function renderStyles( - lElement: LElementNode, context: StylingContext, renderer: Renderer3, - styleStore?: {[key: string]: any}) { +export function renderStyling( + context: StylingContext, renderer: Renderer3, styleStore?: {[key: string]: any}, + classStore?: {[key: string]: boolean}) { if (isContextDirty(context)) { - const native = lElement.native; + const native = context[StylingIndex.ElementPosition] !.native; const multiStartIndex = getMultiStartIndex(context); for (let i = StylingIndex.SingleStylesStartPosition; i < context.length; i += StylingIndex.Size) { @@ -412,27 +580,35 @@ export function renderStyles( const prop = getProp(context, i); const value = getValue(context, i); const flag = getPointers(context, i); + const isClassBased = flag & StylingFlags.Class ? true : false; const isInSingleRegion = i < multiStartIndex; - let styleToApply: string|null = value; + let valueToApply: string|boolean|null = value; - // STYLE DEFER CASE 1: Use a multi value instead of a null single value + // VALUE DEFER CASE 1: Use a multi value instead of a null single value // this check implies that a single value was removed and we // should now defer to a multi value and use that (if set). - if (isInSingleRegion && styleToApply == null) { + if (isInSingleRegion && !valueExists(valueToApply, isClassBased)) { // single values ALWAYS have a reference to a multi index const multiIndex = getMultiOrSingleIndex(flag); - styleToApply = getValue(context, multiIndex); + valueToApply = getValue(context, multiIndex); } - // STYLE DEFER CASE 2: Use the initial value if all else fails (is null) + // VALUE DEFER CASE 2: Use the initial value if all else fails (is falsy) // the initial value will always be a string or null, // therefore we can safely adopt it incase there's nothing else - if (styleToApply == null) { - styleToApply = getInitialValue(context, flag); + // note that this should always be a falsy check since `false` is used + // for both class and style comparisons (styles can't be false and false + // classes are turned off and should therefore defer to their initial values) + if (!valueExists(valueToApply, isClassBased)) { + valueToApply = getInitialValue(context, flag); } - setStyle(native, prop, styleToApply, renderer, styleStore); + if (isClassBased) { + setClass(native, prop, valueToApply ? true : false, renderer, classStore); + } else { + setStyle(native, prop, valueToApply as string | null, renderer, styleStore); + } setDirty(context, i, false); } } @@ -443,7 +619,7 @@ export function renderStyles( /** * This function renders a given CSS prop/value entry using the - * provided renderer. If a `styleStore` value is provided then + * provided renderer. If a `store` value is provided then * that will be used a render context instead of the provided * renderer. * @@ -451,23 +627,51 @@ export function renderStyles( * @param prop the CSS style property that will be rendered * @param value the CSS style value that will be rendered * @param renderer - * @param styleStore an optional key/value map that will be used as a context to render styles on + * @param store an optional key/value map that will be used as a context to render styles on */ function setStyle( native: any, prop: string, value: string | null, renderer: Renderer3, - styleStore?: {[key: string]: any}) { - if (styleStore) { - styleStore[prop] = value; - } else if (value == null) { - ngDevMode && ngDevMode.rendererRemoveStyle++; - isProceduralRenderer(renderer) ? - renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase) : - native['style'].removeProperty(prop); - } else { + store?: {[key: string]: any}) { + if (store) { + store[prop] = value; + } else if (value) { ngDevMode && ngDevMode.rendererSetStyle++; isProceduralRenderer(renderer) ? renderer.setStyle(native, prop, value, RendererStyleFlags3.DashCase) : native['style'].setProperty(prop, value); + } else { + ngDevMode && ngDevMode.rendererRemoveStyle++; + isProceduralRenderer(renderer) ? + renderer.removeStyle(native, prop, RendererStyleFlags3.DashCase) : + native['style'].removeProperty(prop); + } +} + +/** + * This function renders a given CSS class value using the provided + * renderer (by adding or removing it from the provided element). + * If a `store` value is provided then that will be used a render + * context instead of the provided renderer. + * + * @param native the DOM Element + * @param prop the CSS style property that will be rendered + * @param value the CSS style value that will be rendered + * @param renderer + * @param store an optional key/value map that will be used as a context to render styles on + */ +function setClass( + native: any, className: string, add: boolean, renderer: Renderer3, + store?: {[key: string]: boolean}) { + if (store) { + store[className] = add; + } else if (add) { + ngDevMode && ngDevMode.rendererAddClass++; + isProceduralRenderer(renderer) ? renderer.addClass(native, className) : + native['classList'].add(className); + } else { + ngDevMode && ngDevMode.rendererRemoveClass++; + isProceduralRenderer(renderer) ? renderer.removeClass(native, className) : + native['classList'].remove(className); } } @@ -487,8 +691,14 @@ function isDirty(context: StylingContext, index: number): boolean { return ((context[adjustedIndex] as number) & StylingFlags.Dirty) == StylingFlags.Dirty; } +function isClassBased(context: StylingContext, index: number): boolean { + const adjustedIndex = + index >= StylingIndex.SingleStylesStartPosition ? (index + StylingIndex.FlagsOffset) : index; + return ((context[adjustedIndex] as number) & StylingFlags.Class) == StylingFlags.Class; +} + function pointers(configFlag: number, staticIndex: number, dynamicIndex: number) { - return (configFlag & StylingFlags.Dirty) | (staticIndex << StylingFlags.BitCountSize) | + return (configFlag & StylingFlags.BitMask) | (staticIndex << StylingFlags.BitCountSize) | (dynamicIndex << (StylingIndex.BitCountSize + StylingFlags.BitCountSize)); } @@ -515,7 +725,7 @@ function setProp(context: StylingContext, index: number, prop: string) { context[index + StylingIndex.PropertyOffset] = prop; } -function setValue(context: StylingContext, index: number, value: string | null) { +function setValue(context: StylingContext, index: number, value: string | null | boolean) { context[index + StylingIndex.ValueOffset] = value; } @@ -531,8 +741,8 @@ function getPointers(context: StylingContext, index: number): number { return context[adjustedIndex] as number; } -function getValue(context: StylingContext, index: number): string|null { - return context[index + StylingIndex.ValueOffset] as string | null; +function getValue(context: StylingContext, index: number): string|boolean|null { + return context[index + StylingIndex.ValueOffset] as string | boolean | null; } function getProp(context: StylingContext, index: number): string { @@ -597,20 +807,23 @@ function updateSinglePointerValues(context: StylingContext, indexStartPosition: if (singleIndex > 0) { const singleFlag = getPointers(context, singleIndex); const initialIndexForSingle = getInitialIndex(singleFlag); - const updatedFlag = pointers( - isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None, - initialIndexForSingle, i); + const flagValue = (isDirty(context, singleIndex) ? StylingFlags.Dirty : StylingFlags.None) | + (isClassBased(context, singleIndex) ? StylingFlags.Class : StylingFlags.None); + const updatedFlag = pointers(flagValue, initialIndexForSingle, i); setFlag(context, singleIndex, updatedFlag); } } } function insertNewMultiProperty( - context: StylingContext, index: number, name: string, value: string): void { + context: StylingContext, index: number, classBased: boolean, name: string, + value: string | boolean): void { const doShift = index < context.length; // prop does not exist in the list, add it in - context.splice(index, 0, StylingFlags.Dirty, name, value); + context.splice( + index, 0, StylingFlags.Dirty | (classBased ? StylingFlags.Class : StylingFlags.None), name, + value); if (doShift) { // because the value was inserted midway into the array then we @@ -619,3 +832,10 @@ function insertNewMultiProperty( updateSinglePointerValues(context, index + StylingIndex.Size); } } + +function valueExists(value: string | null | boolean, isClassBased?: boolean) { + if (isClassBased) { + return value ? true : false; + } + return value !== null; +} diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index 3688f68c89..22e73caeb2 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -5,6 +5,10 @@ * 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 {assertLessThan} from './assert'; +import {LElementNode} from './interfaces/node'; +import {HEADER_OFFSET, LViewData} from './interfaces/view'; + /** * Must use this method for CD (instead of === ) since NaN !== NaN @@ -56,3 +60,27 @@ export function flatten(list: any[]): any[] { return result; } + +/** Retrieves a value from any `LViewData`. */ +export function loadInternal(index: number, arr: LViewData): T { + ngDevMode && assertDataInRangeInternal(index + HEADER_OFFSET, arr); + return arr[index + HEADER_OFFSET]; +} + +export function assertDataInRangeInternal(index: number, arr: any[]) { + assertLessThan(index, arr ? arr.length : 0, 'index expected to be a valid data index'); +} + +/** Retrieves an element value from the provided `viewData`. + * + * Elements that are read may be wrapped in a style context, + * therefore reading the value may involve unwrapping that. + */ +export function loadElementInternal(index: number, arr: LViewData): LElementNode { + const value = loadInternal(index, arr); + return readElementValue(value); +} + +export function readElementValue(value: LElementNode | any[]): LElementNode { + return (Array.isArray(value) ? (value as any as any[])[0] : value) as LElementNode; +} diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index f2c007b7b3..5914081fe2 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -191,6 +191,9 @@ { "name": "namespaceHTML" }, + { + "name": "readElementValue" + }, { "name": "refreshChildComponents" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index ba013de043..86469d9074 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -119,6 +119,9 @@ { "name": "RecordViewTuple" }, + { + "name": "RendererStyleFlags3" + }, { "name": "SANITIZER" }, @@ -206,6 +209,15 @@ { "name": "_c16" }, + { + "name": "_c17" + }, + { + "name": "_c18" + }, + { + "name": "_c19" + }, { "name": "_c2" }, @@ -260,6 +272,9 @@ { "name": "addToViewTree" }, + { + "name": "allocStylingContext" + }, { "name": "appendChild" }, @@ -332,6 +347,9 @@ { "name": "createRootContext" }, + { + "name": "createStylingContextTemplate" + }, { "name": "createTNode" }, @@ -344,6 +362,9 @@ { "name": "createViewQuery" }, + { + "name": "currentElementNode" + }, { "name": "defineComponent" }, @@ -378,7 +399,7 @@ "name": "domRendererFactory3" }, { - "name": "elementClassNamed" + "name": "elementClassProp" }, { "name": "elementEnd" @@ -389,6 +410,12 @@ { "name": "elementStart" }, + { + "name": "elementStyling" + }, + { + "name": "elementStylingApply" + }, { "name": "enterView" }, @@ -446,9 +473,21 @@ { "name": "getCurrentSanitizer" }, + { + "name": "getInitialIndex" + }, + { + "name": "getInitialValue" + }, { "name": "getLViewChild" }, + { + "name": "getMultiOrSingleIndex" + }, + { + "name": "getMultiStartIndex" + }, { "name": "getNextLNode" }, @@ -479,12 +518,18 @@ { "name": "getParentState" }, + { + "name": "getPointers" + }, { "name": "getPreviousIndex" }, { "name": "getPreviousOrParentNode" }, + { + "name": "getProp" + }, { "name": "getRenderFlags" }, @@ -494,6 +539,9 @@ { "name": "getRootView" }, + { + "name": "getStylingContext" + }, { "name": "getSymbolIterator" }, @@ -506,6 +554,9 @@ { "name": "getTypeNameForDebugging$1" }, + { + "name": "getValue" + }, { "name": "hostElement" }, @@ -536,6 +587,9 @@ { "name": "invertObject" }, + { + "name": "isContextDirty" + }, { "name": "isCssClassMatching" }, @@ -545,6 +599,9 @@ { "name": "isDifferent" }, + { + "name": "isDirty" + }, { "name": "isJsObject" }, @@ -575,6 +632,12 @@ { "name": "load" }, + { + "name": "loadElement" + }, + { + "name": "loadElementInternal" + }, { "name": "loadInternal" }, @@ -602,6 +665,9 @@ { "name": "notImplemented" }, + { + "name": "pointers" + }, { "name": "projectionNodeStack" }, @@ -626,6 +692,9 @@ { "name": "queueViewHooks" }, + { + "name": "readElementValue" + }, { "name": "refreshChildComponents" }, @@ -653,6 +722,9 @@ { "name": "renderEmbeddedTemplate" }, + { + "name": "renderStyling" + }, { "name": "resetApplicationState" }, @@ -677,9 +749,21 @@ { "name": "searchMatchesQueuedForCreation" }, + { + "name": "setClass" + }, + { + "name": "setContextDirty" + }, { "name": "setCurrentInjector" }, + { + "name": "setDirty" + }, + { + "name": "setFlag" + }, { "name": "setHostBindings" }, @@ -689,9 +773,18 @@ { "name": "setInputsFromAttrs" }, + { + "name": "setProp" + }, + { + "name": "setStyle" + }, { "name": "setUpAttributes" }, + { + "name": "setValue" + }, { "name": "storeCleanupFn" }, @@ -725,9 +818,18 @@ { "name": "trackByIdentity" }, + { + "name": "updateClassProp" + }, + { + "name": "updateStyleProp" + }, { "name": "updateViewQuery" }, + { + "name": "valueExists" + }, { "name": "viewAttached" }, diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts index 06ee01368b..1d1c8dd41a 100644 --- a/packages/core/test/render3/compiler_canonical/elements_spec.ts +++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts @@ -15,7 +15,6 @@ import {ComponentDefInternal, InitialStylingFlags} from '../../../src/render3/in import {ComponentFixture, renderComponent, toHtml} from '../render_util'; - /// See: `normative.md` describe('elements', () => { // Saving type as $any$, etc to simplify testing for compiler, as types aren't saved @@ -271,6 +270,7 @@ describe('elements', () => { }); it('should bind to a specific class', () => { + const c1: (string | InitialStylingFlags | boolean)[] = ['foo']; type $MyComponent$ = MyComponent; @Component({selector: 'my-component', template: `
        `}) @@ -283,10 +283,13 @@ describe('elements', () => { factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { if (rf & 1) { - $r3$.ɵEe(0, 'div'); + $r3$.ɵE(0, 'div'); + $r3$.ɵs(null, c1); + $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someFlag)); + $r3$.ɵcp(0, 0, ctx.someFlag); + $r3$.ɵsa(0); } } }); @@ -320,13 +323,13 @@ describe('elements', () => { template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1, c0); + $r3$.ɵs(c0); $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵsp(1, 0, ctx.someColor); - $r3$.ɵsp(1, 1, ctx.someWidth, 'px'); - $r3$.ɵsa(1); + $r3$.ɵsp(0, 0, ctx.someColor); + $r3$.ɵsp(0, 1, ctx.someWidth, 'px'); + $r3$.ɵsa(0); } } }); @@ -353,7 +356,9 @@ describe('elements', () => { it('should bind to many and keep order', () => { type $MyComponent$ = MyComponent; - const c0 = ['color', InitialStylingFlags.INITIAL_STYLES, 'color', 'red']; + const c0 = ['color', InitialStylingFlags.VALUES_MODE, 'color', 'red']; + const c1 = ['foo']; + @Component({ selector: 'my-component', template: @@ -369,12 +374,13 @@ describe('elements', () => { template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1, c0); + $r3$.ɵs(c0, c1); $r3$.ɵe(); } if (rf & 2) { $r3$.ɵp(0, 'id', $r3$.ɵb(ctx.someString + 1)); - $r3$.ɵkn(0, 'foo', $r3$.ɵb(ctx.someString == 'initial')); + $r3$.ɵcp(0, 0, ctx.someString == 'initial'); + $r3$.ɵsa(0); } } }); @@ -406,13 +412,12 @@ describe('elements', () => { template: function StyleComponent_Template(rf: $RenderFlags$, ctx: $StyleComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1); + $r3$.ɵs(); $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵk(0, $r3$.ɵb(ctx.classExp)); - $r3$.ɵsm(1, ctx.styleExp); - $r3$.ɵsa(1); + $r3$.ɵsm(0, ctx.styleExp, ctx.classExp); + $r3$.ɵsa(0); } } }); diff --git a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts index ea57ab7674..43fea1b77b 100644 --- a/packages/core/test/render3/compiler_canonical/sanitize_spec.ts +++ b/packages/core/test/render3/compiler_canonical/sanitize_spec.ts @@ -44,17 +44,17 @@ describe('compiler sanitization', () => { template: function MyComponent_Template(rf: $RenderFlags$, ctx: $MyComponent$) { if (rf & 1) { $r3$.ɵE(0, 'div'); - $r3$.ɵs(1, ['background-image']); + $r3$.ɵs(['background-image']); $r3$.ɵe(); - $r3$.ɵEe(2, 'img'); + $r3$.ɵEe(1, 'img'); } if (rf & 2) { $r3$.ɵp(0, 'innerHTML', $r3$.ɵb(ctx.innerHTML), $r3$.ɵsanitizeHtml); $r3$.ɵp(0, 'hidden', $r3$.ɵb(ctx.hidden)); - $r3$.ɵsp(1, 0, ctx.style, $r3$.ɵsanitizeStyle); - $r3$.ɵsa(1); - $r3$.ɵp(2, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl); - $r3$.ɵa(2, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl); + $r3$.ɵsp(0, 0, ctx.style, $r3$.ɵsanitizeStyle); + $r3$.ɵsa(0); + $r3$.ɵp(1, 'src', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl); + $r3$.ɵa(1, 'srcset', $r3$.ɵb(ctx.url), $r3$.ɵsanitizeUrl); } } }); diff --git a/packages/core/test/render3/exports_spec.ts b/packages/core/test/render3/exports_spec.ts index 824b76e6c6..8db0c81559 100644 --- a/packages/core/test/render3/exports_spec.ts +++ b/packages/core/test/render3/exports_spec.ts @@ -7,8 +7,9 @@ */ import {defineComponent, defineDirective} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions'; -import {RenderFlags} from '../../src/render3/interfaces/definition'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions'; +import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; + import {ComponentFixture, createComponent, renderToHtml} from './render_util'; describe('exports', () => { @@ -212,13 +213,15 @@ describe('exports', () => { function Template(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'div'); + elementStyling(null, [InitialStylingFlags.VALUES_MODE, 'red', true]); elementEnd(); elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']); elementEnd(); } const tmp = load(2) as any; if (rf & RenderFlags.Update) { - elementClassNamed(0, 'red', bind(tmp.checked)); + elementClassProp(0, 0, tmp.checked); + elementStylingApply(0); } } diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index e8a9339480..2e3f74a812 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common'; import {RenderFlags, directiveInject} from '../../src/render3'; import {defineComponent} from '../../src/render3/definition'; -import {bind, container, element, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleProp, elementStyling, elementStylingApply, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions'; import {InitialStylingFlags} from '../../src/render3/interfaces/definition'; import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node'; import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; @@ -23,13 +23,13 @@ import {ComponentFixture, TemplateFixture} from './render_util'; describe('instructions', () => { function createAnchor() { elementStart(0, 'a'); - elementStyling(1); + elementStyling(); elementEnd(); } function createDiv(initialStyles?: (string | number)[]) { elementStart(0, 'div'); - elementStyling(1, initialStyles && Array.isArray(initialStyles) ? initialStyles : null); + elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null); elementEnd(); } @@ -193,15 +193,15 @@ describe('instructions', () => { it('should use sanitizer function', () => { const t = new TemplateFixture(() => { return createDiv(['background-image']); }); t.update(() => { - elementStyleProp(1, 0, 'url("http://server")', sanitizeStyle); - elementStylingApply(1); + elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle); + elementStylingApply(0); }); // nothing is set because sanitizer suppresses it. expect(t.html).toEqual('
        '); t.update(() => { - elementStyleProp(1, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle); - elementStylingApply(1); + elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle); + elementStylingApply(0); }); expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image')) .toEqual('url("http://server")'); @@ -211,25 +211,33 @@ describe('instructions', () => { describe('elementStyleMap', () => { function createDivWithStyle() { elementStart(0, 'div'); - elementStyling(1, ['height', InitialStylingFlags.INITIAL_STYLES, 'height', '10px']); + elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']); elementEnd(); } it('should add style', () => { const fixture = new TemplateFixture(createDivWithStyle); fixture.update(() => { - elementStyle(1, {'background-color': 'red'}); - elementStylingApply(1); + elementStylingMap(0, {'background-color': 'red'}); + elementStylingApply(0); }); expect(fixture.html).toEqual('
        '); }); }); describe('elementClass', () => { + function createDivWithStyling() { + elementStart(0, 'div'); + elementStyling(); + elementEnd(); + } it('should add class', () => { - const fixture = new TemplateFixture(createDiv); - fixture.update(() => elementClass(0, 'multiple classes')); + const fixture = new TemplateFixture(createDivWithStyling); + fixture.update(() => { + elementStylingMap(0, null, 'multiple classes'); + elementStylingApply(0); + }); expect(fixture.html).toEqual('
        '); }); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index e334b2a96c..1f1182e012 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -9,7 +9,8 @@ import {RenderFlags} from '@angular/core/src/render3'; import {defineComponent, defineDirective} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassProp, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {InitialStylingFlags} from '../../src/render3/interfaces/definition'; import {HEADER_OFFSET} from '../../src/render3/interfaces/view'; import {sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; @@ -747,12 +748,12 @@ describe('render3 integration test', () => { function Template(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'span'); - elementStyling(1, ['border-color']); + elementStyling(['border-color']); elementEnd(); } if (rf & RenderFlags.Update) { - elementStyleProp(1, 0, ctx); - elementStylingApply(1); + elementStyleProp(0, 0, ctx); + elementStylingApply(0); } } @@ -766,12 +767,12 @@ describe('render3 integration test', () => { function Template(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'span'); - elementStyling(1, ['font-size']); + elementStyling(['font-size']); elementEnd(); } if (rf & RenderFlags.Update) { - elementStyleProp(1, 0, ctx, 'px'); - elementStylingApply(1); + elementStyleProp(0, 0, ctx, 'px'); + elementStylingApply(0); } } @@ -787,10 +788,12 @@ describe('render3 integration test', () => { function Template(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { elementStart(0, 'span'); + elementStyling(null, ['active']); elementEnd(); } if (rf & RenderFlags.Update) { - elementClassNamed(0, 'active', bind(ctx)); + elementClassProp(0, 0, ctx); + elementStylingApply(0); } } @@ -809,11 +812,14 @@ describe('render3 integration test', () => { it('should work correctly with existing static classes', () => { function Template(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { - elementStart(0, 'span', ['class', 'existing']); + elementStart(0, 'span'); + elementStyling( + null, ['existing', 'active', InitialStylingFlags.VALUES_MODE, 'existing', true]); elementEnd(); } if (rf & RenderFlags.Update) { - elementClassNamed(0, 'active', bind(ctx)); + elementClassProp(0, 1, ctx); + elementStylingApply(0); } } diff --git a/packages/core/test/render3/styling_spec.ts b/packages/core/test/render3/styling_spec.ts index 0522d3a424..c7c558d6f1 100644 --- a/packages/core/test/render3/styling_spec.ts +++ b/packages/core/test/render3/styling_spec.ts @@ -5,37 +5,64 @@ * 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 {elementEnd, elementStart, elementStyle, elementStyleProp, elementStyling, elementStylingApply} from '../../src/render3/instructions'; +import {elementEnd, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap} from '../../src/render3/instructions'; import {InitialStylingFlags, RenderFlags} from '../../src/render3/interfaces/definition'; import {LElementNode} from '../../src/render3/interfaces/node'; import {Renderer3} from '../../src/render3/interfaces/renderer'; -import {StylingContext, StylingFlags, StylingIndex, allocStylingContext, createStylingContextTemplate, isContextDirty, renderStyles as _renderStyles, setContextDirty, updateStyleMap, updateStyleProp} from '../../src/render3/styling'; +import {StylingContext, StylingFlags, StylingIndex, allocStylingContext, createStylingContextTemplate, isContextDirty, renderStyling as _renderStyling, setContextDirty, updateClassProp, updateStyleProp, updateStylingMap} from '../../src/render3/styling'; import {renderToHtml} from './render_util'; describe('styling', () => { - let lElement: LElementNode|null = null; - beforeEach(() => { lElement = { native: {} } as any; }); + let element: LElementNode|null = null; + beforeEach(() => { element = {} as any; }); - function initContext(styles?: (number | string)[]): StylingContext { - return allocStylingContext(createStylingContextTemplate(styles)); + function initContext( + styles?: (number | string)[] | null, + classes?: (string | number | boolean)[] | null): StylingContext { + return allocStylingContext(element, createStylingContextTemplate(styles, classes)); } function renderStyles(context: StylingContext, renderer?: Renderer3) { const styles: {[key: string]: any} = {}; - _renderStyles(lElement !, context, (renderer || {}) as Renderer3, styles); + _renderStyling(context, (renderer || {}) as Renderer3, styles); return styles; } function trackStylesFactory() { const styles: {[key: string]: any} = {}; return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { - _renderStyles(lElement !, context, (renderer || {}) as Renderer3, styles); + _renderStyling(context, (renderer || {}) as Renderer3, styles); return styles; }; } - function clean(a: number = 0, b: number = 0): number { + function trackClassesFactory() { + const classes: {[className: string]: boolean} = {}; + return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { + _renderStyling(context, (renderer || {}) as Renderer3, {}, classes); + return classes; + }; + } + + function trackStylesAndClasses() { + const classes: {[className: string]: boolean} = {}; + const styles: {[prop: string]: any} = {}; + return function(context: StylingContext, renderer?: Renderer3): {[key: string]: any} { + _renderStyling(context, (renderer || {}) as Renderer3, styles, classes); + return [styles, classes]; + }; + } + + function updateClasses(context: StylingContext, classes: string | {[key: string]: any} | null) { + updateStylingMap(context, null, classes); + } + + function cleanStyle(a: number = 0, b: number = 0): number { return _clean(a, b, false); } + + function cleanClass(a: number, b: number) { return _clean(a, b, true); } + + function _clean(a: number = 0, b: number = 0, isClassBased: boolean): number { let num = 0; if (a) { num |= a << StylingFlags.BitCountSize; @@ -43,614 +70,913 @@ describe('styling', () => { if (b) { num |= b << (StylingFlags.BitCountSize + StylingIndex.BitCountSize); } + if (isClassBased) { + num |= StylingFlags.Class; + } return num; } - function dirty(a: number = 0, b: number = 0): number { return clean(a, b) | StylingFlags.Dirty; } + function _dirty(a: number = 0, b: number = 0, isClassBased: boolean): number { + return _clean(a, b, isClassBased) | StylingFlags.Dirty; + } - describe('createStylingContextTemplate', () => { - it('should initialize empty template', () => { - const template = createStylingContextTemplate(); - expect(template).toEqual([ - [null], - clean(0, 2), - ]); + function dirtyStyle(a: number = 0, b: number = 0): number { + return _dirty(a, b, false) | StylingFlags.Dirty; + } + + function dirtyClass(a: number, b: number) { return _dirty(a, b, true); } + + describe('styles', () => { + describe('createStylingContextTemplate', () => { + it('should initialize empty template', () => { + const template = initContext(); + expect(template).toEqual([element, [null], cleanStyle(0, 5), 0, null]); + }); + + it('should initialize static styles', () => { + const template = + initContext([InitialStylingFlags.VALUES_MODE, 'color', 'red', 'width', '10px']); + expect(template).toEqual([ + element, + [null, 'red', '10px'], + dirtyStyle(0, 11), // + 0, + null, + + // #5 + cleanStyle(1, 11), + 'color', + null, + + // #8 + cleanStyle(2, 14), + 'width', + null, + + // #11 + dirtyStyle(1, 5), + 'color', + null, + + // #14 + dirtyStyle(2, 8), + 'width', + null, + ]); + }); }); - it('should initialize static styles', () => { - debugger; - const template = createStylingContextTemplate( - [InitialStylingFlags.INITIAL_STYLES, 'color', 'red', 'width', '10px']); - expect(template).toEqual([ - [null, 'red', '10px'], - dirty(0, 8), // + describe('instructions', () => { + it('should handle a combination of initial, multi and singular style values (in that order)', + () => { + function Template(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(0, 'span'); + elementStyling([ + 'width', 'height', 'opacity', // + InitialStylingFlags.VALUES_MODE, 'width', '100px', 'height', '100px', 'opacity', + '0.5' + ]); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementStylingMap(0, ctx.myStyles); + elementStyleProp(0, 0, ctx.myWidth); + elementStylingApply(0); + } + } - // #2 - clean(1, 8), - 'color', - null, + expect(renderToHtml(Template, { + myStyles: {width: '200px', height: '200px'}, + myWidth: '300px' + })).toEqual(''); - // #5 - clean(2, 11), - 'width', - null, + expect(renderToHtml(Template, {myStyles: {width: '200px', height: null}, myWidth: null})) + .toEqual(''); + }); + }); - // #8 - dirty(1, 2), - 'color', - null, + describe('helper functions', () => { + it('should build a list of multiple styling values', () => { + const getStyles = trackStylesFactory(); + const stylingContext = initContext(); + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + updateStylingMap(stylingContext, {height: '200px'}); + expect(getStyles(stylingContext)).toEqual({width: null, height: '200px'}); + }); - // #11 - dirty(2, 5), - 'width', - null, - ]); + it('should evaluate the delta between style changes when rendering occurs', () => { + const stylingContext = + initContext(['width', 'height', InitialStylingFlags.VALUES_MODE, 'width', '100px']); + updateStylingMap(stylingContext, { + height: '200px', + }); + expect(renderStyles(stylingContext)).toEqual({width: '100px', height: '200px'}); + expect(renderStyles(stylingContext)).toEqual({}); + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + expect(renderStyles(stylingContext)).toEqual({height: '100px'}); + updateStyleProp(stylingContext, 1, '100px'); + expect(renderStyles(stylingContext)).toEqual({}); + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + expect(renderStyles(stylingContext)).toEqual({}); + }); + + it('should update individual values on a set of styles', () => { + const getStyles = trackStylesFactory(); + const stylingContext = initContext(['width', 'height']); + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + updateStyleProp(stylingContext, 1, '200px'); + expect(getStyles(stylingContext)).toEqual({width: '100px', height: '200px'}); + }); + + it('should only mark itself as updated when one or more properties have been applied', () => { + const stylingContext = initContext(); + expect(isContextDirty(stylingContext)).toBeFalsy(); + + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + expect(isContextDirty(stylingContext)).toBeTruthy(); + + setContextDirty(stylingContext, false); + + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + expect(isContextDirty(stylingContext)).toBeFalsy(); + + updateStylingMap(stylingContext, { + width: '200px', + height: '100px', + }); + expect(isContextDirty(stylingContext)).toBeTruthy(); + }); + + it('should only mark itself as updated when any single properties have been applied', () => { + const stylingContext = initContext(['height']); + updateStylingMap(stylingContext, { + width: '100px', + height: '100px', + }); + + setContextDirty(stylingContext, false); + + updateStyleProp(stylingContext, 0, '100px'); + expect(isContextDirty(stylingContext)).toBeFalsy(); + + setContextDirty(stylingContext, false); + + updateStyleProp(stylingContext, 0, '200px'); + expect(isContextDirty(stylingContext)).toBeTruthy(); + }); + + it('should prioritize multi and single styles over initial styles', () => { + const getStyles = trackStylesFactory(); + + const stylingContext = initContext([ + 'width', 'height', 'opacity', InitialStylingFlags.VALUES_MODE, 'width', '100px', 'height', + '100px', 'opacity', '0' + ]); + + expect(getStyles(stylingContext)).toEqual({ + width: '100px', + height: '100px', + opacity: '0', + }); + + updateStylingMap(stylingContext, {width: '200px', height: '200px'}); + + expect(getStyles(stylingContext)).toEqual({ + width: '200px', + height: '200px', + opacity: '0', + }); + + updateStyleProp(stylingContext, 0, '300px'); + + expect(getStyles(stylingContext)).toEqual({ + width: '300px', + height: '200px', + opacity: '0', + }); + + updateStyleProp(stylingContext, 0, null); + + expect(getStyles(stylingContext)).toEqual({ + width: '200px', + height: '200px', + opacity: '0', + }); + + updateStylingMap(stylingContext, {}); + + expect(getStyles(stylingContext)).toEqual({ + width: '100px', + height: '100px', + opacity: '0', + }); + }); + + it('should cleanup removed styles from the context once the styles are built', () => { + const stylingContext = initContext(['width', 'height']); + const getStyles = trackStylesFactory(); + + updateStylingMap(stylingContext, {width: '100px', height: '100px'}); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 11), // + 2, + null, + + // #5 + cleanStyle(0, 11), + 'width', + null, + + // #8 + cleanStyle(0, 14), + 'height', + null, + + // #11 + dirtyStyle(0, 5), + 'width', + '100px', + + // #14 + dirtyStyle(0, 8), + 'height', + '100px', + ]); + + getStyles(stylingContext); + updateStylingMap(stylingContext, {width: '200px', opacity: '0'}); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 11), // + 2, + null, + + // #5 + cleanStyle(0, 11), + 'width', + null, + + // #8 + cleanStyle(0, 17), + 'height', + null, + + // #11 + dirtyStyle(0, 5), + 'width', + '200px', + + // #14 + dirtyStyle(), + 'opacity', + '0', + + // #17 + dirtyStyle(0, 8), + 'height', + null, + ]); + + getStyles(stylingContext); + expect(stylingContext).toEqual([ + element, + [null], + cleanStyle(0, 11), // + 2, + null, + + // #5 + cleanStyle(0, 11), + 'width', + null, + + // #8 + cleanStyle(0, 17), + 'height', + null, + + // #11 + cleanStyle(0, 5), + 'width', + '200px', + + // #14 + cleanStyle(), + 'opacity', + '0', + + // #17 + cleanStyle(0, 8), + 'height', + null, + ]); + + updateStylingMap(stylingContext, {width: null}); + updateStyleProp(stylingContext, 0, '300px'); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 11), // + 2, + null, + + // #5 + dirtyStyle(0, 11), + 'width', + '300px', + + // #8 + cleanStyle(0, 17), + 'height', + null, + + // #11 + cleanStyle(0, 5), + 'width', + null, + + // #14 + dirtyStyle(), + 'opacity', + null, + + // #17 + cleanStyle(0, 8), + 'height', + null, + ]); + + getStyles(stylingContext); + + updateStyleProp(stylingContext, 0, null); + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 11), // + 2, + null, + + // #5 + dirtyStyle(0, 11), + 'width', + null, + + // #8 + cleanStyle(0, 17), + 'height', + null, + + // #11 + cleanStyle(0, 5), + 'width', + null, + + // #14 + cleanStyle(), + 'opacity', + null, + + // #17 + cleanStyle(0, 8), + 'height', + null, + ]); + }); + + it('should find the next available space in the context when data is added after being removed before', + () => { + const stylingContext = initContext(['lineHeight']); + const getStyles = trackStylesFactory(); + + updateStylingMap(stylingContext, {width: '100px', height: '100px', opacity: '0.5'}); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + cleanStyle(0, 17), + 'lineHeight', + null, + + // #8 + dirtyStyle(), + 'width', + '100px', + + // #11 + dirtyStyle(), + 'height', + '100px', + + // #14 + dirtyStyle(), + 'opacity', + '0.5', + + // #17 + cleanStyle(0, 5), + 'lineHeight', + null, + ]); + + getStyles(stylingContext); + + updateStylingMap(stylingContext, {}); + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + cleanStyle(0, 17), + 'lineHeight', + null, + + // #8 + dirtyStyle(), + 'width', + null, + + // #11 + dirtyStyle(), + 'height', + null, + + // #14 + dirtyStyle(), + 'opacity', + null, + + // #17 + cleanStyle(0, 5), + 'lineHeight', + null, + ]); + + getStyles(stylingContext); + updateStylingMap(stylingContext, { + borderWidth: '5px', + }); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + cleanStyle(0, 20), + 'lineHeight', + null, + + // #8 + dirtyStyle(), + 'borderWidth', + '5px', + + // #11 + cleanStyle(), + 'width', + null, + + // #14 + cleanStyle(), + 'height', + null, + + // #17 + cleanStyle(), + 'opacity', + null, + + // #20 + cleanStyle(0, 5), + 'lineHeight', + null, + ]); + + updateStyleProp(stylingContext, 0, '200px'); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + dirtyStyle(0, 20), + 'lineHeight', + '200px', + + // #8 + dirtyStyle(), + 'borderWidth', + '5px', + + // #11 + cleanStyle(), + 'width', + null, + + // #14 + cleanStyle(), + 'height', + null, + + // #17 + cleanStyle(), + 'opacity', + null, + + // #20 + cleanStyle(0, 5), + 'lineHeight', + null, + ]); + + updateStylingMap(stylingContext, {borderWidth: '15px', borderColor: 'red'}); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + dirtyStyle(0, 23), + 'lineHeight', + '200px', + + // #8 + dirtyStyle(), + 'borderWidth', + '15px', + + // #11 + dirtyStyle(), + 'borderColor', + 'red', + + // #14 + cleanStyle(), + 'width', + null, + + // #17 + cleanStyle(), + 'height', + null, + + // #20 + cleanStyle(), + 'opacity', + null, + + // #23 + cleanStyle(0, 5), + 'lineHeight', + null, + ]); + }); + + it('should render all data as not being dirty after the styles are built', () => { + const getStyles = trackStylesFactory(); + const stylingContext = initContext(['height']); + + updateStylingMap(stylingContext, { + width: '100px', + }); + + updateStyleProp(stylingContext, 0, '200px'); + + expect(stylingContext).toEqual([ + element, + [null], + dirtyStyle(0, 8), // + 1, + null, + + // #5 + dirtyStyle(0, 11), + 'height', + '200px', + + // #5 + dirtyStyle(), + 'width', + '100px', + + // #11 + cleanStyle(0, 5), + 'height', + null, + ]); + + getStyles(stylingContext); + + expect(stylingContext).toEqual([ + element, + [null], + cleanStyle(0, 8), // + 1, + null, + + // #5 + cleanStyle(0, 11), + 'height', + '200px', + + // #5 + cleanStyle(), + 'width', + '100px', + + // #11 + cleanStyle(0, 5), + 'height', + null, + ]); + }); }); }); - describe('instructions', () => { - it('should handle a combination of initial, multi and singular style values (in that order)', + describe('classes', () => { + it('should initialize with the provided classes', () => { + const template = + initContext(null, [InitialStylingFlags.VALUES_MODE, 'one', true, 'two', true]); + expect(template).toEqual([ + element, [null, true, true], dirtyStyle(0, 11), // + 0, null, + + // #5 + cleanClass(1, 11), 'one', null, + + // #8 + cleanClass(2, 14), 'two', null, + + // #11 + dirtyClass(1, 5), 'one', null, + + // #14 + dirtyClass(2, 8), 'two', null + ]); + }); + + it('should update multi class properties against the static classes', () => { + const getClasses = trackClassesFactory(); + const stylingContext = initContext(null, ['bar']); + expect(getClasses(stylingContext)).toEqual({}); + updateClasses(stylingContext, {foo: true, bar: false}); + expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': false}); + updateClasses(stylingContext, 'bar'); + expect(getClasses(stylingContext)).toEqual({'foo': false, 'bar': true}); + }); + + it('should update single class properties against the static classes', () => { + const getClasses = trackClassesFactory(); + const stylingContext = + initContext(null, ['bar', 'foo', InitialStylingFlags.VALUES_MODE, 'bar', true]); + expect(getClasses(stylingContext)).toEqual({'bar': true}); + + updateClassProp(stylingContext, 0, true); + updateClassProp(stylingContext, 1, true); + expect(getClasses(stylingContext)).toEqual({'bar': true, 'foo': true}); + + updateClassProp(stylingContext, 0, false); + updateClassProp(stylingContext, 1, false); + expect(getClasses(stylingContext)).toEqual({'bar': true, 'foo': false}); + }); + + it('should understand updating multi-classes using a string-based value while respecting single class-based props', () => { - function Template(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - elementStart(0, 'span'); - elementStyling(1, [ - 'width', 'height', 'opacity', // - 0, 'width', '100px', 'height', '100px', 'opacity', '0.5' - ]); - elementEnd(); - } - if (rf & RenderFlags.Update) { - elementStyle(1, ctx.myStyles); - elementStyleProp(1, 0, ctx.myWidth); - elementStylingApply(1); - } - } + const getClasses = trackClassesFactory(); + const stylingContext = initContext(null, ['guy']); + expect(getClasses(stylingContext)).toEqual({}); - expect(renderToHtml(Template, { - myStyles: {width: '200px', height: '200px'}, - myWidth: '300px' - })).toEqual(''); + updateStylingMap(stylingContext, null, 'foo bar guy'); + expect(getClasses(stylingContext)).toEqual({'foo': true, 'bar': true, 'guy': true}); - expect(renderToHtml(Template, {myStyles: {width: '200px', height: null}, myWidth: null})) - .toEqual(''); + updateStylingMap(stylingContext, null, 'foo man'); + updateClassProp(stylingContext, 0, true); + expect(getClasses(stylingContext)) + .toEqual({'foo': true, 'man': true, 'bar': false, 'guy': true}); }); - }); - - describe('helper functions', () => { - it('should build a list of multiple styling values', () => { - const getStyles = trackStylesFactory(); - const stylingContext = initContext(); - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - updateStyleMap(stylingContext, {height: '200px'}); - expect(getStyles(stylingContext)).toEqual({width: null, height: '200px'}); - }); - - it('should evaluate the delta between style changes when rendering occurs', () => { - const stylingContext = initContext(['width', 'height', 0, 'width', '100px']); - updateStyleMap(stylingContext, { - height: '200px', - }); - expect(renderStyles(stylingContext)).toEqual({width: '100px', height: '200px'}); - expect(renderStyles(stylingContext)).toEqual({}); - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - expect(renderStyles(stylingContext)).toEqual({height: '100px'}); - updateStyleProp(stylingContext, 1, '100px'); - expect(renderStyles(stylingContext)).toEqual({}); - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - expect(renderStyles(stylingContext)).toEqual({}); - }); - - it('should update individual values on a set of styles', () => { - const getStyles = trackStylesFactory(); - const stylingContext = initContext(['width', 'height']); - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - updateStyleProp(stylingContext, 1, '200px'); - expect(getStyles(stylingContext)).toEqual({width: '100px', height: '200px'}); - }); - - it('should only mark itself as updated when one or more properties have been applied', () => { - const stylingContext = initContext(); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeTruthy(); - - setContextDirty(stylingContext, false); - - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - updateStyleMap(stylingContext, { - width: '200px', - height: '100px', - }); - expect(isContextDirty(stylingContext)).toBeTruthy(); - }); - - it('should only mark itself as updated when any single properties have been applied', () => { - const stylingContext = initContext(['height']); - updateStyleMap(stylingContext, { - width: '100px', - height: '100px', - }); - - setContextDirty(stylingContext, false); - - updateStyleProp(stylingContext, 0, '100px'); - expect(isContextDirty(stylingContext)).toBeFalsy(); - - setContextDirty(stylingContext, false); - - updateStyleProp(stylingContext, 0, '200px'); - expect(isContextDirty(stylingContext)).toBeTruthy(); - }); - - it('should prioritize multi and single styles over initial styles', () => { - const getStyles = trackStylesFactory(); - - const stylingContext = initContext( - ['width', 'height', 'opacity', 0, 'width', '100px', 'height', '100px', 'opacity', '0']); - - expect(getStyles(stylingContext)).toEqual({ - width: '100px', - height: '100px', - opacity: '0', - }); - - updateStyleMap(stylingContext, {width: '200px', height: '200px'}); - - expect(getStyles(stylingContext)).toEqual({ - width: '200px', - height: '200px', - opacity: '0', - }); - - updateStyleProp(stylingContext, 0, '300px'); - - expect(getStyles(stylingContext)).toEqual({ - width: '300px', - height: '200px', - opacity: '0', - }); - - updateStyleProp(stylingContext, 0, null); - - expect(getStyles(stylingContext)).toEqual({ - width: '200px', - height: '200px', - opacity: '0', - }); - - updateStyleMap(stylingContext, {}); - - expect(getStyles(stylingContext)).toEqual({ - width: '100px', - height: '100px', - opacity: '0', - }); - }); - - it('should cleanup removed styles from the context once the styles are built', () => { - const stylingContext = initContext(['width', 'height']); - const getStyles = trackStylesFactory(); - - updateStyleMap(stylingContext, {width: '100px', height: '100px'}); + it('should house itself inside the context alongside styling in harmony', () => { + const getStylesAndClasses = trackStylesAndClasses(); + const initialStyles = ['width', 'height', InitialStylingFlags.VALUES_MODE, 'width', '100px']; + const initialClasses = ['wide', 'tall', InitialStylingFlags.VALUES_MODE, 'wide', true]; + const stylingContext = initContext(initialStyles, initialClasses); expect(stylingContext).toEqual([ - [null], - dirty(0, 8), // - - // #2 - clean(0, 8), - 'width', + element, + [null, '100px', true], + dirtyStyle(0, 17), // + 2, null, // #5 - clean(0, 11), - 'height', + cleanStyle(1, 17), + 'width', null, // #8 - dirty(0, 2), - 'width', - '100px', + cleanStyle(0, 20), + 'height', + null, // #11 - dirty(0, 5), - 'height', - '100px', - ]); + cleanClass(2, 23), + 'wide', + null, - getStyles(stylingContext); - updateStyleMap(stylingContext, {width: '200px', opacity: '0'}); + // #14 + cleanClass(0, 26), + 'tall', + null, - expect(stylingContext).toEqual([ - [null], - dirty(0, 8), // - - // #2 - clean(0, 8), + // #17 + dirtyStyle(1, 5), 'width', null, - // #5 - clean(0, 14), + // #20 + cleanStyle(0, 8), 'height', null, + // #23 + dirtyClass(2, 11), + 'wide', + null, + + // #26 + cleanClass(0, 14), + 'tall', + null, + ]); + + expect(getStylesAndClasses(stylingContext)).toEqual([{width: '100px'}, {wide: true}]); + + updateStylingMap(stylingContext, {width: '200px', opacity: '0.5'}, 'tall round'); + expect(stylingContext).toEqual([ + element, + [null, '100px', true], + dirtyStyle(0, 17), // + 2, + 'tall round', + + // #5 + cleanStyle(1, 17), + 'width', + null, + // #8 - dirty(0, 2), + cleanStyle(0, 32), + 'height', + null, + + // #11 + cleanClass(2, 29), + 'wide', + null, + + // #14 + cleanClass(0, 23), + 'tall', + null, + + // #17 + dirtyStyle(1, 5), 'width', '200px', - // #11 - dirty(), + // #20 + dirtyStyle(0, 0), 'opacity', - '0', + '0.5', - // #14 - dirty(0, 5), + // #23 + dirtyClass(0, 14), + 'tall', + true, + + // #26 + dirtyClass(0, 0), + 'round', + true, + + // #29 + cleanClass(2, 11), + 'wide', + null, + + // #32 + cleanStyle(0, 8), 'height', null, ]); - getStyles(stylingContext); - expect(stylingContext).toEqual([ - [null], - clean(0, 8), // - - // #2 - clean(0, 8), - 'width', - null, - - // #5 - clean(0, 14), - 'height', - null, - - // #8 - clean(0, 2), - 'width', - '200px', - - // #11 - clean(), - 'opacity', - '0', - - // #14 - clean(0, 5), - 'height', - null, + expect(getStylesAndClasses(stylingContext)).toEqual([ + {width: '200px', opacity: '0.5'}, {tall: true, round: true, wide: true} ]); - updateStyleMap(stylingContext, {width: null}); + updateStylingMap(stylingContext, {width: '500px'}, {tall: true, wide: true}); updateStyleProp(stylingContext, 0, '300px'); expect(stylingContext).toEqual([ - [null], - dirty(0, 8), // + element, + [null, '100px', true], + dirtyStyle(0, 17), // + 2, + null, - // #2 - dirty(0, 8), + // #5 + dirtyStyle(1, 17), 'width', '300px', - // #5 - clean(0, 14), - 'height', - null, - // #8 - clean(0, 2), - 'width', + cleanStyle(0, 32), + 'height', null, // #11 - dirty(), - 'opacity', + cleanClass(2, 23), + 'wide', null, // #14 - clean(0, 5), - 'height', + cleanClass(0, 20), + 'tall', null, - ]); - getStyles(stylingContext); - - updateStyleProp(stylingContext, 0, null); - expect(stylingContext).toEqual([ - [null], - dirty(0, 8), // - - // #2 - dirty(0, 8), + // #17 + cleanStyle(1, 5), 'width', + '500px', + + // #20 + cleanClass(0, 14), + 'tall', + true, + + // #23 + cleanClass(2, 11), + 'wide', + true, + + // #26 + dirtyClass(0, 0), + 'round', null, - // #5 - clean(0, 14), - 'height', - null, - - // #8 - clean(0, 2), - 'width', - null, - - // #11 - clean(), + // #29 + dirtyStyle(0, 0), 'opacity', null, - // #14 - clean(0, 5), - 'height', - null, - ]); - }); - - it('should find the next available space in the context when data is added after being removed before', - () => { - const stylingContext = initContext(['lineHeight']); - const getStyles = trackStylesFactory(); - - updateStyleMap(stylingContext, {width: '100px', height: '100px', opacity: '0.5'}); - - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - clean(0, 14), - 'lineHeight', - null, - - // #5 - dirty(), - 'width', - '100px', - - // #8 - dirty(), - 'height', - '100px', - - // #11 - dirty(), - 'opacity', - '0.5', - - // #14 - dirty(0, 2), - 'lineHeight', - null, - ]); - - getStyles(stylingContext); - - updateStyleMap(stylingContext, {}); - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - clean(0, 14), - 'lineHeight', - null, - - // #5 - dirty(), - 'width', - null, - - // #8 - dirty(), - 'height', - null, - - // #11 - dirty(), - 'opacity', - null, - - // #14 - clean(0, 2), - 'lineHeight', - null, - ]); - - getStyles(stylingContext); - updateStyleMap(stylingContext, { - borderWidth: '5px', - }); - - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - clean(0, 17), - 'lineHeight', - null, - - // #5 - dirty(), - 'borderWidth', - '5px', - - // #8 - clean(), - 'width', - null, - - // #11 - clean(), - 'height', - null, - - // #14 - clean(), - 'opacity', - null, - - // #17 - clean(0, 2), - 'lineHeight', - null, - ]); - - updateStyleProp(stylingContext, 0, '200px'); - - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - dirty(0, 17), - 'lineHeight', - '200px', - - // #5 - dirty(), - 'borderWidth', - '5px', - - // #8 - clean(), - 'width', - null, - - // #11 - clean(), - 'height', - null, - - // #14 - clean(), - 'opacity', - null, - - // #17 - clean(0, 2), - 'lineHeight', - null, - ]); - - updateStyleMap(stylingContext, {borderWidth: '15px', borderColor: 'red'}); - - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - dirty(0, 20), - 'lineHeight', - '200px', - - // #5 - dirty(), - 'borderWidth', - '15px', - - // #8 - dirty(), - 'borderColor', - 'red', - - // #11 - clean(), - 'width', - null, - - // #14 - clean(), - 'height', - null, - - // #17 - clean(), - 'opacity', - null, - - // #20 - clean(0, 2), - 'lineHeight', - null, - ]); - }); - - it('should render all data as not being dirty after the styles are built', () => { - const getStyles = trackStylesFactory(); - const stylingContext = initContext(['height']); - - updateStyleMap(stylingContext, { - width: '100px', - }); - - updateStyleProp(stylingContext, 0, '200px'); - - expect(stylingContext).toEqual([ - [null], - dirty(0, 5), // - - // #2 - dirty(0, 8), - 'height', - '200px', - - // #2 - dirty(), - 'width', - '100px', - - // #8 - clean(0, 2), + // #32 + cleanStyle(0, 8), 'height', null, ]); - getStyles(stylingContext); - - expect(stylingContext).toEqual([ - [null], - clean(0, 5), // - - // #2 - clean(0, 8), - 'height', - '200px', - - // #2 - clean(), - 'width', - '100px', - - // #8 - clean(0, 2), - 'height', - null, + expect(getStylesAndClasses(stylingContext)).toEqual([ + {width: '300px', opacity: null}, {tall: true, round: false, wide: true} ]); }); }); From 06a33984af3f875f7434375592e6fc3f6fff95bc Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 11 Jul 2018 07:46:38 -0700 Subject: [PATCH 488/582] build: rename angular_devkit dependency to angular_cli (#24842) PR Close #24842 --- WORKSPACE | 2 +- packages/bazel/src/BUILD.bazel | 2 +- packages/bazel/src/ng_rollup_bundle.bzl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 8b909046f9..05919663e4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -58,7 +58,7 @@ http_archive( # Even better, things like aspects will visit the entire graph including # ts_library rules in the devkit repository. http_archive( - name = "angular_devkit", + name = "angular_cli", url = "https://github.com/angular/angular-cli/archive/v6.1.0-rc.0.zip", strip_prefix = "angular-cli-6.1.0-rc.0", sha256 = "8cf320ea58c321e103f39087376feea502f20eaf79c61a4fdb05c7286c8684fd", diff --git a/packages/bazel/src/BUILD.bazel b/packages/bazel/src/BUILD.bazel index 0f01be9729..b7f54bad54 100644 --- a/packages/bazel/src/BUILD.bazel +++ b/packages/bazel/src/BUILD.bazel @@ -14,7 +14,7 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") nodejs_binary( name = "rollup_with_build_optimizer", - data = ["@angular_devkit//packages/angular_devkit/build_optimizer:lib"], + data = ["@angular_cli//packages/angular_devkit/build_optimizer:lib"], # Since our rule extends the one in rules_nodejs, we use the same runtime # dependency @build_bazel_rules_nodejs_rollup_deps. We don't need any # additional npm dependencies when we run rollup or uglify. diff --git a/packages/bazel/src/ng_rollup_bundle.bzl b/packages/bazel/src/ng_rollup_bundle.bzl index 49d8b330f0..b90c7e5942 100644 --- a/packages/bazel/src/ng_rollup_bundle.bzl +++ b/packages/bazel/src/ng_rollup_bundle.bzl @@ -24,7 +24,7 @@ load(":esm5.bzl", "esm5_outputs_aspect", "flatten_esm5", "esm5_root_dir") PACKAGES=["packages/core/src", "packages/common/src", "packages/compiler/src", "external/rxjs"] PLUGIN_CONFIG="{sideEffectFreeModules: [\n%s]}" % ",\n".join( [" '.esm5/{0}'".format(p) for p in PACKAGES]) -BO_ROLLUP="angular_devkit/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js" +BO_ROLLUP="angular_cli/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js" BO_PLUGIN="require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG) def _use_plain_rollup(ctx): From 0b28732d779f86f05b4152e77a1cd0f198ce74fe Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Mon, 25 Jun 2018 22:37:19 +0200 Subject: [PATCH 489/582] docs: typos in directives docs (#24665) Fixes some typos introduced by #23902 PR Close #24665 --- packages/core/src/metadata/directives.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index 4c5a08f544..0784cef48b 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -342,9 +342,12 @@ export interface Directive { * View queries are set before the `ngAfterViewInit` callback is called. * * @usageNotes + * * ### Example * - * The followoing example (shows what??) + * The following example shows how queries are defined + * and when their results are available in lifecycle hooks: + * * ``` * @Component({ * selector: 'someDir', @@ -475,7 +478,7 @@ export interface Directive { } /** - * Type of the Component metadata. + * Type of the Directive metadata. */ export const Directive: DirectiveDecorator = makeDecorator( 'Directive', (dir: Directive = {}) => dir, undefined, undefined, From 23dc9a90b0c7e82b23aabb7bf10aea11a14ffcd2 Mon Sep 17 00:00:00 2001 From: Jamie Krug Date: Wed, 7 Mar 2018 13:23:30 -0500 Subject: [PATCH 490/582] docs: fix typo in user input guide (#22630) PR Close #22630 --- aio/content/guide/user-input.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/user-input.md b/aio/content/guide/user-input.md index 7838c9f82c..6f519bcc3c 100644 --- a/aio/content/guide/user-input.md +++ b/aio/content/guide/user-input.md @@ -69,7 +69,7 @@ DOM event object in the `$event` variable which this code passes as a parameter The properties of an `$event` object vary depending on the type of DOM event. For example, -a mouse event includes different information than a input box editing event. +a mouse event includes different information than an input box editing event. All [standard DOM event objects](https://developer.mozilla.org/en-US/docs/Web/API/Event) have a `target` property, a reference to the element that raised the event. From d76531d16eb57391a8ee4cf3375a550e72307b67 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Tue, 17 Jul 2018 15:49:06 +0200 Subject: [PATCH 491/582] fix(animations): @internal must use JSDoc tags. (#24928) This change fixes up several comments that accidentally used the JSDoc tag @internal in regular block comments (`/*` instead of `/**`). This prevents a problem with Closure Compiler that balks at `@` tags occuring in regular block comments, because it assumes they were intended to be tags for the compiler. When occuring in `/**` JSDoc, tsickle escapes the tags, so they do not cause problems. PR Close #24928 --- .../src/render/css_keyframes/css_keyframes_player.ts | 2 +- .../browser/src/render/transition_animation_engine.ts | 2 +- .../src/render/web_animations/web_animations_player.ts | 2 +- packages/animations/src/players/animation_group_player.ts | 2 +- packages/animations/src/players/animation_player.ts | 8 ++++---- .../platform-browser/animations/src/animation_renderer.ts | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/animations/browser/src/render/css_keyframes/css_keyframes_player.ts b/packages/animations/browser/src/render/css_keyframes/css_keyframes_player.ts index 8369201a36..88c1a2e07d 100644 --- a/packages/animations/browser/src/render/css_keyframes/css_keyframes_player.ts +++ b/packages/animations/browser/src/render/css_keyframes/css_keyframes_player.ts @@ -124,7 +124,7 @@ export class CssKeyframesPlayer implements AnimationPlayer { DEFAULT_FILL_MODE, () => this.finish()); } - /* @internal */ + /** @internal */ triggerCallback(phaseName: string): void { const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns; methods.forEach(fn => fn()); diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index 700c34b15e..2d1222936a 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -1514,7 +1514,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer { getPosition(): number { return this.queued ? 0 : this._player.getPosition(); } - /* @internal */ + /** @internal */ triggerCallback(phaseName: string): void { const p = this._player as any; if (p.triggerCallback) { 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 8db8088fe3..441a6b244b 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 @@ -155,7 +155,7 @@ export class WebAnimationsPlayer implements AnimationPlayer { this.currentSnapshot = styles; } - /* @internal */ + /** @internal */ triggerCallback(phaseName: string): void { const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns; methods.forEach(fn => fn()); diff --git a/packages/animations/src/players/animation_group_player.ts b/packages/animations/src/players/animation_group_player.ts index 07b600cbdf..ba65d89639 100644 --- a/packages/animations/src/players/animation_group_player.ts +++ b/packages/animations/src/players/animation_group_player.ts @@ -140,7 +140,7 @@ export class AnimationGroupPlayer implements AnimationPlayer { }); } - /* @internal */ + /** @internal */ triggerCallback(phaseName: string): void { const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns; methods.forEach(fn => fn()); diff --git a/packages/animations/src/players/animation_player.ts b/packages/animations/src/players/animation_player.ts index de3dfb4ffa..32f5526b56 100644 --- a/packages/animations/src/players/animation_player.ts +++ b/packages/animations/src/players/animation_player.ts @@ -31,9 +31,9 @@ export interface AnimationPlayer { parentPlayer: AnimationPlayer|null; readonly totalTime: number; beforeDestroy?: () => any; - /* @internal */ + /** @internal */ triggerCallback?: (phaseName: string) => void; - /* @internal */ + /** @internal */ disabled?: boolean; } @@ -70,7 +70,7 @@ export class NoopAnimationPlayer implements AnimationPlayer { this._started = true; } - /* @internal */ + /** @internal */ triggerMicrotask() { scheduleMicroTask(() => this._onFinish()); } private _onStart() { @@ -96,7 +96,7 @@ export class NoopAnimationPlayer implements AnimationPlayer { setPosition(p: number): void {} getPosition(): number { return 0; } - /* @internal */ + /** @internal */ triggerCallback(phaseName: string): void { const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns; methods.forEach(fn => fn()); diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index b7cf1cc2f0..bdaee22f97 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -74,7 +74,7 @@ export class AnimationRendererFactory implements RendererFactory2 { this.promise.then(() => { this._microtaskId++; }); } - /* @internal */ + /** @internal */ scheduleListenerCallback(count: number, fn: (e: any) => any, data: any) { if (count >= 0 && count < this._microtaskId) { this._zone.run(() => fn(data)); From 99a393e84fef1390a5e24e23633802f4c2518134 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Tue, 19 Jun 2018 12:56:58 -0500 Subject: [PATCH 492/582] docs: add new Reactive Forms guide (#24578) PR Close #24578 --- aio/content/examples/reactive-forms/debug.log | 3 - .../reactive-forms/e2e/src/app.e2e-spec.ts | 1091 ++--------- .../reactive-forms/final.stackblitz.json | 24 - .../src/app/app.component.1.html | 14 +- .../reactive-forms/src/app/app.component.html | 21 +- .../src/app/app.component.spec.ts | 27 + .../reactive-forms/src/app/app.component.ts | 19 +- .../reactive-forms/src/app/app.module.ts | 55 +- .../reactive-forms/src/app/data-model.ts | 40 - .../src/app/demo.component.html | 40 - .../reactive-forms/src/app/demo.component.ts | 49 - .../reactive-forms/src/app/demo.module.ts | 33 - .../hero-detail/hero-detail-1.component.html | 8 - .../hero-detail/hero-detail-1.component.ts | 15 - .../hero-detail/hero-detail-2.component.html | 18 - .../hero-detail/hero-detail-2.component.ts | 17 - .../hero-detail/hero-detail-3.component.html | 16 - .../hero-detail/hero-detail-3.component.ts | 27 - .../hero-detail/hero-detail-3a.component.ts | 25 - .../hero-detail/hero-detail-4.component.html | 46 - .../hero-detail/hero-detail-4.component.ts | 34 - .../hero-detail/hero-detail-5.component.html | 56 - .../hero-detail/hero-detail-5.component.ts | 35 - .../hero-detail/hero-detail-6.component.html | 46 - .../hero-detail/hero-detail-6.component.ts | 66 - .../hero-detail/hero-detail-7.component.html | 46 - .../hero-detail/hero-detail-7.component.ts | 68 - .../hero-detail/hero-detail-8.component.html | 72 - .../hero-detail/hero-detail-8.component.ts | 74 - .../app/hero-detail/hero-detail.component.css | 0 .../hero-detail/hero-detail.component.html | 73 - .../app/hero-detail/hero-detail.component.ts | 113 -- .../app/hero-list/hero-list.component.1.html | 8 - .../src/app/hero-list/hero-list.component.css | 0 .../app/hero-list/hero-list.component.html | 17 - .../src/app/hero-list/hero-list.component.ts | 32 - .../reactive-forms/src/app/hero.service.ts | 25 - .../app/name-editor/name-editor.component.css | 19 + .../name-editor/name-editor.component.html | 21 + .../app/name-editor/name-editor.component.ts | 22 + .../profile-editor.component.1.html | 67 + .../profile-editor.component.1.ts | 40 + .../profile-editor.component.2.ts | 58 + .../profile-editor.component.css | 39 + .../profile-editor.component.html | 80 + .../profile-editor.component.ts | 73 + .../reactive-forms/src/index-final.html | 17 - .../examples/reactive-forms/src/index.html | 3 +- .../examples/reactive-forms/src/main-final.ts | 13 - .../examples/reactive-forms/src/main.ts | 5 +- .../examples/reactive-forms/src/styles.1.css | 1 - .../examples/reactive-forms/stackblitz.json | 1 + aio/content/guide/reactive-forms.md | 1657 +++++------------ .../guide/reactive-forms/address-group.png | Bin 6022 -> 0 bytes .../guide/reactive-forms/addresses-array.png | Bin 4692 -> 0 bytes .../guide/reactive-forms/hero-detail.png | Bin 4608 -> 0 bytes .../images/guide/reactive-forms/hero-list.png | Bin 5950 -> 0 bytes .../guide/reactive-forms/json-output.png | Bin 14427 -> 0 bytes .../guide/reactive-forms/just-formcontrol.png | Bin 8732 -> 0 bytes .../guide/reactive-forms/name-editor-1.png | Bin 0 -> 8582 bytes .../guide/reactive-forms/name-editor-2.png | Bin 0 -> 22715 bytes .../guide/reactive-forms/profile-editor-1.png | Bin 0 -> 18881 bytes .../guide/reactive-forms/profile-editor-2.png | Bin 0 -> 17538 bytes .../guide/reactive-forms/profile-editor-3.png | Bin 0 -> 34124 bytes .../guide/reactive-forms/profile-editor-4.png | Bin 0 -> 42477 bytes .../reactive-forms/save-revert-buttons.png | Bin 7023 -> 0 bytes .../reactive-forms/validators-json-output.png | Bin 24228 -> 0 bytes 67 files changed, 1101 insertions(+), 3368 deletions(-) delete mode 100644 aio/content/examples/reactive-forms/debug.log delete mode 100644 aio/content/examples/reactive-forms/final.stackblitz.json create mode 100644 aio/content/examples/reactive-forms/src/app/app.component.spec.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/data-model.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/demo.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/demo.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/demo.module.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.css delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.1.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.css delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.html delete mode 100644 aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/app/hero.service.ts create mode 100644 aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.css create mode 100644 aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html create mode 100644 aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.html create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.ts create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.css create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html create mode 100644 aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.ts delete mode 100644 aio/content/examples/reactive-forms/src/index-final.html delete mode 100644 aio/content/examples/reactive-forms/src/main-final.ts delete mode 100644 aio/content/examples/reactive-forms/src/styles.1.css delete mode 100644 aio/content/images/guide/reactive-forms/address-group.png delete mode 100644 aio/content/images/guide/reactive-forms/addresses-array.png delete mode 100644 aio/content/images/guide/reactive-forms/hero-detail.png delete mode 100644 aio/content/images/guide/reactive-forms/hero-list.png delete mode 100644 aio/content/images/guide/reactive-forms/json-output.png delete mode 100644 aio/content/images/guide/reactive-forms/just-formcontrol.png create mode 100644 aio/content/images/guide/reactive-forms/name-editor-1.png create mode 100644 aio/content/images/guide/reactive-forms/name-editor-2.png create mode 100644 aio/content/images/guide/reactive-forms/profile-editor-1.png create mode 100644 aio/content/images/guide/reactive-forms/profile-editor-2.png create mode 100644 aio/content/images/guide/reactive-forms/profile-editor-3.png create mode 100644 aio/content/images/guide/reactive-forms/profile-editor-4.png delete mode 100644 aio/content/images/guide/reactive-forms/save-revert-buttons.png delete mode 100644 aio/content/images/guide/reactive-forms/validators-json-output.png diff --git a/aio/content/examples/reactive-forms/debug.log b/aio/content/examples/reactive-forms/debug.log deleted file mode 100644 index 9c0793f598..0000000000 --- a/aio/content/examples/reactive-forms/debug.log +++ /dev/null @@ -1,3 +0,0 @@ -[1030/162525.401:ERROR:process_reader_win.cc(123)] NtOpenThread: {Acceso denegado} Un proceso ha solicitado acceso a un objeto, pero no se le han concedido esos derechos de acceso. (0xc0000022) -[1030/162525.402:ERROR:exception_snapshot_win.cc(87)] thread ID 26896 not found in process -[1030/162525.402:WARNING:crash_report_exception_handler.cc(62)] ProcessSnapshotWin::Initialize failed diff --git a/aio/content/examples/reactive-forms/e2e/src/app.e2e-spec.ts b/aio/content/examples/reactive-forms/e2e/src/app.e2e-spec.ts index 59c1ea797d..25022d2290 100644 --- a/aio/content/examples/reactive-forms/e2e/src/app.e2e-spec.ts +++ b/aio/content/examples/reactive-forms/e2e/src/app.e2e-spec.ts @@ -2,1019 +2,144 @@ import { browser, element, by } from 'protractor'; -function finalDemoAddressForm(element: any, index: number) { - let form = { - street: element.all(by.css('input[formcontrolname=street]')).get(index).getAttribute('value'), - city: element.all(by.css('input[formcontrolname=city]')).get(index).getAttribute('value'), - state: element.all(by.css('select[formcontrolname=state]')).get(index).getAttribute('value'), - zip: element.all(by.css('input[formcontrolname=zip]')).get(index).getAttribute('value') - }; - return form; -} +describe('Reactive forms', function () { + const nameEditor = element(by.css('app-name-editor')); + const profileEditor = element(by.css('app-profile-editor')); + const nameEditorLink = element(by.cssContainingText('app-root > nav > a', 'Name Editor')); + const profileEditorLink = element(by.cssContainingText('app-root > nav > a', 'Profile Editor')); -describe('Reactive forms', function() { - let select: any; - - beforeEach(function() { + beforeAll(function () { browser.get(''); - select = element(by.css('.container > h4 > select')); }); - describe('navigation', function() { - it('should display the title', function() { - let title = element(by.css('.container > h1')); - expect(title.getText()).toBe('Reactive Forms'); + describe('Name Editor', function () { + const nameInput = nameEditor.element(by.css('input')); + const updateButton = nameEditor.element(by.buttonText('Update Name')); + const nameText = 'John Smith'; + + beforeAll(async () => { + await nameEditorLink.click(); }); - it('should contain a dropdown with each example', function() { - expect(select.isDisplayed()).toBe(true); + beforeEach(async () => { + await nameInput.clear(); }); - it('should have 9 options for different demos', function() { - let options = select.all(by.tagName('option')); - expect(options.count()).toBe(9); + it('should update the name value when the name control is updated', async () => { + await nameInput.sendKeys(nameText); + + const value = await nameInput.getAttribute('value'); + + expect(value).toBe(nameText); }); - it('should start with Final Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('Final Demo'); - }); + it('should update the name control when the Update Name button is clicked', async () => { + await nameInput.sendKeys(nameText); + const value = await nameInput.getAttribute('value'); + + expect(value).toBe(nameText); + await updateButton.click(); + + const value = await nameInput.getAttribute('value'); + + expect(value).toBe('Nancy'); + }); + + it('should update the displayed control value when the name control updated', async () => { + await nameInput.sendKeys(nameText); + const valueElement = nameEditor.element(by.cssContainingText('p', 'Value:')); + const nameValueElement = await valueElement.getText(); + const nameValue = nameValueElement.toString().replace('Value: ', ''); + + expect(nameValue).toBe(nameText); }); }); -// *************Begin Final Demo test******************************* + describe('Profile Editor', function () { + const firstNameInput = getInput('firstName'); + const lastNameInput = getInput('lastName'); + const streetInput = getInput('street'); + const addAliasButton = element(by.buttonText('Add Alias')); + const updateButton = profileEditor.element(by.buttonText('Update Profile')); + const profile = { + firstName: 'John', + lastName: 'Smith', + street: '345 South Lane', + city: 'Northtown', + state: 'XX', + zip: 12345 + }; - describe('final demo', function() { - it('does not select any hero by default', function() { - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); + beforeAll(async () => { + await profileEditorLink.click(); }); - it('refreshes the page upon button click', function() { - // We move to another page... - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - let refresh = element(by.css('button')); - refresh.click(); - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); + beforeEach(async () => { + await browser.get(''); + await profileEditorLink.click(); }); - describe('Whirlwind form', function() { - beforeEach(function() { - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwind'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Whirlwind'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('123 Main'); - expect(address1.state).toBe('CA'); - expect(address1.zip).toBe('94801'); - expect(address1.city).toBe('Anywhere'); - let address2 = finalDemoAddressForm(element, 1); - expect(address2.street).toBe('456 Maple'); - expect(address2.state).toBe('VA'); - expect(address2.zip).toBe('23226'); - expect(address2.city).toBe('Somewhere'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail > p')); - expect(json.getText()).toContain('Whirlwind'); - expect(json.getText()).toContain('Anywhere'); - expect(json.getText()).toContain('Somewhere'); - expect(json.getText()).toContain('VA'); - }); - - it('has two disabled buttons by default', function() { - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBe('true'); - expect(buttons.get(1).getAttribute('disabled')).toBe('true'); - }); - - it('enables the buttons after we edit the form', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBeNull(); - expect(buttons.get(1).getAttribute('disabled')).toBeNull(); - }); - - it('saves the changes when the save button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let save = element.all(by.css('app-hero-detail > form > div > button')).get(0); - save.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwinda'); - }); - - it('reverts the changes when the revert button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let revert = element.all(by.css('app-hero-detail > form > div > button')).get(1); - revert.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwind'); - expect(nameInput.getAttribute('value')).toBe('Whirlwind'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(3); - newLairButton.click(); - let address3 = finalDemoAddressForm(element, 2); - expect(address3.street).toBe(''); - expect(address3.state).toBe(''); - expect(address3.zip).toBe(''); - expect(address3.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); + it('should be invalid by default', async () => { + expect(await profileEditor.getText()).toContain('Form Status: INVALID'); }); - describe('Bombastic form', function() { - beforeEach(function() { - let bombastaButton = element.all(by.css('nav a')).get(1); - bombastaButton.click(); - }); + it('should be valid if the First Name is filled in', async () => { + await firstNameInput.clear(); + await firstNameInput.sendKeys('John Smith'); - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Bombastic'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Bombastic'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('789 Elm'); - // expect(address1.state).toBe('OH'); - expect(address1.zip).toBe('04501'); - expect(address1.city).toBe('Smallville'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail > p')); - expect(json.getText()).toContain('Bombastic'); - expect(json.getText()).toContain('Smallville'); - expect(json.getText()).toContain('OH'); - expect(json.getText()).toContain('04501'); - }); - - it('has two disabled buttons by default', function() { - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBe('true'); - expect(buttons.get(1).getAttribute('disabled')).toBe('true'); - }); - - it('enables the buttons after we edit the form', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBeNull(); - expect(buttons.get(1).getAttribute('disabled')).toBeNull(); - }); - - it('saves the changes when the save button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let save = element.all(by.css('app-hero-detail > form > div > button')).get(0); - save.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Bombastica'); - }); - - it('reverts the changes when the revert button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let revert = element.all(by.css('app-hero-detail > form > div > button')).get(1); - revert.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Bombastic'); - expect(nameInput.getAttribute('value')).toBe('Bombastic'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(3); - newLairButton.click(); - let address2 = finalDemoAddressForm(element, 1); - expect(address2.street).toBe(''); - expect(address2.state).toBe(''); - expect(address2.zip).toBe(''); - expect(address2.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); + expect(await profileEditor.getText()).toContain('Form Status: VALID'); }); - describe('Magneta form', function() { + it('should update the name when the button is clicked', async () => { + await firstNameInput.clear(); + await streetInput.clear(); + await firstNameInput.sendKeys('John'); + await streetInput.sendKeys('345 Smith Lane'); + const firstNameInitial = await firstNameInput.getAttribute('value'); + const streetNameInitial = await streetInput.getAttribute('value'); - beforeEach(function() { - let magnetaButton = element.all(by.css('nav a')).get(2); - magnetaButton.click(); - }); + expect(firstNameInitial).toBe('John'); + expect(streetNameInitial).toBe('345 Smith Lane'); + await updateButton.click(); - it('should show hero information when the button is clicked', function() { - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Magneta'); - }); + const nameValue = await firstNameInput.getAttribute('value'); + const streetValue = await streetInput.getAttribute('value'); - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Magneta'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail > p')); - expect(json.getText()).toContain('Magneta'); - }); - - it('has two disabled buttons by default', function() { - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBe('true'); - expect(buttons.get(1).getAttribute('disabled')).toBe('true'); - }); - - it('enables the buttons after we edit the form', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let buttons = element.all(by.css('app-hero-detail > form > div > button')); - expect(buttons.get(0).getAttribute('disabled')).toBeNull(); - expect(buttons.get(1).getAttribute('disabled')).toBeNull(); - }); - - it('saves the changes when the save button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let save = element.all(by.css('app-hero-detail > form > div > button')).get(0); - save.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Magnetaa'); - }); - - it('reverts the changes when the revert button is clicked', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - nameInput.sendKeys('a'); - let revert = element.all(by.css('app-hero-detail > form > div > button')).get(1); - revert.click(); - let editMessage = element(by.css('app-hero-list > div > h3')); - expect(editMessage.getText()).toBe('Editing: Magneta'); - expect(nameInput.getAttribute('value')).toBe('Magneta'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(3); - newLairButton.click(); - let address = finalDemoAddressForm(element, 0); - expect(address.street).toBe(''); - expect(address.state).toBe(''); - expect(address.zip).toBe(''); - expect(address.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - }); - }); // final demo - -// *************Begin FormArray Demo test******************************* - - - describe('formArray demo', function() { - beforeEach(function() { - let FormArrayOption = element.all(by.css('select option')).get(7); - FormArrayOption.click(); + expect(nameValue).toBe('Nancy'); + expect(streetValue).toBe('123 Drew Street'); }); - it('should show FormArray Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('FormArray Demo'); - }); - }); + it('should add an alias field when the Add Alias button is clicked', async () => { + await addAliasButton.click(); - it('does not select any hero by default', function() { - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); + const aliasInputs = profileEditor.all(by.cssContainingText('label', 'Alias')); + + expect(await aliasInputs.count()).toBe(2); }); - it('refreshes the page upon button click', function() { - // We move to another page... - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - let refresh = element(by.css('button')); - refresh.click(); - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); + it('should update the displayed form value when form inputs are updated', async () => { + const aliasText = 'Johnny'; + const inputs = await Promise.all( + Object.keys(profile) + .map(key => + getInput(key).sendKeys(`${profile[key]}`) + ) + ); + + const aliasInputs = profileEditor.all(by.cssContainingText('label', 'Alias')); + const aliasInput = aliasInputs.get(0).element(by.css('input')); + await aliasInput.sendKeys(aliasText); + const formValueElement = profileEditor.all(by.cssContainingText('p', 'Form Value:')); + const formValue = await formValueElement.getText(); + const formJson = JSON.parse(formValue.toString().replace('Form Value:', '')); + + expect(profile.firstName).toBe(formJson.firstName); + expect(profile.lastName).toBe(formJson.lastName); + expect(formJson.aliases[0]).toBe(aliasText); }); + }); - describe('Whirlwind form', function() { - beforeEach(function() { - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - }); - - it('should show hero information when the button is clicked', function() { - let editMessage = element(by.css('div.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwind'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Whirlwind'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('123 Main'); - expect(address1.state).toBe('CA'); - expect(address1.zip).toBe('94801'); - expect(address1.city).toBe('Anywhere'); - let address2 = finalDemoAddressForm(element, 1); - expect(address2.street).toBe('456 Maple'); - expect(address2.state).toBe('VA'); - expect(address2.zip).toBe('23226'); - expect(address2.city).toBe('Somewhere'); - }); - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-8 > p')); - expect(json.getText()).toContain('Whirlwind'); - expect(json.getText()).toContain('Anywhere'); - expect(json.getText()).toContain('Somewhere'); - expect(json.getText()).toContain('VA'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(1); - newLairButton.click(); - let address2 = finalDemoAddressForm(element, 2); - expect(address2.street).toBe(''); - expect(address2.state).toBe(''); - expect(address2.zip).toBe(''); - expect(address2.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - }); // Whirlwind form - - describe('Bombastic FormArray form', function() { - beforeEach(function() { - let bombasticButton = element.all(by.css('nav a')).get(1); - bombasticButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('div.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Bombastic'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - // nameInput.getAttribute('value').then(function(name: string) { - // expect(name).toBe('Whirlwind'); - // }); - expect(nameInput.getAttribute('value')).toBe('Bombastic'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('789 Elm'); - // expect(address1.state).toBe('OH'); - // This select should be OH not CA, which it shows in the UI, the JSON shows OH. - expect(address1.zip).toBe('04501'); - expect(address1.city).toBe('Smallville'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-8 > p')); - expect(json.getText()).toContain('Bombastic'); - expect(json.getText()).toContain('Smallville'); - expect(json.getText()).toContain('04501'); - expect(json.getText()).toContain('789 Elm'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(1); - newLairButton.click(); - let address1 = finalDemoAddressForm(element, 1); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - }); // Bombastic FormArray form - - describe('Magneta FormArray form', function() { - beforeEach(function() { - let magnetaButton = element.all(by.css('nav a')).get(2); - magnetaButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('div.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Magneta'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Magneta'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-8 > p')); - expect(json.getText()).toContain('Magneta'); - }); - - it('is able to add a new empty address', function() { - let newLairButton = element.all(by.css('button')).get(1); - newLairButton.click(); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - }); // Magneta FormArray form - - }); // formArray demo - - -// *************Begin SetValue Demo test******************************* - - describe('SetValue demo', function() { - beforeEach(function() { - let SetValueOption = element.all(by.css('select option')).get(6); - SetValueOption.click(); - }); - - it('should show SetValue Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('SetValue Demo'); - }); - }); - - it('does not select any hero by default', function() { - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); - }); - - it('refreshes the page upon button click', function() { - // We move to another page... - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - let refresh = element(by.css('button')); - refresh.click(); - let heroSection = element(by.css('app-hero-list > div')); - expect(heroSection.isPresent()).toBe(false); - }); - - describe('Whirlwind setValue form', function() { - beforeEach(function() { - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwind'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Whirlwind'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('123 Main'); - expect(address1.state).toBe('CA'); - expect(address1.zip).toBe('94801'); - expect(address1.city).toBe('Anywhere'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-7 > p')); - expect(json.getText()).toContain('Whirlwind'); - expect(json.getText()).toContain('Anywhere'); - let nameOutput = element(by.css('app-hero-detail-7 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Whirlwind'); - let streetOutput = element(by.css('app-hero-detail-7 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value: 123 Main'); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - }); // Whirlwind setValue form - - describe('Bombastic setValue form', function() { - beforeEach(function() { - let bombasticButton = element.all(by.css('nav a')).get(1); - bombasticButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Bombastic'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Bombastic'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe('789 Elm'); - expect(address1.state).toBe('OH'); - expect(address1.zip).toBe('04501'); - expect(address1.city).toBe('Smallville'); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-7 > p')); - expect(json.getText()).toContain('Bombastic'); - expect(json.getText()).toContain('Smallville'); - expect(json.getText()).toContain('04501'); - expect(json.getText()).toContain('789 Elm'); - let nameOutput = element(by.css('app-hero-detail-7 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Bombastic'); - let streetOutput = element(by.css('app-hero-detail-7 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value: 789 Elm'); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - }); // Bombastic setValue form - - describe('Magneta setValue form', function() { - beforeEach(function() { - let magnetaButton = element.all(by.css('nav a')).get(2); - magnetaButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('.demo > div > div > h3')); - expect(editMessage.getText()).toBe('Editing: Magneta'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Magneta'); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-7 > p')); - expect(json.getText()).toContain('Magneta'); - let nameOutput = element(by.css('app-hero-detail-7 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Magneta'); - let streetOutput = element(by.css('app-hero-detail-7 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value:'); - }); - - }); // Magneta setValue form - }); // SetValue demo - -// *************Begin patchValue Demo test******************************* - - describe('patchValue demo', function() { - beforeEach(function() { - let SetValueOption = element.all(by.css('select option')).get(5); - SetValueOption.click(); - }); - - it('should show patchValue Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('PatchValue Demo'); - }); - }); - - it('does not select any hero by default', function() { - let heroSection = element(by.css('.demo > div > div')); - expect(heroSection.isPresent()).toBe(false); - }); - - it('refreshes the page upon button click', function() { - // We move to another page... - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - let refresh = element(by.css('button')); - refresh.click(); - let heroSection = element(by.css('.demo > div > div')); - expect(heroSection.isPresent()).toBe(false); - }); - - describe('Whirlwind patchValue form', function() { - beforeEach(function() { - let whirlwindButton = element.all(by.css('nav a')).get(0); - whirlwindButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('h2 ~ h3')); - expect(editMessage.getText()).toBe('Editing: Whirlwind'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Whirlwind'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-6 > p')); - expect(json.getText()).toContain('Whirlwind'); - let nameOutput = element(by.css('app-hero-detail-6 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Whirlwind'); - let streetOutput = element(by.css('app-hero-detail-6 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value:'); - }); - - - }); // Bombastic patchValue form - describe('Bombastic patchValue form', function() { - beforeEach(function() { - let bombasticButton = element.all(by.css('nav a')).get(1); - bombasticButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('h2 ~ h3')); - expect(editMessage.getText()).toBe('Editing: Bombastic'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Bombastic'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-6 > p')); - expect(json.getText()).toContain('Bombastic'); - let nameOutput = element(by.css('app-hero-detail-6 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Bombastic'); - let streetOutput = element(by.css('app-hero-detail-6 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value:'); - }); - }); // Bombastic patchValue form - - describe('Magneta patchValue form', function() { - beforeEach(function() { - let magnetaButton = element.all(by.css('nav a')).get(2); - magnetaButton.click(); - }); - - it('should show a hero information when the button is clicked', function() { - let editMessage = element(by.css('h2 ~ h3')); - expect(editMessage.getText()).toBe('Editing: Magneta'); - }); - - it('should show a form with the selected hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe('Magneta'); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-6 > p')); - expect(json.getText()).toContain('Magneta'); - let nameOutput = element(by.css('app-hero-detail-6 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value: Magneta'); - let streetOutput = element(by.css('app-hero-detail-6 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value:'); - }); - - }); // Magneta patchValue form - }); // PatchValue demo - - - -// *************Begin Nested FormBuilder Demo test******************************* - - describe('Nested FormBuilder demo', function() { - beforeEach(function() { - let NestedFormBuilderOption = element.all(by.css('select option')).get(4); - NestedFormBuilderOption.click(); - }); - - it('should show Nested FormBuilder Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('Nested FormBuilder group Demo'); - }); - }); - - it('should show a form for hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe(''); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-5 > p')); - expect(json.getText()).toContain('address'); - let nameOutput = element(by.css('app-hero-detail-5 > p ~ p')); - expect(nameOutput.getText()).toContain('Name value:'); - let streetOutput = element(by.css('app-hero-detail-5 > p ~ p ~ p')); - expect(streetOutput.getText()).toContain('Street value:'); - }); - - }); // Nested FormBuilder demo - -// *************Begin Group with multiple controls Demo test******************************* - - describe('Group with multiple controls demo', function() { - beforeEach(function() { - let NestedFormBuilderOption = element.all(by.css('select option')).get(3); - NestedFormBuilderOption.click(); - }); - - it('should show Group with multiple controls Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('Group with multiple controls Demo'); - }); - }); - - it('should show header', function() { - let header = element(by.css('app-hero-detail-4 > h3')); - expect(header.getText()).toBe('A FormGroup with multiple FormControls'); - }); - - it('should show a form for hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe(''); - let address1 = finalDemoAddressForm(element, 0); - expect(address1.street).toBe(''); - expect(address1.state).toBe(''); - expect(address1.zip).toBe(''); - expect(address1.city).toBe(''); - }); - - it('should show three radio buttons', function() { - let radioButtons = element.all(by.css('input[formcontrolname=power]')); - expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); - expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); - expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); - }); - it('should show a checkbox', function() { - let checkbox = element(by.css('input[formcontrolname=sidekick]')); - expect(checkbox.getAttribute('checked')).toBe(null); - }); - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-4 > p')); - expect(json.getText()).toContain('power'); - }); - -}); // Group with multiple controls demo - - - -// *************Begin Group with multiple controls Demo test******************************* - - describe('Simple FormBuilder Group demo', function() { - beforeEach(function() { - let SimpleFormBuilderOption = element.all(by.css('select option')).get(2); - SimpleFormBuilderOption.click(); - }); - - it('should show Simple FormBuilder group Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('Simple FormBuilder group Demo'); - }); - }); - - it('should show header', function() { - let header = element(by.css('app-hero-detail-3 > h3')); - expect(header.getText()).toBe('A FormGroup with a single FormControl using FormBuilder'); - }); - - it('should show a form for hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe(''); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-3 > p')); - expect(json.getText()).toContain('name'); - let validStatus = element(by.css('app-hero-detail-3 > p ~ p')); - expect(validStatus.getText()).toContain('INVALID'); - }); - -}); // Group with multiple controls demo - - -// *************Begin FormControl in a FormGroup Demo test******************************* - - describe('FormControl in a FormGroup demo', function() { - beforeEach(function() { - let SimpleFormBuilderOption = element.all(by.css('select option')).get(1); - SimpleFormBuilderOption.click(); - }); - - it('should show FormControl in a FormGroup Demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('FormControl in a FormGroup Demo'); - }); - }); - - it('should show header', function() { - let header = element(by.css('app-hero-detail-2 > h3')); - expect(header.getText()).toBe('FormControl in a FormGroup'); - }); - - it('should show a form for hero information', function() { - let nameInput = element(by.css('input[formcontrolname=name]')); - expect(nameInput.getAttribute('value')).toBe(''); - }); - - it('shows a json output from the form', function() { - let json = element(by.css('app-hero-detail-2 > p')); - expect(json.getText()).toContain('name'); - }); - -}); // Group with multiple controls demo - -// *************Begin Just A FormControl Demo test******************************* - - describe('Just a FormControl demo', function() { - beforeEach(function() { - let FormControlOption = element.all(by.css('select option')).get(0); - FormControlOption.click(); - }); - - it('should show Just a FormControl demo', function() { - select.getAttribute('value').then(function(demo: string) { - expect(demo).toBe('Just a FormControl Demo'); - }); - }); - - it('should show header', function() { - let header = element(by.css('app-hero-detail-1 > h3')); - expect(header.getText()).toBe('Just a FormControl'); - }); - - it('should show a form for hero information', function() { - let nameInput = element(by.css('input')); - expect(nameInput.getAttribute('value')).toBe(''); - }); - - }); // Just a FormControl demo test - - -}); // reactive forms + function getInput(key: string) { + return element(by.css(`input[formcontrolname=${key}`)); + } +}); diff --git a/aio/content/examples/reactive-forms/final.stackblitz.json b/aio/content/examples/reactive-forms/final.stackblitz.json deleted file mode 100644 index f789236245..0000000000 --- a/aio/content/examples/reactive-forms/final.stackblitz.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "description": "Angular Reactive Forms (final)", - "files":[ - "src/styles.css", - - "src/app/app.component.ts", - "src/app/app.component.html", - "src/app/app.component.css", - "src/app/app.module.ts", - "src/app/data-model.ts", - "src/app/hero.service.ts", - "src/app/hero-detail/hero-detail.component.html", - "src/app/hero-detail/hero-detail.component.ts", - "src/app/hero-detail/hero-detail.component.css", - "src/app/hero-list/hero-list.component.html", - "src/app/hero-list/hero-list.component.ts", - "src/app/hero-list/hero-list.component.css", - - "src/main-final.ts", - "src/index-final.html" - ], - "main": "src/index-final.html", - "tags": ["reactive", "forms"] -} diff --git a/aio/content/examples/reactive-forms/src/app/app.component.1.html b/aio/content/examples/reactive-forms/src/app/app.component.1.html index 14316fbe62..57ca7e22fb 100644 --- a/aio/content/examples/reactive-forms/src/app/app.component.1.html +++ b/aio/content/examples/reactive-forms/src/app/app.component.1.html @@ -1,4 +1,10 @@ -
        -

        Reactive Forms

        - -
        + +

        Reactive Forms

        + + + + + + + + \ No newline at end of file diff --git a/aio/content/examples/reactive-forms/src/app/app.component.html b/aio/content/examples/reactive-forms/src/app/app.component.html index b7d0e6d580..a953480634 100644 --- a/aio/content/examples/reactive-forms/src/app/app.component.html +++ b/aio/content/examples/reactive-forms/src/app/app.component.html @@ -1,4 +1,17 @@ -
        -

        Reactive Forms

        - -
        + + +

        Reactive Forms

        + + + + + + + + + + + diff --git a/aio/content/examples/reactive-forms/src/app/app.component.spec.ts b/aio/content/examples/reactive-forms/src/app/app.component.spec.ts new file mode 100644 index 0000000000..fff9fa666d --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/app.component.spec.ts @@ -0,0 +1,27 @@ +import { TestBed, async } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + it(`should have as title 'app'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app'); + })); + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to reactive-forms!'); + })); +}); diff --git a/aio/content/examples/reactive-forms/src/app/app.component.ts b/aio/content/examples/reactive-forms/src/app/app.component.ts index f7baece9b5..5c9993b6b6 100644 --- a/aio/content/examples/reactive-forms/src/app/app.component.ts +++ b/aio/content/examples/reactive-forms/src/app/app.component.ts @@ -1,9 +1,24 @@ -// #docregion import { Component } from '@angular/core'; +export type EditorType = 'name' | 'profile'; + @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent { } +export class AppComponent { + editor: EditorType = 'name'; + + get showNameEditor() { + return this.editor === 'name'; + } + + get showProfileEditor() { + return this.editor === 'profile'; + } + + toggleEditor(type: EditorType) { + this.editor = type; + } +} diff --git a/aio/content/examples/reactive-forms/src/app/app.module.ts b/aio/content/examples/reactive-forms/src/app/app.module.ts index 39b074c178..9ccc26e4af 100644 --- a/aio/content/examples/reactive-forms/src/app/app.module.ts +++ b/aio/content/examples/reactive-forms/src/app/app.module.ts @@ -1,45 +1,34 @@ // #docplaster -// #docregion -// #docregion v1 -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +// #docregion imports +import { ReactiveFormsModule } from '@angular/forms'; -import { AppComponent } from './app.component'; -import { HeroDetailComponent } from './hero-detail/hero-detail.component'; -// #enddocregion v1 -// #docregion hero-service-list -// add JavaScript imports -import { HeroListComponent } from './hero-list/hero-list.component'; -import { HeroService } from './hero.service'; -// #docregion v1 +// #enddocregion imports +import { AppComponent } from './app.component'; +import { NameEditorComponent } from './name-editor/name-editor.component'; +import { ProfileEditorComponent } from './profile-editor/profile-editor.component'; +// #docregion imports @NgModule({ +// #enddocregion imports declarations: [ AppComponent, - HeroDetailComponent, -// #enddocregion v1 - HeroListComponent // <--declare HeroListComponent -// #docregion v1 + NameEditorComponent, + ProfileEditorComponent ], - // #enddocregion hero-service-list +// #docregion imports imports: [ +// #enddocregion imports BrowserModule, - ReactiveFormsModule // <-- #2 add to @NgModule imports +// #docregion imports + // other imports ... + ReactiveFormsModule ], - // #enddocregion v1 - // export for the DemoModule - // #docregion hero-service-list - // ... - exports: [ - AppComponent, - HeroDetailComponent, - HeroListComponent // <-- export HeroListComponent - ], - providers: [ HeroService ], // <-- provide HeroService -// #enddocregion hero-service-list -// #docregion v1 - bootstrap: [ AppComponent ] +// #enddocregion imports + providers: [], + bootstrap: [AppComponent] +// #docregion imports }) export class AppModule { } -// #enddocregion v1 +// #enddocregion imports diff --git a/aio/content/examples/reactive-forms/src/app/data-model.ts b/aio/content/examples/reactive-forms/src/app/data-model.ts deleted file mode 100644 index ad01ddee56..0000000000 --- a/aio/content/examples/reactive-forms/src/app/data-model.ts +++ /dev/null @@ -1,40 +0,0 @@ -// #docregion -// #docregion model-classes -export class Hero { - id = 0; - name = ''; - addresses: Address[]; -} - -export class Address { - street = ''; - city = ''; - state = ''; - zip = ''; -} -// #enddocregion model-classes - -export const heroes: Hero[] = [ - { - id: 1, - name: 'Whirlwind', - addresses: [ - {street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'}, - {street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'}, - ] - }, - { - id: 2, - name: 'Bombastic', - addresses: [ - {street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'}, - ] - }, - { - id: 3, - name: 'Magneta', - addresses: [ ] - }, -]; - -export const states = ['CA', 'MD', 'OH', 'VA']; diff --git a/aio/content/examples/reactive-forms/src/app/demo.component.html b/aio/content/examples/reactive-forms/src/app/demo.component.html deleted file mode 100644 index 369427a739..0000000000 --- a/aio/content/examples/reactive-forms/src/app/demo.component.html +++ /dev/null @@ -1,40 +0,0 @@ -
        -

        Reactive Forms

        -

        Pick a demo: - -

        - -
        - -
        - - - - - - - -
        - -

        Loading heroes ...

        -

        Select a hero:

        - - - -
        -
        -

        Hero Detail

        -

        Editing: {{selectedHero.name}}

        - - - - -
        -
        -
        -
        diff --git a/aio/content/examples/reactive-forms/src/app/demo.component.ts b/aio/content/examples/reactive-forms/src/app/demo.component.ts deleted file mode 100644 index 71658e7b6b..0000000000 --- a/aio/content/examples/reactive-forms/src/app/demo.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* tslint:disable:member-ordering */ -import { Component } from '@angular/core'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; - -import { Hero } from './data-model'; -import { HeroService } from './hero.service'; - -@Component({ - selector: 'app-root', - templateUrl: './demo.component.html' -}) -export class DemoComponent { - - demos: string[] = [ - 'Just a FormControl', - 'FormControl in a FormGroup', - 'Simple FormBuilder group', - 'Group with multiple controls', - 'Nested FormBuilder group', - 'PatchValue', - 'SetValue', - 'FormArray', - 'Final'].map(n => n + ' Demo'); - - final = this.demos.length; - demo = this.final; // current demo - - heroes: Observable; - isLoading = false; - selectedHero: Hero; - - constructor(private heroService: HeroService) { } - - getHeroes() { - this.isLoading = true; - this.heroes = this.heroService.getHeroes().pipe( - finalize(() => this.isLoading = false) - ); - this.selectedHero = undefined; - } - - select(hero: Hero) { this.selectedHero = hero; } - - selectDemo(demo: number) { - this.demo = demo + 1; - this.getHeroes(); - } -} diff --git a/aio/content/examples/reactive-forms/src/app/demo.module.ts b/aio/content/examples/reactive-forms/src/app/demo.module.ts deleted file mode 100644 index a8b71ab5b6..0000000000 --- a/aio/content/examples/reactive-forms/src/app/demo.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { ReactiveFormsModule } from '@angular/forms'; - -import { AppModule } from './app.module'; -import { DemoComponent } from './demo.component'; -import { HeroDetailComponent1 } from './hero-detail/hero-detail-1.component'; -import { HeroDetailComponent2 } from './hero-detail/hero-detail-2.component'; -import { HeroDetailComponent3 } from './hero-detail/hero-detail-3.component'; -import { HeroDetailComponent4 } from './hero-detail/hero-detail-4.component'; -import { HeroDetailComponent5 } from './hero-detail/hero-detail-5.component'; -import { HeroDetailComponent6 } from './hero-detail/hero-detail-6.component'; -import { HeroDetailComponent7 } from './hero-detail/hero-detail-7.component'; -import { HeroDetailComponent8 } from './hero-detail/hero-detail-8.component'; - -@NgModule({ - imports: [ - BrowserModule, - ReactiveFormsModule, - AppModule, - ], - declarations: [ DemoComponent, - HeroDetailComponent1, - HeroDetailComponent2, - HeroDetailComponent3, - HeroDetailComponent4, - HeroDetailComponent5, - HeroDetailComponent6, - HeroDetailComponent7, - HeroDetailComponent8], - bootstrap: [ DemoComponent ] -}) -export class DemoModule { } diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.html deleted file mode 100644 index 7217708d22..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.html +++ /dev/null @@ -1,8 +0,0 @@ - -

        Hero Detail

        -

        Just a FormControl

        - - - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.ts deleted file mode 100644 index 7ce88f3522..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-1.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* tslint:disable:component-class-suffix */ - -import { Component } from '@angular/core'; -// #docregion import -import { FormControl } from '@angular/forms'; -// #enddocregion import - -@Component({ - selector: 'app-hero-detail-1', - templateUrl: './hero-detail-1.component.html' -}) -// #docregion v1 -export class HeroDetailComponent1 { - name = new FormControl(); -} diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.html deleted file mode 100644 index 79410c4a6d..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.html +++ /dev/null @@ -1,18 +0,0 @@ - -

        Hero Detail

        -

        FormControl in a FormGroup

        -
        -
        - -
        -
        - - - -

        Form value: {{ heroForm.value | json }}

        - - - - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.ts deleted file mode 100644 index 6508904ffa..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-2.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion imports -import { Component } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-2', - templateUrl: './hero-detail-2.component.html' -}) -// #docregion v2 -export class HeroDetailComponent2 { - heroForm = new FormGroup ({ - name: new FormControl() - }); -} -// #enddocregion v2 diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.html deleted file mode 100644 index 89c5513566..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.html +++ /dev/null @@ -1,16 +0,0 @@ - -

        Hero Detail

        -

        A FormGroup with a single FormControl using FormBuilder

        -
        -
        - -
        -
        - - - -

        Form value: {{ heroForm.value | json }}

        -

        Form status: {{ heroForm.status | json }}

        - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.ts deleted file mode 100644 index 81388c8faa..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion imports -import { Component } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-3', - templateUrl: './hero-detail-3.component.html' -}) -// #docregion v3 -export class HeroDetailComponent3 { - heroForm: FormGroup; // <--- heroForm is of type FormGroup - - constructor(private fb: FormBuilder) { // <--- inject FormBuilder - this.createForm(); - } - - createForm() { - // #docregion required - this.heroForm = this.fb.group({ - name: ['', Validators.required ], - }); - // #enddocregion required - } -} -// #enddocregion v3 diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts deleted file mode 100644 index 7cc0b1ca8f..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-3a.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion imports -import { Component } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-3', - templateUrl: './hero-detail-3.component.html' -}) -// #docregion v3a -export class HeroDetailComponent3 { - heroForm: FormGroup; // <--- heroForm is of type FormGroup - - constructor(private fb: FormBuilder) { // <--- inject FormBuilder - this.createForm(); - } - - createForm() { - this.heroForm = this.fb.group({ - name: '', // <--- the FormControl called "name" - }); - } -} -// #enddocregion v3a diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.html deleted file mode 100644 index 8702eeb540..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.html +++ /dev/null @@ -1,46 +0,0 @@ - -

        Hero Detail

        -

        A FormGroup with multiple FormControls

        -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        -

        Super power:

        - - - -
        -
        - -
        -
        - - -

        Form value: {{ heroForm.value | json }}

        diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.ts deleted file mode 100644 index b9a61aa1ea..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-4.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion imports -import { Component } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -import { states } from '../data-model'; -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-4', - templateUrl: './hero-detail-4.component.html' -}) -// #docregion v4 -export class HeroDetailComponent4 { - heroForm: FormGroup; - states = states; - - constructor(private fb: FormBuilder) { - this.createForm(); - } - - createForm() { - this.heroForm = this.fb.group({ - name: ['', Validators.required ], - street: '', - city: '', - state: '', - zip: '', - power: '', - sidekick: '' - }); - } -} -// #enddocregion v4 diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.html deleted file mode 100644 index bc73ce006e..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.html +++ /dev/null @@ -1,56 +0,0 @@ - -
        -
        - -
        - -
        -

        Secret Lair

        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -

        Super power:

        - - - -
        -
        - -
        -
        - -

        heroForm value: {{ heroForm.value | json}}

        -

        Extra info for the curious:

        - -

        Name value: {{ heroForm.get('name').value }}

        - - - -

        Street value: {{ heroForm.get('address.street').value}}

        - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.ts deleted file mode 100644 index 1193265c5b..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-5.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* tslint:disable:component-class-suffix */ -import { Component } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -import { states } from '../data-model'; - -@Component({ - selector: 'app-hero-detail-5', - templateUrl: './hero-detail-5.component.html' -}) -// #docregion v5 -export class HeroDetailComponent5 { - heroForm: FormGroup; - states = states; - - constructor(private fb: FormBuilder) { - this.createForm(); - } - - createForm() { - this.heroForm = this.fb.group({ // <-- the parent FormGroup - name: ['', Validators.required ], - address: this.fb.group({ // <-- the child FormGroup - street: '', - city: '', - state: '', - zip: '' - }), - power: '', - sidekick: '' - }); - } -} -// #enddocregion v5 - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.html deleted file mode 100644 index a6f352a9b9..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.html +++ /dev/null @@ -1,46 +0,0 @@ - -

        Hero Detail

        -

        PatchValue to initialize a value

        -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        -

        Super power:

        - - - -
        -
        - -
        -
        - - -

        Form value: {{ heroForm.value | json }}

        diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.ts deleted file mode 100644 index 042b21aaf7..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-6.component.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion import-input -import { Component, Input, OnChanges } from '@angular/core'; -// #enddocregion import-input -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -// #docregion import-hero -import { Hero, states } from '../data-model'; -// #enddocregion import-hero - -////////// 6 //////////////////// - -@Component({ - selector: 'app-hero-detail-6', - templateUrl: './hero-detail-5.component.html' -}) -// #docregion v6 -export class HeroDetailComponent6 implements OnChanges { - // #docregion hero - @Input() hero: Hero; - // #enddocregion hero - - heroForm: FormGroup; - states = states; - - constructor(private fb: FormBuilder) { - this.createForm(); - } - - createForm() { - // #docregion hero-form-model - this.heroForm = this.fb.group({ - name: ['', Validators.required ], - address: this.fb.group({ - street: '', - city: '', - state: '', - zip: '' - }), - power: '', - sidekick: '' - }); - // #enddocregion hero-form-model - } - - // #docregion patch-value-on-changes - ngOnChanges() { // <-- call rebuildForm in ngOnChanges - this.rebuildForm(); - } - // #enddocregion patch-value-on-changes - - // #docregion patch-value-rebuildform - rebuildForm() { // <-- wrap patchValue in rebuildForm - this.heroForm.reset(); - // #docregion patch-value - this.heroForm.patchValue({ - name: this.hero.name - }); - // #enddocregion patch-value - } - // #enddocregion patch-value-rebuildform -} - - - -// #enddocregion v6 diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.html deleted file mode 100644 index 57b00ca92c..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.html +++ /dev/null @@ -1,46 +0,0 @@ - -

        Hero Detail

        -

        A FormGroup with multiple FormControls

        -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        -

        Super power:

        - - - -
        -
        - -
        -
        - - -

        Form value: {{ heroForm.value | json }}

        diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.ts deleted file mode 100644 index b70b2e879d..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-7.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docplaster -// #docregion imports -import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -// #docregion import-address -import { Address, Hero, states } from '../data-model'; -// #enddocregion import-address - -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-7', - templateUrl: './hero-detail-5.component.html' -}) -// #docregion v7 -export class HeroDetailComponent7 implements OnChanges { - @Input() hero: Hero; - - heroForm: FormGroup; - states = states; - - constructor(private fb: FormBuilder) { - this.createForm(); - } - - createForm() { - // #docregion address-form-group - this.heroForm = this.fb.group({ - name: ['', Validators.required ], - address: this.fb.group(new Address()), // <-- a FormGroup with a new address - power: '', - sidekick: '' - }); - // #enddocregion address-form-group - } - - // #docregion ngOnChanges - ngOnChanges() { - this.rebuildForm(); - } - // #enddocregion ngOnChanges - - // #docregion rebuildForm - rebuildForm() { - this.heroForm.reset({ - name: this.hero.name, - // #docregion set-value-address - address: this.hero.addresses[0] || new Address() - // #enddocregion set-value-address - }); - } - // #enddocregion rebuildForm - - /* First version of rebuildForm */ - rebuildForm1() { - // #docregion reset - this.heroForm.reset(); - // #enddocregion reset - // #docregion set-value - this.heroForm.setValue({ - name: this.hero.name, - address: this.hero.addresses[0] || new Address() - }); - // #enddocregion set-value - } -} diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.html deleted file mode 100644 index bfb591551e..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.html +++ /dev/null @@ -1,72 +0,0 @@ - -

        Using FormArray to add groups

        - -
        -

        Form Changed: {{ heroForm.dirty }}

        - -
        - -
        - - - -
        - -
        - - -

        Address #{{i + 1}}

        -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        -
        - - -
        - - - - - - - -
        - - -
        -

        Super power:

        - - - -
        -
        - -
        -
        - -

        heroForm value: {{ heroForm.value | json}}

        diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.ts deleted file mode 100644 index f3e09df7fe..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail-8.component.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* tslint:disable:component-class-suffix */ -// #docregion imports -import { Component, Input, OnChanges } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; - -import { Address, Hero, states } from '../data-model'; -// #enddocregion imports - -@Component({ - selector: 'app-hero-detail-8', - templateUrl: './hero-detail-8.component.html' -}) -// #docregion v8 -export class HeroDetailComponent8 implements OnChanges { - @Input() hero: Hero; - - heroForm: FormGroup; - states = states; - - // #docregion ctor - constructor(private fb: FormBuilder) { - this.createForm(); - this.logNameChange(); - } - // #enddocregion ctor - - createForm() { - // #docregion secretLairs-form-array - this.heroForm = this.fb.group({ - name: ['', Validators.required ], - secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray - power: '', - sidekick: '' - }); - // #enddocregion secretLairs-form-array - } - - logNameChange() {/* Coming soon */} - - // #docregion onchanges - ngOnChanges() { - this.rebuildForm(); - } - // #enddocregion onchanges - - // #docregion rebuildform - rebuildForm() { - this.heroForm.reset({ - name: this.hero.name - }); - this.setAddresses(this.hero.addresses); - } -// #enddocregion rebuildform - - // #docregion get-secret-lairs - get secretLairs(): FormArray { - return this.heroForm.get('secretLairs') as FormArray; - }; - // #enddocregion get-secret-lairs - - // #docregion set-addresses - setAddresses(addresses: Address[]) { - const addressFGs = addresses.map(address => this.fb.group(address)); - const addressFormArray = this.fb.array(addressFGs); - this.heroForm.setControl('secretLairs', addressFormArray); - } - // #enddocregion set-addresses - - // #docregion add-lair - addLair() { - this.secretLairs.push(this.fb.group(new Address())); - } - // #enddocregion add-lair -} diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.css b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.html b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.html deleted file mode 100644 index c5e77ba43d..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.html +++ /dev/null @@ -1,73 +0,0 @@ - - - -
        -
        -   - -
        - - - -
        - -
        - -
        -
        - -

        Address #{{i + 1}}

        -
        -
        - -
        -
        - -
        -
        - -
        -
        - -
        -
        -
        - -
        - -
        - -
        -

        Super power:

        - - - -
        -
        - -
        -
        - - -

        heroForm value: {{ heroForm.value | json}}

        - - -

        Name change log

        -
        {{name}}
        - diff --git a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.ts b/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.ts deleted file mode 100644 index 13ef66b0f9..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-detail/hero-detail.component.ts +++ /dev/null @@ -1,113 +0,0 @@ -// #docplaster -// #docregion -import { Component, Input, OnChanges } from '@angular/core'; -import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; - -import { Address, Hero, states } from '../data-model'; -// #docregion import-service -import { HeroService } from '../hero.service'; -// #enddocregion import-service - -@Component({ - selector: 'app-hero-detail', - templateUrl: './hero-detail.component.html', - styleUrls: ['./hero-detail.component.css'] -}) - -// #docregion onchanges-implementation -export class HeroDetailComponent implements OnChanges { -// #enddocregion onchanges-implementation - @Input() hero: Hero; - - heroForm: FormGroup; - // #docregion log-name-change - nameChangeLog: string[] = []; - // #enddocregion log-name-change - states = states; - - // #docregion ctor - constructor( - private fb: FormBuilder, - private heroService: HeroService) { - - this.createForm(); - this.logNameChange(); - } - // #enddocregion ctor - - createForm() { - this.heroForm = this.fb.group({ - name: '', - secretLairs: this.fb.array([]), - power: '', - sidekick: '' - }); - } - - ngOnChanges() { - this.rebuildForm(); - } - - rebuildForm() { - this.heroForm.reset({ - name: this.hero.name - }); - this.setAddresses(this.hero.addresses); - } - - get secretLairs(): FormArray { - return this.heroForm.get('secretLairs') as FormArray; - }; - - setAddresses(addresses: Address[]) { - const addressFGs = addresses.map(address => this.fb.group(address)); - const addressFormArray = this.fb.array(addressFGs); - this.heroForm.setControl('secretLairs', addressFormArray); - } - - addLair() { - this.secretLairs.push(this.fb.group(new Address())); - } - - // #docregion on-submit - onSubmit() { - this.hero = this.prepareSaveHero(); - this.heroService.updateHero(this.hero).subscribe(/* error handling */); - this.rebuildForm(); - } - // #enddocregion on-submit - - // #docregion prepare-save-hero - prepareSaveHero(): Hero { - const formModel = this.heroForm.value; - - // deep copy of form model lairs - const secretLairsDeepCopy: Address[] = formModel.secretLairs.map( - (address: Address) => Object.assign({}, address) - ); - - // return new `Hero` object containing a combination of original hero value(s) - // and deep copies of changed form model values - const saveHero: Hero = { - id: this.hero.id, - name: formModel.name as string, - // addresses: formModel.secretLairs // <-- bad! - addresses: secretLairsDeepCopy - }; - return saveHero; - } - // #enddocregion prepare-save-hero - - // #docregion revert - revert() { this.rebuildForm(); } - // #enddocregion revert - - // #docregion log-name-change - logNameChange() { - const nameControl = this.heroForm.get('name'); - nameControl.valueChanges.forEach( - (value: string) => this.nameChangeLog.push(value) - ); - } - // #enddocregion log-name-change -} diff --git a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.1.html b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.1.html deleted file mode 100644 index 409c9b2a5c..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.1.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -
        - -
        diff --git a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.css b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.html b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.html deleted file mode 100644 index 78356caeb2..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.html +++ /dev/null @@ -1,17 +0,0 @@ - -

        Loading heroes ...

        -

        Select a hero:

        - - - -
        -
        -

        Hero Detail

        -

        Editing: {{selectedHero.name}}

        - - - -
        diff --git a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts deleted file mode 100644 index 1243a0a5c0..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -// #docregion -import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { finalize } from 'rxjs/operators'; - -import { Hero } from '../data-model'; -import { HeroService } from '../hero.service'; - -@Component({ - selector: 'app-hero-list', - templateUrl: './hero-list.component.html', - styleUrls: ['./hero-list.component.css'] -}) -export class HeroListComponent implements OnInit { - heroes: Observable; - isLoading = false; - selectedHero: Hero; - - constructor(private heroService: HeroService) { } - - ngOnInit() { this.getHeroes(); } - - getHeroes() { - this.isLoading = true; - this.heroes = this.heroService.getHeroes() - // TODO: error handling - .pipe(finalize(() => this.isLoading = false)); - this.selectedHero = undefined; - } - - select(hero: Hero) { this.selectedHero = hero; } -} diff --git a/aio/content/examples/reactive-forms/src/app/hero.service.ts b/aio/content/examples/reactive-forms/src/app/hero.service.ts deleted file mode 100644 index ff1caf064a..0000000000 --- a/aio/content/examples/reactive-forms/src/app/hero.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -// #docregion -import { Injectable } from '@angular/core'; - -import { Observable, of } from 'rxjs'; -import { delay } from 'rxjs/operators'; - -import { Hero, heroes } from './data-model'; - -@Injectable() -export class HeroService { - - delayMs = 500; - - // Fake server get; assume nothing can go wrong - getHeroes(): Observable { - return of(heroes).pipe(delay(this.delayMs)); // simulate latency with delay - } - - // Fake server update; assume nothing can go wrong - updateHero(hero: Hero): Observable { - const oldHero = heroes.find(h => h.id === hero.id); - const newHero = Object.assign(oldHero, hero); // Demo: mutate cached hero - return of(newHero).pipe(delay(this.delayMs)); // simulate latency with delay - } -} diff --git a/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.css b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.css new file mode 100644 index 0000000000..c89ab4cc0b --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.css @@ -0,0 +1,19 @@ +:host { + display: flex; + flex-direction: column; + padding-top: 24px; +} + +label { + display: block; + width: 6em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} + +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} \ No newline at end of file diff --git a/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html new file mode 100644 index 0000000000..95f1948ffa --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html @@ -0,0 +1,21 @@ + + + + + + + +

        + Value: {{ name.value }} +

        + + + + +

        + +

        + diff --git a/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts new file mode 100644 index 0000000000..b549b560ea --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts @@ -0,0 +1,22 @@ +// #docplaster +// #docregion create-control +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-name-editor', + templateUrl: './name-editor.component.html', + styleUrls: ['./name-editor.component.css'] +}) +export class NameEditorComponent { + name = new FormControl(''); +// #enddocregion create-control + +// #docregion update-value + updateName() { + this.name.setValue('Nancy'); + } +// #enddocregion update-value +// #docregion create-control +} +// #enddocregion create-control diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.html b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.html new file mode 100644 index 0000000000..c1fd2a10d2 --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.html @@ -0,0 +1,67 @@ + + +
        + + + + + + + +
        +

        Address

        + + + + + + + + +
        + + + +
        +

        Aliases

        + +
        + + +
        +
        + + +
        + + +

        + Form Value: {{ profileForm.value | json }} +

        + + +

        + +

        + \ No newline at end of file diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.ts b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.ts new file mode 100644 index 0000000000..94d388847e --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.1.ts @@ -0,0 +1,40 @@ +// #docplaster +// #docregion formgroup, nested-formgroup +import { Component } from '@angular/core'; +// #docregion imports +import { FormGroup, FormControl } from '@angular/forms'; +// #enddocregion imports + +@Component({ + selector: 'app-profile-editor', + templateUrl: './profile-editor.component.html', + styleUrls: ['./profile-editor.component.css'] +}) +export class ProfileEditorComponent { +// #docregion formgroup-compare + profileForm = new FormGroup({ + firstName: new FormControl(''), + lastName: new FormControl(''), +// #enddocregion formgroup + address: new FormGroup({ + street: new FormControl(''), + city: new FormControl(''), + state: new FormControl(''), + zip: new FormControl('') + }) +// #docregion formgroup + }); +// #enddocregion formgroup, nested-formgroup, formgroup-compare +// #docregion patch-value + updateProfile() { + this.profileForm.patchValue({ + firstName: 'Nancy', + address: { + street: '123 Drew Street' + } + }); + } +// #enddocregion patch-value +// #docregion formgroup, nested-formgroup +} +// #enddocregion formgroup diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts new file mode 100644 index 0000000000..d482d70b01 --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts @@ -0,0 +1,58 @@ +// #docplaster +// #docregion form-builder +import { Component } from '@angular/core'; +// #docregion form-builder-imports +import { FormBuilder } from '@angular/forms'; +// #enddocregion form-builder-imports, form-builder +// #docregion form-array-imports +import { FormArray } from '@angular/forms'; +// #docregion form-builder-imports, form-builder +// #enddocregion form-builder-imports, form-array-imports + +@Component({ + selector: 'app-profile-editor', + templateUrl: './profile-editor.component.html', + styleUrls: ['./profile-editor.component.css'] +}) +export class ProfileEditorComponent { +// #docregion formgroup-compare + profileForm = this.fb.group({ + firstName: [''], + lastName: [''], + address: this.fb.group({ + street: [''], + city: [''], + state: [''], + zip: [''] + }), +// #enddocregion form-builder, formgroup-compare + aliases: this.fb.array([ + this.fb.control('') + ]) +// #docregion form-builder, formgroup-compare + }); +// #enddocregion form-builder, formgroup-compare + get aliases() { + return this.profileForm.get('aliases') as FormArray; + } + +// #docregion inject-form-builder, form-builder + + constructor(private fb: FormBuilder) { } +// #enddocregion inject-form-builder, form-builder + + updateProfile() { + this.profileForm.patchValue({ + firstName: 'Nancy', + address: { + street: '123 Drew Street' + } + }); + } + + addAlias() { + this.aliases.push(this.fb.control('')); + } +// #docregion form-builder +} +// #enddocregion form-builder diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.css b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.css new file mode 100644 index 0000000000..6d29164b1a --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.css @@ -0,0 +1,39 @@ +/* ProfileEditorComponent's private CSS styles */ +:host { + display: flex; + flex-direction: column; + padding-top: 24px; +} + +label { + display: block; + width: 6em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} + +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} + +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: #cfd8dc; +} + +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html new file mode 100644 index 0000000000..86178e1c3c --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.html @@ -0,0 +1,80 @@ + + +
        + + + + + +
        +

        Address

        + + + + + + + + +
        + + +
        +

        Aliases

        + +
        + + +
        +
        + + + + + +
        + +
        + + + +

        + Form Value: {{ profileForm.value | json }} +

        + + + + +

        + Form Status: {{ profileForm.status }} +

        + + + +

        + +

        + diff --git a/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.ts b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.ts new file mode 100644 index 0000000000..813c97d5c0 --- /dev/null +++ b/aio/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.ts @@ -0,0 +1,73 @@ +// #docplaster +// #docregion form-builder +import { Component } from '@angular/core'; +// #docregion form-builder-imports +import { FormBuilder } from '@angular/forms'; +// #enddocregion form-builder-imports +// #docregion validator-imports +import { Validators } from '@angular/forms'; +// #enddocregion validator-imports +// #docregion form-array-imports +import { FormArray } from '@angular/forms'; +// #enddocregion form-array-imports + +@Component({ + selector: 'app-profile-editor', + templateUrl: './profile-editor.component.html', + styleUrls: ['./profile-editor.component.css'] +}) +export class ProfileEditorComponent { +// #docregion required-validator, aliases + profileForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: [''], + address: this.fb.group({ + street: [''], + city: [''], + state: [''], + zip: [''] + }), +// #enddocregion form-builder, required-validator + aliases: this.fb.array([ + this.fb.control('') + ]) +// #docregion form-builder, required-validator + }); +// #enddocregion form-builder, required-validator, aliases +// #docregion aliases-getter + + get aliases() { + return this.profileForm.get('aliases') as FormArray; + } + +// #enddocregion aliases-getter +// #docregion inject-form-builder, form-builder + constructor(private fb: FormBuilder) { } + +// #enddocregion inject-form-builder + + updateProfile() { + this.profileForm.patchValue({ + firstName: 'Nancy', + address: { + street: '123 Drew Street' + } + }); + } +// #enddocregion form-builder +// #docregion add-alias + + addAlias() { + this.aliases.push(this.fb.control('')); + } +// #enddocregion add-alias +// #docregion on-submit + + onSubmit() { + // TODO: Use EventEmitter with form value + console.warn(this.profileForm.value); + } +// #enddocregion on-submit +// #docregion form-builder +} +// #enddocregion form-builder diff --git a/aio/content/examples/reactive-forms/src/index-final.html b/aio/content/examples/reactive-forms/src/index-final.html deleted file mode 100644 index 13416ffe40..0000000000 --- a/aio/content/examples/reactive-forms/src/index-final.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Hero Form - - - - - - - - - - - diff --git a/aio/content/examples/reactive-forms/src/index.html b/aio/content/examples/reactive-forms/src/index.html index 7121ad3455..4a6b8fdac0 100644 --- a/aio/content/examples/reactive-forms/src/index.html +++ b/aio/content/examples/reactive-forms/src/index.html @@ -2,10 +2,9 @@ - Hero Form + Angular Reactive Forms - diff --git a/aio/content/examples/reactive-forms/src/main-final.ts b/aio/content/examples/reactive-forms/src/main-final.ts deleted file mode 100644 index 0a1ea31596..0000000000 --- a/aio/content/examples/reactive-forms/src/main-final.ts +++ /dev/null @@ -1,13 +0,0 @@ -// tslint:disable:no-unused-variable -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -platformBrowserDynamic().bootstrapModule(AppModule); - diff --git a/aio/content/examples/reactive-forms/src/main.ts b/aio/content/examples/reactive-forms/src/main.ts index bd1865a45b..6c835d9116 100644 --- a/aio/content/examples/reactive-forms/src/main.ts +++ b/aio/content/examples/reactive-forms/src/main.ts @@ -2,12 +2,11 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; // just the final version -import { DemoModule } from './app/demo.module'; // demo picker +import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(DemoModule); +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/reactive-forms/src/styles.1.css b/aio/content/examples/reactive-forms/src/styles.1.css deleted file mode 100644 index 167a66be4f..0000000000 --- a/aio/content/examples/reactive-forms/src/styles.1.css +++ /dev/null @@ -1 +0,0 @@ -@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css'); diff --git a/aio/content/examples/reactive-forms/stackblitz.json b/aio/content/examples/reactive-forms/stackblitz.json index 2b325c037b..5696d23db7 100644 --- a/aio/content/examples/reactive-forms/stackblitz.json +++ b/aio/content/examples/reactive-forms/stackblitz.json @@ -3,6 +3,7 @@ "files":[ "!**/*.d.ts", "!**/*.js", + "!**/*.[0-9].*", "!src/app/app.component.1.ts", "!src/app/hero-list.component.1.html", diff --git a/aio/content/guide/reactive-forms.md b/aio/content/guide/reactive-forms.md index 34cb42ecca..8e7467fe07 100644 --- a/aio/content/guide/reactive-forms.md +++ b/aio/content/guide/reactive-forms.md @@ -1,261 +1,465 @@ -# Reactive Forms +# Reactive forms + +_Reactive forms_ provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a simple form control, progress to using multiple controls in a group, validate form values, and implement more advanced forms. -_Reactive forms_ is an Angular technique for creating forms in a _reactive_ style. -This guide explains reactive forms as you follow the steps to build a "Hero Detail Editor" form. {@a toc} -Try the Reactive Forms live-example. - -You can also run the Reactive Forms Demo version -and choose one of the intermediate steps from the "demo picker" at the top. - +Try the Reactive Forms live-example. {@a intro} +## Introduction to reactive forms -## Introduction to Reactive Forms +Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. Each change to the form state returns a new state, which maintains the integrity of the model between changes. Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, also while giving you synchronous access to the data. This approach allows your templates to take advantage of these streams of form state changes, rather than to be dependent to them. -Angular offers two form-building technologies: _reactive_ forms and _template-driven_ forms. -The two technologies belong to the `@angular/forms` library -and share a common set of form control classes. +Reactive forms also allow for easier testing because you have an assurance that your data is consistent and predictable when requested. Consumers outside your templates have access to the same streams, where they can manipulate that data safely. -But they diverge markedly in philosophy, programming style, and technique. -They even have their own modules: the `ReactiveFormsModule` and the `FormsModule`. +Reactive forms differ from template-driven forms in distinct ways. Reactive forms provide more predictability with synchronous access to the data model, immutability with observable operators, and change tracking through observable streams. If you prefer direct access to modify data in your template, template-driven forms are less explicit because they rely on directives embedded in the template, along with mutable data to track changes asynchronously. See the [Appendix](#appendix) for detailed comparisons between the two paradigms. -### Reactive forms -Angular _reactive_ forms facilitate a _reactive style_ of programming -that favors explicit management of the data flowing between -a non-UI _data model_ (typically retrieved from a server) and a -UI-oriented _form model_ that retains the states -and values of the HTML controls on screen. Reactive forms offer the ease -of using reactive patterns, testing, and validation. +## Getting started -With _reactive_ forms, you create a tree of Angular form control objects -in the component class and bind them to native form control elements in the -component template, using techniques described in this guide. +This section describes the key steps to add a single form control. The example allows a user to enter their name into an input field, captures that input value, and displays the current value of the form control element. -You create and manipulate form control objects directly in the -component class. As the component class has immediate access to both the data -model and the form control structure, you can push data model values into -the form controls and pull user-changed values back out. The component can -observe changes in form control state and react to those changes. +### Step 1 - Register the `ReactiveFormsModule` -One advantage of working with form control objects directly is that value and validity updates -are [always synchronous and under your control](guide/reactive-forms#async-vs-sync "Async vs sync"). -You won't encounter the timing issues that sometimes plague a template-driven form -and reactive forms can be easier to unit test. +To use reactive forms, import `ReactiveFormsModule` from the `@angular/forms` package and add it to your NgModule's `imports` array. -In keeping with the reactive paradigm, the component -preserves the immutability of the _data model_, -treating it as a pure source of original values. -Rather than update the data model directly, -the component extracts user changes and forwards them to an external component or service, -which does something with them (such as saving them) -and returns a new _data model_ to the component that reflects the updated model state. + -Using reactive form directives does not require you to follow all reactive priniciples, -but it does facilitate the reactive programming approach should you choose to use it. + -### Template-driven forms +### Step 2 - Import and create a new form control -_Template-driven_ forms, introduced in the [Template guide](guide/forms), take a completely different approach. - -You place HTML form controls (such as `` and `` element for the hero name. - -A `FormControl` constructor accepts three, optional arguments: -the initial data value, an array of validators, and an array of async validators. - - -
        - -This simple control doesn't have data or validators. -In real apps, most form controls have both. For in-depth information on -`Validators`, see the [Form Validation](guide/form-validation) guide. +*Note*: For a more detailed list of classes and directives provided by the `ReactiveFormsModule`, see the [Reactive Forms API](#reactive-forms-api) section.
        -{@a create-template} +Using the template binding syntax, the form control is now registered to the `name` input element in the template. The form control and DOM element communicate with each other: the view reflects changes in the model, and the model reflects changes in the view. -## Create the template +#### Display the component -Now update the component's template with the following markup. +The `FormControl` assigned to `name` is displayed once the component is added to a template. - + -To let Angular know that this is the input that you want to -associate to the `name` `FormControl` in the class, -you need `[formControl]="name"` in the template on the ``. +
        + Name Editor +
        -
        +## Managing control values -Disregard the `form-control` CSS class. It belongs to the -Bootstrap CSS library, -not Angular, and styles the form but in no way impacts the logic. +Reactive forms give you access to the form control state and value at a point in time. You can manipulate +the current state and value through the component class or the component template. The following examples display the value of a `FormControl` and change it. + +{@a display-value} + +### Display the control’s value + +Every `FormControl` provides its current value as an observable through the `valueChanges` property. You can listen to changes in the form’s value in the template using the `AsyncPipe` or in the component class using the `subscribe()` method. The `value` property also gives you a snapshot of the current value. + +Display the current value using interpolation in the template as shown in the following example. + + + + + +The displayed value changes as you update the form control element. + +Reactive forms also provide access to more information about a given control through properties and methods provided with each instance. These properties and methods of the underlying [AbstractControl](api/forms/AbstractControl) are used to control form state and determine when to display messages when handling validation. For more information, see [Simple Form Validation](#simple-form-validation) later in this guide. + +Read about other `FormControl` properties and methods in the [Reactive Forms API](#reactive-forms-api) section. + +### Replace the form control value + +Reactive forms have methods to change a control's value programmatically, which gives you the flexibility to update the control’s value without user interaction. The `FormControl` provides a `setValue()` method which updates the value of the form control and validates the structure of the value provided against the control’s structure. For example, when retrieving form data from a backend API or service, use the `setValue()` method to update the control to its new value, replacing the old value entirely. + +The following example adds a method to the component class to update the value of the control to _Nancy_ using the `setValue()` method. + + + + + +Update the template with a button to simulate a name update. Any value entered in the form control element before clicking the `Update Name` button will be reflected as its current value. + + + + + +Because the form model is the source of truth for the control, when you click the button the value of the input is also changed within the component class, overriding its current value. + +
        + Name Editor Update +
        + +
        + +*Note*: In this example, you are only using a single control, but when using the `setValue()` method with a `FormGroup` or `FormArray` the value needs to match the structure of the group or array.
        -{@a import} +## Grouping form controls -## Import the `ReactiveFormsModule` +Just as a `FormControl` instance gives you control over a single input field, a `FormGroup` tracks the form state of a group of `FormControl` instances (for example, a form). Each control in `FormGroup` is tracked by name when creating the `FormGroup`. The following example shows how to manage multiple `FormControl` instances in a single group. -The `HeroDetailComponent` template uses the `formControlName` -directive from the `ReactiveFormsModule`. +Generate a `ProfileEditor` component and import the `FormGroup` and `FormControl` classes from the `@angular/forms` package. -Do the following two things in `app.module.ts`: + -1. Use a JavaScript `import` statement to access -the `ReactiveFormsModule`. -1. Add `ReactiveFormsModule` to the `AppModule`'s `imports` list. - - + ng generate component ProfileEditor -{@a update} - -## Display the `HeroDetailComponent` -Revise the `AppComponent` template so it displays the `HeroDetailComponent`. - - + -{@a essentials} +### Step 1 - Create a `FormGroup` -## Essential form classes -This guide uses four fundamental classes to build a reactive form: +Create a property in the component class named `profileForm` and set the property to a new instance of a `FormGroup`. To initialize the `FormGroup`, provide the constructor with an object of controls with their respective names. +For the profile form, add two `FormControl` instances with the names `firstName` and `lastName`. + + + + + +The individual form controls are now collected within a group. The `FormGroup` provides its model value as an object reduced from the values of each control in the group. A `FormGroup` instance has the same properties (such as `value`, `untouched`) and methods (such as `setValue()`) as a `FormControl` instance. + +### Step 2 - Associate the `FormGroup` model and view + +The `FormGroup` also tracks the status and changes of each of its controls, so if one of the control’s status or value changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view. + + + + + +Note that just as the `FormGroup` contains a group of controls, the _profileForm_ `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in the `FormGroup`. The form controls communicate with their respective elements. The also communicate changes to the `FormGroup`, which provides the source of truth for the model value. + +### Save form data + +The `ProfileEditor` component takes input from the user, but in a real scenario you want to capture the form value for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. + +Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method. + + + + + +The `onSubmit()` method in the `ProfileEditor` component captures the current value of the `profileForm`. To keep the form encapsulated, to provide the form value outside the component, use an `EventEmitter`. The following example uses `console.warn` to log to the browser console. + + + + + +The `submit` event is emitted by the `form` tag using the native DOM event. You trigger the event by clicking a button with `submit` type. This allows the user to use the enter key to trigger submission after filling out the form. + +Add a `button` to the bottom of the form to trigger the form submission. + + + + + +
        + +*Note:* The button in the snippet above also has a `disabled` binding attached to it to disable the button when the `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Simple form validation is covered later in the [Form Validation](#simple-form-validation) section. + +
        + +#### Display the component + +The `ProileEditor` component that contains the form is displayed when added to a component template. + + + + + +The `ProfileEditor` allows you to manage the `FormControl` instances for the `firstName` and `lastName` controls within the `FormGroup`. + +
        + Profile Editor +
        + +## Nesting form groups + +When building complex forms, managing the different areas of information is easier in smaller sections, and some groups of information naturally fall into the same group. Using a nested `FormGroup` allows you to break large forms groups into smaller, more manageable ones. + +### Step 1 - Create a nested group + +An address is a good example of information that can be grouped together. A `FormGroup` can accept both `FormControl` and `FormGroup` instances as children. This makes composing complex form models easier to maintain and logically group together. To create a nested group in the `profileForm`, add a nested `address` `FormGroup`. + + + + + +In this example, the `address group` combines the current `firstName` and `lastName` controls with the new `street`, `city`, `state` and `zip` controls. Even though the `address` `FormGroup` is a child of the overall `profileForm` `FormGroup`, the same rules still apply with value and status changes. Changes in status and value from the nested form group will propagate up to the parent form group, maintaining consistency with the overall model. + +### Step 2 - Group the nested form in the template + +After you update the model in the component class, update the template to connect the `FormGroup` instance and its input elements. + +Add the `address` form group containing the `firstName` and `lastName` fields to the `ProfileEditor` template. + + + + + +The `ProfileEditor` form is displayed as one group, but the model is broken down further to represent the logical grouping areas. + +
        + Profile Editor Update +
        + +
        + +*Note*: Display the value for the `FormGroup` in the component template using the `value` property and the `JsonPipe`. + +
        + +## Partial model updates + +When updating the value for a `FormGroup` that contains multiple controls, you may only want to update parts of the model instead of replacing its entire value. This section covers how to update specific parts of an `AbstractControl` model. + +### Patch the model value + +With a single control, you used the `setValue()` method to set the new value for an individual control. The `setValue()` method is more strict about adhering to the structure of the `FormGroup` and replaces the entire value for the control. The `patchValue()` method is more forgiving; it only replaces properties defined in the object that have changed in the form model, because you’re only providing partial updates. The strict checks in `setValue()` help catch errors in the nesting of complex forms, while `patchValue()` will fail silently in those cases. + +In the `ProfileEditorComponent`, the `updateProfile` method with the following example below to update the first name and street address for the user. + + + + + +Simulate an update by adding a button to the template to update the user profile on demand. + + + + + +When the button is clicked, the `profileForm` model is updated with just the `firstName` and `street` being modified. Notice that the `street` is provided in an object inside the `address` property. This is necessary because the `patchValue()` method applies the update against the model structure. `PatchValue()` only updates properties that the form model defines. + +## Generating form controls with `FormBuilder` + +Creating multiple form control instances manually can become very repetitive when dealing with multiple forms. The `FormBuilder` service provides convenience methods to handle generating controls. Underneath, the `FormBuilder` is creating and returning the instances in the same manner, but with much less work. The following section refactors the `ProfileEditor` component to use the `FormBuilder` instead of creating each `FormControl` and `FormGroup` by hand. + +### Step 1 - Import the `FormBuilder` class + +To use the `FormBuilder` service, import its class from the `@angular/forms` package. + + + + + +### Step 2 - Inject the `FormBuilder` service + +The FormBuilder is an injectable service that is provided with the `ReactiveFormsModule`. Inject this dependency by adding it to the component constructor. + + + + + +### Step 3 - Generate form controls + +The `FormBuilder` service has three methods: `control()`, `group()`, and `array()`. These methods are factory methods for generating form controls in your component class including a `FormControl`, `FormGroup`, and `FormArray` respectively. + +Replace the creation of the `profileForm` by using the `group` method to create the controls. + + + + + +In the example above, you use the `group()` method with the same names to define the properties in the model. Here, the value for each control name is an array containing the initial value as the first item. + +
        + +*Note*: You can define the control with just the initial value, but if your controls need sync or async validation, add sync and async validators as the second and third items in the array. + +
        + +Compare the two paths to achieve the same result. + + + + + + + + + + + + + +## Simple form validation + +Form validation is necessary when receiving user input through forms. This section covers adding a single validator to a form control and displaying the overall form status. Form validation is covered more extensively in the [Form Validation](guide/form-validation) guide. + +### Step 1 - Import a validator function + +Reactive forms include a set of validator functions out of the box for common use cases. These functions receive a control to validate against and return an error object or null based on the validation check. + +Import the `Validators` class from the `@angular/forms` package. + + + + + +### Step 2 - Make a field required + +The most common validation is making a field required. This section describes how to add a required validation to the `firstName` control. + +In the `ProfileEditor` component, add the `Validators.required` static method as the second item in the array for the `firstName` control. + + + + + +HTML5 has a set of built-in attributes that can be used for native validation, including `required`, `minlength`, `maxlength`, and more. Although _optional_, you can take advantage of these as additional attributes on your form input elements. Add the `required` attribute to the `firstName` input element. + + + + + +
        + +*Note:* These HTML5 validation attributes should be used _in combination with_ the built-in validators provided by Angular's reactive forms. Using these two validation practices in combination prevents errors about the expression being changed after the template has been checked. + +
        + +### Display form status + +Now that you’ve added a required field to the form control, its initial status is invalid. This invalid status propagates to the parent `FormGroup`, making its status invalid. You have access to the current status of the `FormGroup` through the `status` property on the instance. + +Display the current status of the `profileForm` using interpolation. + + + + + +
        + Profile Editor Validation +
        + +The submit button is disabled because the `profileForm` is invalid due to the required `firstName` form control. After you fill out the `firstName` input, the form becomes valid and the submit button is enabled. + +For more on form validation, visit the [Form Validation](guide/form-validation) guide. + +## Dynamic controls using form arrays + +A `FormArray` is an alternative to a `FormGroup` for managing any number of unnamed controls. As with `FormGroup` instances, you can dynamically insert and remove controls from a `FormArray`, and the `FormArray` instance's value and validation status is calculated from its child controls. However, you don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance. The following example shows you how to manage an array of _aliases_ in the `ProfileEditor`. + +### Step 1 - Import the `FormArray` + +Import the `FormArray` class from `@angular/forms` to use for type information. The `FormBuilder` service is ready to create a `FormArray` instance. + + + + + +### Step 2 - Define a `FormArray` + +You can initialize a `FormArray` with any number of controls, from zero to many, by defining them in an array. Add an `aliases` property to the `FormGroup` for the `profileForm` to define the `FormArray`. + +Use the `FormBuilder.array()` method to define the array, and the `FormBuilder.control()` method to populate the array with an initial control. + + + + + +The _aliases_ control in the `FormGroup` is now populated with a single control until more are added dynamically. + +### Step 3 - Access the `FormArray` control + +Because a `FormArray` represents an undefined number of controls in array, accessing the control through a getter provides convenience and reusability. Use the _getter_ syntax to create an _aliases_ class property to retrieve the alias's `FormArray` control from the parent `FormGroup`. + + + + + +The getter provides easy access to the aliases `FormArray` instead of repeating the `profileForm.get()` method to get the instance. + +
        + +*Note*: Because the returned control is of type `AbstractControl`, you provide an explicit type to access the `FormArray` specific syntax for the methods. + +
        + +Define a method to dynamically insert an alias control into the alias's `FormArray`. The `FormArray.push()` method inserts the control as a new item in the array. + + + + + +In the template, the controls are iterated over to display each control as a separate input field. + +### Step 4 - Display the form array in the template + +After you define the aliases `FormArray` in your model, you must add it to the template for user input. Similar to the `formGroupName` input provided by the `FormGroupNameDirective`, a `formArrayName` binds communication from the `FormArray` to the template with the `FormArrayNameDirective`. + +Add the template HTML below after the closing `formGroupName` `
        ` element. + + + + + +The `*ngFor` directive iterates over each `FormControl` provided by the aliases `FormArray`. Because `FormArray` elements are unnamed, you assign the _index_ to the `i` variable and pass it to each control to bind it to the `formControlName` input. + +
        + Profile Editor Aliases +
        + +Each time a new `alias` is added, the `FormArray` is provided its control based on the index. This allows you to track each individual control when calculating the status and value of the root control. + +#### Add an Alias + +Initially, the form only contains one `Alias` field. Click the `Add Alias` button, and another field appears. You can also validate the array of aliases reported by the form model displayed by the `Form Value` at the bottom of the template. + +
        + +*Note*: Instead of a `FormControl` for each alias, you could compose another `FormGroup` with additional fields. The process of defining a control for each item is the same. + +
        + +{@a appendix} + +## Appendix + +{@a reactive-forms-api} + +### Reactive forms API + +Listed below are the base classes and services used to create and manage form controls. + +#### Classes @@ -279,9 +483,7 @@ This guide uses four fundamental classes to build a reactive form: @@ -295,9 +497,7 @@ It provides their common behaviors and properties. @@ -311,10 +511,7 @@ It corresponds to an HTML form control such as an `` or ` @@ -328,356 +525,38 @@ The top-level form in your component is a `FormGroup`. + + + + + + + +
        - [`AbstractControl`](api/forms/AbstractControl "API Reference: FormControl") is the abstract base class for the three concrete form control classes; -`FormControl`, `FormGroup`, and `FormArray`. -It provides their common behaviors and properties. + The abstract base class for the three concrete form control classes; `FormControl`, `FormGroup`, and `FormArray`. It provides their common behaviors and properties. - [`FormControl`](api/forms/FormControl "API Reference: FormControl") -tracks the value and validity status of an individual form control. -It corresponds to an HTML form control such as an `` or `` or ` - [`FormGroup`](api/forms/FormGroup "API Reference: FormGroup") -tracks the value and validity state of a group of `AbstractControl` instances. -The group's properties include its child controls. -The top-level form in your component is a `FormGroup`. + Manages the value and validity state of a group of `AbstractControl` instances. The group's properties include its child controls. The top-level form in your component is a `FormGroup`. - [`FormArray`](api/forms/FormArray "API Reference: FormArray") -tracks the value and validity state of a numerically indexed array of `AbstractControl` instances. + Manages the value and validity state of a numerically indexed array of `AbstractControl` instances.
        + FormBuilder + + + An injectable service that provides factory methods for creating control instances. + +
        +When importing the `ReactiveFormsModule`, you also gain access to directives to use in your templates for binding the data model to the forms declaratively. -## Style the app +#### Directives -To use the bootstrap CSS classes that are in the template HTML of both the `AppComponent` and the `HeroDetailComponent`, -add the `bootstrap` CSS stylesheet to the head of `styles.css`: - - - - - - -Now that everything is wired up, serve the app with: - - - - ng serve - - - -The browser should display something like this: - - -
        - Single FormControl -
        - -{@a formgroup} - -## Add a FormGroup -Usually, if you have multiple `FormControls`, you register -them within a parent `FormGroup`. -To add a `FormGroup`, add it to the imports section -of `hero-detail.component.ts`: - - - - - -In the class, wrap the `FormControl` in a `FormGroup` called `heroForm` as follows: - - - - - -Now that you've made changes in the class, they need to be reflected in the -template. Update `hero-detail.component.html` by replacing it with the following. - - - - - -Notice that now the single `` is in a `
        ` element. - -`formGroup` is a reactive form directive that takes an existing -`FormGroup` instance and associates it with an HTML element. -In this case, it associates the `FormGroup` you saved as -`heroForm` with the `` element. - -Because the class now has a `FormGroup`, you must update the template -syntax for associating the `` with the corresponding -`FormControl` in the component class. -Without a parent `FormGroup`, -`[formControl]="name"` worked earlier because that directive -can stand alone, that is, it works without being in a `FormGroup`. -With a parent `FormGroup`, the `name` `` needs the syntax -`formControlName=name` in order to be associated -with the correct `FormControl` -in the class. This syntax tells Angular to look for the parent -`FormGroup`, in this case `heroForm`, and then _inside_ that group -to look for a `FormControl` called `name`. - - -{@a json} - -## Taking a look at the form model - -When the user enters data into an ``, the value -goes into the **_form model_**. -To see the form model, add the following line after the -closing `` tag in the `hero-detail.component.html`: - - - - - - - -The `heroForm.value` returns the _form model_. -Piping it through the `JsonPipe` renders the model as JSON in the browser: - - -
        - JSON output -
        - -The initial `name` property value is the empty string. -Type into the name `` and watch the keystrokes appear in the JSON. - -In real life apps, forms get big fast. -`FormBuilder` makes form development and maintenance easier. - - -{@a formbuilder} - -## Introduction to `FormBuilder` - -The `FormBuilder` class helps reduce repetition and -clutter by handling details of control creation for you. - -To use `FormBuilder`, import it into `hero-detail.component.ts`. You can remove `FormControl`: - - - - - -Use it to refactor the `HeroDetailComponent` into something that's easier to read and write, -by following this plan: - -* Explicitly declare the type of the `heroForm` property to be `FormGroup`; you'll initialize it later. -* Inject a `FormBuilder` into the constructor. -* Add a new method that uses the `FormBuilder` to define the `heroForm`; call it `createForm()`. -* Call `createForm()` in the constructor. - -The revised `HeroDetailComponent` looks like this: - - - - - - - -`FormBuilder.group` is a factory method that creates a `FormGroup`.   -`FormBuilder.group` takes an object whose keys and values are `FormControl` names and their definitions. -In this example, the `name` control is defined by its initial data value, an empty string. - -Defining a group of controls in a single object makes your code more compact and readable because you don't have to write repeated `new FormControl(...)` statements. - -{@a validators} - -### `Validators.required` -Though this guide doesn't go deeply into validations, here is one example that -demonstrates the simplicity of using `Validators.required` in reactive forms. - -First, import the `Validators` symbol. - - - - - - - -To make the `name` `FormControl` required, replace the `name` -property in the `FormGroup` with an array. -The first item is the initial value for `name`; -the second is the required validator, `Validators.required`. - - - - - - - - -
        - -Reactive validators are simple, composable functions. -Configuring validation is different in template-driven forms in that you must wrap validators in a directive. - -
        - -Update the diagnostic message at the bottom of the template to display the form's validity status. - - - - - -The browser displays the following: - -
        - Single FormControl -
        - -`Validators.required` is working. The status is `INVALID` because the `` has no value. -Type into the `` to see the status change from `INVALID` to `VALID`. - -In a real app, you'd replace the diagnosic message with a user-friendly experience. - -Using `Validators.required` is optional for the rest of the guide. -It remains in each of the following examples with the same configuration. - -For more on validating Angular forms, see the -[Form Validation](guide/form-validation) guide. - -### More `FormControl`s - -This section adds additional `FormControl`s for the address, a super power, and a sidekick. - -Additionally, the address has a state property. The user will select a state with a `` elements, a ``. - -To make this change visually obvious, add an `

        ` header near the top with the text, _Secret Lair_. -The new address HTML looks like this: - - - - - - - -After these changes, the JSON output in the browser shows the revised form model -with the nested address `FormGroup`: - -
        - JSON output -
        - -This shows that the template -and the form model are talking to one another. - -{@a properties} - -## Inspect `FormControl` Properties - -You can inspect an individual `FormControl` within a form by extracting it with the `get()` method. -You can do this within the component class or display it on the -page by adding the following to the template, -immediately after the `{{form.value | json}}` interpolation as follows: - - - - - -To get the state of a `FormControl` that’s inside a `FormGroup`, use dot notation to traverse to the control. - - - - - -
        - -*Note*: If you're coding along, remember to remove this reference to `address.street` when you get to the section on `FormArray`. In that section, you change the name of address in the component class and it will throw an error if you leave it in the template. - -
        - -You can use this technique to display any property of a `FormControl` -such as one of the following: - - - - - - - - - - - - - - +
        - - - - + + + + + + + + @@ -748,638 +637,46 @@ such as one of the following:
        - Property + Directive @@ -688,59 +567,69 @@ such as one of the following:
        - myControl.value + + FormControlDirective + Syncs a standalone `FormControl` instance to a form control element. - the value of a `FormControl`.
        - myControl.status + + FormControlName + Syncs a `FormControl` in an existing `FormGroup` to a form control element by name. - the validity of a `FormControl`. Possible values: `VALID`, - `INVALID`, `PENDING`, or `DISABLED`.
        - myControl.pristine + + FormGroupDirective + Syncs an existing `FormGroup` to a DOM element. - `true` if the user has _not_ changed the value in the UI. - Its opposite is `myControl.dirty`.
        - myControl.untouched + + FormGroupName + Syncs a nested `FormGroup` to a DOM element. - `true` if the control user has not yet entered the HTML control - and triggered its blur event. Its opposite is `myControl.touched`. +
        + FormArrayName + + + Syncs a nested `FormArray` to a DOM element.
        +### Comparison to template-driven forms +_Template-driven_ forms, introduced in the [Template-driven forms guide](guide/forms), take a completely different approach. -Read about other `FormControl` properties in the -[_AbstractControl_](api/forms/AbstractControl) API reference. +* You place HTML form controls (such as `` and ``. -You should see a new name in the log after each keystroke. - -### When to use it - -An interpolation binding is the easier way to display a name change. -Subscribing to an observable `FormControl` property is handy for triggering -application logic within the component class. - -{@a save} - -## Save form data - -The `HeroDetailComponent` captures user input but it doesn't do anything with it. -In a real app, you'd probably save those hero changes, revert unsaved changes, and resume editing. -After you implement both features in this section, the form will look like this: - - -
        - Form with save & revert buttons -
        - - - -### Save -When the user submits the form, -the `HeroDetailComponent` will pass an instance of the hero _data model_ -to a save method on the injected `HeroService`. Add the following to `HeroDetailComponent`. - - - - - - - -This original `hero` had the pre-save values. The user's changes are still in the _form model_. -So you create a new `hero` from a combination of original hero values (the `hero.id`) -and deep copies of the changed form model values, using the `prepareSaveHero()` helper. - - - - - - -Make sure to import `HeroService` and add it to the constructor: - - - - - - - - - -
        - -**Address deep copy** - -Had you assigned the `formModel.secretLairs` to `saveHero.addresses` (see line commented out), -the addresses in `saveHero.addresses` array would be the same objects -as the lairs in the `formModel.secretLairs`. -A user's subsequent changes to a lair street would mutate an address street in the `saveHero`. - -The `prepareSaveHero` method makes copies of the form model's `secretLairs` objects so that can't happen. - - -
        - - - -### Revert (cancel changes) -The user cancels changes and reverts the form to the original state by pressing the Revert button. - -Reverting is easy. Simply re-execute the `rebuildForm()` method that built the form model from the original, unchanged `hero` data model. - - - - - - - -### Buttons -Add the "Save" and "Revert" buttons near the top of the component's template: - - - - - - - -The buttons are disabled until the user "dirties" the form by changing a value in any of its form controls (`heroForm.dirty`). - -Clicking a button of type `"submit"` triggers the `ngSubmit` event which calls the component's `onSubmit` method. -Clicking the revert button triggers a call to the component's `revert` method. -Users now can save or revert changes. - -Try the . - -{@a source-code} - - -The key files of the final version are as follows: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -You can download the complete source for all steps in this guide -from the Reactive Forms Demo live example. diff --git a/aio/content/images/guide/reactive-forms/address-group.png b/aio/content/images/guide/reactive-forms/address-group.png deleted file mode 100644 index ee1587c1072fd6793cdce709c9c73413b6654837..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6022 zcmZ{oWmH>Dx5pEpKyfGpONzU@yCxJZPD@kV-JuXD6lyn|9tzn^Y-XZl+Q*#Axw1d@9)L2Jh_$H(c`YiPPE^9 z9Abz3`RcBRR?i(RNeTb6&8pXJxiW#XwR1D&M)OAt-yO{wo!pn`Y_1afHRt(Ww_2eF z;^~O14snRUSLn6ynYY_b8Y&4xvx+s+juxA$v(a-vb;B?peGb(n<$!LmW=JiKo@N1&~JeZLo*=Jjc{0&gz%vcy~kdyU?_Q;DSHeYI<2QI=0ah~+Kj zd=jb@NmP7)xe{{hI$*d5mjTN5xjMMI2{uLnaUB>s^;y|IA^`#!zsf{C9F9iRsYW2DTeX7oZh&^1Im8 zmAF0~pwmw0fBX1wA54Z_t=7P#7WWY>k+||AQy9ABfJ*+AsBFB$1CfyB;GwvXLGdFa^6qtnl0{LC^6j|{jA`Q9*-Ap^M`b#*zM&d zLU)#})Y##*$5*YKR*$Wr?~ZelyNym}p)V$QYHp#c8y%OZ+r#=XFrR6|?y~R7lLWt1 z0WT+F4AqZR1@|ARg=BAbncRnZ?_y@;mBgN9F|yiJ$1UX7vS_4ozv+|)3HXUk^|%wH zyPs_ielEA9C6Rk~b8&sX^OAJKbP}tR#JJIkTFabh{SCl4Z0N;m_V>(Rn?)4)-q(cB zOjEaZn^uDBU9QX)xDT0ZpEv2Oq1?@G@2skngRkdelCgEZZYWMqt4s8B94)s+j(MoM zoh1Sc9S0f}yabHmu_Hiy=6BsVbPYM=5--2Ls0|CPob}#IlXZ}+Z84Q$^?XZqUb*iu zj1YFKOGkKmzAIp-k5K5hw_L>G0-eUjs|_^Mt6h?c?Aewdsv zmj)Yw9vl6>VA(w(4R;A*%ALy0#dRIE2qVy%eEzUPBNd_TKss{e)owFNQ{K#sKL%CF zvSTmrs|nKC)aWgWeAqH_8W?D`HeQc26BI}muw1J#_$2pmWf{!aEW&*>r=LkJkwX1_ z4?DpoJf1`c)Zc8ic9+QQh(w>zNah|qcn1AJJO&$ox8HQQ5gWs93YU=u=5xNYoCcU3 zv|uTR%p_S!?Rdge?X+UX)S)`9GaIoM@kzj+fXlSMSaA~c)7j_8L73RB*@=Qd$ck)G^|tKW~w`*Sd-W+i&{chS{TPufd3U1y>* zqn1pW(U}BVoQ%4f%_fMAROT*WAZp1Tph#s`3p{rKQ|QJ{RQ!GwN?t|=feuV)7683- zBs}|}PX%!d`0T(%>a*jZ{f1We4b_|J5p6GP;7YA!WOrK&PFq$EMlB5V!omRe?IN`w z)qAqi&g1Vabm-a_dPWnT^QA|x42^%#^QsxF(nx+I_t~=Ic#5NWM!di z?gXmWXTy9sn+)r36nEOqLH>s-JkI`fUUr7x$z8C_BRi%m8F|8V=tGj4w04Q@m)(nLNt;)$>%|2p9Zd%kO)%0A{`mYk!tSd$I*32Y!iM1Ngp!7x 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 A`Tzg` diff --git a/aio/content/images/guide/reactive-forms/addresses-array.png b/aio/content/images/guide/reactive-forms/addresses-array.png deleted file mode 100644 index b86733613e27001c1ce38e2ed3df2599d4828e3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4692 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 diff --git a/aio/content/images/guide/reactive-forms/hero-list.png b/aio/content/images/guide/reactive-forms/hero-list.png deleted file mode 100644 index 151eee981f5bc32dfa1a22a5f16a667fb94b8bb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5950 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 literal 0 HcmV?d00001 diff --git a/aio/content/images/guide/reactive-forms/name-editor-2.png b/aio/content/images/guide/reactive-forms/name-editor-2.png new file mode 100644 index 0000000000000000000000000000000000000000..e94e4a1961e00196d99c308629b1b92ff4639823 GIT binary patch literal 22715 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%0DR~tyhIMpoKSb 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 b8|0c;mV57X=z*Peg} literal 0 HcmV?d00001 diff --git a/aio/content/images/guide/reactive-forms/profile-editor-2.png b/aio/content/images/guide/reactive-forms/profile-editor-2.png new file mode 100644 index 0000000000000000000000000000000000000000..06396d10decfdc7141925bd6eff701f3f2bda727 GIT binary patch literal 17538 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

        fe0rAHZa|%jWiDk8~qn9rvDBQ^Hu&g znW-gh|MlO0Ky+e)e2V`hWGKjcAr^^bGF<3^QOY{p_N(2GKp6%fTk&rRh0nQuVV7$g z%3dEf@qoPg&^Jrx^S$sV%`Q0q38|R{W#;cj7$MMp=+oT4Hy4-bz> z&KCwjaPR^=VJLpOVnzJNQHfw$eY)gs7vgP|16u7IyZ=>Q1{vO4YqRygnhg{x%lMyWGx@98>@VzB{%JOA@M^n1e=Gf0AP+KpU+G@UW6Sx! z+r#o*N;6Pj(Z7UyDc|DxM)OV6>7NJ0{4QnPduQ%%LZ2JS?_j@6r3W>~Un$)U{auQ6 j_qEyIgxxMA*Iz(bT@Fk`j=Wg^egDLSqy;Pab$$N_7#v*= literal 0 HcmV?d00001 diff --git a/aio/content/images/guide/reactive-forms/profile-editor-4.png b/aio/content/images/guide/reactive-forms/profile-editor-4.png new file mode 100644 index 0000000000000000000000000000000000000000..9de55211469585e908be75df19123d88ed76fe9a GIT binary patch literal 42477 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 function createTemplate() { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_A': 1, 'START_B': 2, 'START_REMOVE_ME': 3, 'START_C': 4}]); + } + elementStart(0, 'div'); { // Start of translated section 1 // - i18n sections do not contain any text() instruction elementStart(1, 'a'); // START_A { - elementStart(2, 'b'); // START_B - elementEnd(); - elementStart(3, 'remove-me'); // START_REMOVE_ME - elementEnd(); + element(2, 'b'); // START_B + element(3, 'remove-me'); // START_REMOVE_ME } elementEnd(); - elementStart(4, 'c'); // START_C - elementEnd(); - } // End of translated section 1 + element(4, 'c'); // START_C + } // End of translated section 1 elementEnd(); i18nApply(1, i18n_1[0]); } @@ -65,7 +66,7 @@ describe('Runtime i18n', () => { it('should support expressions', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 1, EXP_2: 2}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -86,6 +87,10 @@ describe('Runtime i18n', () => { //

        `D^U{nN3^vc^4R+ zk5q33HjzPI-KER^WCE`vAU~N7W(=91RH#hu5^ooZCru%MNLaqx;wzuD8zPRbk2ixX zPGYH))LhQ)0WW#YcD^?JE=G>5EX8iR!ou@Q=x9hI?E-_kyYq}B9>i1jG)1s63^AbQdk2ra1(Tt~P zi0=*7f`3|Rm{h5tvlo~u4J{^h-_84LG%yQa6C1;poqTb#i$fCf@VzpcnFhGfmA;H2 z4QanG0Yu+EbRWJRSawtb+$oQG38bJ|IlBqtpBQNaD4+~213;3I00zY9ez-e+ngG^6 zN`?cAcO;GdzGDwBv>Py66tAWWtolDK8-23-{MkSMx__sF@0~AfWefP5YdCa< z9cv!vcow7AM;9zMk31n38-X16C({S7`BcpGV2sf?%r59*MTG&U_j`3a91y?D-}h65 z%x#{cqkr=Rz~APBfJ@T4|J<4r)A;ohr0Yh0;T_N1u_F2=>c7^?mZ<%x^zAO+KuhR5 zStfW99#0GIQDU0=l@zhxTLf)&|znhpNi)nWtr4k0w!g~l#j9+gVu z)9G6RF>~`Xy_w94`AO6JF}P2yqQhbB!{F8`Y7C6KXWegT0Z(ZEXa8^loZf|1hy$E7 zlg8lMNS5*_eC)0R!+A#%Z2%MchI7oR75q>9g`9wxTYMUW%OBw~`ZIPTQrxn_^LOXp bi&1C+&+T$ENKPjH^)Q!{Qj#o(82bMQTXCWe diff --git a/aio/content/images/guide/reactive-forms/validators-json-output.png b/aio/content/images/guide/reactive-forms/validators-json-output.png deleted file mode 100644 index aca7c0450d14c4f9cca8550146102b7b3df057f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24228 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 B7E=HK From f9669e50ff08cd39f0826f6e608b091996f03031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Thu, 19 Jul 2018 13:17:30 -0700 Subject: [PATCH 493/582] release: cut the v6.1.0-rc.3 release Note that RC1 and RC2 glitched out midway during the npm release. Therefore there is only one commit --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ package.json | 4 ++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df46098787..23f330f65f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ + +# [6.1.0-rc.3](https://github.com/angular/angular/compare/6.1.0-rc.2...6.1.0-rc.3) (2018-07-19) + + +### Bug Fixes + +* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384) +* **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([0b4d85e](https://github.com/angular/angular/commit/0b4d85e)), closes [#24831](https://github.com/angular/angular/issues/24831) +* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155) +* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609) +* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f)) +* **compiler:** i18n_extractor now outputs the correct source file name ([#24885](https://github.com/angular/angular/issues/24885)) ([c8ad965](https://github.com/angular/angular/commit/c8ad965)), closes [#24884](https://github.com/angular/angular/issues/24884) +* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3)) +* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261)) +* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939) +* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420) +* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea)) +* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d)) +* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc)) +* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468) +* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9)) + + +### Features + +* **bazel:** Initial commit of protractor_web_test_suite ([#24787](https://github.com/angular/angular/issues/24787)) ([71e0df0](https://github.com/angular/angular/commit/71e0df0)) +* **bazel:** protractor_web_test_suite for release ([#24787](https://github.com/angular/angular/issues/24787)) ([161ff5c](https://github.com/angular/angular/commit/161ff5c)) +* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977)) +* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6)) +* **router:** add urlUpdateStrategy allow updating the browser URL at the beginning of navigation ([#24820](https://github.com/angular/angular/issues/24820)) ([328971f](https://github.com/angular/angular/commit/328971f)), closes [#24616](https://github.com/angular/angular/issues/24616) +* **service-worker:** add support for `?` in SW config globbing ([#24105](https://github.com/angular/angular/issues/24105)) ([250527c](https://github.com/angular/angular/commit/250527c)) + + +Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC.3 instead. Sorry! :-) + + ## [6.0.9](https://github.com/angular/angular/compare/6.0.8...6.0.9) (2018-07-11) diff --git a/package.json b/package.json index e9d9072de4..ec0c270098 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.1.0-rc.0", + "version": "6.1.0-rc.3", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", @@ -124,4 +124,4 @@ "xhr2": "0.1.4", "yargs": "9.0.1" } -} \ No newline at end of file +} From 2aab1c9dd6b8104614199fa2189542ef9b96e871 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 20 Jul 2018 10:17:05 -0700 Subject: [PATCH 494/582] ci: remove Tina from pullaprove (#25006) She has been removed as a collaborator to the project and pullaprove rejects this config file which still lists her name. PR Close #25006 --- .pullapprove.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pullapprove.yml b/.pullapprove.yml index 9ca33a0c8d..1ac9acfb6b 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -23,7 +23,6 @@ # petebacondarwin - Pete Bacon Darwin # pkozlowski-opensource - Pawel Kozlowski # robwormald - Rob Wormald -# tinayuangao - Tina Gao # vicb - Victor Berchet # vikerman - Vikram Subramanian @@ -218,7 +217,6 @@ groups: - "aio/content/examples/reactive-forms/*" users: - kara #primary - - tinayuangao #secondary - IgorMinar #fallback - mhevery #fallback From 5840a86f98da200ce657573c2ad6c1c7ae1fc990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Jamet?= Date: Tue, 29 May 2018 15:03:48 +0200 Subject: [PATCH 495/582] docs: Add notes on manual sanitization to security guide (#24176) Some users have remarked that we don't explain how to manually call sanitization, so add a few lines on that. PR Close #24176 --- aio/content/guide/security.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/aio/content/guide/security.md b/aio/content/guide/security.md index aa0dde3293..27bda4d597 100644 --- a/aio/content/guide/security.md +++ b/aio/content/guide/security.md @@ -127,13 +127,19 @@ tag but keeps safe content such as the text content of the ` - + + \ No newline at end of file diff --git a/modules/benchmarks/src/largetable/render3/index.ts b/modules/benchmarks/src/largetable/render3/index.ts index e8eb07fceb..8b42c36d96 100644 --- a/modules/benchmarks/src/largetable/render3/index.ts +++ b/modules/benchmarks/src/largetable/render3/index.ts @@ -26,3 +26,5 @@ export function main() { profile(() => createDom(component), () => destroyDom(component), 'create')); } } + +main(); diff --git a/modules/benchmarks/src/largetable/render3/protractor.on-prepare.js b/modules/benchmarks/src/largetable/render3/protractor.on-prepare.js new file mode 100644 index 0000000000..6671d2d233 --- /dev/null +++ b/modules/benchmarks/src/largetable/render3/protractor.on-prepare.js @@ -0,0 +1,21 @@ +/** + * @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 + */ + +const protractorUtils = require('@angular/bazel/protractor-utils'); +const protractor = require('protractor'); + +module.exports = function(config) { + return protractorUtils.runServer(config.workspace, config.server, '-port', []) + .then(serverSpec => { + const serverUrl = `http://localhost:${serverSpec.port}`; + // Since the browser restarts in this benchmark we need to set both the browser.baseUrl + // for the first test and the protractor config.baseUrl for the subsequent tests + protractor.browser.baseUrl = serverUrl; + return protractor.browser.getProcessedConfig().then((config) => config.baseUrl = serverUrl); + }); +}; diff --git a/modules/benchmarks/src/largetable/render3/tsconfig.json b/modules/benchmarks/src/largetable/render3/tsconfig.json new file mode 100644 index 0000000000..8f3c656796 --- /dev/null +++ b/modules/benchmarks/src/largetable/render3/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": ["es2015"] + } +} diff --git a/modules/e2e_util/BUILD.bazel b/modules/e2e_util/BUILD.bazel new file mode 100644 index 0000000000..a70647c870 --- /dev/null +++ b/modules/e2e_util/BUILD.bazel @@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "lib", + testonly = 1, + srcs = glob(["*.ts"]), + deps = [ + "//packages:types", + "//packages/benchpress", + ], +) diff --git a/modules/e2e_util/e2e_util.ts b/modules/e2e_util/e2e_util.ts index 1defd9b5df..d0e9c1e8fd 100644 --- a/modules/e2e_util/e2e_util.ts +++ b/modules/e2e_util/e2e_util.ts @@ -20,8 +20,10 @@ export function readCommandLine(extraOptions?: {[key: string]: any}) { const options: {[key: string]: any} = { 'bundles': {describe: 'Whether to use the angular bundles or not.', default: false} }; - for (const key in extraOptions) { - options[key] = extraOptions[key]; + if (extraOptions) { + for (const key in extraOptions) { + options[key] = extraOptions[key]; + } } cmdArgs = yargs.usage('Angular e2e test options.').options(options).help('ng-help').wrap(40).argv; diff --git a/packages/benchpress/BUILD.bazel b/packages/benchpress/BUILD.bazel new file mode 100644 index 0000000000..243f8db349 --- /dev/null +++ b/packages/benchpress/BUILD.bazel @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "benchpress", + srcs = glob( + [ + "*.ts", + "src/**/*.ts", + ], + ), + module_name = "@angular/benchpress", + deps = [ + "//packages:types", + "//packages/core", + ], +) diff --git a/protractor-perf.conf.js b/protractor-perf.conf.js index a2a618be42..d5e937bb09 100644 --- a/protractor-perf.conf.js +++ b/protractor-perf.conf.js @@ -6,9 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ +// Determine if we run under bazel +const isBazel = !!process.env.RUNFILES; + // Make sure that the command line is read as the first thing // as this could exit node if the help script should be printed. -require('./dist/all/e2e_util/perf_util').readCommandLine(); +const BASE = isBazel ? 'angular/modules' : 'dist/all'; +require(`./${BASE}/e2e_util/perf_util`).readCommandLine(); var CHROME_OPTIONS = { 'args': ['--js-flags=--expose-gc', '--no-sandbox'], @@ -38,14 +42,19 @@ var BROWSER_CAPS = { } }; -exports.config = { +function mergeInto(src, target) { + for (var prop in src) { + target[prop] = src[prop]; + } + return target; +} + +const config = { onPrepare: function() { beforeEach(function() { browser.ignoreSynchronization = false; }); }, restartBrowserBetweenTests: true, allScriptsTimeout: 11000, - specs: ['dist/all/**/e2e_test/**/*_perf.js'], capabilities: process.env.TRAVIS ? BROWSER_CAPS.ChromeOnTravis : BROWSER_CAPS.LocalChrome, directConnect: true, - baseUrl: 'http://localhost:8000/', framework: 'jasmine2', jasmineNodeOpts: { showColors: true, @@ -55,9 +64,13 @@ exports.config = { useAllAngular2AppRoots: true }; -function mergeInto(src, target) { - for (var prop in src) { - target[prop] = src[prop]; - } - return target; +// Bazel has different strategy for how specs and baseUrl are specified +if (!isBazel) { + config.baseUrl = 'http://localhost:8000/'; + config.specs = [ + 'dist/all/**/e2e_test/**/*_perf.spec.js', + 'dist/all/**/e2e_test/**/*_perf.js', + ] } + +exports.config = config; From 8450e0ab2f7dc34670bea021c0b2f11d6cb5872c Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Fri, 13 Jul 2018 06:35:23 -0700 Subject: [PATCH 552/582] build(bazel): fix broken travis CI (#24788) PR Close #24788 --- modules/benchmarks/src/largetable/render3/index.html | 8 +++++++- modules/benchmarks/src/largetable/render3/index.ts | 8 +++++++- protractor-perf.conf.js | 3 +++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/benchmarks/src/largetable/render3/index.html b/modules/benchmarks/src/largetable/render3/index.html index cd7538e7d0..85dcfbb742 100644 --- a/modules/benchmarks/src/largetable/render3/index.html +++ b/modules/benchmarks/src/largetable/render3/index.html @@ -28,8 +28,14 @@ diff --git a/modules/benchmarks/src/largetable/render3/index.ts b/modules/benchmarks/src/largetable/render3/index.ts index 8b42c36d96..c8d774dbfe 100644 --- a/modules/benchmarks/src/largetable/render3/index.ts +++ b/modules/benchmarks/src/largetable/render3/index.ts @@ -27,4 +27,10 @@ export function main() { } } -main(); +const isBazel = location.pathname.indexOf('/all/') !== 0; +// isBazel needed while 'scripts/ci/test-e2e.sh test.e2e.protractor-e2e' is run +// on Travis +// TODO: port remaining protractor e2e tests to bazel protractor_web_test_suite rule +if (isBazel) { + main(); +} diff --git a/protractor-perf.conf.js b/protractor-perf.conf.js index d5e937bb09..409617f293 100644 --- a/protractor-perf.conf.js +++ b/protractor-perf.conf.js @@ -8,6 +8,9 @@ // Determine if we run under bazel const isBazel = !!process.env.RUNFILES; +// isBazel needed while 'scripts/ci/test-e2e.sh test.e2e.protractor-e2e' is run +// on Travis +// TODO: port remaining protractor e2e tests to bazel protractor_web_test_suite rule // Make sure that the command line is read as the first thing // as this could exit node if the help script should be printed. From 2cb0f68a7bc0d2aa5eebee936346084f178dc864 Mon Sep 17 00:00:00 2001 From: Wei Huang Date: Mon, 16 Jul 2018 12:10:11 -0400 Subject: [PATCH 553/582] test(bazel): allow no sandbox for protractor tests (#24906) It specifies --no-sandbox flag when running the protractor tests as root. This is needed for running the tests inside a docker container. PR Close #24906 --- packages/bazel/src/protractor/protractor.conf.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/bazel/src/protractor/protractor.conf.js b/packages/bazel/src/protractor/protractor.conf.js index 70ee78a475..dae9bb3c8d 100644 --- a/packages/bazel/src/protractor/protractor.conf.js +++ b/packages/bazel/src/protractor/protractor.conf.js @@ -107,6 +107,11 @@ if (process.env['WEB_TEST_METADATA']) { if (headless) { args.push('--headless'); args.push('--disable-gpu'); + const isRoot = process.getuid && process.getuid() == 0; + if (isRoot) { + // Specify --no-sandbox if it is started by root. + args.push('--no-sandbox'); + } } setConf(conf, 'directConnect', true, 'is set to true for chrome'); setConf( From c0e3852384643e26382e2af70993ddde8e03e102 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 24 Jul 2018 14:26:08 -0700 Subject: [PATCH 554/582] Revert "build: update to newer circleCI bazel remote cache proxy (#25054)" (#25076) This reverts commit d6016f1d1d37e9ab6ee3e7725add1bb63e41f7e4. PR Close #25076 --- .circleci/setup_cache.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/setup_cache.sh b/.circleci/setup_cache.sh index d5c8c35b5a..232596df4a 100755 --- a/.circleci/setup_cache.sh +++ b/.circleci/setup_cache.sh @@ -5,7 +5,7 @@ set -u -e -readonly DOWNLOAD_URL="https://6-116431813-gh.circle-artifacts.com/0/pkg/bazel-remote-proxy-$(uname -s)_$(uname -m)" +readonly DOWNLOAD_URL="https://5-116431813-gh.circle-artifacts.com/0/pkg/bazel-remote-proxy-$(uname -s)_$(uname -m)" curl --fail -o ~/bazel-remote-proxy "$DOWNLOAD_URL" chmod +x ~/bazel-remote-proxy From 72dd10f78f81e53270bbbaa0b03d0dc4f3bd4796 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Mon, 9 Jul 2018 20:59:43 +0200 Subject: [PATCH 555/582] refactor(ivy): cleanup runtime i18n code (#24805) Fixes #24785 PR Close #24805 --- packages/core/src/render3/i18n.ts | 77 ++++---- packages/core/test/render3/i18n_spec.ts | 251 +++++++++++++++--------- 2 files changed, 200 insertions(+), 128 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index fdcbee7dff..279c8d5b37 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -9,7 +9,7 @@ import {assertEqual, assertLessThan} from './assert'; import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions'; import {RENDER_PARENT} from './interfaces/container'; -import {LContainerNode, LElementNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node'; +import {LContainerNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node'; import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view'; import {appendChild, createTextNode, getParentLNode, removeChild} from './node_manipulation'; import {stringify} from './util'; @@ -22,8 +22,10 @@ export const enum I18nInstructions { Text = 1 << 29, Element = 2 << 29, Expression = 3 << 29, - CloseNode = 4 << 29, - RemoveNode = 5 << 29, + TemplateRoot = 4 << 29, + Any = 5 << 29, + CloseNode = 6 << 29, + RemoveNode = 7 << 29, /** Used to decode the number encoded with the instruction. */ IndexMask = (1 << 29) - 1, /** Used to test the type of instruction. */ @@ -46,7 +48,7 @@ export type I18nExpInstruction = number | string; export type PlaceholderMap = { [name: string]: number }; -const i18nTagRegex = /\{\$([^}]+)\}/g; +const i18nTagRegex = /{\$([^}]+)}/g; /** * Takes a translation string, the initial list of placeholders (elements and expressions) and the @@ -62,8 +64,8 @@ const i18nTagRegex = /\{\$([^}]+)\}/g; * their indexes. * @param expressions An array containing, for each template, the maps of expression placeholders * and their indexes. - * @param tmplContainers An array of template container placeholders whose content should be ignored - * when generating the instructions for their parent template. + * @param templateRoots An array of template roots whose content should be ignored when + * generating the instructions for their parent template. * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is * an ng-container. * @@ -71,13 +73,13 @@ const i18nTagRegex = /\{\$([^}]+)\}/g; */ export function i18nMapping( translation: string, elements: (PlaceholderMap | null)[] | null, - expressions?: (PlaceholderMap | null)[] | null, tmplContainers?: string[] | null, + expressions?: (PlaceholderMap | null)[] | null, templateRoots?: string[] | null, lastChildIndex?: number | null): I18nInstruction[][] { const translationParts = translation.split(i18nTagRegex); const instructions: I18nInstruction[][] = []; generateMappingInstructions( - 0, translationParts, instructions, elements, expressions, tmplContainers, lastChildIndex); + 0, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); return instructions; } @@ -96,8 +98,8 @@ export function i18nMapping( * their indexes. * @param expressions An array containing, for each template, the maps of expression placeholders * and their indexes. - * @param tmplContainers An array of template container placeholders whose content should be ignored - * when generating the instructions for their parent template. + * @param templateRoots An array of template roots whose content should be ignored when + * generating the instructions for their parent template. * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is * an ng-container. * @returns the current index in `translationParts` @@ -105,12 +107,16 @@ export function i18nMapping( function generateMappingInstructions( index: number, translationParts: string[], instructions: I18nInstruction[][], elements: (PlaceholderMap | null)[] | null, expressions?: (PlaceholderMap | null)[] | null, - tmplContainers?: string[] | null, lastChildIndex?: number | null): number { + templateRoots?: string[] | null, lastChildIndex?: number | null): number { const tmplIndex = instructions.length; const tmplInstructions: I18nInstruction[] = []; - const phVisited = []; + const phVisited: string[] = []; let openedTagCount = 0; let maxIndex = 0; + let currentElements: PlaceholderMap|null = + elements && elements[tmplIndex] ? elements[tmplIndex] : null; + let currentExpressions: PlaceholderMap|null = + expressions && expressions[tmplIndex] ? expressions[tmplIndex] : null; instructions.push(tmplInstructions); @@ -120,22 +126,27 @@ function generateMappingInstructions( // Odd indexes are placeholders if (index & 1) { let phIndex; - - if (elements && elements[tmplIndex] && - typeof(phIndex = elements[tmplIndex] ![value]) !== 'undefined') { + if (currentElements && currentElements[value] !== undefined) { + phIndex = currentElements[value]; // The placeholder represents a DOM element // Add an instruction to move the element - tmplInstructions.push(phIndex | I18nInstructions.Element); + const isTemplateRoot = templateRoots && templateRoots[tmplIndex] === value; + if (isTemplateRoot) { + // This is a template root, it has no closing tag, not treating it as an element + tmplInstructions.push(phIndex | I18nInstructions.TemplateRoot); + } else { + tmplInstructions.push(phIndex | I18nInstructions.Element); + openedTagCount++; + } phVisited.push(value); - openedTagCount++; - } else if ( - expressions && expressions[tmplIndex] && - typeof(phIndex = expressions[tmplIndex] ![value]) !== 'undefined') { + } else if (currentExpressions && currentExpressions[value] !== undefined) { + phIndex = currentExpressions[value]; // The placeholder represents an expression // Add an instruction to move the expression tmplInstructions.push(phIndex | I18nInstructions.Expression); phVisited.push(value); - } else { // It is a closing tag + } else { + // It is a closing tag tmplInstructions.push(I18nInstructions.CloseNode); if (tmplIndex > 0) { @@ -148,14 +159,14 @@ function generateMappingInstructions( } } - if (typeof phIndex !== 'undefined' && phIndex > maxIndex) { + if (phIndex !== undefined && phIndex > maxIndex) { maxIndex = phIndex; } - if (tmplContainers && tmplContainers.indexOf(value) !== -1 && - tmplContainers.indexOf(value) >= tmplIndex) { + if (templateRoots && templateRoots.indexOf(value) !== -1 && + templateRoots.indexOf(value) >= tmplIndex) { index = generateMappingInstructions( - index, translationParts, instructions, elements, expressions, tmplContainers, + index, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); } @@ -165,7 +176,7 @@ function generateMappingInstructions( } } - // Check if some elements from the template are missing from the translation + // Add instructions to remove elements that are not used in the translation if (elements) { const tmplElements = elements[tmplIndex]; @@ -188,7 +199,7 @@ function generateMappingInstructions( } } - // Check if some expressions from the template are missing from the translation + // Add instructions to remove expressions that are not used in the translation if (expressions) { const tmplExpressions = expressions[tmplIndex]; @@ -222,9 +233,7 @@ function generateMappingInstructions( if (ngDevMode) { assertLessThan(i.toString(2).length, 28, `Index ${i} is too big and will overflow`); } - // We consider those additional placeholders as expressions because we don't care about - // their children, all we need to do is to append them - tmplInstructions.push(i | I18nInstructions.Expression); + tmplInstructions.push(i | I18nInstructions.Any); } } @@ -258,8 +267,6 @@ function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) { // Template containers also have a comment node for the `ViewContainerRef` that should be moved if (node.tNode.type === TNodeType.Container && node.dynamicLContainerNode) { - // (node.native as RComment).textContent = 'test'; - // console.log(node.native); appendChild(parentNode, node.dynamicLContainerNode.native || null, viewData); if (firstTemplatePass) { node.tNode.dynamicContainerNode = node.dynamicLContainerNode.tNode; @@ -302,8 +309,10 @@ export function i18nApply(startIndex: number, instructions: I18nInstruction[]): localParentNode = element; break; case I18nInstructions.Expression: - const expr: LNode = load(instruction & I18nInstructions.IndexMask); - localPreviousNode = appendI18nNode(expr, localParentNode, localPreviousNode); + case I18nInstructions.TemplateRoot: + case I18nInstructions.Any: + const node: LNode = load(instruction & I18nInstructions.IndexMask); + localPreviousNode = appendI18nNode(node, localParentNode, localPreviousNode); break; case I18nInstructions.Text: if (ngDevMode) { diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index b3cc3a957f..d5709c2bd4 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -9,8 +9,8 @@ import {NgForOfContext} from '@angular/common'; import {Component} from '../../src/core'; import {defineComponent} from '../../src/render3/definition'; -import {i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {I18nExpInstruction, I18nInstruction, i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; +import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgForOf} from './common_with_def'; import {ComponentFixture, TemplateFixture} from './render_util'; @@ -21,8 +21,7 @@ describe('Runtime i18n', () => { // Open tag placeholders are never re-used (closing tag placeholders can be). const MSG_DIV_SECTION_1 = `{$START_C}trad 1{$END_C}{$START_A}trad 2{$START_B}trad 3{$END_B}{$END_A}`; - const i18n_1 = - i18nMapping(MSG_DIV_SECTION_1, [{START_A: 1, START_B: 2, START_REMOVE_ME: 3, START_C: 4}]); + let i18n_1: I18nInstruction[][]; // Initial template: //

        template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1, 'EXP_2': 2}]); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -121,7 +126,7 @@ describe('Runtime i18n', () => { it('should support expressions on removed nodes', () => { const MSG_DIV_SECTION_1 = `message`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 1}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -141,6 +146,10 @@ describe('Runtime i18n', () => { //
        template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 1}]); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -172,7 +181,7 @@ describe('Runtime i18n', () => { it('should support expressions in attributes', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {EXP_1: 0, EXP_2: 1}); + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); class MyApp { exp1: any = '1'; @@ -189,10 +198,7 @@ describe('Runtime i18n', () => { //
        template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - // Start of translated section 1 - // End of translated section 1 - elementEnd(); + element(0, 'div'); // translated section 1 } if (rf & RenderFlags.Update) { elementProperty(0, 'title', i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2)); @@ -218,11 +224,8 @@ describe('Runtime i18n', () => { it('should support both html elements, expressions and expressions in attributes', () => { const MSG_DIV_SECTION_1 = `{$EXP_1} {$START_P}trad {$EXP_2}{$END_P}`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, - [{START_REMOVE_ME_1: 2, START_REMOVE_ME_2: 3, START_REMOVE_ME_3: 4, START_P: 5}], - [{EXP_1: 1, EXP_2: 6, EXP_3: 7}]); - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0, EXP_2: 1}); + let i18n_1: I18nInstruction[][]; + let i18n_2: I18nExpInstruction[]; class MyApp { exp1 = '1'; @@ -255,16 +258,28 @@ describe('Runtime i18n', () => { //
    template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{ + 'START_REMOVE_ME_1': 2, + 'START_REMOVE_ME_2': 3, + 'START_REMOVE_ME_3': 4, + 'START_P': 5 + }], + [{'EXP_1': 1, 'EXP_2': 6, 'EXP_3': 7}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0, 'EXP_2': 1}); + } + elementStart(0, 'div'); { // Start of translated section 1 text(1); // EXP_1 elementStart(2, 'remove-me-1'); // START_REMOVE_ME_1 { - elementStart(3, 'remove-me-2'); // START_REMOVE_ME_2 - elementEnd(); - elementStart(4, 'remove-me-3'); // START_REMOVE_ME_3 - elementEnd(); + element(3, 'remove-me-2'); // START_REMOVE_ME_2 + element(4, 'remove-me-3'); // START_REMOVE_ME_3 } elementEnd(); elementStart(5, 'p'); // START_P @@ -305,9 +320,9 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `trad {$EXP_1}`; const MSG_DIV_SECTION_2 = `{$START_C}trad{$END_C}`; const MSG_ATTR_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 2}]); - const i18n_2 = i18nMapping(MSG_DIV_SECTION_2, [{START_C: 5}]); - const i18n_3 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0, EXP_2: 1}); + let i18n_1: I18nInstruction[][]; + let i18n_2: I18nInstruction[][]; + let i18n_3: I18nExpInstruction[]; class MyApp { exp1 = '1'; @@ -340,6 +355,16 @@ describe('Runtime i18n', () => { //
template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 2}]); + } + if (!i18n_2) { + i18n_2 = i18nMapping(MSG_DIV_SECTION_2, [{'START_C': 5}]); + } + if (!i18n_3) { + i18n_3 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0, 'EXP_2': 1}); + } + elementStart(0, 'div'); { elementStart(1, 'a'); @@ -353,8 +378,7 @@ describe('Runtime i18n', () => { elementStart(4, 'b'); { // Start of translated section 2 - elementStart(5, 'c'); // START_C - elementEnd(); + element(5, 'c'); // START_C // End of translated section 2 } elementEnd(); @@ -393,7 +417,7 @@ describe('Runtime i18n', () => { it('should support containers', () => { const MSG_DIV_SECTION_1 = `valeur: {$EXP_1}`; // The indexes are based on the main template function - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 0}]); + let i18n_1: I18nInstruction[][]; class MyApp { exp1 = '1'; @@ -417,6 +441,10 @@ describe('Runtime i18n', () => { // ) after template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 0}]); + } + text(0, 'before ('); container(1); text(2, ') after'); @@ -456,7 +484,7 @@ describe('Runtime i18n', () => { // its children are not the only children of their parent, some nodes which are not // translated might also be the children of the same parent. // This is why we need to pass the `lastChildIndex` to `i18nMapping` - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_B: 2, START_C: 3}], null, null, 4); + let i18n_1: I18nInstruction[][]; // Initial template: //
// @@ -476,20 +504,20 @@ describe('Runtime i18n', () => { // //
function createTemplate() { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_B': 2, 'START_C': 3}], null, null, 4); + } + elementStart(0, 'div'); { - elementStart(1, 'a'); - elementEnd(); + element(1, 'a'); { // Start of translated section 1 - elementStart(2, 'b'); // START_B - elementEnd(); - elementStart(3, 'c'); // START_C - elementEnd(); + element(2, 'b'); // START_B + element(3, 'c'); // START_C // End of translated section 1 } - elementStart(4, 'd'); - elementEnd(); + element(4, 'd'); } elementEnd(); i18nApply(2, i18n_1[0]); @@ -502,8 +530,7 @@ describe('Runtime i18n', () => { it('should support embedded templates', () => { const MSG_DIV_SECTION_1 = `{$START_LI}valeur: {$EXP_1}!{$END_LI}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -522,6 +549,12 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_LI': 1}, {'START_LI': 0}], [null, {'EXP_1': 1}], + ['START_LI']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -581,9 +614,7 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_LI_0}valeur: {$EXP_1}!{$END_LI_0}{$START_LI_1}valeur bis: {$EXP_2}!{$END_LI_1}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [null, {START_LI_0: 0}, {START_LI_1: 0}], - [{START_LI_0: 1, START_LI_1: 2}, {EXP_1: 1}, {EXP_2: 1}], ['START_LI_0', 'START_LI_1']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -604,6 +635,13 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI_0': 1, 'START_LI_1': 2}, {'START_LI_0': 0}, {'START_LI_1': 0}], + [null, {'EXP_1': 1}, {'EXP_2': 1}], ['START_LI_0', 'START_LI_1']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -685,9 +723,7 @@ describe('Runtime i18n', () => { it('should support nested embedded templates', () => { const MSG_DIV_SECTION_1 = `{$START_LI}{$START_SPAN}valeur: {$EXP_1}!{$END_SPAN}{$END_LI}`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [null, {START_LI: 0}, {START_SPAN: 0}], - [{START_LI: 1}, {START_SPAN: 1}, {EXP_1: 1}], ['START_LI', 'START_SPAN']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['1', '2']; @@ -710,6 +746,13 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI': 1}, {'START_LI': 0, 'START_SPAN': 1}, {'START_SPAN': 0}], + [null, null, {'EXP_1': 1}], ['START_LI', 'START_SPAN']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -792,9 +835,7 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_LI_0}début{$END_LI_0}{$START_LI_1}valeur: {$EXP_1}{$END_LI_1}fin`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI_0: 1, START_LI_2: 3}, {START_LI_1: 0}], - [{START_LI_1: 2}, {EXP_1: 1}], ['START_LI_1']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['first', 'second']; @@ -818,11 +859,17 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI_0': 1, 'START_LI_1': 2, 'START_LI_2': 3}, {'START_LI_1': 0}], + [null, {'EXP_1': 1}], ['START_LI_1']); + } + elementStart(0, 'ul'); { // Start of translated section 1 - elementStart(1, 'li'); // START_LI_0 - elementEnd(); + element(1, 'li'); // START_LI_0 container(2, liTemplate, null, ['ngForOf', '']); // START_LI_1 elementStart(3, 'li'); // START_LI_2 { text(4, 'delete me'); } @@ -859,7 +906,7 @@ describe('Runtime i18n', () => { expect(fixture.html) .toEqual('
  • début
  • valeur: first
  • valeur: second
  • fin
'); - // // Change detection cycle, no model changes + // Change detection cycle, no model changes fixture.update(); expect(fixture.html) .toEqual('
  • début
  • valeur: first
  • valeur: second
  • fin
'); @@ -884,8 +931,7 @@ describe('Runtime i18n', () => { it('should be able to remove containers', () => { const MSG_DIV_SECTION_1 = `loop`; // The indexes are based on each template function - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{START_LI: 1}, {START_LI: 0}], [null, {EXP_1: 1}], ['START_LI']); + let i18n_1: I18nInstruction[][]; class MyApp { items: string[] = ['first', 'second']; @@ -905,6 +951,12 @@ describe('Runtime i18n', () => { // template: (rf: RenderFlags, myApp: MyApp) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{'START_LI': 1}, {'START_LI': 0}], [null, {'EXP_1': 1}], + ['START_LI']); + } + elementStart(0, 'ul'); { // Start of translated section 1 @@ -982,17 +1034,9 @@ describe('Runtime i18n', () => { const MSG_DIV_SECTION_1 = `{$START_CHILD}Je suis projeté depuis {$START_B}{$EXP_1}{$END_B}{$END_CHILD}`; - const i18n_1 = i18nMapping( - MSG_DIV_SECTION_1, [{ - START_CHILD: 1, - START_B: 2, - START_REMOVE_ME_1: 4, - START_REMOVE_ME_2: 5, - START_REMOVE_ME_3: 6 - }], - [{EXP_1: 3}]); + let i18n_1: I18nInstruction[][]; const MSG_ATTR_1 = `Enfant de {$EXP_1}`; - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); + let i18n_2: I18nExpInstruction[]; @Component({ selector: 'parent', @@ -1017,6 +1061,21 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, [{ + 'START_CHILD': 1, + 'START_B': 2, + 'START_REMOVE_ME_1': 4, + 'START_REMOVE_ME_2': 5, + 'START_REMOVE_ME_3': 6 + }], + [{'EXP_1': 3}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0}); + } + elementStart(0, 'div'); { // Start of translated section 1 @@ -1024,17 +1083,14 @@ describe('Runtime i18n', () => { { elementStart(2, 'b'); // START_B { - text(3); // EXP_1 - elementStart(4, 'remove-me-1'); // START_REMOVE_ME_1 - elementEnd(); + text(3); // EXP_1 + element(4, 'remove-me-1'); // START_REMOVE_ME_1 } elementEnd(); - elementStart(5, 'remove-me-2'); // START_REMOVE_ME_2 - elementEnd(); + element(5, 'remove-me-2'); // START_REMOVE_ME_2 } elementEnd(); - elementStart(6, 'remove-me-3'); // START_REMOVE_ME_3 - elementEnd(); + element(6, 'remove-me-3'); // START_REMOVE_ME_3 // End of translated section 1 } elementEnd(); @@ -1073,9 +1129,9 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `Je suis projeté depuis {$EXP_1}`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{EXP_1: 4}]); + let i18n_1: I18nInstruction[][]; const MSG_ATTR_1 = `Enfant de {$EXP_1}`; - const i18n_2 = i18nExpMapping(MSG_ATTR_1, {EXP_1: 0}); + let i18n_2: I18nExpInstruction[]; @Component({ selector: 'parent', @@ -1101,12 +1157,18 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, null, [{'EXP_1': 4}]); + } + if (!i18n_2) { + i18n_2 = i18nExpMapping(MSG_ATTR_1, {'EXP_1': 0}); + } + elementStart(0, 'div'); { elementStart(1, 'child'); { - elementStart(2, 'any'); - elementEnd(); + element(2, 'any'); elementStart(3, 'b'); { // Start of translated section 1 @@ -1114,8 +1176,7 @@ describe('Runtime i18n', () => { // End of translated section 1 } elementEnd(); - elementStart(5, 'any'); - elementEnd(); + element(5, 'any'); } elementEnd(); } @@ -1174,7 +1235,7 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `{$START_B}Bonjour{$END_B} Monde!`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_B: 1}]); + let i18n_1: I18nInstruction[][]; @Component({ selector: 'parent', @@ -1191,11 +1252,14 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_B': 1}]); + } + elementStart(0, 'child'); { // Start of translated section 1 - elementStart(1, 'b'); // START_B - elementEnd(); + element(1, 'b'); // START_B // End of translated section 1 } elementEnd(); @@ -1232,7 +1296,7 @@ describe('Runtime i18n', () => { } const MSG_DIV_SECTION_1 = `{$START_SPAN_0}Contenu{$END_SPAN_0}`; - const i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{START_SPAN_0: 1, START_SPAN_1: 2}]); + let i18n_1: I18nInstruction[][]; @Component({ selector: 'parent', @@ -1253,13 +1317,15 @@ describe('Runtime i18n', () => { factory: () => new Parent(), template: (rf: RenderFlags, cmp: Parent) => { if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping(MSG_DIV_SECTION_1, [{'START_SPAN_0': 1, 'START_SPAN_1': 2}]); + } + elementStart(0, 'child'); { // Start of translated section 1 - elementStart(1, 'span', ['title', 'keepMe']); // START_SPAN_0 - elementEnd(); - elementStart(2, 'span', ['title', 'deleteMe']); // START_SPAN_1 - elementEnd(); + element(1, 'span', ['title', 'keepMe']); // START_SPAN_0 + element(2, 'span', ['title', 'deleteMe']); // START_SPAN_1 // End of translated section 1 } elementEnd(); @@ -1276,7 +1342,7 @@ describe('Runtime i18n', () => { it('i18nInterpolation should return the same value as i18nInterpolationV', () => { const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {EXP_1: 0, EXP_2: 1}); + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); let interpolation; let interpolationV; @@ -1295,10 +1361,7 @@ describe('Runtime i18n', () => { //
template: (rf: RenderFlags, ctx: MyApp) => { if (rf & RenderFlags.Create) { - elementStart(0, 'div'); - // Start of translated section 1 - // End of translated section 1 - elementEnd(); + element(0, 'div'); // translated section 1 } if (rf & RenderFlags.Update) { interpolation = i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2); From 22731a7588d771ac2fe5f351007e1939b61ff7fc Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Thu, 19 Jul 2018 11:02:22 +0200 Subject: [PATCH 556/582] refactor(ivy): split i18nInterpolation into 8 functions (#24805) PR Close #24805 --- .../core/src/core_render3_private_export.ts | 9 +- packages/core/src/render3/i18n.ts | 372 +++++++++++++++--- packages/core/src/render3/index.ts | 11 +- packages/core/src/render3/instructions.ts | 2 +- .../render3/compiler_canonical/i18n_spec.ts | 2 +- packages/core/test/render3/i18n_spec.ts | 320 +++++++++++++-- 6 files changed, 614 insertions(+), 102 deletions(-) diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index c88234bda0..8107ad315f 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -94,7 +94,14 @@ export { whenRendered as ɵwhenRendered, iA as ɵiA, iEM as ɵiEM, - iI as ɵiI, + iI1 as ɵiI1, + iI2 as ɵiI2, + iI3 as ɵiI3, + iI4 as ɵiI4, + iI5 as ɵiI5, + iI6 as ɵiI6, + iI7 as ɵiI7, + iI8 as ɵiI8, iIV as ɵIV, iM as ɵiM, I18nInstruction as ɵI18nInstruction, diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 279c8d5b37..6c64793290 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -7,7 +7,7 @@ */ import {assertEqual, assertLessThan} from './assert'; -import {NO_CHANGE, bindingUpdated, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions'; +import {NO_CHANGE, bindingUpdated, bindingUpdated2, bindingUpdated4, createLNode, getPreviousOrParentNode, getRenderer, getViewData, load, resetApplicationState} from './instructions'; import {RENDER_PARENT} from './interfaces/container'; import {LContainerNode, LNode, TContainerNode, TElementNode, TNodeType} from './interfaces/node'; import {BINDING_INDEX, HEADER_OFFSET, TVIEW} from './interfaces/view'; @@ -369,43 +369,16 @@ export function i18nExpMapping( } /** - * Checks if the value of up to 8 expressions have changed and replaces them by their values in a - * translation, or returns NO_CHANGE. + * Checks if the value of an expression has changed and replaces it by its value in a translation, + * or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. * * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. */ -export function i18nInterpolation( - instructions: I18nExpInstruction[], numberOfExp: number, v0: any, v1?: any, v2?: any, v3?: any, - v4?: any, v5?: any, v6?: any, v7?: any): string|NO_CHANGE { - let different = bindingUpdated(v0); - - if (numberOfExp > 1) { - different = bindingUpdated(v1) || different; - - if (numberOfExp > 2) { - different = bindingUpdated(v2) || different; - - if (numberOfExp > 3) { - different = bindingUpdated(v3) || different; - - if (numberOfExp > 4) { - different = bindingUpdated(v4) || different; - - if (numberOfExp > 5) { - different = bindingUpdated(v5) || different; - - if (numberOfExp > 6) { - different = bindingUpdated(v6) || different; - - if (numberOfExp > 7) { - different = bindingUpdated(v7) || different; - } - } - } - } - } - } - } +export function i18nInterpolation1(instructions: I18nExpInstruction[], v0: any): string|NO_CHANGE { + const different = bindingUpdated(v0); if (!different) { return NO_CHANGE; @@ -413,35 +386,308 @@ export function i18nInterpolation( let res = ''; for (let i = 0; i < instructions.length; i++) { - let value: any; - // Odd indexes are placeholders + // Odd indexes are bindings if (i & 1) { - switch (instructions[i]) { - case 0: - value = v0; - break; - case 1: - value = v1; - break; - case 2: - value = v2; - break; - case 3: - value = v3; - break; - case 4: - value = v4; - break; - case 5: - value = v5; - break; - case 6: - value = v6; - break; - case 7: - value = v7; - break; - } + res += stringify(v0); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 2 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation2(instructions: I18nExpInstruction[], v0: any, v1: any): string| + NO_CHANGE { + const different = bindingUpdated2(v0, v1); + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b1 ? v1 : v0; + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 3 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation3( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any): string|NO_CHANGE { + let different = bindingUpdated2(v0, v1); + different = bindingUpdated(v2) || different; + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b2 ? v2 : (b1 ? v1 : v0); + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 4 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * @param v3 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation4( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any): string|NO_CHANGE { + const different = bindingUpdated4(v0, v1, v2, v3); + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b2 ? (b1 ? v3 : v2) : (b1 ? v1 : v0); + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 5 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * @param v3 value checked for change. + * @param v4 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation5( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any): string| + NO_CHANGE { + let different = bindingUpdated4(v0, v1, v2, v3); + different = bindingUpdated(v4) || different; + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b4 = idx & 4; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b4 ? v4 : (b2 ? (b1 ? v3 : v2) : (b1 ? v1 : v0)); + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 6 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * @param v3 value checked for change. + * @param v4 value checked for change. + * @param v5 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ export function +i18nInterpolation6( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any): + string|NO_CHANGE { + let different = bindingUpdated4(v0, v1, v2, v3); + different = bindingUpdated2(v4, v5) || different; + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b4 = idx & 4; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b4 ? (b1 ? v5 : v4) : (b2 ? (b1 ? v3 : v2) : (b1 ? v1 : v0)); + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 7 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * @param v3 value checked for change. + * @param v4 value checked for change. + * @param v5 value checked for change. + * @param v6 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation7( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, + v6: any): string|NO_CHANGE { + let different = bindingUpdated4(v0, v1, v2, v3); + different = bindingUpdated2(v4, v5) || different; + different = bindingUpdated(v6) || different; + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b4 = idx & 4; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = b4 ? (b2 ? v6 : (b1 ? v5 : v4)) : (b2 ? (b1 ? v3 : v2) : (b1 ? v1 : v0)); + + res += stringify(value); + } else { + res += instructions[i]; + } + } + + return res; +} + +/** + * Checks if the values of up to 8 expressions have changed and replaces them by their values in a + * translation, or returns NO_CHANGE. + * + * @param instructions A list of instructions that will be used to translate an attribute. + * @param v0 value checked for change. + * @param v1 value checked for change. + * @param v2 value checked for change. + * @param v3 value checked for change. + * @param v4 value checked for change. + * @param v5 value checked for change. + * @param v6 value checked for change. + * @param v7 value checked for change. + * + * @returns The concatenated string when any of the arguments changes, `NO_CHANGE` otherwise. + */ +export function i18nInterpolation8( + instructions: I18nExpInstruction[], v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, + v6: any, v7: any): string|NO_CHANGE { + let different = bindingUpdated4(v0, v1, v2, v3); + different = bindingUpdated4(v4, v5, v6, v7) || different; + + if (!different) { + return NO_CHANGE; + } + + let res = ''; + for (let i = 0; i < instructions.length; i++) { + // Odd indexes are bindings + if (i & 1) { + // Extract bits + const idx = instructions[i] as number; + const b4 = idx & 4; + const b2 = idx & 2; + const b1 = idx & 1; + // Get the value from the argument vx where x = idx + const value = + b4 ? (b2 ? (b1 ? v7 : v6) : (b1 ? v5 : v4)) : (b2 ? (b1 ? v3 : v2) : (b1 ? v1 : v0)); res += stringify(value); } else { diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 014a092e66..70ba550eb3 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -11,7 +11,7 @@ import {defineComponent, defineDirective, defineNgModule, definePipe} from './de import {InheritDefinitionFeature} from './features/inherit_definition_feature'; import {NgOnChangesFeature} from './features/ng_onchanges_feature'; import {PublicFeature} from './features/public_feature'; -import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation, i18nInterpolationV} from './i18n'; +import {I18nExpInstruction, I18nInstruction, i18nExpMapping, i18nInterpolation1, i18nInterpolation2, i18nInterpolation3, i18nInterpolation4, i18nInterpolation5, i18nInterpolation6, i18nInterpolation7, i18nInterpolation8, i18nInterpolationV} from './i18n'; import {ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef} from './component_ref'; @@ -88,7 +88,14 @@ export { export { i18nApply as iA, i18nMapping as iM, - i18nInterpolation as iI, + i18nInterpolation1 as iI1, + i18nInterpolation2 as iI2, + i18nInterpolation3 as iI3, + i18nInterpolation4 as iI4, + i18nInterpolation5 as iI5, + i18nInterpolation6 as iI6, + i18nInterpolation7 as iI7, + i18nInterpolation8 as iI8, i18nInterpolationV as iIV, i18nExpMapping as iEM, I18nInstruction, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 3b27b0d5d8..f99b34be06 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -2454,7 +2454,7 @@ export function interpolation2( return different ? prefix + stringify(v0) + i0 + stringify(v1) + suffix : NO_CHANGE; } -/** Creates an interpolation bindings with 3 expressions. */ +/** Creates an interpolation binding with 3 expressions. */ export function interpolation3( prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): string| NO_CHANGE { diff --git a/packages/core/test/render3/compiler_canonical/i18n_spec.ts b/packages/core/test/render3/compiler_canonical/i18n_spec.ts index 3811eb5c18..a27ddb89d2 100644 --- a/packages/core/test/render3/compiler_canonical/i18n_spec.ts +++ b/packages/core/test/render3/compiler_canonical/i18n_spec.ts @@ -86,7 +86,7 @@ describe('i18n', () => { $r3$.ɵe(); } if (rf & 2) { - $r3$.ɵp(0, 'title', $r3$.ɵiI($i18n_1$, 2, ctx.exp1)); + $r3$.ɵp(0, 'title', $r3$.ɵiI1($i18n_1$, ctx.exp1)); } } }); diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index d5709c2bd4..d185583385 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -9,7 +9,7 @@ import {NgForOfContext} from '@angular/common'; import {Component} from '../../src/core'; import {defineComponent} from '../../src/render3/definition'; -import {I18nExpInstruction, I18nInstruction, i18nApply, i18nExpMapping, i18nInterpolation, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; +import {I18nExpInstruction, I18nInstruction, i18nApply, i18nExpMapping, i18nInterpolation1, i18nInterpolation2, i18nInterpolation3, i18nInterpolation4, i18nInterpolation5, i18nInterpolation6, i18nInterpolation7, i18nInterpolation8, i18nInterpolationV, i18nMapping} from '../../src/render3/i18n'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgForOf} from './common_with_def'; @@ -201,7 +201,7 @@ describe('Runtime i18n', () => { element(0, 'div'); // translated section 1 } if (rf & RenderFlags.Update) { - elementProperty(0, 'title', i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2)); + elementProperty(0, 'title', i18nInterpolation2(i18n_1, ctx.exp1, ctx.exp2)); } } }); @@ -295,7 +295,7 @@ describe('Runtime i18n', () => { textBinding(1, bind(ctx.exp1)); textBinding(6, bind(ctx.exp2)); textBinding(7, bind(ctx.exp3)); - elementProperty(0, 'title', i18nInterpolation(i18n_2, 2, ctx.exp1, ctx.exp2)); + elementProperty(0, 'title', i18nInterpolation2(i18n_2, ctx.exp1, ctx.exp2)); } } }); @@ -389,7 +389,7 @@ describe('Runtime i18n', () => { } if (rf & RenderFlags.Update) { textBinding(2, bind(ctx.exp1)); - elementProperty(4, 'title', i18nInterpolation(i18n_3, 2, ctx.exp1, ctx.exp2)); + elementProperty(4, 'title', i18nInterpolation2(i18n_3, ctx.exp1, ctx.exp2)); } } }); @@ -1097,7 +1097,7 @@ describe('Runtime i18n', () => { i18nApply(1, i18n_1[0]); } if (rf & RenderFlags.Update) { - elementProperty(2, 'title', i18nInterpolation(i18n_2, 1, cmp.name)); + elementProperty(2, 'title', i18nInterpolation1(i18n_2, cmp.name)); textBinding(3, bind(cmp.name)); } } @@ -1184,7 +1184,7 @@ describe('Runtime i18n', () => { i18nApply(4, i18n_1[0]); } if (rf & RenderFlags.Update) { - elementProperty(3, 'title', i18nInterpolation(i18n_2, 1, cmp.name)); + elementProperty(3, 'title', i18nInterpolation1(i18n_2, cmp.name)); textBinding(4, bind(cmp.name)); } } @@ -1340,40 +1340,292 @@ describe('Runtime i18n', () => { }); }); - it('i18nInterpolation should return the same value as i18nInterpolationV', () => { - const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; - const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); - let interpolation; - let interpolationV; + describe('i18nInterpolation', () => { + it('i18nInterpolation should return the same value as i18nInterpolationV', () => { + const MSG_DIV_SECTION_1 = `start {$EXP_2} middle {$EXP_1} end`; + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1}); + let interpolation; + let interpolationV; - class MyApp { - exp1: any = '1'; - exp2: any = '2'; + class MyApp { + exp1: any = '1'; + exp2: any = '2'; - static ngComponentDef = defineComponent({ - type: MyApp, - factory: () => new MyApp(), - selectors: [['my-app']], - // Initial template: - //
+ static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
- // Translated to: - //
- template: (rf: RenderFlags, ctx: MyApp) => { - if (rf & RenderFlags.Create) { - element(0, 'div'); // translated section 1 + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + interpolation = i18nInterpolation2(i18n_1, ctx.exp1, ctx.exp2); + interpolationV = i18nInterpolationV(i18n_1, [ctx.exp1, ctx.exp2]); + elementProperty(0, 'title', interpolation); + } } - if (rf & RenderFlags.Update) { - interpolation = i18nInterpolation(i18n_1, 2, ctx.exp1, ctx.exp2); - interpolationV = i18nInterpolationV(i18n_1, [ctx.exp1, ctx.exp2]); - elementProperty(0, 'title', interpolation); + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(interpolation).toBeDefined(); + expect(interpolation).toEqual(interpolationV); + }); + + it('i18nInterpolation3 should work', () => { + const MSG_DIV_SECTION_1 = `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} end`; + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1, 'EXP_3': 2}); + + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'title', i18nInterpolation3(i18n_1, ctx.exp1, ctx.exp2, ctx.exp3)); + } } - } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + + it('i18nInterpolation4 should work', () => { + const MSG_DIV_SECTION_1 = `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} _ {$EXP_4} end`; + const i18n_1 = + i18nExpMapping(MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1, 'EXP_3': 2, 'EXP_4': 3}); + + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + exp4: any = '4'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty( + 0, 'title', i18nInterpolation4(i18n_1, ctx.exp1, ctx.exp2, ctx.exp3, ctx.exp4)); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + + it('i18nInterpolation5 should work', () => { + const MSG_DIV_SECTION_1 = `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} _ {$EXP_4} _ {$EXP_5} end`; + const i18n_1 = i18nExpMapping( + MSG_DIV_SECTION_1, {'EXP_1': 0, 'EXP_2': 1, 'EXP_3': 2, 'EXP_4': 3, 'EXP_5': 4}); + + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + exp4: any = '4'; + exp5: any = '5'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty( + 0, 'title', + i18nInterpolation5(i18n_1, ctx.exp1, ctx.exp2, ctx.exp3, ctx.exp4, ctx.exp5)); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + + it('i18nInterpolation6 should work', () => { + const MSG_DIV_SECTION_1 = + `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} _ {$EXP_4} _ {$EXP_5} _ {$EXP_6} end`; + const i18n_1 = i18nExpMapping( + MSG_DIV_SECTION_1, + {'EXP_1': 0, 'EXP_2': 1, 'EXP_3': 2, 'EXP_4': 3, 'EXP_5': 4, 'EXP_6': 5}); + + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + exp4: any = '4'; + exp5: any = '5'; + exp6: any = '6'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty( + 0, 'title', + i18nInterpolation6( + i18n_1, ctx.exp1, ctx.exp2, ctx.exp3, ctx.exp4, ctx.exp5, ctx.exp6)); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + + it('i18nInterpolation7 should work', () => { + const MSG_DIV_SECTION_1 = + `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} _ {$EXP_4} _ {$EXP_5} _ {$EXP_6} _ {$EXP_7} end`; + const i18n_1 = i18nExpMapping( + MSG_DIV_SECTION_1, + {'EXP_1': 0, 'EXP_2': 1, 'EXP_3': 2, 'EXP_4': 3, 'EXP_5': 4, 'EXP_6': 5, 'EXP_7': 6}); + + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + exp4: any = '4'; + exp5: any = '5'; + exp6: any = '6'; + exp7: any = '7'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty( + 0, 'title', i18nInterpolation7( + i18n_1, ctx.exp1, ctx.exp2, ctx.exp3, ctx.exp4, ctx.exp5, + ctx.exp6, ctx.exp7)); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + + it('i18nInterpolation8 should work', () => { + const MSG_DIV_SECTION_1 = + `start {$EXP_1} _ {$EXP_2} _ {$EXP_3} _ {$EXP_4} _ {$EXP_5} _ {$EXP_6} _ {$EXP_7} _ {$EXP_8} end`; + const i18n_1 = i18nExpMapping(MSG_DIV_SECTION_1, { + 'EXP_1': 0, + 'EXP_2': 1, + 'EXP_3': 2, + 'EXP_4': 3, + 'EXP_5': 4, + 'EXP_6': 5, + 'EXP_7': 6, + 'EXP_8': 7 }); - } - const fixture = new ComponentFixture(MyApp); - expect(interpolation).toBeDefined(); - expect(interpolation).toEqual(interpolationV); + class MyApp { + exp1: any = '1'; + exp2: any = '2'; + exp3: any = '3'; + exp4: any = '4'; + exp5: any = '5'; + exp6: any = '6'; + exp7: any = '7'; + exp8: any = '8'; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
+ + // Translated to: + //
+ template: (rf: RenderFlags, ctx: MyApp) => { + if (rf & RenderFlags.Create) { + element(0, 'div'); // translated section 1 + } + if (rf & RenderFlags.Update) { + elementProperty( + 0, 'title', i18nInterpolation8( + i18n_1, ctx.exp1, ctx.exp2, ctx.exp3, ctx.exp4, ctx.exp5, + ctx.exp6, ctx.exp7, ctx.exp8)); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html).toEqual('
'); + }); + }); }); From 1ceddb6290737fe90dc980fb29e6ed9512b6b4e5 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Fri, 20 Jul 2018 16:21:05 +0200 Subject: [PATCH 557/582] fix(ivy): support re-order embedded templates (#24805) PR Close #24805 --- packages/core/src/render3/i18n.ts | 51 ++++++----- packages/core/test/render3/i18n_spec.ts | 114 +++++++++++++++++++++++- 2 files changed, 140 insertions(+), 25 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 6c64793290..78e011c7b4 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -76,10 +76,11 @@ export function i18nMapping( expressions?: (PlaceholderMap | null)[] | null, templateRoots?: string[] | null, lastChildIndex?: number | null): I18nInstruction[][] { const translationParts = translation.split(i18nTagRegex); - const instructions: I18nInstruction[][] = []; + const nbTemplates = templateRoots ? templateRoots.length + 1 : 1; + const instructions: I18nInstruction[][] = (new Array(nbTemplates)).fill(undefined); generateMappingInstructions( - 0, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); + 0, 0, translationParts, instructions, elements, expressions, templateRoots, lastChildIndex); return instructions; } @@ -90,7 +91,9 @@ export function i18nMapping( * * See `i18nMapping()` for more details. * - * @param index The current index in `translationParts`. + * @param tmplIndex The order of appearance of the template. + * 0 for the root template, following indexes match the order in `templateRoots`. + * @param partIndex The current index in `translationParts`. * @param translationParts The translation string split into an array of placeholders and text * elements. * @param instructions The current list of instructions to update. @@ -102,13 +105,14 @@ export function i18nMapping( * generating the instructions for their parent template. * @param lastChildIndex The index of the last child of the i18n node. Used when the i18n block is * an ng-container. + * * @returns the current index in `translationParts` */ function generateMappingInstructions( - index: number, translationParts: string[], instructions: I18nInstruction[][], - elements: (PlaceholderMap | null)[] | null, expressions?: (PlaceholderMap | null)[] | null, - templateRoots?: string[] | null, lastChildIndex?: number | null): number { - const tmplIndex = instructions.length; + tmplIndex: number, partIndex: number, translationParts: string[], + instructions: I18nInstruction[][], elements: (PlaceholderMap | null)[] | null, + expressions?: (PlaceholderMap | null)[] | null, templateRoots?: string[] | null, + lastChildIndex?: number | null): number { const tmplInstructions: I18nInstruction[] = []; const phVisited: string[] = []; let openedTagCount = 0; @@ -118,20 +122,20 @@ function generateMappingInstructions( let currentExpressions: PlaceholderMap|null = expressions && expressions[tmplIndex] ? expressions[tmplIndex] : null; - instructions.push(tmplInstructions); + instructions[tmplIndex] = tmplInstructions; - for (; index < translationParts.length; index++) { - const value = translationParts[index]; + for (; partIndex < translationParts.length; partIndex++) { + // The value can either be text or the name of a placeholder (element/template root/expression) + const value = translationParts[partIndex]; // Odd indexes are placeholders - if (index & 1) { + if (partIndex & 1) { let phIndex; if (currentElements && currentElements[value] !== undefined) { phIndex = currentElements[value]; - // The placeholder represents a DOM element - // Add an instruction to move the element - const isTemplateRoot = templateRoots && templateRoots[tmplIndex] === value; - if (isTemplateRoot) { + // The placeholder represents a DOM element, add an instruction to move it + let templateRootIndex = templateRoots ? templateRoots.indexOf(value) : -1; + if (templateRootIndex !== -1 && (templateRootIndex + 1) !== tmplIndex) { // This is a template root, it has no closing tag, not treating it as an element tmplInstructions.push(phIndex | I18nInstructions.TemplateRoot); } else { @@ -141,8 +145,7 @@ function generateMappingInstructions( phVisited.push(value); } else if (currentExpressions && currentExpressions[value] !== undefined) { phIndex = currentExpressions[value]; - // The placeholder represents an expression - // Add an instruction to move the expression + // The placeholder represents an expression, add an instruction to move it tmplInstructions.push(phIndex | I18nInstructions.Expression); phVisited.push(value); } else { @@ -163,11 +166,13 @@ function generateMappingInstructions( maxIndex = phIndex; } - if (templateRoots && templateRoots.indexOf(value) !== -1 && - templateRoots.indexOf(value) >= tmplIndex) { - index = generateMappingInstructions( - index, translationParts, instructions, elements, expressions, templateRoots, - lastChildIndex); + if (templateRoots) { + const newTmplIndex = templateRoots.indexOf(value) + 1; + if (newTmplIndex !== 0 && newTmplIndex !== tmplIndex) { + partIndex = generateMappingInstructions( + newTmplIndex, partIndex, translationParts, instructions, elements, expressions, + templateRoots, lastChildIndex); + } } } else if (value) { @@ -237,7 +242,7 @@ function generateMappingInstructions( } } - return index; + return partIndex; } function appendI18nNode(node: LNode, parentNode: LNode, previousNode: LNode) { diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index d185583385..0c96c43d20 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -720,6 +720,116 @@ describe('Runtime i18n', () => { '
  • valeur: one!
  • valeur: two!
  • valeur bis: one!
  • valeur bis: two!
'); }); + it('should support changing the order of multiple template roots in the same template', () => { + const MSG_DIV_SECTION_1 = + `{$START_LI_1}valeur bis: {$EXP_2}!{$END_LI_1}{$START_LI_0}valeur: {$EXP_1}!{$END_LI_0}`; + // The indexes are based on each template function + let i18n_1: I18nInstruction[][]; + class MyApp { + items: string[] = ['1', '2']; + + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + // Initial template: + //
    + //
  • value: {{item}}
  • + //
  • value bis: {{item}}
  • + //
+ + // Translated to: + //
    + //
  • valeur bis: {{item}}!
  • + //
  • valeur: {{item}}!
  • + //
+ template: (rf: RenderFlags, myApp: MyApp) => { + if (rf & RenderFlags.Create) { + if (!i18n_1) { + i18n_1 = i18nMapping( + MSG_DIV_SECTION_1, + [{'START_LI_0': 1, 'START_LI_1': 2}, {'START_LI_0': 0}, {'START_LI_1': 0}], + [null, {'EXP_1': 1}, {'EXP_2': 1}], ['START_LI_0', 'START_LI_1']); + } + + elementStart(0, 'ul'); + { + // Start of translated section 1 + container(1, liTemplate, null, ['ngForOf', '']); // START_LI_0 + container(2, liTemplateBis, null, ['ngForOf', '']); // START_LI_1 + // End of translated section 1 + } + elementEnd(); + i18nApply(1, i18n_1[0]); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(myApp.items)); + elementProperty(2, 'ngForOf', bind(myApp.items)); + } + + function liTemplate(rf1: RenderFlags, row: NgForOfContext) { + if (rf1 & RenderFlags.Create) { + // This is a container so the whole template is a translated section + // Start of translated section 2 + elementStart(0, 'li'); // START_LI_0 + { text(1); } // EXP_1 + elementEnd(); + // End of translated section 2 + i18nApply(0, i18n_1[1]); + } + if (rf1 & RenderFlags.Update) { + textBinding(1, bind(row.$implicit)); + } + } + + function liTemplateBis(rf1: RenderFlags, row: NgForOfContext) { + if (rf1 & RenderFlags.Create) { + // This is a container so the whole template is a translated section + // Start of translated section 3 + elementStart(0, 'li'); // START_LI_1 + { text(1); } // EXP_2 + elementEnd(); + // End of translated section 3 + i18nApply(0, i18n_1[2]); + } + if (rf1 & RenderFlags.Update) { + textBinding(1, bind(row.$implicit)); + } + } + }, + directives: () => [NgForOf] + }); + } + + const fixture = new ComponentFixture(MyApp); + expect(fixture.html) + .toEqual( + '
  • valeur bis: 1!
  • valeur bis: 2!
  • valeur: 1!
  • valeur: 2!
'); + + // Change detection cycle, no model changes + fixture.update(); + expect(fixture.html) + .toEqual( + '
  • valeur bis: 1!
  • valeur bis: 2!
  • valeur: 1!
  • valeur: 2!
'); + + // Remove the last item + fixture.component.items.length = 1; + fixture.update(); + expect(fixture.html).toEqual('
  • valeur bis: 1!
  • valeur: 1!
'); + + // Change an item + fixture.component.items[0] = 'one'; + fixture.update(); + expect(fixture.html).toEqual('
  • valeur bis: one!
  • valeur: one!
'); + + // Add an item + fixture.component.items.push('two'); + fixture.update(); + expect(fixture.html) + .toEqual( + '
  • valeur bis: one!
  • valeur bis: two!
  • valeur: one!
  • valeur: two!
'); + }); + it('should support nested embedded templates', () => { const MSG_DIV_SECTION_1 = `{$START_LI}{$START_SPAN}valeur: {$EXP_1}!{$END_SPAN}{$END_LI}`; // The indexes are based on each template function @@ -831,7 +941,7 @@ describe('Runtime i18n', () => { '
  • valeur: one!valeur: two!
  • valeur: one!valeur: two!
'); }); - it('should be able to move template directives around', () => { + it('should be able to move template roots around', () => { const MSG_DIV_SECTION_1 = `{$START_LI_0}début{$END_LI_0}{$START_LI_1}valeur: {$EXP_1}{$END_LI_1}fin`; // The indexes are based on each template function @@ -928,7 +1038,7 @@ describe('Runtime i18n', () => { .toEqual('
  • début
  • valeur: one
  • valeur: two
  • fin
'); }); - it('should be able to remove containers', () => { + it('should be able to remove template roots', () => { const MSG_DIV_SECTION_1 = `loop`; // The indexes are based on each template function let i18n_1: I18nInstruction[][]; From 65e18dc1bf1c4390e3c61af1e63c04c37b6c107d Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 20 Jul 2018 14:31:54 +0200 Subject: [PATCH 558/582] docs: refactor style guide example 03-06 (#24996) PR Close #24996 --- .../app/heroes/shared/hero.service.avoid.ts | 12 ++++++------ .../src/03-06/app/heroes/shared/hero.service.ts | 17 +++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts index 3b983f5bda..9f125bbf0e 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts @@ -3,7 +3,7 @@ /* avoid */ import { ExceptionService, SpinnerService, ToastService } from '../../core'; -import { Http } from '@angular/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; import { Hero } from './hero.model'; @@ -16,17 +16,17 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: Http + private http: HttpClient ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response.json().data as Hero)); + return this.http.get(`api/heroes/${id}`).pipe( + map(response => response.data as Hero)); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response.json().data as Hero[])); + return this.http.get(`api/heroes`).pipe( + map(response => response.data as Hero[])); } } diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts index 0a50c11bb4..573e847091 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts @@ -1,11 +1,12 @@ // #docregion // #docregion example +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; -import { map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; -import { Hero } from './hero.model'; import { ExceptionService, SpinnerService, ToastService } from '../../core'; +import { Hero } from './hero.model'; + // #enddocregion example @Injectable() @@ -16,17 +17,17 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: Http + private http: HttpClient ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response.json() as Hero)); + return this.http.get(`api/heroes/${id}`).pipe( + map(response => response as Hero)); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response.json() as Hero[])); + return this.http.get(`api/heroes`).pipe( + map(response => response as Hero[])); } } From d0c066a22332a44014f413203a0c0b80a113a270 Mon Sep 17 00:00:00 2001 From: Daniel Sogl Date: Tue, 24 Jul 2018 16:09:47 +0200 Subject: [PATCH 559/582] docs: replaced old angular/http example (#25065) PR Close #25065 --- aio/content/guide/glossary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aio/content/guide/glossary.md b/aio/content/guide/glossary.md index 5a93329d23..3c615c6c7f 100644 --- a/aio/content/guide/glossary.md +++ b/aio/content/guide/glossary.md @@ -580,7 +580,7 @@ For more information, see https://www.npmjs.com/package/@angular-devkit/schemati ## Scoped package A way to group related npm packages. -NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/http`, and `@angular/router`. +NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/forms`, and `@angular/router`. Import a scoped package in the same way that you import a normal package. From 1e79014fc4d43fb248a8fd4851e0c8aea53c915b Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Jul 2018 16:21:25 +0200 Subject: [PATCH 560/582] docs: replace angular/http with HttpClient (#25066) PR Close #25066 --- aio/content/examples/bootstrapping/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/content/examples/bootstrapping/src/app/app.module.ts b/aio/content/examples/bootstrapping/src/app/app.module.ts index 58c78b33ac..9b8a4fcaef 100644 --- a/aio/content/examples/bootstrapping/src/app/app.module.ts +++ b/aio/content/examples/bootstrapping/src/app/app.module.ts @@ -5,7 +5,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; // #docregion directive-import @@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive'; imports: [ BrowserModule, FormsModule, - HttpModule + HttpClientModule ], providers: [], bootstrap: [AppComponent] From 777bd412b23f26f7bb39f9f70afd76a68876589c Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Jul 2018 16:24:24 +0200 Subject: [PATCH 561/582] docs: replace angular/http with HttpClient (#25068) PR Close #25068 --- .../dependency-injection-in-action/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts b/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts index a240e21f7c..490670a71c 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/app.module.ts @@ -1,7 +1,7 @@ // #docregion import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; +import { HttpClientModule } from '@angular/common/http'; // import { AppRoutingModule } from './app-routing.module'; import { LocationStrategy, @@ -54,7 +54,7 @@ const c_components = [ imports: [ BrowserModule, FormsModule, - HttpModule, + HttpClientModule, InMemoryWebApiModule.forRoot(HeroData) // AppRoutingModule TODO: add routes ], From c205516f0da61b5071d2371b3d52cf93075e87aa Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Jul 2018 16:39:23 +0200 Subject: [PATCH 562/582] docs: refactor ngmodules example (#25072) PR Close #25072 --- aio/content/examples/ngmodules/src/app/app.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/aio/content/examples/ngmodules/src/app/app.module.ts b/aio/content/examples/ngmodules/src/app/app.module.ts index a19fbbae52..cad301d980 100644 --- a/aio/content/examples/ngmodules/src/app/app.module.ts +++ b/aio/content/examples/ngmodules/src/app/app.module.ts @@ -1,7 +1,5 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpModule } from '@angular/http'; /* App Root */ import { AppComponent } from './app.component'; From d4ac9698ba7dd829600ca4c8129fcbeae9cb4bed Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 24 Jul 2018 22:11:30 -0700 Subject: [PATCH 563/582] Revert "docs: refactor style guide example 03-06 (#24996)" This reverts commit 65e18dc1bf1c4390e3c61af1e63c04c37b6c107d. --- .../app/heroes/shared/hero.service.avoid.ts | 12 ++++++------ .../src/03-06/app/heroes/shared/hero.service.ts | 17 ++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts index 9f125bbf0e..3b983f5bda 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts @@ -3,7 +3,7 @@ /* avoid */ import { ExceptionService, SpinnerService, ToastService } from '../../core'; -import { HttpClient } from '@angular/common/http'; +import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; import { Hero } from './hero.model'; @@ -16,17 +16,17 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: HttpClient + private http: Http ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response.data as Hero)); + return this.http.get(`api/heroes/${id}`).pipe( + map(response => response.json().data as Hero)); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response.data as Hero[])); + return this.http.get(`api/heroes`).pipe( + map(response => response.json().data as Hero[])); } } diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts index 573e847091..0a50c11bb4 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts @@ -1,12 +1,11 @@ // #docregion // #docregion example -import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { map } from 'rxjs/operators'; +import { Http } from '@angular/http'; +import { map } from 'rxjs/operators'; -import { ExceptionService, SpinnerService, ToastService } from '../../core'; import { Hero } from './hero.model'; - +import { ExceptionService, SpinnerService, ToastService } from '../../core'; // #enddocregion example @Injectable() @@ -17,17 +16,17 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: HttpClient + private http: Http ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response as Hero)); + return this.http.get(`api/heroes/${id}`).pipe( + map(response => response.json() as Hero)); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response as Hero[])); + return this.http.get(`api/heroes`).pipe( + map(response => response.json() as Hero[])); } } From cf81823b07a1388fda456448804f737fa3e9f0f6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Jul 2018 13:44:47 +0200 Subject: [PATCH 564/582] docs: refactor style guide example 03-06 (#24996) docs: refactor style guide example 03-06 docs: refactor style guide example 03-06 docs: refactor style guide example 03-06 PR Close #24996 --- .../03-06/app/heroes/shared/hero.service.avoid.ts | 12 ++++-------- .../src/03-06/app/heroes/shared/hero.service.ts | 15 ++++++--------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts index 3b983f5bda..fea2e49d99 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.avoid.ts @@ -3,9 +3,8 @@ /* avoid */ import { ExceptionService, SpinnerService, ToastService } from '../../core'; -import { Http } from '@angular/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { map } from 'rxjs/operators'; import { Hero } from './hero.model'; // #enddocregion example @@ -16,18 +15,15 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: Http + private http: HttpClient ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response.json().data as Hero)); + return this.http.get(`api/heroes/${id}`); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response.json().data as Hero[])); + return this.http.get(`api/heroes`); } } - diff --git a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts index 0a50c11bb4..0dd5931a7a 100644 --- a/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts +++ b/aio/content/examples/styleguide/src/03-06/app/heroes/shared/hero.service.ts @@ -1,11 +1,11 @@ // #docregion // #docregion example +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; -import { map } from 'rxjs/operators'; -import { Hero } from './hero.model'; import { ExceptionService, SpinnerService, ToastService } from '../../core'; +import { Hero } from './hero.model'; + // #enddocregion example @Injectable() @@ -16,18 +16,15 @@ export class HeroService { private exceptionService: ExceptionService, private spinnerService: SpinnerService, private toastService: ToastService, - private http: Http + private http: HttpClient ) { } getHero(id: number) { - return this.http.get(`api/heroes/${id}`).pipe( - map(response => response.json() as Hero)); + return this.http.get(`api/heroes/${id}`); } getHeroes() { - return this.http.get(`api/heroes`).pipe( - map(response => response.json() as Hero[])); + return this.http.get(`api/heroes`); } } - From 0bcf20c9fa7273371d053ffe1ef91451714b592d Mon Sep 17 00:00:00 2001 From: Johan CHOUQUET <1401825+johanchouquet@users.noreply.github.com> Date: Thu, 8 Mar 2018 16:37:26 +0100 Subject: [PATCH 565/582] docs(animations): typo fix in the comments (#22652) PR Close #22652 --- packages/animations/src/animation_builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/animations/src/animation_builder.ts b/packages/animations/src/animation_builder.ts index 0bc5f1404d..ba6c6ec2ac 100644 --- a/packages/animations/src/animation_builder.ts +++ b/packages/animations/src/animation_builder.ts @@ -13,7 +13,7 @@ import {AnimationPlayer} from './players/animation_player'; * BrowserAnimationsModule BrowserAnimationsModule} or {@link NoopAnimationsModule * NoopAnimationsModule} modules are used within an application. * - * The purpose if this service is to produce an animation sequence programmatically within an + * The purpose of this service is to produce an animation sequence programmatically within an * angular component or directive. * * Programmatic animations are first built and then a player is created when the build animation is From 1e28495c898d531db15a8ac26c6c6a31cebcafb9 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 24 Jul 2018 11:56:35 +0200 Subject: [PATCH 566/582] fix(ivy): update compiler with latest runtime for view queries (#25061) PR Close #25061 --- .../compliance/r3_compiler_compliance_spec.ts | 10 ++- .../compiler/src/render3/view/compiler.ts | 87 ++++++++++++------- .../compiler/src/render3/view/template.ts | 44 +++------- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts index 3d549e7665..049c9ba8d9 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -963,16 +963,20 @@ describe('compiler compliance', () => { type: ViewQueryComponent, selectors: [["view-query-component"]], factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, - template: function ViewQueryComponent_Template(rf, ctx) { - var $tmp$; + viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { $r3$.ɵQ(0, SomeDirective, true); - $r3$.ɵEe(1, "div", $e0_attrs$); } if (rf & 2) { + var $tmp$; ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first)); } }, + template: function ViewQueryComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵEe(1, "div", $e0_attrs$); + } + }, directives:[SomeDirective] });`; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index f008fa572e..4e6bd46be8 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -27,8 +27,8 @@ import {Render3ParseResult} from '../r3_template_transform'; import {typeWithParameters} from '../util'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api'; -import {BindingScope, TemplateDefinitionBuilder} from './template'; -import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; +import {BindingScope, TemplateDefinitionBuilder, renderFlagCheckIfStmt} from './template'; +import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util'; function baseDirectiveFields( meta: R3DirectiveMetadata, constantPool: ConstantPool, @@ -138,6 +138,10 @@ export function compileComponentFromMetadata( directiveMatcher = matcher; } + if (meta.viewQueries.length) { + definitionMap.set('viewQuery', createViewQueriesFunction(meta, constantPool)); + } + // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` const templateTypeName = meta.name; const templateName = templateTypeName ? `${templateTypeName}_Template` : null; @@ -322,32 +326,22 @@ function selectorsFromGlobalMetadata( return o.NULL_EXPR; } -/** - * - * @param meta - * @param constantPool - */ -function createQueryDefinitions( - queries: R3QueryMetadata[], constantPool: ConstantPool): o.Expression[]|undefined { - const queryDefinitions: o.Expression[] = []; - for (let i = 0; i < queries.length; i++) { - const query = queries[i]; - const predicate = getQueryPredicate(query, constantPool); +function createQueryDefinition( + query: R3QueryMetadata, constantPool: ConstantPool, idx: number | null): o.Expression { + const predicate = getQueryPredicate(query, constantPool); - // e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false) - const parameters = [ - o.literal(null, o.INFERRED_TYPE), - predicate, - o.literal(query.descendants), - ]; + // e.g. r3.Q(null, somePredicate, false) or r3.Q(0, ['div'], false) + const parameters = [ + o.literal(idx, o.INFERRED_TYPE), + predicate, + o.literal(query.descendants), + ]; - if (query.read) { - parameters.push(query.read); - } - - queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); + if (query.read) { + parameters.push(query.read); } - return queryDefinitions.length > 0 ? queryDefinitions : undefined; + + return o.importExpr(R3.query).callFn(parameters); } // Turn a directive selector into an R3-compatible selector for directive def @@ -371,11 +365,10 @@ function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null // Return a contentQueries function or null if one is not necessary. function createContentQueriesFunction( meta: R3DirectiveMetadata, constantPool: ConstantPool): o.Expression|null { - const queryDefinitions = createQueryDefinitions(meta.queries, constantPool); - - if (queryDefinitions) { - const statements: o.Statement[] = queryDefinitions.map((qd: o.Expression) => { - return o.importExpr(R3.registerContentQuery).callFn([qd]).toStmt(); + if (meta.queries.length) { + const statements: o.Statement[] = meta.queries.map((query: R3QueryMetadata) => { + const queryDefinition = createQueryDefinition(query, constantPool, null); + return o.importExpr(R3.registerContentQuery).callFn([queryDefinition]).toStmt(); }); const typeName = meta.name; return o.fn( @@ -426,6 +419,40 @@ function createContentQueriesRefreshFunction(meta: R3DirectiveMetadata): o.Expre return null; } +// Define and update any view queries +function createViewQueriesFunction( + meta: R3ComponentMetadata, constantPool: ConstantPool): o.Expression { + const createStatements: o.Statement[] = []; + const updateStatements: o.Statement[] = []; + const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME); + + for (let i = 0; i < meta.viewQueries.length; i++) { + const query = meta.viewQueries[i]; + + // creation, e.g. r3.Q(0, somePredicate, true); + const queryDefinition = createQueryDefinition(query, constantPool, i); + createStatements.push(queryDefinition.toStmt()); + + // update, e.g. (r3.qR(tmp = r3.ɵld(0)) && (ctx.someDir = tmp)); + const temporary = tempAllocator(); + const getQueryList = o.importExpr(R3.load).callFn([o.literal(i)]); + const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]); + const updateDirective = o.variable(CONTEXT_NAME) + .prop(query.propertyName) + .set(query.first ? temporary.prop('first') : temporary); + updateStatements.push(refresh.and(updateDirective).toStmt()); + } + + const viewQueryFnName = meta.name ? `${meta.name}_Query` : null; + return o.fn( + [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)], + [ + renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements), + renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements) + ], + o.INFERRED_TYPE, null, viewQueryFnName); +} + // Return a host binding function or null if one is not necessary. function createHostBindingsFunction( meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index a2a937d7ea..cdb8f76452 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -46,6 +46,12 @@ function mapBindingToInstruction(type: BindingType): o.ExternalReference|undefin } } +// if (rf & flags) { .. } +export function renderFlagCheckIfStmt( + flags: core.RenderFlags, statements: o.Statement[]): o.IfStmt { + return o.ifStmt(o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(flags), null, false), statements); +} + export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _dataIndex = 0; private _bindingContext = 0; @@ -54,7 +60,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private _variableCode: o.Statement[] = []; private _bindingCode: o.Statement[] = []; private _postfixCode: o.Statement[] = []; - private _temporary = temporaryAllocator(this._prefixCode, TEMPORARY_NAME); private _valueConverter: ValueConverter; private _unsupported = unsupported; private _bindingScope: BindingScope; @@ -75,6 +80,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private directiveMatcher: SelectorMatcher|null, private directives: Set, private pipeTypeByName: Map, private pipes: Set, private _namespace: o.ExternalReference) { + // view queries can take up space in data and allocation happens earlier (in the "viewQuery" + // function) + this._dataIndex = viewQueries.length; this._bindingScope = parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => { this._bindingCode.push( @@ -127,32 +135,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.instruction(this._creationCode, null, R3.projectionDef, ...parameters); } - // Define and update any view queries - for (let query of this.viewQueries) { - // e.g. r3.Q(0, somePredicate, true); - const querySlot = this.allocateDataSlot(); - const predicate = getQueryPredicate(query, this.constantPool); - const args: o.Expression[] = [ - o.literal(querySlot, o.INFERRED_TYPE), - predicate, - o.literal(query.descendants, o.INFERRED_TYPE), - ]; - - if (query.read) { - args.push(query.read); - } - this.instruction(this._creationCode, null, R3.query, ...args); - - // (r3.qR(tmp = r3.ɵld(0)) && (ctx.someDir = tmp)); - const temporary = this._temporary(); - const getQueryList = o.importExpr(R3.load).callFn([o.literal(querySlot)]); - const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]); - const updateDirective = o.variable(CONTEXT_NAME) - .prop(query.propertyName) - .set(query.first ? temporary.prop('first') : temporary); - this._bindingCode.push(refresh.and(updateDirective).toStmt()); - } - t.visitAll(this, nodes); if (this._pureFunctionSlots > 0) { @@ -161,15 +143,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } const creationCode = this._creationCode.length > 0 ? - [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), - this._creationCode)] : + [renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] : []; const updateCode = this._bindingCode.length > 0 ? - [o.ifStmt( - o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Update), null, false), - this._bindingCode)] : + [renderFlagCheckIfStmt(core.RenderFlags.Update, this._bindingCode)] : []; // Generate maps of placeholder name to node indexes From 968f153491a57effca41299174d10c6466d5131a Mon Sep 17 00:00:00 2001 From: Adrien Samson Date: Fri, 23 Feb 2018 10:24:51 +0100 Subject: [PATCH 567/582] fix(router): Fix _lastPathIndex in deeply nested empty paths (#22394) PR Close #22394 --- packages/router/src/recognize.ts | 34 +++++++++++++------- packages/router/src/router.ts | 7 +++- packages/router/src/router_module.ts | 34 ++++++++++++++++++++ packages/router/test/recognize.spec.ts | 39 +++++++++++++++++++++++ tools/public_api_guard/router/router.d.ts | 2 ++ 5 files changed, 103 insertions(+), 13 deletions(-) diff --git a/packages/router/src/recognize.ts b/packages/router/src/recognize.ts index b6eeded31a..6c3f8b327f 100644 --- a/packages/router/src/recognize.ts +++ b/packages/router/src/recognize.ts @@ -20,20 +20,24 @@ class NoMatch {} export function recognize( rootComponentType: Type| null, config: Routes, urlTree: UrlTree, url: string, - paramsInheritanceStrategy: ParamsInheritanceStrategy = - 'emptyOnly'): Observable { - return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy) + paramsInheritanceStrategy: ParamsInheritanceStrategy = 'emptyOnly', + relativeLinkResolution: 'legacy' | 'corrected' = 'legacy'): Observable { + return new Recognizer( + rootComponentType, config, urlTree, url, paramsInheritanceStrategy, + relativeLinkResolution) .recognize(); } class Recognizer { constructor( private rootComponentType: Type|null, private config: Routes, private urlTree: UrlTree, - private url: string, private paramsInheritanceStrategy: ParamsInheritanceStrategy) {} + private url: string, private paramsInheritanceStrategy: ParamsInheritanceStrategy, + private relativeLinkResolution: 'legacy'|'corrected') {} recognize(): Observable { try { - const rootSegmentGroup = split(this.urlTree.root, [], [], this.config).segmentGroup; + const rootSegmentGroup = + split(this.urlTree.root, [], [], this.config, this.relativeLinkResolution).segmentGroup; const children = this.processSegmentGroup(this.config, rootSegmentGroup, PRIMARY_OUTLET); @@ -134,8 +138,8 @@ class Recognizer { const childConfig: Route[] = getChildConfig(route); - const {segmentGroup, slicedSegments} = - split(rawSegment, consumedSegments, rawSlicedSegments, childConfig); + const {segmentGroup, slicedSegments} = split( + rawSegment, consumedSegments, rawSlicedSegments, childConfig, this.relativeLinkResolution); if (slicedSegments.length === 0 && segmentGroup.hasChildren()) { const children = this.processChildren(childConfig, segmentGroup); @@ -232,7 +236,7 @@ function getPathIndexShift(segmentGroup: UrlSegmentGroup): number { function split( segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[], - config: Route[]) { + config: Route[], relativeLinkResolution: 'legacy' | 'corrected') { if (slicedSegments.length > 0 && containsEmptyPathMatchesWithNamedOutlets(segmentGroup, slicedSegments, config)) { const s = new UrlSegmentGroup( @@ -248,7 +252,8 @@ function split( containsEmptyPathMatches(segmentGroup, slicedSegments, config)) { const s = new UrlSegmentGroup( segmentGroup.segments, addEmptyPathsToChildrenIfNeeded( - segmentGroup, slicedSegments, config, segmentGroup.children)); + segmentGroup, consumedSegments, slicedSegments, config, + segmentGroup.children, relativeLinkResolution)); s._sourceSegment = segmentGroup; s._segmentIndexShift = consumedSegments.length; return {segmentGroup: s, slicedSegments}; @@ -261,14 +266,19 @@ function split( } function addEmptyPathsToChildrenIfNeeded( - segmentGroup: UrlSegmentGroup, slicedSegments: UrlSegment[], routes: Route[], - children: {[name: string]: UrlSegmentGroup}): {[name: string]: UrlSegmentGroup} { + segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[], + routes: Route[], children: {[name: string]: UrlSegmentGroup}, + relativeLinkResolution: 'legacy' | 'corrected'): {[name: string]: UrlSegmentGroup} { const res: {[name: string]: UrlSegmentGroup} = {}; for (const r of routes) { if (emptyPathMatch(segmentGroup, slicedSegments, r) && !children[getOutlet(r)]) { const s = new UrlSegmentGroup([], {}); s._sourceSegment = segmentGroup; - s._segmentIndexShift = segmentGroup.segments.length; + if (relativeLinkResolution === 'legacy') { + s._segmentIndexShift = segmentGroup.segments.length; + } else { + s._segmentIndexShift = consumedSegments.length; + } res[getOutlet(r)] = s; } } diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index cd8fb98aa4..8ce079e304 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -297,6 +297,11 @@ export class Router { */ urlUpdateStrategy: 'deferred'|'eager' = 'deferred'; + /** + * See {@link RouterModule} for more information. + */ + relativeLinkResolution: 'legacy'|'corrected' = 'legacy'; + /** * Creates the router service. */ @@ -676,7 +681,7 @@ export class Router { urlAndSnapshot$ = redirectsApplied$.pipe(mergeMap((appliedUrl: UrlTree) => { return recognize( this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl), - this.paramsInheritanceStrategy) + this.paramsInheritanceStrategy, this.relativeLinkResolution) .pipe(map((snapshot: any) => { (this.events as Subject) .next(new RoutesRecognized( diff --git a/packages/router/src/router_module.ts b/packages/router/src/router_module.ts index 7566ee1400..384f7e987a 100644 --- a/packages/router/src/router_module.ts +++ b/packages/router/src/router_module.ts @@ -417,6 +417,36 @@ export interface ExtraOptions { * - `'eager'`, updates browser URL at the beginning of navigation. */ urlUpdateStrategy?: 'deferred'|'eager'; + + /** + * Enables a bug fix that corrects relative link resolution in components with empty paths. + * Example: + * + * ``` + * const routes = [ + * { + * path: '', + * component: ContainerComponent, + * children: [ + * { path: 'a', component: AComponent }, + * { path: 'b', component: BComponent }, + * ] + * } + * ]; + * ``` + * + * From the `ContainerComponent`, this will not work: + * + * `Link to A` + * + * However, this will work: + * + * `Link to A` + * + * In other words, you're required to use `../` rather than `./`. The current default in v6 + * is `legacy`, and this option will be removed in v7 to default to the corrected behavior. + */ + relativeLinkResolution?: 'legacy'|'corrected'; } export function setupRouter( @@ -465,6 +495,10 @@ export function setupRouter( router.urlUpdateStrategy = opts.urlUpdateStrategy; } + if (opts.relativeLinkResolution) { + router.relativeLinkResolution = opts.relativeLinkResolution; + } + return router; } diff --git a/packages/router/test/recognize.spec.ts b/packages/router/test/recognize.spec.ts index b2537d13c6..0494f2fe66 100644 --- a/packages/router/test/recognize.spec.ts +++ b/packages/router/test/recognize.spec.ts @@ -452,6 +452,45 @@ describe('recognize', () => { }); }); + it('should set url segment and index properly with the "corrected" option for nested empty-path segments', + () => { + const url = tree('a/b') as any; + recognize( + RootComponent, [{ + path: 'a', + children: [{ + path: 'b', + component: ComponentB, + children: [{ + path: '', + component: ComponentC, + children: [{path: '', component: ComponentD}] + }] + }] + }], + url, 'a/b', 'emptyOnly', 'corrected') + .forEach((s: any) => { + expect(s.root._urlSegment).toBe(url.root); + expect(s.root._lastPathIndex).toBe(-1); + + const a = s.firstChild(s.root) !; + expect(a._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); + expect(a._lastPathIndex).toBe(0); + + const b = s.firstChild(a) !; + expect(b._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); + expect(b._lastPathIndex).toBe(1); + + const c = s.firstChild(b) !; + expect(c._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); + expect(c._lastPathIndex).toBe(1); + + const d = s.firstChild(c) !; + expect(d._urlSegment).toBe(url.root.children[PRIMARY_OUTLET]); + expect(d._lastPathIndex).toBe(1); + }); + }); + it('should set url segment and index properly when nested empty-path segments (2)', () => { const url = tree(''); recognize( diff --git a/tools/public_api_guard/router/router.d.ts b/tools/public_api_guard/router/router.d.ts index 6575a15279..c97c290390 100644 --- a/tools/public_api_guard/router/router.d.ts +++ b/tools/public_api_guard/router/router.d.ts @@ -119,6 +119,7 @@ export interface ExtraOptions { onSameUrlNavigation?: 'reload' | 'ignore'; paramsInheritanceStrategy?: 'emptyOnly' | 'always'; preloadingStrategy?: any; + relativeLinkResolution?: 'legacy' | 'corrected'; scrollOffset?: [number, number] | (() => [number, number]); scrollPositionRestoration?: 'disabled' | 'enabled' | 'top'; urlUpdateStrategy?: 'deferred' | 'eager'; @@ -320,6 +321,7 @@ export declare class Router { navigated: boolean; onSameUrlNavigation: 'reload' | 'ignore'; paramsInheritanceStrategy: 'emptyOnly' | 'always'; + relativeLinkResolution: 'legacy' | 'corrected'; routeReuseStrategy: RouteReuseStrategy; readonly routerState: RouterState; readonly url: string; From e1c6fd5453bfb3f55863be07ded82376f86b7e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Wed, 25 Jul 2018 10:19:44 -0700 Subject: [PATCH 568/582] Revert "feat(core): add support for using async/await with Jasmine" (#25096) This reverts commit f6829aba55e07609e312b4f67dbc9dbbf36e4e46. PR Close #25096 --- .../test/metadata/resource_loading_spec.ts | 120 +++++++++--------- .../test/testability/jasmine_await_spec.ts | 31 ----- packages/core/testing/src/jasmine_await.ts | 34 ----- packages/core/testing/src/testing.ts | 1 - tools/public_api_guard/core/testing.d.ts | 5 - 5 files changed, 59 insertions(+), 132 deletions(-) delete mode 100644 packages/core/test/testability/jasmine_await_spec.ts delete mode 100644 packages/core/testing/src/jasmine_await.ts diff --git a/packages/core/test/metadata/resource_loading_spec.ts b/packages/core/test/metadata/resource_loading_spec.ts index 5ee74007ce..2c15e02a90 100644 --- a/packages/core/test/metadata/resource_loading_spec.ts +++ b/packages/core/test/metadata/resource_loading_spec.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {jasmineAwait} from '@angular/core/testing'; - import {Component} from '../../src/core'; import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading'; import {ComponentType} from '../../src/render3/interfaces/definition'; @@ -60,59 +58,59 @@ Did you run and wait for 'resolveComponentResources()'?`.trim()); } beforeEach(() => resourceFetchCount = 0); - it('should resolve template', jasmineAwait(async() => { - const MyComponent: ComponentType = (class MyComponent{}) as any; - const metadata: Component = {templateUrl: 'test://content'}; - compileComponent(MyComponent, metadata); - await resolveComponentResources(testResolver); - expect(MyComponent.ngComponentDef).toBeDefined(); - expect(metadata.templateUrl).toBe(undefined); - expect(metadata.template).toBe('content'); - expect(resourceFetchCount).toBe(1); - })); + it('should resolve template', async() => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const metadata: Component = {templateUrl: 'test://content'}; + compileComponent(MyComponent, metadata); + await resolveComponentResources(testResolver); + expect(MyComponent.ngComponentDef).toBeDefined(); + expect(metadata.templateUrl).toBe(undefined); + expect(metadata.template).toBe('content'); + expect(resourceFetchCount).toBe(1); + }); - it('should resolve styleUrls', jasmineAwait(async() => { - const MyComponent: ComponentType = (class MyComponent{}) as any; - const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style2']}; - compileComponent(MyComponent, metadata); - await resolveComponentResources(testResolver); - expect(MyComponent.ngComponentDef).toBeDefined(); - expect(metadata.styleUrls).toBe(undefined); - expect(metadata.styles).toEqual(['style1', 'style2']); - expect(resourceFetchCount).toBe(2); - })); + it('should resolve styleUrls', async() => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style2']}; + compileComponent(MyComponent, metadata); + await resolveComponentResources(testResolver); + expect(MyComponent.ngComponentDef).toBeDefined(); + expect(metadata.styleUrls).toBe(undefined); + expect(metadata.styles).toEqual(['style1', 'style2']); + expect(resourceFetchCount).toBe(2); + }); - it('should cache multiple resolution to same URL', jasmineAwait(async() => { - const MyComponent: ComponentType = (class MyComponent{}) as any; - const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style1']}; - compileComponent(MyComponent, metadata); - await resolveComponentResources(testResolver); - expect(MyComponent.ngComponentDef).toBeDefined(); - expect(metadata.styleUrls).toBe(undefined); - expect(metadata.styles).toEqual(['style1', 'style1']); - expect(resourceFetchCount).toBe(1); - })); + it('should cache multiple resolution to same URL', async() => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style1']}; + compileComponent(MyComponent, metadata); + await resolveComponentResources(testResolver); + expect(MyComponent.ngComponentDef).toBeDefined(); + expect(metadata.styleUrls).toBe(undefined); + expect(metadata.styles).toEqual(['style1', 'style1']); + expect(resourceFetchCount).toBe(1); + }); - it('should keep order even if the resolution is out of order', jasmineAwait(async() => { - const MyComponent: ComponentType = (class MyComponent{}) as any; - const metadata: Component = { - template: '', - styles: ['existing'], - styleUrls: ['test://style1', 'test://style2'] - }; - compileComponent(MyComponent, metadata); - const resolvers: any[] = []; - const resolved = resolveComponentResources( - (url) => new Promise((resolve, response) => resolvers.push(url, resolve))); - // Out of order resolution - expect(resolvers[0]).toEqual('test://style1'); - expect(resolvers[2]).toEqual('test://style2'); - resolvers[3]('second'); - resolvers[1]('first'); - await resolved; - expect(metadata.styleUrls).toBe(undefined); - expect(metadata.styles).toEqual(['existing', 'first', 'second']); - })); + it('should keep order even if the resolution is out of order', async() => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const metadata: Component = { + template: '', + styles: ['existing'], + styleUrls: ['test://style1', 'test://style2'] + }; + compileComponent(MyComponent, metadata); + const resolvers: any[] = []; + const resolved = resolveComponentResources( + (url) => new Promise((resolve, response) => resolvers.push(url, resolve))); + // Out of order resolution + expect(resolvers[0]).toEqual('test://style1'); + expect(resolvers[2]).toEqual('test://style2'); + resolvers[3]('second'); + resolvers[1]('first'); + await resolved; + expect(metadata.styleUrls).toBe(undefined); + expect(metadata.styles).toEqual(['existing', 'first', 'second']); + }); }); @@ -123,14 +121,14 @@ Did you run and wait for 'resolveComponentResources()'?`.trim()); } as any as Response); } - it('should work with fetch', jasmineAwait(async() => { - const MyComponent: ComponentType = (class MyComponent{}) as any; - const metadata: Component = {templateUrl: 'test://content'}; - compileComponent(MyComponent, metadata); - await resolveComponentResources(fetch); - expect(MyComponent.ngComponentDef).toBeDefined(); - expect(metadata.templateUrl).toBe(undefined); - expect(metadata.template).toBe('response for test://content'); - })); + it('should work with fetch', async() => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const metadata: Component = {templateUrl: 'test://content'}; + compileComponent(MyComponent, metadata); + await resolveComponentResources(fetch); + expect(MyComponent.ngComponentDef).toBeDefined(); + expect(metadata.templateUrl).toBe(undefined); + expect(metadata.template).toBe('response for test://content'); + }); }); }); diff --git a/packages/core/test/testability/jasmine_await_spec.ts b/packages/core/test/testability/jasmine_await_spec.ts deleted file mode 100644 index e4da8fb785..0000000000 --- a/packages/core/test/testability/jasmine_await_spec.ts +++ /dev/null @@ -1,31 +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 {jasmineAwait} from '../../testing'; - - -describe('await', () => { - let pass: boolean; - beforeEach(() => pass = false); - afterEach(() => expect(pass).toBe(true)); - - it('should convert passes', jasmineAwait(async() => { pass = await Promise.resolve(true); })); - - it('should convert failures', (done) => { - const error = new Error(); - const fakeDone: DoneFn = function() { fail('passed, but should have failed'); } as any; - fakeDone.fail = function(value: any) { - expect(value).toBe(error); - done(); - }; - jasmineAwait(async() => { - pass = await Promise.resolve(true); - throw error; - })(fakeDone); - }); -}); \ No newline at end of file diff --git a/packages/core/testing/src/jasmine_await.ts b/packages/core/testing/src/jasmine_await.ts deleted file mode 100644 index a863e62149..0000000000 --- a/packages/core/testing/src/jasmine_await.ts +++ /dev/null @@ -1,34 +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 - */ - -/** - * Converts an `async` function, with `await`, into a function which is compatible with Jasmine test - * framework. - * - * For asynchronous function blocks, Jasmine expects `it` (and friends) to take a function which - * takes a `done` callback. (Jasmine does not understand functions which return `Promise`.) The - * `jasmineAwait()` wrapper converts the test function returning `Promise` into a function which - * Jasmine understands. - * - * - * Example: - * ``` - * it('...', jasmineAwait(async() => { - * doSomething(); - * await asyncFn(); - * doSomethingAfter(); - * })); - * ``` - * - */ -export function jasmineAwait(fn: () => Promise): - (done: {(): void; fail: (message?: Error | string) => void;}) => void { - return function(done: {(): void; fail: (message?: Error | string) => void;}) { - fn().then(done, done.fail); - }; -} diff --git a/packages/core/testing/src/testing.ts b/packages/core/testing/src/testing.ts index c1ea067aa3..d2d9476c80 100644 --- a/packages/core/testing/src/testing.ts +++ b/packages/core/testing/src/testing.ts @@ -13,7 +13,6 @@ */ export * from './async'; -export * from './jasmine_await'; export * from './component_fixture'; export * from './fake_async'; export * from './test_bed'; diff --git a/tools/public_api_guard/core/testing.d.ts b/tools/public_api_guard/core/testing.d.ts index ce962ef4bb..f7676a5f27 100644 --- a/tools/public_api_guard/core/testing.d.ts +++ b/tools/public_api_guard/core/testing.d.ts @@ -53,11 +53,6 @@ export declare class InjectSetupWrapper { inject(tokens: any[], fn: Function): () => any; } -export declare function jasmineAwait(fn: () => Promise): (done: { - (): void; - fail: (message?: Error | string) => void; -}) => void; - /** @experimental */ export declare type MetadataOverride = { add?: Partial; From 48d7205873f13c9a8ea417364d59e23b46478c79 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 25 Jul 2018 14:23:05 -0700 Subject: [PATCH 569/582] release: cut the v6.1.0 release --- CHANGELOG.md | 224 ++++++++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 87 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f330f65f..ca772990c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,38 +1,118 @@ - -# [6.1.0-rc.3](https://github.com/angular/angular/compare/6.1.0-rc.2...6.1.0-rc.3) (2018-07-19) - + +# [6.1.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0) (2018-07-25) ### Bug Fixes +* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0)) +* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4)) +* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687)) +* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e)) +* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25)) +* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4)) +* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d)) +* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351)) +* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41)) * **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384) * **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([0b4d85e](https://github.com/angular/angular/commit/0b4d85e)), closes [#24831](https://github.com/angular/angular/issues/24831) * **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155) * **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609) +* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384) +* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155) +* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609) +* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584)) * **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f)) * **compiler:** i18n_extractor now outputs the correct source file name ([#24885](https://github.com/angular/angular/issues/24885)) ([c8ad965](https://github.com/angular/angular/commit/c8ad965)), closes [#24884](https://github.com/angular/angular/issues/24884) +* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f)) +* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363) +* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930)) +* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d)) +* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013) +* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5)) * **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3)) +* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3)) +* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f)) +* **core:** stop reusing provider definitions across NgModuleRef instances ([#25022](https://github.com/angular/angular/issues/25022)) ([6b859da](https://github.com/angular/angular/commit/6b859da)), closes [#25018](https://github.com/angular/angular/issues/25018) * **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261)) * **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939) +* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261)) +* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939) +* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d)) +* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45)) +* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818) +* **docs-infra:** fix table header layout in API pages ([#24919](https://github.com/angular/angular/issues/24919)) ([3cd9645](https://github.com/angular/angular/commit/3cd9645)) +* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36)) +* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24)) +* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195) +* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420) * **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420) * **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea)) * **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d)) * **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc)) +* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea)) +* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d)) +* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc)) +* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8)) +* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023) +* **platform-server:** don't reflect innerHTML property to attribute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278) +* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133) +* **router:** Fix _lastPathIndex in deeply nested empty paths ([#22394](https://github.com/angular/angular/issues/22394)) ([968f153](https://github.com/angular/angular/commit/968f153)) * **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468) +* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468) +* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981) +* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617) +* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474) +* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680)) +* **service-worker:** don't include sourceMappingURL in ngsw-worker ([#24877](https://github.com/angular/angular/issues/24877)) ([8620373](https://github.com/angular/angular/commit/8620373)), closes [#23596](https://github.com/angular/angular/issues/23596) * **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9)) - +* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9)) +* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095) +* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196) +* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e)) +* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526) +* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2)) ### Features * **bazel:** Initial commit of protractor_web_test_suite ([#24787](https://github.com/angular/angular/issues/24787)) ([71e0df0](https://github.com/angular/angular/commit/71e0df0)) * **bazel:** protractor_web_test_suite for release ([#24787](https://github.com/angular/angular/issues/24787)) ([161ff5c](https://github.com/angular/angular/commit/161ff5c)) +* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7)) +* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e)) +* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67)) +* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c)) +* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27)) +* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5)) * **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977)) * **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6)) -* **router:** add urlUpdateStrategy allow updating the browser URL at the beginning of navigation ([#24820](https://github.com/angular/angular/issues/24820)) ([328971f](https://github.com/angular/angular/commit/328971f)), closes [#24616](https://github.com/angular/angular/issues/24616) +* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977)) +* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6)) +(https://github.com/angular/angular/commit/328971f)), closes [#24616](https://github.com/angular/angular/issues/24616) +* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d)) +* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce)) +* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb)) +* **router:** add urlUpdateStrategy allow updating the browser URL at the beginning of navigation ([#24820](https://github.com/angular/angular/issues/24820)) ([328971f] +* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202) +* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595) * **service-worker:** add support for `?` in SW config globbing ([#24105](https://github.com/angular/angular/issues/24105)) ([250527c](https://github.com/angular/angular/commit/250527c)) +* typescript 2.9 support ([#24652](https://github.com/angular/angular/issues/24652)) ([e3064d5](https://github.com/angular/angular/commit/e3064d5)) +### build -Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC.3 instead. Sorry! :-) +* **bazel:** turn on preserve-symlinks ([#24881](https://github.com/angular/angular/issues/24881)) ([c438b5e](https://github.com/angular/angular/commit/c438b5e)) +### BREAKING CHANGES + +* **bazel:** Use of @angular/bazel rules now requires calling ng_setup_workspace() in your WORKSPACE file. + +For example: + + local_repository( + name = "angular", + path = "node_modules/@angular/bazel", + ) + + load("@angular//:index.bzl", "ng_setup_workspace") + + ng_setup_workspace() ## [6.0.9](https://github.com/angular/angular/compare/6.0.8...6.0.9) (2018-07-11) @@ -42,37 +122,6 @@ Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC. * **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([3c93d07](https://github.com/angular/angular/commit/3c93d07)), closes [#24831](https://github.com/angular/angular/issues/24831) - - -# [6.1.0-rc.0](https://github.com/angular/angular/compare/6.1.0-beta.3...6.1.0-rc.0) (2018-07-11) - - -### Bug Fixes - -* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384) -* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155) -* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609) -* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f)) -* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3)) -* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261)) -* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939) -* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420) -* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea)) -* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d)) -* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc)) -* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468) -* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9)) - - -### Features - -* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977)) -* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6)) -* typescript 2.9 support ([#24652](https://github.com/angular/angular/issues/24652)) ([e3064d5](https://github.com/angular/angular/commit/e3064d5)) -* **service-worker:** add support for `?` in SW config globbing ([#24105](https://github.com/angular/angular/issues/24105)) ([250527c](https://github.com/angular/angular/commit/250527c)) - - - ## [6.0.8](https://github.com/angular/angular/compare/6.0.7...6.0.8) (2018-07-11) @@ -93,15 +142,6 @@ Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC. * **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([6c55a13](https://github.com/angular/angular/commit/6c55a13)) - - -# [6.1.0-beta.3](https://github.com/angular/angular/compare/6.1.0-beta.2...6.1.0-beta.3) (2018-06-28) - -### Bug Fixes - -* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4)) -* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584)) - ## [6.0.7](https://github.com/angular/angular/compare/6.0.6...6.0.7) (2018-06-27) @@ -111,18 +151,6 @@ Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC. * **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4)) * **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584)) - - - -# [6.1.0-beta.2](https://github.com/angular/angular/compare/6.1.0-beta.1...6.1.0-beta.2) (2018-06-20) - - -### Bug Fixes - -* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363) -* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d)) - - ## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20) @@ -132,39 +160,6 @@ Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC. * **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([e543c73](https://github.com/angular/angular/commit/e543c73)), closes [#20363](https://github.com/angular/angular/issues/20363) * **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([f5b3661](https://github.com/angular/angular/commit/f5b3661)) - - - -# [6.1.0-beta.1](https://github.com/angular/angular/compare/6.1.0-beta.0...6.1.0-beta.1) (2018-06-13) - - -### Bug Fixes - -* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0)) -* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351)) -* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([8be6892](https://github.com/angular/angular/commit/8be6892)), closes [#23647](https://github.com/angular/angular/issues/23647) -* **ivy:** compute transitive scopes from NgModuleDef only ([#24334](https://github.com/angular/angular/issues/24334)) ([1135563](https://github.com/angular/angular/commit/1135563)) -* **ivy:** correctly handle queries with embedded views ([#24418](https://github.com/angular/angular/issues/24418)) ([014949f](https://github.com/angular/angular/commit/014949f)) -* **ivy:** remove debugger statement ([#24480](https://github.com/angular/angular/issues/24480)) ([70ef061](https://github.com/angular/angular/commit/70ef061)) -* **ivy:** special case [style] and [class] bindings for future use ([#23232](https://github.com/angular/angular/issues/23232)) ([1b253e1](https://github.com/angular/angular/commit/1b253e1)) -* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981) -* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095) - - -### Features - -* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7)) -* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c)) -* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27)) -* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5)) -* **ivy:** a generic visitor which allows prefixing nodes for ngtsc ([#24230](https://github.com/angular/angular/issues/24230)) ([ca79e11](https://github.com/angular/angular/commit/ca79e11)) -* **ivy:** add support of ApplicationRef.bootstrapModuleFactory ([#23811](https://github.com/angular/angular/issues/23811)) ([e3759f7](https://github.com/angular/angular/commit/e3759f7)) -* **ivy:** namespaced attributes added to output instructions ([#24386](https://github.com/angular/angular/issues/24386)) ([82c5313](https://github.com/angular/angular/commit/82c5313)) -* **ivy:** now supports SVG and MathML elements ([#24377](https://github.com/angular/angular/issues/24377)) ([8c1ac28](https://github.com/angular/angular/commit/8c1ac28)) -* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595) - - - ## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13) @@ -174,53 +169,6 @@ Note: the RC.1/2 releases on npm accidentally glitched-out midway, so we cut RC. * **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([d20877b](https://github.com/angular/angular/commit/d20877b)), closes [#10981](https://github.com/angular/angular/issues/10981) * **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([ea2987c](https://github.com/angular/angular/commit/ea2987c)), closes [#24095](https://github.com/angular/angular/issues/24095) - - - -# [6.1.0-beta.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0-beta.0) (2018-06-06) - - -### Bug Fixes - -* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687)) -* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e)) -* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25)) -* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4)) -* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d)) -* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41)) -* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930)) -* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d)) -* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013) -* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5)) -* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f)) -* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45)) -* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818) -* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36)) -* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24)) -* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195) -* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8)) -* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023) -* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278) -* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133) -* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617) -* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474) -* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680)) -* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196) -* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e)) -* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526) -* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2)) - - -### Features - -* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e)) -* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67)) -* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d)) -* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce)) -* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb)) -* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202) - - ## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06) diff --git a/package.json b/package.json index ec0c270098..3ea3ec2179 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.1.0-rc.3", + "version": "6.1.0", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", From f902b5ec59a7499dc3bb02d9f20c83ef60028a7c Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 24 Jul 2018 16:05:23 -0700 Subject: [PATCH 570/582] feat(ivy): resolve forwardRef() for queries (#25080) @ContentChild[ren] and @ViewChild[ren] can contain a forwardRef() to a type. This commit allows ngtsc to unwrap the forward reference and deal with the node inside. It includes two modes of support for forward reference resolution - a foreign function resolver which understands deeply nested forward references in expressions that are being statically evaluated, and an unwrapForwardRef() function which deals only with top-level nodes. Both will be useful in the future, but for now only unwrapForwardRef() is used. PR Close #25080 --- .../src/ngtsc/annotations/src/directive.ts | 7 +- .../src/ngtsc/annotations/src/ng_module.ts | 4 +- .../src/ngtsc/annotations/src/util.ts | 68 ++++++++++++++++++- .../src/ngtsc/metadata/src/resolver.ts | 36 +++++----- .../test/ngtsc/fake_core/index.ts | 6 +- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 24 +++++++ 6 files changed, 122 insertions(+), 23 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index 36909e4226..5b8f39063d 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -14,7 +14,7 @@ import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticall import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {SelectorScopeRegistry} from './selector_scope'; -import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util'; +import {getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; @@ -156,12 +156,13 @@ export function extractQueryMetadata( throw new Error(`@${name} must have arguments`); } const first = name === 'ViewChild' || name === 'ContentChild'; - const arg = staticallyResolve(args[0], reflector, checker); + const node = unwrapForwardRef(args[0], reflector); + const arg = staticallyResolve(node, reflector, checker); // Extract the predicate let predicate: Expression|string[]|null = null; if (arg instanceof Reference) { - predicate = new WrappedNodeExpr(args[0]); + predicate = new WrappedNodeExpr(node); } else if (typeof arg === 'string') { predicate = [arg]; } else if (isStringArrayOrDie(arg, '@' + name)) { diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index f0fe5b322d..68f6fd8744 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -67,14 +67,14 @@ export class NgModuleDecoratorHandler implements DecoratorHandler this._extractModuleFromModuleWithProvidersFn(node)); + ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); imports = resolveTypeList(importsMeta, 'imports'); } let exports: Reference[] = []; if (ngModule.has('exports')) { const exportsMeta = staticallyResolve( ngModule.get('exports') !, this.reflector, this.checker, - node => this._extractModuleFromModuleWithProvidersFn(node)); + ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); exports = resolveTypeList(exportsMeta, 'exports'); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index fe82cfdfca..8cd5ea2caf 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -10,7 +10,7 @@ import {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeE import * as ts from 'typescript'; import {Decorator, ReflectionHost} from '../../host'; -import {Reference} from '../../metadata'; +import {AbsoluteReference, Reference} from '../../metadata'; export function getConstructorDependencies( clazz: ts.ClassDeclaration, reflector: ReflectionHost, @@ -103,3 +103,69 @@ export function unwrapExpression(node: ts.Expression): ts.Expression { } return node; } + +function expandForwardRef(arg: ts.Expression): ts.Expression|null { + if (!ts.isArrowFunction(arg) && !ts.isFunctionExpression(arg)) { + return null; + } + + const body = arg.body; + // Either the body is a ts.Expression directly, or a block with a single return statement. + if (ts.isBlock(body)) { + // Block body - look for a single return statement. + if (body.statements.length !== 1) { + return null; + } + const stmt = body.statements[0]; + if (!ts.isReturnStatement(stmt) || stmt.expression === undefined) { + return null; + } + return stmt.expression; + } else { + // Shorthand body - return as an expression. + return body; + } +} + +/** + * Possibly resolve a forwardRef() expression into the inner value. + * + * @param node the forwardRef() expression to resolve + * @param reflector a ReflectionHost + * @returns the resolved expression, if the original expression was a forwardRef(), or the original + * expression otherwise + */ +export function unwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression { + if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) || + node.arguments.length !== 1) { + return node; + } + const expr = expandForwardRef(node.arguments[0]); + if (expr === null) { + return node; + } + const imp = reflector.getImportOfIdentifier(node.expression); + if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') { + return node; + } else { + return expr; + } +} + +/** + * A foreign function resolver for `staticallyResolve` which unwraps forwardRef() expressions. + * + * @param ref a Reference to the declaration of the function being called (which might be + * forwardRef) + * @param args the arguments to the invocation of the forwardRef expression + * @returns an unwrapped argument if `ref` pointed to forwardRef, or null otherwise + */ +export function forwardRefResolver( + ref: Reference, + args: ts.Expression[]): ts.Expression|null { + if (!(ref instanceof AbsoluteReference) || ref.moduleName !== '@angular/core' || + ref.symbolName !== 'forwardRef' || args.length !== 1) { + return null; + } + return expandForwardRef(args[0]); +} diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts index 98a4e7cb21..698d3ab15d 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts @@ -84,8 +84,8 @@ type Scope = Map; * For example, if an expression evaluates to a function or class definition, it will be returned * as a `Reference` (assuming references are allowed in evaluation). */ -export abstract class Reference { - constructor(readonly node: ts.Node) {} +export abstract class Reference { + constructor(readonly node: T) {} /** * Whether an `Expression` can be generated which references the node. @@ -110,8 +110,8 @@ export abstract class Reference { * This is used for returning references to things like method declarations, which are not directly * referenceable. */ -export class NodeReference extends Reference { - constructor(node: ts.Node, readonly moduleName: string|null) { super(node); } +export class NodeReference extends Reference { + constructor(node: T, readonly moduleName: string|null) { super(node); } toExpression(context: ts.SourceFile): null { return null; } @@ -123,8 +123,8 @@ export class NodeReference extends Reference { * * Imports generated by `ResolvedReference`s are always relative. */ -export class ResolvedReference extends Reference { - constructor(node: ts.Node, protected identifier: ts.Identifier) { super(node); } +export class ResolvedReference extends Reference { + constructor(node: T, protected identifier: ts.Identifier) { super(node); } readonly expressable = true; @@ -169,7 +169,7 @@ export class ResolvedReference extends Reference { export class AbsoluteReference extends Reference { constructor( node: ts.Node, private identifier: ts.Identifier, readonly moduleName: string, - private symbolName: string) { + readonly symbolName: string) { super(node); } @@ -203,8 +203,9 @@ export class AbsoluteReference extends Reference { */ export function staticallyResolve( node: ts.Expression, host: ReflectionHost, checker: ts.TypeChecker, - foreignFunctionResolver?: (node: ts.FunctionDeclaration | ts.MethodDeclaration) => - ts.Expression | null): ResolvedValue { + foreignFunctionResolver?: + (node: Reference, args: ts.Expression[]) => + ts.Expression | null): ResolvedValue { return new StaticInterpreter(host, checker).visit(node, { absoluteModuleName: null, scope: new Map(), foreignFunctionResolver, @@ -253,7 +254,9 @@ const UNARY_OPERATORS = new Map any>([ interface Context { absoluteModuleName: string|null; scope: Scope; - foreignFunctionResolver?(node: ts.FunctionDeclaration|ts.MethodDeclaration): ts.Expression|null; + foreignFunctionResolver? + (ref: Reference, + args: ReadonlyArray): ts.Expression|null; } class StaticInterpreter { @@ -516,7 +519,7 @@ class StaticInterpreter { const lhs = this.visitExpression(node.expression, context); if (!(lhs instanceof Reference)) { throw new Error(`attempting to call something that is not a function: ${lhs}`); - } else if (!isFunctionOrMethodDeclaration(lhs.node)) { + } else if (!isFunctionOrMethodReference(lhs)) { throw new Error( `calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]}`); } @@ -528,10 +531,11 @@ class StaticInterpreter { if (fn.body === undefined) { let expr: ts.Expression|null = null; if (context.foreignFunctionResolver) { - expr = context.foreignFunctionResolver(fn); + expr = context.foreignFunctionResolver(lhs, node.arguments); } if (expr === null) { - throw new Error(`could not resolve foreign function declaration`); + throw new Error( + `could not resolve foreign function declaration: ${node.getSourceFile().fileName} ${(lhs.node.name as ts.Identifier).text}`); } // If the function is declared in a different file, resolve the foreign function expression @@ -642,9 +646,9 @@ class StaticInterpreter { } } -function isFunctionOrMethodDeclaration(node: ts.Node): node is ts.FunctionDeclaration| - ts.MethodDeclaration { - return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node); +function isFunctionOrMethodReference(ref: Reference): + ref is Reference { + return ts.isFunctionDeclaration(ref.node) || ts.isMethodDeclaration(ref.node); } function literal(value: ResolvedValue): any { diff --git a/packages/compiler-cli/test/ngtsc/fake_core/index.ts b/packages/compiler-cli/test/ngtsc/fake_core/index.ts index e20fecbc82..2f5dbf682f 100644 --- a/packages/compiler-cli/test/ngtsc/fake_core/index.ts +++ b/packages/compiler-cli/test/ngtsc/fake_core/index.ts @@ -48,4 +48,8 @@ export class ChangeDetectorRef {} export class ElementRef {} export class Injector {} export class TemplateRef {} -export class ViewContainerRef {} \ No newline at end of file +export class ViewContainerRef {} + +export function forwardRef(fn: () => T): T { + return fn(); +} diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 044480c114..352bcb4e02 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -449,6 +449,30 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain(`i0.ɵQ(1, ["test1"], true)`); }); + it('should handle queries that use forwardRef', () => { + writeConfig(); + write(`test.ts`, ` + import {Component, ContentChild, TemplateRef, ViewContainerRef, forwardRef} from '@angular/core'; + + @Component({ + selector: 'test', + template: '
', + }) + class FooCmp { + @ContentChild(forwardRef(() => TemplateRef)) child: any; + + @ContentChild(forwardRef(function() { return ViewContainerRef; })) child2: any; + } + `); + + const exitCode = main(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); + const jsContents = getContents('test.js'); + expect(jsContents).toContain(`i0.ɵQ(null, TemplateRef, true)`); + expect(jsContents).toContain(`i0.ɵQ(null, ViewContainerRef, true)`); + }); + it('should generate host bindings for directives', () => { writeConfig(); write(`test.ts`, ` From ed7aa1c3e57f5462d8006991f0bd8af9bec4d925 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 24 Jul 2018 16:10:15 -0700 Subject: [PATCH 571/582] fix(ivy): force new imports for .d.ts files (#25080) When ngtsc encounters a reference to a type (for example, a Component type listed in an NgModule declarations array), it traces the import of that type and attempts to determine the best way to refer to it. In the event the type is defined in the same file where a reference is being generated, the identifier of the type is used. If the type was imported, ngtsc has a choice. It can use the identifier from the original import, or it can write a new import to the module where the type came from. ngtsc has a bug currently when it elects to rely on the user's import. When writing a .d.ts file, the user's import may have been elided as the type was not referred to from the type side of the program. Thus, in .d.ts files ngtsc must always assume the import may not exist, and generate a new one. In .js output the import is guaranteed to still exist, so it's preferable for ngtsc to continue using the existing import if one is available. This commit changes how @angular/compiler writes type definitions, and allows it to use a different expression to write a type definition than is used to write the value. This allows ngtsc to specify that types in type definitions should always be imported. A corresponding change to the staticallyResolve() Reference system allows the choice of which type of import to use when generating an Expression from a Reference. PR Close #25080 --- .../src/ngtsc/annotations/src/ng_module.ts | 8 +-- .../ngtsc/annotations/src/selector_scope.ts | 4 +- .../src/ngtsc/annotations/src/util.ts | 13 ++-- .../compiler-cli/src/ngtsc/metadata/index.ts | 2 +- .../src/ngtsc/metadata/src/resolver.ts | 41 +++++++++++-- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 59 +++++++++++++++++++ packages/compiler/src/compiler.ts | 1 + .../src/render3/r3_module_compiler.ts | 18 +++--- packages/compiler/src/render3/util.ts | 5 ++ packages/core/src/render3/jit/module.ts | 19 ++++-- 10 files changed, 136 insertions(+), 34 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 68f6fd8744..1421362c39 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -14,7 +14,7 @@ import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {SelectorScopeRegistry} from './selector_scope'; -import {getConstructorDependencies, isAngularCore, referenceToExpression, unwrapExpression} from './util'; +import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util'; export interface NgModuleAnalysis { ngModuleDef: R3NgModuleMetadata; @@ -87,9 +87,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler referenceToExpression(decl, context)), - exports: exports.map(exp => referenceToExpression(exp, context)), - imports: imports.map(imp => referenceToExpression(imp, context)), + declarations: declarations.map(decl => toR3Reference(decl, context)), + exports: exports.map(exp => toR3Reference(exp, context)), + imports: imports.map(imp => toR3Reference(imp, context)), emitInline: false, }; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts index 997c60c96e..8927f24c84 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts @@ -13,7 +13,7 @@ import {ReflectionHost} from '../../host'; import {AbsoluteReference, Reference, reflectTypeEntityToDeclaration} from '../../metadata'; import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector'; -import {referenceToExpression} from './util'; +import {toR3Reference} from './util'; @@ -384,7 +384,7 @@ function convertReferenceMap( map: Map, context: ts.SourceFile): Map { return new Map(Array.from(map.entries()).map(([selector, ref]): [ string, Expression - ] => [selector, referenceToExpression(ref, context)])); + ] => [selector, toR3Reference(ref, context).value])); } function convertScopeToExpressions( diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 8cd5ea2caf..45cc51d530 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler'; +import {Expression, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler'; import * as ts from 'typescript'; import {Decorator, ReflectionHost} from '../../host'; -import {AbsoluteReference, Reference} from '../../metadata'; +import {AbsoluteReference, ImportMode, Reference} from '../../metadata'; export function getConstructorDependencies( clazz: ts.ClassDeclaration, reflector: ReflectionHost, @@ -79,12 +79,13 @@ export function getConstructorDependencies( return useType; } -export function referenceToExpression(ref: Reference, context: ts.SourceFile): Expression { - const exp = ref.toExpression(context); - if (exp === null) { +export function toR3Reference(ref: Reference, context: ts.SourceFile): R3Reference { + const value = ref.toExpression(context, ImportMode.UseExistingImport); + const type = ref.toExpression(context, ImportMode.ForceNewImport); + if (value === null || type === null) { throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`); } - return exp; + return {value, type}; } export function isAngularCore(decorator: Decorator): boolean { diff --git a/packages/compiler-cli/src/ngtsc/metadata/index.ts b/packages/compiler-cli/src/ngtsc/metadata/index.ts index dd4a581356..3178a1376e 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/index.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/index.ts @@ -7,4 +7,4 @@ */ export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector'; -export {AbsoluteReference, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver'; +export {AbsoluteReference, ImportMode, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver'; diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts index 698d3ab15d..c523822c57 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts @@ -78,6 +78,11 @@ export interface ResolvedValueArray extends Array {} */ type Scope = Map; +export enum ImportMode { + UseExistingImport, + ForceNewImport, +} + /** * A reference to a `ts.Node`. * @@ -99,7 +104,7 @@ export abstract class Reference { * This could be a local variable reference, if the symbol is imported, or it could be a new * import if needed. */ - abstract toExpression(context: ts.SourceFile): Expression|null; + abstract toExpression(context: ts.SourceFile, importMode?: ImportMode): Expression|null; abstract withIdentifier(identifier: ts.Identifier): Reference; } @@ -115,7 +120,7 @@ export class NodeReference extends Reference { toExpression(context: ts.SourceFile): null { return null; } - withIdentifier(identifier: ts.Identifier): NodeReference { return this; } + withIdentifier(identifier: ts.Identifier): NodeReference { return this; } } /** @@ -128,8 +133,20 @@ export class ResolvedReference extends Reference readonly expressable = true; - toExpression(context: ts.SourceFile): Expression { - if (ts.getOriginalNode(context) === ts.getOriginalNode(this.identifier).getSourceFile()) { + toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport): + Expression { + let compareCtx: ts.Node|null = null; + switch (importMode) { + case ImportMode.UseExistingImport: + compareCtx = this.identifier; + break; + case ImportMode.ForceNewImport: + compareCtx = this.node; + break; + default: + throw new Error(`Unsupported ImportMode: ${ImportMode[importMode]}`); + } + if (ts.getOriginalNode(context) === ts.getOriginalNode(compareCtx).getSourceFile()) { return new WrappedNodeExpr(this.identifier); } else { // Relative import from context -> this.node.getSourceFile(). @@ -175,8 +192,20 @@ export class AbsoluteReference extends Reference { readonly expressable = true; - toExpression(context: ts.SourceFile): Expression { - if (ts.getOriginalNode(context) === ts.getOriginalNode(this.identifier).getSourceFile()) { + toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport): + Expression { + let compareCtx: ts.Node|null = null; + switch (importMode) { + case ImportMode.UseExistingImport: + compareCtx = this.identifier; + break; + case ImportMode.ForceNewImport: + compareCtx = this.node; + break; + default: + throw new Error(`Unsupported ImportMode: ${ImportMode[importMode]}`); + } + if (ts.getOriginalNode(context) === ts.getOriginalNode(compareCtx).getSourceFile()) { return new WrappedNodeExpr(this.identifier); } else { return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName)); diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 352bcb4e02..be3cc935d8 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -246,6 +246,65 @@ describe('ngtsc behavioral tests', () => { expect(dtsContents).toContain('static ngInjectorDef: i0.ɵInjectorDef'); }); + it('should compile NgModules with references to local components', () => { + writeConfig(); + write('test.ts', ` + import {NgModule} from '@angular/core'; + import {Foo} from './foo'; + + @NgModule({ + declarations: [Foo], + }) + export class FooModule {} + `); + write('foo.ts', ` + import {Component} from '@angular/core'; + @Component({selector: 'foo', template: ''}) + export class Foo {} + `); + + const exitCode = main(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); + + const jsContents = getContents('test.js'); + const dtsContents = getContents('test.d.ts'); + + expect(jsContents).toContain('import { Foo } from \'./foo\';'); + expect(jsContents).not.toMatch(/as i[0-9] from '.\/foo'/); + expect(dtsContents).toContain('as i1 from \'./foo\';'); + }); + + it('should compile NgModules with references to absolute components', () => { + writeConfig(); + write('test.ts', ` + import {NgModule} from '@angular/core'; + import {Foo} from 'foo'; + + @NgModule({ + declarations: [Foo], + }) + export class FooModule {} + `); + write('node_modules/foo/index.d.ts', ` + import * as i0 from '@angular/core'; + export class Foo { + static ngComponentDef: i0.ɵComponentDef; + } + `); + + const exitCode = main(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); + + const jsContents = getContents('test.js'); + const dtsContents = getContents('test.d.ts'); + + expect(jsContents).toContain('import { Foo } from \'foo\';'); + expect(jsContents).not.toMatch(/as i[0-9] from 'foo'/); + expect(dtsContents).toContain('as i1 from \'foo\';'); + }); + it('should compile Pipes without errors', () => { writeConfig(); write('test.ts', ` diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 2f81a1cd85..ca762310af 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -86,5 +86,6 @@ export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler'; export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; export {makeBindingParser, parseTemplate} from './render3/view/template'; +export {R3Reference} from './render3/util'; export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler'; // This file only reexports content of the `src` folder. Keep it that way. \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index e9938bf5a5..a1153a0ef7 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -15,7 +15,7 @@ import {OutputContext} from '../util'; import {R3DependencyMetadata, compileFactoryFunction} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; -import {convertMetaToOutput, mapToMapExpression} from './util'; +import {R3Reference, convertMetaToOutput, mapToMapExpression} from './util'; export interface R3NgModuleDef { expression: o.Expression; @@ -40,17 +40,17 @@ export interface R3NgModuleMetadata { /** * An array of expressions representing the directives and pipes declared by the module. */ - declarations: o.Expression[]; + declarations: R3Reference[]; /** * An array of expressions representing the imports of the module. */ - imports: o.Expression[]; + imports: R3Reference[]; /** * An array of expressions representing the exports of the module. */ - exports: o.Expression[]; + exports: R3Reference[]; /** * Whether to emit the selector scope values (declarations, imports, exports) inline into the @@ -68,9 +68,9 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression({ type: moduleType, bootstrap: o.literalArr(bootstrap), - declarations: o.literalArr(declarations), - imports: o.literalArr(imports), - exports: o.literalArr(exports), + declarations: o.literalArr(declarations.map(ref => ref.value)), + imports: o.literalArr(imports.map(ref => ref.value)), + exports: o.literalArr(exports.map(ref => ref.value)), })]); const type = new o.ExpressionType(o.importExpr(R3.NgModuleDef, [ @@ -148,7 +148,7 @@ function accessExportScope(module: o.Expression): o.Expression { return new o.ReadPropExpr(selectorScope, 'exported'); } -function tupleTypeOf(exp: o.Expression[]): o.Type { - const types = exp.map(type => o.typeofExpr(type)); +function tupleTypeOf(exp: R3Reference[]): o.Type { + const types = exp.map(ref => o.typeofExpr(ref.type)); return exp.length > 0 ? o.expressionType(o.literalArr(types)) : o.NONE_TYPE; } \ No newline at end of file diff --git a/packages/compiler/src/render3/util.ts b/packages/compiler/src/render3/util.ts index ae619ef2da..c006c298f5 100644 --- a/packages/compiler/src/render3/util.ts +++ b/packages/compiler/src/render3/util.ts @@ -47,3 +47,8 @@ export function typeWithParameters(type: o.Expression, numParams: number): o.Exp } return o.expressionType(type, null, params); } + +export interface R3Reference { + value: o.Expression; + type: o.Expression; +} diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 823f30a001..91e1297306 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, R3InjectorMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileInjector, compileNgModule as compileR3NgModule, jitExpression} from '@angular/compiler'; +import {Expression, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, WrappedNodeExpr, compileInjector, compileNgModule as compileR3NgModule, jitExpression} from '@angular/compiler'; import {ModuleWithProviders, NgModule, NgModuleDefInternal, NgModuleTransitiveScopes} from '../../metadata/ng_module'; import {Type} from '../../type'; @@ -28,11 +28,13 @@ export function compileNgModule(type: Type, ngModule: NgModule): void { const meta: R3NgModuleMetadata = { type: wrap(type), bootstrap: flatten(ngModule.bootstrap || EMPTY_ARRAY).map(wrap), - declarations: declarations.map(wrap), - imports: - flatten(ngModule.imports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), - exports: - flatten(ngModule.exports || EMPTY_ARRAY).map(expandModuleWithProviders).map(wrap), + declarations: declarations.map(wrapReference), + imports: flatten(ngModule.imports || EMPTY_ARRAY) + .map(expandModuleWithProviders) + .map(wrapReference), + exports: flatten(ngModule.exports || EMPTY_ARRAY) + .map(expandModuleWithProviders) + .map(wrapReference), emitInline: true, }; const res = compileR3NgModule(meta); @@ -210,6 +212,11 @@ function wrap(value: Type): Expression { return new WrappedNodeExpr(value); } +function wrapReference(value: Type): R3Reference { + const wrapped = wrap(value); + return {value: wrapped, type: wrapped}; +} + function isModuleWithProviders(value: any): value is ModuleWithProviders { return (value as{ngModule?: any}).ngModule !== undefined; } From 13a0d527f6a85349a2d454ab47533aeda521598b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 25 Jul 2018 11:16:00 -0700 Subject: [PATCH 572/582] fix(ivy): correctly write cross-file references (#25080) There is a bug in the existing handling for cross-file references. Suppose there are two files, module.ts and component.ts. component.ts declares two components, one of which uses the other. In the Ivy model, this means the component will get a directives: reference to the other in its defineComponent call. That reference is generated by looking at the declared components of the module (in module.ts). However, the way ngtsc tracks this reference, it ends up comparing the identifier of the component in module.ts with the component.ts file, detecting they're not in the same file, and generating a relative import. This commit changes ngtsc to track all identifiers of a reference, including the one by which it is declared. This allows toExpression() to correctly decide that a local reference is okay in component.ts. PR Close #25080 --- .../src/ngtsc/metadata/src/resolver.ts | 74 +++++++++---------- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 34 +++++++++ 2 files changed, 68 insertions(+), 40 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts index c523822c57..66f4e66648 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts @@ -106,7 +106,7 @@ export abstract class Reference { */ abstract toExpression(context: ts.SourceFile, importMode?: ImportMode): Expression|null; - abstract withIdentifier(identifier: ts.Identifier): Reference; + abstract addIdentifier(identifier: ts.Identifier): void; } /** @@ -120,7 +120,7 @@ export class NodeReference extends Reference { toExpression(context: ts.SourceFile): null { return null; } - withIdentifier(identifier: ts.Identifier): NodeReference { return this; } + addIdentifier(identifier: ts.Identifier): void {} } /** @@ -129,25 +129,18 @@ export class NodeReference extends Reference { * Imports generated by `ResolvedReference`s are always relative. */ export class ResolvedReference extends Reference { - constructor(node: T, protected identifier: ts.Identifier) { super(node); } + protected identifiers: ts.Identifier[] = []; + + constructor(node: T, protected primaryIdentifier: ts.Identifier) { super(node); } readonly expressable = true; toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport): Expression { - let compareCtx: ts.Node|null = null; - switch (importMode) { - case ImportMode.UseExistingImport: - compareCtx = this.identifier; - break; - case ImportMode.ForceNewImport: - compareCtx = this.node; - break; - default: - throw new Error(`Unsupported ImportMode: ${ImportMode[importMode]}`); - } - if (ts.getOriginalNode(context) === ts.getOriginalNode(compareCtx).getSourceFile()) { - return new WrappedNodeExpr(this.identifier); + const localIdentifier = + pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode); + if (localIdentifier !== null) { + return new WrappedNodeExpr(localIdentifier); } else { // Relative import from context -> this.node.getSourceFile(). // TODO(alxhub): investigate the impact of multiple source roots here. @@ -165,16 +158,14 @@ export class ResolvedReference extends Reference // same. if (relative === './') { // Same file after all. - return new WrappedNodeExpr(this.identifier); + return new WrappedNodeExpr(this.primaryIdentifier); } else { - return new ExternalExpr(new ExternalReference(relative, this.identifier.text)); + return new ExternalExpr(new ExternalReference(relative, this.primaryIdentifier.text)); } } } - withIdentifier(identifier: ts.Identifier): ResolvedReference { - return new ResolvedReference(this.node, identifier); - } + addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); } } /** @@ -184,8 +175,9 @@ export class ResolvedReference extends Reference * the module specifier will be an absolute module name, not a relative path. */ export class AbsoluteReference extends Reference { + private identifiers: ts.Identifier[] = []; constructor( - node: ts.Node, private identifier: ts.Identifier, readonly moduleName: string, + node: ts.Node, private primaryIdentifier: ts.Identifier, readonly moduleName: string, readonly symbolName: string) { super(node); } @@ -194,26 +186,29 @@ export class AbsoluteReference extends Reference { toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport): Expression { - let compareCtx: ts.Node|null = null; - switch (importMode) { - case ImportMode.UseExistingImport: - compareCtx = this.identifier; - break; - case ImportMode.ForceNewImport: - compareCtx = this.node; - break; - default: - throw new Error(`Unsupported ImportMode: ${ImportMode[importMode]}`); - } - if (ts.getOriginalNode(context) === ts.getOriginalNode(compareCtx).getSourceFile()) { - return new WrappedNodeExpr(this.identifier); + const localIdentifier = + pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode); + if (localIdentifier !== null) { + return new WrappedNodeExpr(localIdentifier); } else { return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName)); } } - withIdentifier(identifier: ts.Identifier): AbsoluteReference { - return new AbsoluteReference(this.node, identifier, this.moduleName, this.symbolName); + addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); } +} + +function pickIdentifier( + context: ts.SourceFile, primary: ts.Identifier, secondaries: ts.Identifier[], + mode: ImportMode): ts.Identifier|null { + context = ts.getOriginalNode(context) as ts.SourceFile; + let localIdentifier: ts.Identifier|null = null; + if (ts.getOriginalNode(primary).getSourceFile() === context) { + return primary; + } else if (mode === ImportMode.UseExistingImport) { + return secondaries.find(id => ts.getOriginalNode(id).getSourceFile() === context) || null; + } else { + return null; } } @@ -426,10 +421,9 @@ class StaticInterpreter { const result = this.visitDeclaration( decl.node, {...context, absoluteModuleName: decl.viaModule || context.absoluteModuleName}); if (result instanceof Reference) { - return result.withIdentifier(node); - } else { - return result; + result.addIdentifier(node); } + return result; } private visitDeclaration(node: ts.Declaration, context: Context): ResolvedValue { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index be3cc935d8..71e51a193f 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -568,4 +568,38 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain('i0.ɵd(dirIndex).onClick($event)'); expect(jsContents).toContain('i0.ɵd(dirIndex).onChange(i0.ɵd(dirIndex).arg)'); }); + + it('should correctly recognize local symbols', () => { + writeConfig(); + write('module.ts', ` + import {NgModule} from '@angular/core'; + import {Dir, Comp} from './test'; + + @NgModule({ + declarations: [Dir, Comp], + exports: [Dir, Comp], + }) + class Module {} + `); + write(`test.ts`, ` + import {Component, Directive} from '@angular/core'; + + @Directive({ + selector: '[dir]', + }) + export class Dir {} + + @Component({ + selector: 'test', + template: '
Test
', + }) + export class Comp {} + `); + + const exitCode = main(['-p', basePath], errorSpy); + expect(errorSpy).not.toHaveBeenCalled(); + expect(exitCode).toBe(0); + const jsContents = getContents('test.js'); + expect(jsContents).not.toMatch(/import \* as i[0-9] from ['"].\/test['"]/); + }); }); From e0c0c44d997ef3deb9a021500c00e95466dcbdfb Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 26 Jul 2018 08:49:14 -0700 Subject: [PATCH 573/582] fix(ivy): allow relative imports of .d.ts files (#25080) ngtsc used to assume that all .d.ts dependencies (that is, third party packages) were imported via an absolute module path. It turns out this assumption isn't valid; some build tools allow relative imports of other compilation units. In the absolute case, ngtsc assumes (and still does) that all referenced types are available through the entrypoint from which an @NgModule was imported. This commit adds support for relative imports, in which case ngtsc will use relative path resolution to determine the imports. PR Close #25080 --- .../ngtsc/annotations/src/selector_scope.ts | 26 ++++++++++--------- .../compiler-cli/src/ngtsc/metadata/index.ts | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts index 8927f24c84..f504411411 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts @@ -10,7 +10,7 @@ import {Expression, ExternalExpr, ExternalReference} from '@angular/compiler'; import * as ts from 'typescript'; import {ReflectionHost} from '../../host'; -import {AbsoluteReference, Reference, reflectTypeEntityToDeclaration} from '../../metadata'; +import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDeclaration} from '../../metadata'; import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector'; import {toR3Reference} from './util'; @@ -199,10 +199,6 @@ export class SelectorScopeRegistry { } else { // The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type // annotation that specifies the needed metadata. - if (ngModuleImportedFrom === null) { - // TODO(alxhub): handle hand-compiled ngModuleDef in the current Program. - throw new Error(`Need to read .d.ts module but ngModuleImportedFrom is unspecified`); - } data = this._readMetadataFromCompiledClass(node, ngModuleImportedFrom); // Note that data here could still be null, if the class didn't have a precompiled // ngModuleDef. @@ -267,7 +263,7 @@ export class SelectorScopeRegistry { * @param ngModuleImportedFrom module specifier of the import path to assume for all declarations * stemming from this module. */ - private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string): + private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string|null): ModuleData|null { // This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`. // TODO(alxhub): investigate caching of .d.ts module metadata. @@ -348,7 +344,8 @@ export class SelectorScopeRegistry { * This operation assumes that these types should be imported from `ngModuleImportedFrom` unless * they themselves were imported from another absolute path. */ - private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string): Reference[] { + private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string|null): + Reference[] { if (!ts.isTupleTypeNode(def)) { return []; } @@ -357,11 +354,16 @@ export class SelectorScopeRegistry { throw new Error(`Expected TypeQueryNode`); } const type = element.exprName; - const {node, from} = reflectTypeEntityToDeclaration(type, this.checker); - const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom); - const clazz = node as ts.Declaration; - const id = reflectIdentifierOfDeclaration(clazz); - return new AbsoluteReference(node, id !, moduleName, id !.text); + if (ngModuleImportedFrom !== null) { + const {node, from} = reflectTypeEntityToDeclaration(type, this.checker); + const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom); + const id = reflectIdentifierOfDeclaration(node); + return new AbsoluteReference(node, id !, moduleName, id !.text); + } else { + const {node} = reflectTypeEntityToDeclaration(type, this.checker); + const id = reflectIdentifierOfDeclaration(node); + return new ResolvedReference(node, id !); + } }); } } diff --git a/packages/compiler-cli/src/ngtsc/metadata/index.ts b/packages/compiler-cli/src/ngtsc/metadata/index.ts index 3178a1376e..b15062c49d 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/index.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/index.ts @@ -7,4 +7,4 @@ */ export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector'; -export {AbsoluteReference, ImportMode, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver'; +export {AbsoluteReference, ImportMode, Reference, ResolvedReference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver'; From 6fe865b080bf79f9ee0fbbf407018e0491fae170 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 26 Jul 2018 09:10:42 -0700 Subject: [PATCH 574/582] fix(ivy): don't use a custom ts.CompilerHost for ngtsc (#25080) ngtsc used to have a custom ts.CompilerHost which delegated to the plain ts.CompilerHost. There's no need for this wrapper class and it causes issues with CLI integration, so delete it. PR Close #25080 --- .../compiler-cli/src/ngtsc/compiler_host.ts | 77 ------------------- packages/compiler-cli/src/ngtsc/program.ts | 1 - .../src/transformers/compiler_host.ts | 4 - 3 files changed, 82 deletions(-) delete mode 100644 packages/compiler-cli/src/ngtsc/compiler_host.ts diff --git a/packages/compiler-cli/src/ngtsc/compiler_host.ts b/packages/compiler-cli/src/ngtsc/compiler_host.ts deleted file mode 100644 index d8fef66138..0000000000 --- a/packages/compiler-cli/src/ngtsc/compiler_host.ts +++ /dev/null @@ -1,77 +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 ts from 'typescript'; - -/** - * The TypeScript compiler host used by `ngtsc`. - * - * It's mostly identical to the native `CompilerHost`, but also includes the ability to - * asynchronously resolve resources. - */ -export interface CompilerHost extends ts.CompilerHost { - /** - * Begin processing a resource file. - * - * When the returned Promise resolves, `loadResource` should be able to synchronously produce a - * `string` for the given file. - */ - preloadResource(file: string): Promise; - - /** - * Like `readFile`, but reads the contents of a resource file which may have been pre-processed - * by `preloadResource`. - */ - loadResource(file: string): string|undefined; -} - -/** - * Implementation of `CompilerHost` which delegates to a native TypeScript host in most cases. - */ -export class NgtscCompilerHost implements CompilerHost { - constructor(private delegate: ts.CompilerHost) {} - - getSourceFile( - fileName: string, languageVersion: ts.ScriptTarget, - onError?: ((message: string) => void)|undefined, - shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined { - return this.delegate.getSourceFile( - fileName, languageVersion, onError, shouldCreateNewSourceFile); - } - - getDefaultLibFileName(options: ts.CompilerOptions): string { - return this.delegate.getDefaultLibFileName(options); - } - - writeFile( - fileName: string, data: string, writeByteOrderMark: boolean, - onError: ((message: string) => void)|undefined, - sourceFiles: ReadonlyArray): void { - return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles); - } - - getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); } - - getDirectories(path: string): string[] { return this.delegate.getDirectories(path); } - - getCanonicalFileName(fileName: string): string { - return this.delegate.getCanonicalFileName(fileName); - } - - useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); } - - getNewLine(): string { return this.delegate.getNewLine(); } - - fileExists(fileName: string): boolean { return this.delegate.fileExists(fileName); } - - readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); } - - loadResource(file: string): string|undefined { throw new Error('Method not implemented.'); } - - preloadResource(file: string): Promise { throw new Error('Method not implemented.'); } -} diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index d2e5731ab3..54ca39ea67 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -13,7 +13,6 @@ import * as ts from 'typescript'; import * as api from '../transformers/api'; import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations'; -import {CompilerHost} from './compiler_host'; import {TypeScriptReflectionHost} from './metadata'; import {FileResourceLoader, HostResourceLoader} from './resource_loader'; import {IvyCompilation, ivyTransformFactory} from './transform'; diff --git a/packages/compiler-cli/src/transformers/compiler_host.ts b/packages/compiler-cli/src/transformers/compiler_host.ts index 683d4a05c3..5472045d56 100644 --- a/packages/compiler-cli/src/transformers/compiler_host.ts +++ b/packages/compiler-cli/src/transformers/compiler_host.ts @@ -12,7 +12,6 @@ import * as ts from 'typescript'; import {TypeCheckHost} from '../diagnostics/translate_diagnostics'; import {METADATA_VERSION, ModuleMetadata} from '../metadata/index'; -import {NgtscCompilerHost} from '../ngtsc/compiler_host'; import {CompilerHost, CompilerOptions, LibrarySummary} from './api'; import {MetadataReaderHost, createMetadataReaderCache, readMetadata} from './metadata_reader'; @@ -24,9 +23,6 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; export function createCompilerHost( {options, tsHost = ts.createCompilerHost(options, true)}: {options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost { - if (options.enableIvy === 'ngtsc' || options.enableIvy === 'tsc') { - return new NgtscCompilerHost(tsHost); - } return tsHost; } From 0d1d5898e370d111b0e7840c3231bbe3f50173a7 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 26 Jul 2018 09:16:07 -0700 Subject: [PATCH 575/582] fix(bazel): allow compile_strategy to be (privately) imported (#25080) compile_strategy() is used to decide whether to build Angular code using ngc (legacy) or ngtsc (local). In order for g3 BUILD rules to switch properly and allow testing of Ivy in g3, they need to import this function. This commit removes the _ prefix which allows the function to be imported. PR Close #25080 --- packages/bazel/src/ng_module.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index 4244a74567..be0ef7a001 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -14,7 +14,7 @@ load(":rules_typescript.bzl", "ts_providers_dict_to_struct", ) -def _compile_strategy(ctx): +def compile_strategy(ctx): """Detect which strategy should be used to implement ng_module. Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module @@ -50,7 +50,7 @@ def _compiler_name(ctx): the name of the current compiler to be displayed in build output """ - strategy = _compile_strategy(ctx) + strategy = compile_strategy(ctx) if strategy == 'legacy': return 'ngc' elif strategy == 'global': @@ -72,7 +72,7 @@ def _enable_ivy_value(ctx): the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig """ - strategy = _compile_strategy(ctx) + strategy = compile_strategy(ctx) if strategy == 'legacy': return False elif strategy == 'global': @@ -95,7 +95,7 @@ def _include_ng_files(ctx): factory files), false otherwise """ - strategy = _compile_strategy(ctx) + strategy = compile_strategy(ctx) return strategy == 'legacy' or strategy == 'global' def _basename_of(ctx, file): From 8de304c15a0878881ee1bb89f290ebdabb97a855 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 26 Jul 2018 13:32:23 -0700 Subject: [PATCH 576/582] fix(ivy): wait for preanalyze promises in loadNgStructureAsync() (#25080) loadNgStructureAsync() for ngtsc has a bug where it returns a Promise instead of awaiting the entire array of Promises. This commit uses Promise.all() to await the whole set. PR Close #25080 --- packages/compiler-cli/src/ngtsc/program.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 54ca39ea67..6d4544f8af 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -75,12 +75,11 @@ export class NgtscProgram implements api.Program { async loadNgStructureAsync(): Promise { if (this.compilation === undefined) { this.compilation = this.makeCompilation(); - - await this.tsProgram.getSourceFiles() - .filter(file => !file.fileName.endsWith('.d.ts')) - .map(file => this.compilation !.analyzeAsync(file)) - .filter((result): result is Promise => result !== undefined); } + await Promise.all(this.tsProgram.getSourceFiles() + .filter(file => !file.fileName.endsWith('.d.ts')) + .map(file => this.compilation !.analyzeAsync(file)) + .filter((result): result is Promise => result !== undefined)); } listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] { From 3169edd77a0090f482851874aa393b2626760096 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 26 Jul 2018 13:33:55 -0700 Subject: [PATCH 577/582] fix(ivy): don't crash in listLazyRoutes() (#25080) This commit replaces the "not implemented" error when calling listLazyRoutes() with an empty result, which will allow testing in the CLI before listLazyRoutes() is implemented. PR Close #25080 --- packages/compiler-cli/src/ngtsc/program.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 6d4544f8af..48df2fbae4 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -82,9 +82,7 @@ export class NgtscProgram implements api.Program { .filter((result): result is Promise => result !== undefined)); } - listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] { - throw new Error('Method not implemented.'); - } + listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] { return []; } getLibrarySummaries(): Map { throw new Error('Method not implemented.'); From 323faf954b964890c1a4e372aa29cc7256160990 Mon Sep 17 00:00:00 2001 From: George Wilde Date: Mon, 16 Jul 2018 08:45:03 +0100 Subject: [PATCH 578/582] docs(router): Removed unneeded trailing text. (#24894) PR Close #24894 --- packages/router/src/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/router/src/config.ts b/packages/router/src/config.ts index 26512fbb28..047c73bf9c 100644 --- a/packages/router/src/config.ts +++ b/packages/router/src/config.ts @@ -252,7 +252,6 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree'; * Then it will extract the set of routes defined in that NgModule, and will transparently add * those routes to the main configuration. * - * use Routes */ export type Routes = Route[]; From 1d051c58416939b7eabde2966033b0e68a4fb073 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Mon, 25 Jun 2018 11:22:23 -0700 Subject: [PATCH 579/582] build(bazel): use bazel managed node_modules for downstream angular from source build support (#24663) PR Close #24663 --- BUILD.bazel | 221 +-- WORKSPACE | 40 +- index.bzl | 19 + integration/bazel/WORKSPACE | 32 +- karma-js.conf.js | 1 + modules/benchmarks/e2e_test/largeform_perf.ts | 2 +- .../benchmarks/e2e_test/largetable_perf.ts | 12 +- modules/benchmarks/e2e_test/tree_perf.ts | 10 +- modules/types.d.ts | 10 +- package.json | 3 +- packages/bazel/src/ngc-wrapped/index.ts | 6 + .../test/ngc-wrapped/tsconfig_template.ts | 2 +- packages/compiler-cli/BUILD.bazel | 2 +- packages/compiler-cli/test/test_support.ts | 3 +- .../test/transformers/node_emitter_spec.ts | 13 +- .../hello_world_r2/bundle.golden_symbols.json | 6 +- packages/core/test/bundling/todo/BUILD.bazel | 2 +- packages/elements/test/BUILD.bazel | 10 +- packages/types.d.ts | 10 +- packages/upgrade/test/common/test_helpers.ts | 2 +- tools/defaults.bzl | 11 +- tools/ng_setup_workspace.bzl | 238 +++ tools/postinstall-patches.js | 13 + tools/types.d.ts | 6 +- yarn.lock | 1682 +++++++++-------- 25 files changed, 1231 insertions(+), 1125 deletions(-) create mode 100644 index.bzl create mode 100644 tools/ng_setup_workspace.bzl diff --git a/BUILD.bazel b/BUILD.bazel index 768c6c2ebc..74ef2eb41f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -15,220 +15,19 @@ alias( actual = "@nodejs//:yarn", ) -node_modules_filegroup( +alias( name = "node_modules", - packages = [ - "adm-zip", - "ajv", - "angular", - "angular-1.5", - "angular-mocks", - "angular-mocks-1.5", - "anymatch", - "arr-diff", - "arr-flatten", - "arr-union", - "array-unique", - "asn1", - "assert-plus", - "assign-symbols", - "async-each", - "asynckit", - "atob", - "aws-sign2", - "aws4", - "balanced-match", - "base", - "base64-js", - "binary-extensions", - "blocking-proxy", - "brace-expansion", - "braces", - "bytebuffer", - "cache-base", - "caseless", - "chokidar", - "class-utils", - "co", - "collection-visit", - "combined-stream", - "component-emitter", - "concat-map", - "copy-descriptor", - "core-util-is", - "debug", - "decode-uri-component", - "define-property", - "delayed-stream", - "domino", - "expand-brackets", - "expand-range", - "extend", - "extend-shallow", - "extglob", - "extsprintf", - "fast-deep-equal", - "fast-json-stable-stringify", - "filename-regex", - "fill-range", - "for-in", - "for-own", - "forever-agent", - "form-data", - "fragment-cache", - "fs.realpath", - "get-value", - "glob", - "glob-base", - "glob-parent", - "graceful-fs", - "hammerjs", - "har-schema", - "har-validator", - "has-value", - "has-values", - "http-signature", - "https-proxy-agent", - "inflight", - "inherits", - "is-accessor-descriptor", - "is-binary-path", - "is-buffer", - "is-data-descriptor", - "is-descriptor", - "is-dotfile", - "is-equal-shallow", - "is-extendable", - "is-extglob", - "is-glob", - "is-number", - "is-plain-object", - "is-posix-bracket", - "is-primitive", - "is-typedarray", - "is-windows", - "isarray", - "isobject", - "isstream", - "jasmine", - "jasmine-core", - "jasminewd2", - "json-schema", - "json-schema-traverse", - "json-stable-stringify", - "json-stringify-safe", - "jsprim", - "kind-of", - "long", - "lru-cache", - "map-cache", - "map-visit", - "math-random", - "micromatch", - "mime-db", - "mime-types", - "minimatch", - "minimist", - "mixin-deep", - "nanomatch", - "normalize-path", - "oauth-sign", - "object.omit", - "object.pick", - "object-copy", - "object-visit", - "once", - "optimist", - "options", - "os-tmpdir", - "parse-glob", - "pascalcase", - "path-dirname", - "path-is-absolute", - "performance-now", - "posix-character-classes", - "preserve", - "process-nextick-args", - "protobufjs", - "protractor", - "qs", - "randomatic", - "readable-stream", - "readdirp", - "reflect-metadata", - "regex-cache", - "regex-not", - "remove-trailing-separator", - "repeat-element", - "repeat-string", - "request", - "ret", - "rimraf", - "safe-buffer", - "safe-regex", - "sax", - "semver", - "set-immediate-shim", - "set-value", - "shelljs", - "sigmund", - "snapdragon", - "snapdragon-node", - "snapdragon-util", - "source-map", - "source-map-resolve", - "source-map-support", - "source-map-url", - "split-string", - "sshpk", - "static-extend", - "stringstream", - "tmp", - "to-object-path", - "to-regex", - "to-regex-range", - "tough-cookie", - "tsickle", - "tslib", - "tsutils", - "tunnel-agent", - "typescript", - "union-value", - "unset-value", - "upath", - "uri-js", - "urix", - "use", - "util-deprecate", - "uuid", - "verror", - "webdriver-js-extender", - "webdriver-manager", - "wordwrap", - "wrappy", - "xhr2", - "xml2js", - "xmlbuilder", - "zone.js", - "@angular-devkit/core", - "@angular-devkit/schematics", - "@types", - "@webcomponents/custom-elements", - ], - patterns = [ - "node_modules/protractor/**", - "node_modules/@schematics/angular/**", - ], + actual = "@angular_deps//:node_modules", ) filegroup( name = "web_test_bootstrap_scripts", # do not sort srcs = [ - "//:node_modules/reflect-metadata/Reflect.js", - "//:node_modules/zone.js/dist/zone.js", - "//:node_modules/zone.js/dist/zone-testing.js", - "//:node_modules/zone.js/dist/task-tracking.js", + "@angular_deps//:node_modules/reflect-metadata/Reflect.js", + "@angular_deps//:node_modules/zone.js/dist/zone.js", + "@angular_deps//:node_modules/zone.js/dist/zone-testing.js", + "@angular_deps//:node_modules/zone.js/dist/task-tracking.js", "//:test-events.js", ], ) @@ -236,9 +35,9 @@ filegroup( filegroup( name = "angularjs_scripts", srcs = [ - "//:node_modules/angular-1.5/angular.js", - "//:node_modules/angular-mocks-1.5/angular-mocks.js", - "//:node_modules/angular-mocks/angular-mocks.js", - "//:node_modules/angular/angular.js", + "@angular_deps//:node_modules/angular-1.5/angular.js", + "@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js", + "@angular_deps//:node_modules/angular-mocks/angular-mocks.js", + "@angular_deps//:node_modules/angular/angular.js", ], ) diff --git a/WORKSPACE b/WORKSPACE index 550831a6ed..ea4eaaa5dd 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -6,9 +6,9 @@ workspace(name = "angular") http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip", - strip_prefix = "rules_nodejs-0.10.1", - sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c", + url = "https://github.com/bazelbuild/rules_nodejs/archive/20ff5892612f8359aec8aaf26dd3902a24976ada.zip", + strip_prefix = "rules_nodejs-20ff5892612f8359aec8aaf26dd3902a24976ada", + sha256 = "07da9d4c3e688a02745d0f50709a87744706d4f5d1959b799b0ac38e97acd622", ) http_archive( @@ -71,6 +71,22 @@ http_archive( sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828", ) +# +# Point Bazel to WORKSPACEs that live in subdirectories +# + +local_repository( + name = "rxjs", + path = "node_modules/rxjs/src", +) + +# Point to the integration test workspace just so that Bazel doesn't descend into it +# when expanding the //... pattern +local_repository( + name = "bazel_integration_test", + path = "integration/bazel", +) + # # Load and install our dependencies downloaded above. # @@ -100,26 +116,10 @@ load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") ts_setup_workspace() -load("//packages/bazel/src:ng_setup_workspace.bzl", "ng_setup_workspace") +load("@angular//:index.bzl", "ng_setup_workspace") ng_setup_workspace() -# -# Point Bazel to WORKSPACEs that live in subdirectories -# - -local_repository( - name = "rxjs", - path = "node_modules/rxjs/src", -) - -# Point to the integration test workspace just so that Bazel doesn't descend into it -# when expanding the //... pattern -local_repository( - name = "bazel_integration_test", - path = "integration/bazel", -) - # # Ask Bazel to manage these toolchain dependencies for us. # Bazel will run `yarn install` when one of these toolchains is requested during diff --git a/index.bzl b/index.bzl new file mode 100644 index 0000000000..5178b8ba4c --- /dev/null +++ b/index.bzl @@ -0,0 +1,19 @@ +# 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 +""" Public API surface is re-exported here. +""" + +load("//packages/bazel:index.bzl", + _ng_module = "ng_module", + _ng_package = "ng_package", + _protractor_web_test = "protractor_web_test", + _protractor_web_test_suite = "protractor_web_test_suite") +load("//tools:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace") + +ng_module = _ng_module +ng_package = _ng_package +protractor_web_test = _protractor_web_test +protractor_web_test_suite = _protractor_web_test_suite +ng_setup_workspace = _ng_setup_workspace diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index 8fd3999ee0..485675c9b0 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -38,6 +38,20 @@ http_archive( sha256 = "b243c4d64f054c174051785862ab079050d90b37a1cef7da93821c6981cb9ad4", ) +# +# Point Bazel to WORKSPACEs that live in subdirectories +# + +local_repository( + name = "angular", + path = "node_modules/@angular/bazel", +) + +local_repository( + name = "rxjs", + path = "node_modules/rxjs/src", +) + # # Load and install our dependencies downloaded above. # @@ -68,24 +82,6 @@ load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories") sass_repositories() -# -# Point Bazel to WORKSPACEs that live in subdirectories -# - -local_repository( - name = "angular", - path = "node_modules/@angular/bazel", -) - -local_repository( - name = "rxjs", - path = "node_modules/rxjs/src", -) - -# -# Load and install our dependencies from local repositories -# - load("@angular//:index.bzl", "ng_setup_workspace") ng_setup_workspace() diff --git a/karma-js.conf.js b/karma-js.conf.js index 910a13bab3..a6adca6057 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -110,6 +110,7 @@ module.exports = function(config) { // don't need this entire config file. proxies: { '/base/angular/': '/base/', + '/base/angular_deps/': '/base/', }, reporters: ['dots'], diff --git a/modules/benchmarks/e2e_test/largeform_perf.ts b/modules/benchmarks/e2e_test/largeform_perf.ts index a088fef63b..63aaebd3ed 100644 --- a/modules/benchmarks/e2e_test/largeform_perf.ts +++ b/modules/benchmarks/e2e_test/largeform_perf.ts @@ -29,7 +29,7 @@ describe('largeform benchmark perf', () => { [CreateAndDestroyWorker].forEach((worker) => { describe(worker.id, () => { - it('should run for ng2', (done) => { + it('should run for ng2', (done: any) => { runLargeFormBenchmark({ id: `largeform.ng2.${worker.id}`, url: 'all/benchmarks/src/largeform/ng2/index.html', diff --git a/modules/benchmarks/e2e_test/largetable_perf.ts b/modules/benchmarks/e2e_test/largetable_perf.ts index 16b576c6ff..045c5ef56a 100644 --- a/modules/benchmarks/e2e_test/largetable_perf.ts +++ b/modules/benchmarks/e2e_test/largetable_perf.ts @@ -40,7 +40,7 @@ describe('largetable benchmark perf', () => { [CreateOnlyWorker, CreateAndDestroyWorker, UpdateWorker].forEach((worker) => { describe(worker.id, () => { - it('should run for ng2', (done) => { + it('should run for ng2', (done: any) => { runTableBenchmark({ id: `largeTable.ng2.${worker.id}`, url: 'all/benchmarks/src/largetable/ng2/index.html', @@ -48,7 +48,7 @@ describe('largetable benchmark perf', () => { }).then(done, done.fail); }); - it('should run for ng2 with ngSwitch', (done) => { + it('should run for ng2 with ngSwitch', (done: any) => { runTableBenchmark({ id: `largeTable.ng2_switch.${worker.id}`, url: 'all/benchmarks/src/largetable/ng2_switch/index.html', @@ -56,7 +56,7 @@ describe('largetable benchmark perf', () => { }).then(done, done.fail); }); - it('should run for render3', (done) => { + it('should run for render3', (done: any) => { runTableBenchmark({ id: `largeTable.render3.${worker.id}`, url: 'all/benchmarks/src/largetable/render3/index.html', @@ -65,7 +65,7 @@ describe('largetable benchmark perf', () => { }).then(done, done.fail); }); - it('should run for iv', (done) => { + it('should run for iv', (done: any) => { runTableBenchmark({ id: `largeTable.iv.${worker.id}`, url: 'all/benchmarks/src/largetable/iv/index.html', @@ -74,7 +74,7 @@ describe('largetable benchmark perf', () => { }).then(done, done.fail); }); - it('should run for the baseline', (done) => { + it('should run for the baseline', (done: any) => { runTableBenchmark({ id: `largeTable.baseline.${worker.id}`, url: 'all/benchmarks/src/largetable/baseline/index.html', @@ -83,7 +83,7 @@ describe('largetable benchmark perf', () => { }).then(done, done.fail); }); - it('should run for incremental-dom', (done) => { + it('should run for incremental-dom', (done: any) => { runTableBenchmark({ id: `largeTable.incremental_dom.${worker.id}`, url: 'all/benchmarks/src/largetable/incremental_dom/index.html', diff --git a/modules/benchmarks/e2e_test/tree_perf.ts b/modules/benchmarks/e2e_test/tree_perf.ts index 07d700a3e9..ceddb69ed1 100644 --- a/modules/benchmarks/e2e_test/tree_perf.ts +++ b/modules/benchmarks/e2e_test/tree_perf.ts @@ -24,7 +24,7 @@ describe('tree benchmark perf', () => { Benchmarks.forEach(benchmark => { describe(benchmark.id, () => { // This is actually a destroyOnly benchmark - it('should work for createOnly', (done) => { + it('should work for createOnly', (done: any) => { runTreeBenchmark({ id: 'createOnly', benchmark, @@ -33,7 +33,7 @@ describe('tree benchmark perf', () => { }).then(done, done.fail); }); - it('should work for createOnlyForReal', (done) => { + it('should work for createOnlyForReal', (done: any) => { runTreeBenchmark({ id: 'createOnlyForReal', benchmark, @@ -42,7 +42,7 @@ describe('tree benchmark perf', () => { }).then(done, done.fail); }); - it('should work for createDestroy', (done) => { + it('should work for createDestroy', (done: any) => { runTreeBenchmark({ id: 'createDestroy', benchmark, @@ -53,13 +53,13 @@ describe('tree benchmark perf', () => { }).then(done, done.fail); }); - it('should work for update', (done) => { + it('should work for update', (done: any) => { runTreeBenchmark({id: 'update', benchmark, work: () => $(CreateBtn).click()}) .then(done, done.fail); }); if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) { - it('should work for detectChanges', (done) => { + it('should work for detectChanges', (done: any) => { runTreeBenchmark({ id: 'detectChanges', benchmark, diff --git a/modules/types.d.ts b/modules/types.d.ts index 0dbf2fff2b..657b0469a2 100644 --- a/modules/types.d.ts +++ b/modules/types.d.ts @@ -8,11 +8,11 @@ // This file contains all ambient imports needed to compile the modules/ source code -/// -/// -/// -/// -/// +/// +/// +/// +/// +/// /// /// /// diff --git a/package.json b/package.json index 3ea3ec2179..e40acbb2b4 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "prebuildifier": "bazel build --noshow_progress @com_github_bazelbuild_buildtools//buildifier", "buildifier": "find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier", "preinstall": "node tools/yarn/check-yarn.js", - "postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js", + "postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js && yarn patch-types", + "patch-types": "mkdir -p node_modules/@types/zone.js && cp -f node_modules/zone.js/dist/zone.js.d.ts node_modules/@types/zone.js/index.d.ts", "update-webdriver": "webdriver-manager update --gecko false $CHROMEDRIVER_VERSION_ARG", "check-env": "gulp check-env", "commitmsg": "node ./scripts/git/commit-msg.js" diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index a787b4026c..a22de56b4e 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -207,6 +207,12 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, if (fileName === path.join(compilerOpts.baseUrl, bazelOpts.package, compilerOpts.flatModuleOutFile + '.ts')) return true; + // Also handle the case when angular is build from source as an external repository + if (fileName === + path.join( + compilerOpts.baseUrl, 'external/angular', bazelOpts.package, + compilerOpts.flatModuleOutFile + '.ts')) + return true; return origBazelHostShouldNameModule(fileName) || NGC_GEN_FILES.test(fileName); }; diff --git a/packages/bazel/test/ngc-wrapped/tsconfig_template.ts b/packages/bazel/test/ngc-wrapped/tsconfig_template.ts index 05a8ee90b4..07c06ce281 100644 --- a/packages/bazel/test/ngc-wrapped/tsconfig_template.ts +++ b/packages/bazel/test/ngc-wrapped/tsconfig_template.ts @@ -70,7 +70,7 @@ export function createTsConfig(options: TsConfigOptions) { 'tsickleExternsPath': '', // we don't copy the node_modules into our tmp dir, so we should look in // the original workspace directory for it - 'nodeModulesPrefix': '../angular/node_modules', + 'nodeModulesPrefix': '../angular/external/angular_deps/node_modules', }, 'files': options.files, 'angularCompilerOptions': { diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 4cf8888f12..4840768b04 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -21,7 +21,7 @@ ts_library( ], ), module_name = "@angular/compiler-cli", - node_modules = "@//:node_modules", + node_modules = "@angular_deps//:node_modules", tsconfig = ":tsconfig", deps = [ "//packages/compiler", diff --git a/packages/compiler-cli/test/test_support.ts b/packages/compiler-cli/test/test_support.ts index c957a735e4..022e82fd40 100644 --- a/packages/compiler-cli/test/test_support.ts +++ b/packages/compiler-cli/test/test_support.ts @@ -124,7 +124,8 @@ export function setupBazelTo(basePath: string) { } // Link typescript - const typescriptSource = path.join(sources, 'angular/node_modules/typescript'); + const typescriptSource = + path.join(sources, 'angular/external/angular_deps/node_modules/typescript'); const typescriptDest = path.join(nodeModulesPath, 'typescript'); if (fs.existsSync(typescriptSource)) { fs.symlinkSync(typescriptSource, typescriptDest); diff --git a/packages/compiler-cli/test/transformers/node_emitter_spec.ts b/packages/compiler-cli/test/transformers/node_emitter_spec.ts index 353a0c0a64..9fdbe6ccbb 100644 --- a/packages/compiler-cli/test/transformers/node_emitter_spec.ts +++ b/packages/compiler-cli/test/transformers/node_emitter_spec.ts @@ -8,12 +8,13 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler'; import * as o from '@angular/compiler/src/output/output_ast'; -import {MappingItem, RawSourceMap, SourceMapConsumer} from 'source-map'; import * as ts from 'typescript'; import {TypeScriptNodeEmitter} from '../../src/transformers/node_emitter'; import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; +const sourceMap = require('source-map'); + const someGenFilePath = '/somePackage/someGenFile'; const someGenFileName = someGenFilePath + '.ts'; const someSourceFilePath = '/somePackage/someSourceFile'; @@ -469,16 +470,16 @@ describe('TypeScriptNodeEmitter', () => { return result; } - function mappingItemsOf(text: string): MappingItem[] { + function mappingItemsOf(text: string) { // find the source map: const sourceMapMatch = /sourceMappingURL\=data\:application\/json;base64,(.*)$/.exec(text); const sourceMapBase64 = sourceMapMatch ![1]; const sourceMapBuffer = Buffer.from(sourceMapBase64, 'base64'); const sourceMapText = sourceMapBuffer.toString('utf8'); - const sourceMap: RawSourceMap = JSON.parse(sourceMapText); - const consumer = new SourceMapConsumer(sourceMap); - const mappings: MappingItem[] = []; - consumer.eachMapping(mapping => { mappings.push(mapping); }); + const sourceMapParsed = JSON.parse(sourceMapText); + const consumer = new sourceMap.SourceMapConsumer(sourceMapParsed); + const mappings: any[] = []; + consumer.eachMapping((mapping: any) => { mappings.push(mapping); }); return mappings; } diff --git a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json index cabdda2fb0..e686f5de62 100644 --- a/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json @@ -3134,6 +3134,9 @@ { "name": "isIdentifierStart" }, + { + "name": "isInteropObservable" + }, { "name": "isIterable" }, @@ -3167,9 +3170,6 @@ { "name": "isObject" }, - { - "name": "isObservable" - }, { "name": "isPrefixEnd" }, diff --git a/packages/core/test/bundling/todo/BUILD.bazel b/packages/core/test/bundling/todo/BUILD.bazel index 1535f80fb8..1945a0d006 100644 --- a/packages/core/test/bundling/todo/BUILD.bazel +++ b/packages/core/test/bundling/todo/BUILD.bazel @@ -76,7 +76,7 @@ js_expected_symbol_test( genrule( name = "tslib", srcs = [ - "//:node_modules/tslib/tslib.js", + "@angular_deps//:node_modules/tslib/tslib.js", ], outs = [ "tslib.js", diff --git a/packages/elements/test/BUILD.bazel b/packages/elements/test/BUILD.bazel index bdf3c5912c..ec44be216b 100644 --- a/packages/elements/test/BUILD.bazel +++ b/packages/elements/test/BUILD.bazel @@ -24,10 +24,10 @@ filegroup( name = "elements_test_bootstrap_scripts", # do not sort srcs = [ - "//:node_modules/@webcomponents/custom-elements/src/native-shim.js", - "//:node_modules/reflect-metadata/Reflect.js", - "//:node_modules/zone.js/dist/zone.js", - "//:node_modules/zone.js/dist/zone-testing.js", + "@angular_deps//:node_modules/@webcomponents/custom-elements/src/native-shim.js", + "@angular_deps//:node_modules/reflect-metadata/Reflect.js", + "@angular_deps//:node_modules/zone.js/dist/zone.js", + "@angular_deps//:node_modules/zone.js/dist/zone-testing.js", ], ) @@ -38,7 +38,7 @@ ts_web_test_suite( ], # do not sort deps = [ - "//:node_modules/tslib/tslib.js", + "@angular_deps//:node_modules/tslib/tslib.js", "//tools/testing:browser", ":test_lib", ], diff --git a/packages/types.d.ts b/packages/types.d.ts index eb1d9387a5..530beb389c 100644 --- a/packages/types.d.ts +++ b/packages/types.d.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -// This file contains all ambient imports needed to compile the modules/ source code +// This file contains all ambient imports needed to compile the packages/ source code -/// -/// -/// -/// +/// +/// +/// +/// /// /// /// diff --git a/packages/upgrade/test/common/test_helpers.ts b/packages/upgrade/test/common/test_helpers.ts index c0f78a6b4f..ced3860b0e 100644 --- a/packages/upgrade/test/common/test_helpers.ts +++ b/packages/upgrade/test/common/test_helpers.ts @@ -55,7 +55,7 @@ export function createWithEachNg1VersionFn(setNg1: typeof setAngularJSGlobal) { (prev, file) => prev.then(() => new Promise((resolve, reject) => { const restoreMethods = patchJasmineMethods(); const script = document.createElement('script'); - script.src = `base/angular/node_modules/${file}`; + script.src = `base/angular_deps/node_modules/${file}`; script.onerror = reject; script.onload = () => { document.body.removeChild(script); diff --git a/tools/defaults.bzl b/tools/defaults.bzl index c58c92b0e9..9e4a5a105d 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -5,6 +5,7 @@ load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_p load("//packages/bazel/src:ng_module.bzl", _internal_global_ng_module = "internal_global_ng_module") DEFAULT_TSCONFIG = "//packages:tsconfig-build.json" +DEFAULT_NODE_MODULES = "@angular_deps//:node_modules" # Packages which are versioned together on npm ANGULAR_SCOPED_PACKAGES = ["@angular/%s" % p for p in [ @@ -37,17 +38,17 @@ PKG_GROUP_REPLACEMENTS = { ]""" % ",\n ".join(["\"%s\"" % s for s in ANGULAR_SCOPED_PACKAGES]) } -def ts_library(tsconfig = None, **kwargs): +def ts_library(tsconfig = None, node_modules = DEFAULT_NODE_MODULES, **kwargs): if not tsconfig: tsconfig = DEFAULT_TSCONFIG - _ts_library(tsconfig = tsconfig, **kwargs) + _ts_library(tsconfig = tsconfig, node_modules = node_modules, **kwargs) -def ng_module(name, tsconfig = None, entry_point = None, **kwargs): +def ng_module(name, tsconfig = None, entry_point = None, node_modules = DEFAULT_NODE_MODULES, **kwargs): if not tsconfig: tsconfig = DEFAULT_TSCONFIG if not entry_point: entry_point = "public_api.ts" - _ng_module(name = name, flat_module_out_file = name, tsconfig = tsconfig, entry_point = entry_point, **kwargs) + _ng_module(name = name, flat_module_out_file = name, tsconfig = tsconfig, entry_point = entry_point, node_modules = node_modules, **kwargs) # ivy_ng_module behaves like ng_module, and under --define=compile=legacy it runs ngc with global # analysis but produces Ivy outputs. Under other compile modes, it behaves as ng_module. @@ -82,7 +83,7 @@ def ts_web_test_suite(bootstrap = [], deps = [], **kwargs): if not bootstrap: bootstrap = ["//:web_test_bootstrap_scripts"] local_deps = [ - "//:node_modules/tslib/tslib.js", + "@angular_deps//:node_modules/tslib/tslib.js", "//tools/testing:browser", ] + deps diff --git a/tools/ng_setup_workspace.bzl b/tools/ng_setup_workspace.bzl new file mode 100644 index 0000000000..1e3c90bcac --- /dev/null +++ b/tools/ng_setup_workspace.bzl @@ -0,0 +1,238 @@ +# 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 + +"Install angular source dependencies" + +load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install") +load("@angular//packages/bazel/src:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace") + +def ng_setup_workspace(): + """This repository rule should be called from your WORKSPACE file. + + It creates some additional Bazel external repositories that are used internally + to build angular + """ + yarn_install( + name = "angular_deps", + package_json = "@angular//:package.json", + yarn_lock = "@angular//:yarn.lock", + data = ["@angular//:tools/yarn/check-yarn.js", "@angular//:tools/postinstall-patches.js"], + node_modules_filegroup = """ +filegroup( + name = "node_modules", + srcs = glob(["/".join([ + "node_modules", + pkg, + "**", + ext, + ]) for pkg in [ + "adm-zip", + "ajv", + "angular", + "angular-1.5", + "angular-mocks", + "angular-mocks-1.5", + "anymatch", + "arr-diff", + "arr-flatten", + "arr-union", + "array-unique", + "asn1", + "assert-plus", + "assign-symbols", + "async-each", + "asynckit", + "atob", + "aws-sign2", + "aws4", + "balanced-match", + "base", + "base64-js", + "binary-extensions", + "blocking-proxy", + "brace-expansion", + "braces", + "bytebuffer", + "cache-base", + "caseless", + "chokidar", + "class-utils", + "co", + "collection-visit", + "combined-stream", + "component-emitter", + "concat-map", + "copy-descriptor", + "core-util-is", + "debug", + "decode-uri-component", + "define-property", + "delayed-stream", + "domino", + "expand-brackets", + "expand-range", + "extend", + "extend-shallow", + "extglob", + "extsprintf", + "fast-deep-equal", + "fast-json-stable-stringify", + "filename-regex", + "fill-range", + "for-in", + "for-own", + "forever-agent", + "form-data", + "fragment-cache", + "fs.realpath", + "get-value", + "glob", + "glob-base", + "glob-parent", + "graceful-fs", + "hammerjs", + "har-schema", + "har-validator", + "has-value", + "has-values", + "http-signature", + "https-proxy-agent", + "inflight", + "inherits", + "is-accessor-descriptor", + "is-binary-path", + "is-buffer", + "is-data-descriptor", + "is-descriptor", + "is-dotfile", + "is-equal-shallow", + "is-extendable", + "is-extglob", + "is-glob", + "is-number", + "is-plain-object", + "is-posix-bracket", + "is-primitive", + "is-typedarray", + "is-windows", + "isarray", + "isobject", + "isstream", + "jasmine", + "jasmine-core", + "jasminewd2", + "json-schema", + "json-schema-traverse", + "json-stable-stringify", + "json-stringify-safe", + "jsprim", + "kind-of", + "long", + "lru-cache", + "map-cache", + "map-visit", + "math-random", + "micromatch", + "mime-db", + "mime-types", + "minimatch", + "minimist", + "mixin-deep", + "nanomatch", + "normalize-path", + "oauth-sign", + "object.omit", + "object.pick", + "object-copy", + "object-visit", + "once", + "optimist", + "options", + "os-tmpdir", + "parse-glob", + "pascalcase", + "path-dirname", + "path-is-absolute", + "performance-now", + "posix-character-classes", + "preserve", + "process-nextick-args", + "protobufjs", + "protractor", + "qs", + "randomatic", + "readable-stream", + "readdirp", + "reflect-metadata", + "regex-cache", + "regex-not", + "remove-trailing-separator", + "repeat-element", + "repeat-string", + "request", + "ret", + "rimraf", + "safe-buffer", + "safe-regex", + "safer-buffer", + "sax", + "semver", + "set-immediate-shim", + "set-value", + "shelljs", + "sigmund", + "snapdragon", + "snapdragon-node", + "snapdragon-util", + "source-map", + "source-map-resolve", + "source-map-support", + "source-map-url", + "split-string", + "sshpk", + "static-extend", + "stringstream", + "tmp", + "to-object-path", + "to-regex", + "to-regex-range", + "tough-cookie", + "tsickle", + "tslib", + "tsutils", + "tunnel-agent", + "typescript", + "union-value", + "unset-value", + "upath", + "uri-js", + "urix", + "use", + "util-deprecate", + "uuid", + "verror", + "webdriver-js-extender", + "webdriver-manager", + "wordwrap", + "wrappy", + "xhr2", + "xml2js", + "xmlbuilder", + "zone.js", + "@angular-devkit/core", + "@angular-devkit/schematics", + "@types", + "@webcomponents/custom-elements", + ] for ext in [ + "*.js", + "*.json", + "*.d.ts", + ]] + [ + "node_modules/protractor/**", + "node_modules/@schematics/angular/**", + ])) +""") + + _ng_setup_workspace() diff --git a/tools/postinstall-patches.js b/tools/postinstall-patches.js index f406a97d52..68cb13623d 100644 --- a/tools/postinstall-patches.js +++ b/tools/postinstall-patches.js @@ -6,6 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ +try { + require.resolve('shelljs'); +} catch (e) { + // We are in an bazel managed external node_modules repository + // and the resolve has failed because node did not preserve the symlink + // when loading the script. + // This can be fixed using the --preserve-symlinks-main flag which + // is introduced in node 10.2.0 + console.warn( + 'Running postinstall-patches.js script in an external repository requires --preserve-symlinks-main node flag introduced in node 10.2.0'); + process.exit(0); +} + const {set, cd, sed, rm} = require('shelljs'); const path = require('path'); const log = console.log; diff --git a/tools/types.d.ts b/tools/types.d.ts index e4c8edfafa..ab3d9db318 100644 --- a/tools/types.d.ts +++ b/tools/types.d.ts @@ -1,5 +1,5 @@ // This file contains all ambient imports needed to compile the tools source code -/// -/// -/// +/// +/// +/// diff --git a/yarn.lock b/yarn.lock index e4fa0830cb..86a9be8662 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,67 +2,45 @@ # yarn lockfile v1 -"@angular-devkit/core@0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.5.4.tgz#94b7462f5039cf811c7e06db0c87bb2299d61c71" +"@angular-devkit/core@0.5.13": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.5.13.tgz#c48c83c1bf38917e6af3f399d221d29466be9677" dependencies: - ajv "~5.5.1" - chokidar "^1.7.0" - rxjs "^6.0.0-beta.3" + ajv "~6.4.0" + chokidar "^2.0.3" + rxjs "^6.0.0" source-map "^0.5.6" -"@angular-devkit/core@0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.5.5.tgz#659c9ef3f22c3e99c459e325441b1009412a4af6" +"@angular-devkit/schematics@0.5.13", "@angular-devkit/schematics@^0.5.5": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.5.13.tgz#be90657c668cddae4054645a61d4cf852de51ede" dependencies: - ajv "~5.5.1" - chokidar "^1.7.0" - rxjs "^6.0.0-beta.3" - source-map "^0.5.6" - -"@angular-devkit/schematics@0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.5.4.tgz#6a4b0abb30091fa1a5d0751737f9ed036ac8704f" - dependencies: - "@angular-devkit/core" "0.5.4" - "@ngtools/json-schema" "^1.1.0" - rxjs "^6.0.0-beta.3" - -"@angular-devkit/schematics@^0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.5.5.tgz#c0b20980993237f2eeb4182e253c7c9efa299f3b" - dependencies: - "@angular-devkit/core" "0.5.5" - "@ngtools/json-schema" "^1.1.0" - rxjs "^6.0.0-beta.3" + "@angular-devkit/core" "0.5.13" + rxjs "^6.0.0" "@bazel/ibazel@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.1.1.tgz#f970c08b4e4efb0ab17e04ade3cc610554f33bed" -"@ngtools/json-schema@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922" - "@schematics/angular@^0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.5.4.tgz#1c87706703a985fd291d283a4db1a9fc0aa6238f" + version "0.5.13" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.5.13.tgz#e93e249f3903c8465e476c0d0a87668f0d5354bd" dependencies: - "@angular-devkit/core" "0.5.4" - "@angular-devkit/schematics" "0.5.4" - typescript "~2.6.2" + "@angular-devkit/core" "0.5.13" + "@angular-devkit/schematics" "0.5.13" + typescript ">=2.6.2 <2.8" "@types/angular@^1.6.47": - version "1.6.47" - resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.47.tgz#f7a31279a02c0892ed9aa76aae2da1b17791bacd" + version "1.6.48" + resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.48.tgz#e8f77bf41d00d3293421cbaaed2bafb4d5f399f9" "@types/base64-js@1.2.5": version "1.2.5" resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" "@types/chai@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.4.tgz#5ca073b330d90b4066d6ce18f60d57f2084ce8ca" "@types/chokidar@1.7.3": version "1.7.3" @@ -71,12 +49,12 @@ "@types/node" "*" "@types/diff@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@types/diff/-/diff-3.2.2.tgz#4d6f45537322a7a420d353a0939513c7e96d14a6" + version "3.5.1" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-3.5.1.tgz#30253f6e177564ad7da707b1ebe46d3eade71706" "@types/events@*": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02" + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" "@types/fs-extra@4.0.2": version "4.0.2" @@ -115,13 +93,17 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" "@types/node@*": - version "8.0.28" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.28.tgz#86206716f8d9251cf41692e384264cbd7058ad60" + version "10.5.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.2.tgz#f19f05314d5421fe37e74153254201a7bf00a707" -"@types/node@6.0.88", "@types/node@^6.0.46": +"@types/node@6.0.88": version "6.0.88" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66" +"@types/node@^6.0.46": + version "6.0.114" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.114.tgz#c42cd56479f32bc1576a5cb19f8a208da9a2b052" + "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.yarnpkg.com/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5" @@ -131,42 +113,44 @@ resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.7.tgz#5d3613d1ab3ca08b74d19683a3a7c573129ab18f" "@types/selenium-webdriver@^2.53.35", "@types/selenium-webdriver@~2.53.39": - version "2.53.42" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-2.53.42.tgz#74cb77fb6052edaff2a8984ddafd88d419f25cac" + version "2.53.43" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz#2de3d718819bc20165754c4a59afb7e9833f6707" "@types/shelljs@^0.7.8": - version "0.7.8" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.8.tgz#4b4d6ee7926e58d7bca448a50ba442fd9f6715bd" + version "0.7.9" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.9.tgz#3abecb72d9cad9cd4b0e7cb86ed10a97d93ba602" dependencies: "@types/glob" "*" "@types/node" "*" "@types/source-map@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.1.tgz#7e74db5d06ab373a712356eebfaea2fad0ea2367" + version "0.5.7" + resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.7.tgz#165eeb583c1ef00196fe4ef4da5d7832b03b275b" + dependencies: + source-map "*" "@types/systemjs@0.19.32": version "0.19.32" resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-0.19.32.tgz#e9204c4cdbc8e275d645c00e6150e68fc5615a24" "@webcomponents/custom-elements@^1.0.4": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@webcomponents/custom-elements/-/custom-elements-1.0.8.tgz#b7b8ef7248f7681d1ad4286a0ada5fe3c2bc7228" + version "1.1.2" + resolved "https://registry.yarnpkg.com/@webcomponents/custom-elements/-/custom-elements-1.1.2.tgz#041e4c20df35245f4d160b50d044b8cff192962c" Base64@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" JSONStream@^1.0.4: - version "1.3.1" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" + version "1.3.3" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" dependencies: jsonparse "^1.2.0" through ">=2.2.7 <3" abbrev@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" accepts@~1.2.12, accepts@~1.2.13: version "1.2.13" @@ -175,14 +159,7 @@ accepts@~1.2.12, accepts@~1.2.13: mime-types "~2.1.6" negotiator "0.5.3" -accepts@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" - dependencies: - mime-types "~2.1.16" - negotiator "0.6.1" - -accepts@~1.3.4: +accepts@~1.3.0, accepts@~1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: @@ -193,10 +170,14 @@ acorn@^1.0.3: version "1.2.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" -acorn@^4.0.1, acorn@^4.0.3: +acorn@^4.0.1: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" +acorn@^5.2.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -209,9 +190,9 @@ adm-zip@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736" -adm-zip@^0.4.7, adm-zip@~0.4.3, adm-zip@~0.4.x: - version "0.4.7" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1" +adm-zip@^0.4.7, adm-zip@^0.4.9, adm-zip@~0.4.3, adm-zip@~0.4.x: + version "0.4.11" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" after@0.8.2: version "0.8.2" @@ -225,21 +206,19 @@ agent-base@2: semver "~5.0.1" agent-base@4, agent-base@^4.1.0, agent-base@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" dependencies: es6-promisify "^5.0.0" -ajv@^5.1.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: co "^4.6.0" - fast-deep-equal "^1.0.0" - json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" -ajv@~5.5.1: +ajv@^5.1.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -248,6 +227,15 @@ ajv@~5.5.1: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.4.0.tgz#d3aff78e9277549771daf0164cff48482b754fc6" + dependencies: + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + uri-js "^3.0.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -285,12 +273,18 @@ amqplib@^0.5.2: resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.5.11.tgz#a0e1dd0ea55fd77ee7a757d75536c5e964c86f81" "angular-mocks@npm:angular-mocks@1.6": - version "1.6.9" - resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.6.9.tgz#4fed8c8293a5080e0919a7ff0dcf0f704864b7ba" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.6.10.tgz#6a139e43c461d0c9a5a1acebc91e63db16031176" "angular@npm:angular@1.6": - version "1.6.9" - resolved "https://registry.yarnpkg.com/angular/-/angular-1.6.9.tgz#bc812932e18909038412d594a5990f4bb66c0619" + version "1.6.10" + resolved "https://registry.yarnpkg.com/angular/-/angular-1.6.10.tgz#eed3080a34d29d0f681ff119b18ce294e3f74826" + +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + dependencies: + ansi-wrap "0.1.0" ansi-regex@^2.0.0, ansi-regex@^2.1.1: version "2.1.1" @@ -304,6 +298,10 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-wrap@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + any-promise@^1.0.0, any-promise@^1.1.0, any-promise@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -386,8 +384,8 @@ archy@^1.0.0: resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" dependencies: delegates "^1.0.0" readable-stream "^2.0.6" @@ -443,8 +441,8 @@ array-slice@^0.2.3: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" array-slice@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f" + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" array-union@^1.0.1: version "1.0.2" @@ -468,7 +466,7 @@ arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" -arrify@^1.0.0: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -535,13 +533,7 @@ async@^1.3.0, async@^1.4.0, async@^1.5.2, async@~1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" - dependencies: - lodash "^4.14.0" - -async@^2.0.1, async@^2.1.2, async@~2.6.0: +async@^2.0.0, async@^2.0.1, async@^2.1.2, async@~2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: @@ -561,9 +553,9 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" +atob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" aws-sign2@~0.6.0: version "0.6.0" @@ -574,8 +566,8 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" axios@^0.15.3: version "0.15.3" @@ -622,10 +614,14 @@ base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" -base64-js@1.2.1, base64-js@^1.0.2: +base64-js@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + base64-url@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-1.2.1.tgz#199fd661702a0e7b7dcae6e0698bb089c52f6d78" @@ -634,10 +630,6 @@ base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" -base64url@2.0.0, base64url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" - base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -663,8 +655,8 @@ batch@0.5.3: resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" @@ -683,8 +675,8 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" binary-extensions@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" "binary@>= 0.3.0 < 1", binary@^0.3.0: version "0.3.0" @@ -700,10 +692,11 @@ bitsyntax@~0.0.4: buffer-more-ints "0.0.2" bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" dependencies: - readable-stream "^2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" bl@~1.1.2: version "1.1.2" @@ -799,8 +792,8 @@ bower@1.8.2: resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.2.tgz#adf53529c8d4af02ef24fb8d5341c1419d33e2f7" brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -835,8 +828,8 @@ braces@^2.3.0, braces@^2.3.1: to-regex "^3.0.1" browser-resolve@^1.11.0: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" dependencies: resolve "1.1.7" @@ -866,6 +859,17 @@ browserstacktunnel-wrapper@~2.0.1: https-proxy-agent "^1.0.0" unzip "~0.1.9" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + +buffer-alloc@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@^0.2.1: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -874,6 +878,14 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + +buffer-from@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + buffer-more-ints@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz#26b3885d10fa13db7fc01aae3aab870199e0124c" @@ -953,6 +965,14 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase-keys@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" + dependencies: + camelcase "^4.1.0" + map-obj "^2.0.0" + quick-lru "^1.0.0" + camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" @@ -1006,7 +1026,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chokidar@1.7.0, chokidar@^1.0.0, chokidar@^1.7.0: +chokidar@1.7.0, chokidar@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1061,8 +1081,8 @@ clang-format@1.0.41: resolve "^1.1.6" clang-format@^1.0.32: - version "1.0.55" - resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.0.55.tgz#8031271329e27a779a5d08fc5dce24d7c52c14d5" + version "1.2.3" + resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.2.3.tgz#2763561aa7449c43737480f8df3a2b5b66e6cf37" dependencies: async "^1.5.2" glob "^7.0.0" @@ -1144,8 +1164,8 @@ clone@^0.2.0: resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" clone@^1.0.0, clone@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" co@^4.6.0: version "4.6.0" @@ -1166,13 +1186,17 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + colors@0.6.0-1: version "0.6.0-1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.0-1.tgz#6dbb68ceb8bc60f2b313dcc5ce1599f06d19e67a" colors@^1.1.0, colors@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + version "1.3.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.0.tgz#5f20c9fef6945cb1134260aab33bfbdc8295e04e" colour@~0.7.1: version "0.7.1" @@ -1184,18 +1208,12 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combined-stream@1.0.6: +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" - dependencies: - delayed-stream "~1.0.0" - commander@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-1.0.0.tgz#5e6a88e7070ff5908836ead19169548c30f90bcd" @@ -1207,8 +1225,8 @@ commander@2.9.0: graceful-readlink ">= 1.0.0" commander@^2.5.0, commander@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + version "2.16.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" commander@~2.13.0: version "2.13.0" @@ -1252,8 +1270,8 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" compress-commons@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.0.tgz#58587092ef20d37cb58baf000112c9278ff73b9f" + version "1.2.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.2.tgz#524a9f10903f3a813389b0225d27c48bb751890f" dependencies: buffer-crc32 "^0.2.1" crc32-stream "^2.0.0" @@ -1261,14 +1279,14 @@ compress-commons@^1.2.0: readable-stream "^2.0.0" compressible@~2.0.5: - version "2.0.11" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" + version "2.0.14" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.14.tgz#326c5f507fbb055f54116782b969a81b67a29da7" dependencies: - mime-db ">= 1.29.0 < 2" + mime-db ">= 1.34.0 < 2" compression@~1.5.2: version "1.5.2" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.5.2.tgz#b03b8d86e6f8ad29683cba8df91ddc6ffc77b395" + resolved "http://registry.npmjs.org/compression/-/compression-1.5.2.tgz#b03b8d86e6f8ad29683cba8df91ddc6ffc77b395" dependencies: accepts "~1.2.12" bytes "2.1.0" @@ -1282,9 +1300,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" concat-stream@^1.4.7, concat-stream@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" @@ -1372,42 +1391,48 @@ content-type@~1.0.1, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" -conventional-changelog-angular@^1.0.0, conventional-changelog-angular@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.5.0.tgz#50b2d45008448455fdf67e06ea01972fbd08182a" +conventional-changelog-angular@^1.0.0, conventional-changelog-angular@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" dependencies: compare-func "^1.3.1" - q "^1.4.1" + q "^1.5.1" -conventional-changelog-atom@^0.1.0, conventional-changelog-atom@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-0.1.1.tgz#d40a9b297961b53c745e5d1718fd1a3379f6a92f" +conventional-changelog-atom@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-0.1.2.tgz#12595ad5267a6937c34cf900281b1c65198a4c63" dependencies: q "^1.4.1" +conventional-changelog-atom@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz#8037693455990e3256f297320a45fa47ee553a14" + dependencies: + q "^1.5.1" + conventional-changelog-codemirror@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.1.0.tgz#7577a591dbf9b538e7a150a7ee62f65a2872b334" dependencies: q "^1.4.1" -conventional-changelog-codemirror@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.2.0.tgz#3cc925955f3b14402827b15168049821972d9459" +conventional-changelog-codemirror@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz#a1982c8291f4ee4d6f2f62817c6b2ecd2c4b7b47" dependencies: - q "^1.4.1" + q "^1.5.1" -conventional-changelog-core@^1.3.0, conventional-changelog-core@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-1.9.1.tgz#ddf767c405850dfc8df31726c80fa1a6a10bdc7b" +conventional-changelog-core@^1.3.0: + version "1.9.5" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-1.9.5.tgz#5db7566dad7c0cb75daf47fbb2976f7bf9928c1d" dependencies: - conventional-changelog-writer "^2.0.1" - conventional-commits-parser "^2.0.0" + conventional-changelog-writer "^2.0.3" + conventional-commits-parser "^2.1.0" dateformat "^1.0.12" get-pkg-repo "^1.0.0" - git-raw-commits "^1.2.0" + git-raw-commits "^1.3.0" git-remote-origin-url "^2.0.0" - git-semver-tags "^1.2.1" + git-semver-tags "^1.2.3" lodash "^4.0.0" normalize-package-data "^2.3.5" q "^1.4.1" @@ -1415,23 +1440,47 @@ conventional-changelog-core@^1.3.0, conventional-changelog-core@^1.9.1: read-pkg-up "^1.0.1" through2 "^2.0.0" -conventional-changelog-ember@^0.2.0, conventional-changelog-ember@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-0.2.7.tgz#c6aff35976284e7222649f81c62bd96ff3217bd2" +conventional-changelog-core@^2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz#19b5fbd55a9697773ed6661f4e32030ed7e30287" + dependencies: + conventional-changelog-writer "^3.0.9" + conventional-commits-parser "^2.1.7" + dateformat "^3.0.0" + get-pkg-repo "^1.0.0" + git-raw-commits "^1.3.6" + git-remote-origin-url "^2.0.0" + git-semver-tags "^1.3.6" + lodash "^4.2.1" + normalize-package-data "^2.3.5" + q "^1.5.1" + read-pkg "^1.1.0" + read-pkg-up "^1.0.1" + through2 "^2.0.0" + +conventional-changelog-ember@^0.2.0: + version "0.2.10" + resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-0.2.10.tgz#dcd6e4cdc2e6c2b58653cf4d2cb1656a60421929" dependencies: q "^1.4.1" +conventional-changelog-ember@^0.3.12: + version "0.3.12" + resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz#b7d31851756d0fcb49b031dffeb6afa93b202400" + dependencies: + q "^1.5.1" + conventional-changelog-eslint@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-0.1.0.tgz#a52411e999e0501ce500b856b0a643d0330907e2" dependencies: q "^1.4.1" -conventional-changelog-eslint@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-0.2.0.tgz#b4b9b5dc09417844d87c7bcfb16bdcc686c4b1c1" +conventional-changelog-eslint@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz#b13cc7e4b472c819450ede031ff1a75c0e3d07d3" dependencies: - q "^1.4.1" + q "^1.5.1" conventional-changelog-express@^0.1.0: version "0.1.0" @@ -1439,11 +1488,11 @@ conventional-changelog-express@^0.1.0: dependencies: q "^1.4.1" -conventional-changelog-express@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-0.2.0.tgz#8d666ad41b10ebf964a4602062ddd2e00deb518d" +conventional-changelog-express@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz#4a6295cb11785059fb09202180d0e59c358b9c2c" dependencies: - q "^1.4.1" + q "^1.5.1" conventional-changelog-jquery@^0.1.0: version "0.1.0" @@ -1464,19 +1513,23 @@ conventional-changelog-jshint@^0.1.0: compare-func "^1.3.1" q "^1.4.1" -conventional-changelog-jshint@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-0.2.0.tgz#63ad7aec66cd1ae559bafe80348c4657a6eb1872" +conventional-changelog-jshint@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz#9051c1ac0767abaf62a31f74d2fe8790e8acc6c8" dependencies: compare-func "^1.3.1" - q "^1.4.1" + q "^1.5.1" -conventional-changelog-writer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-2.0.1.tgz#47c10d0faba526b78d194389d1e931d09ee62372" +conventional-changelog-preset-loader@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz#40bb0f142cd27d16839ec6c74ee8db418099b373" + +conventional-changelog-writer@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-2.0.3.tgz#073b0c39f1cc8fc0fd9b1566e93833f51489c81c" dependencies: compare-func "^1.3.1" - conventional-commits-filter "^1.0.0" + conventional-commits-filter "^1.1.1" dateformat "^1.0.11" handlebars "^4.0.2" json-stringify-safe "^5.0.1" @@ -1486,6 +1539,21 @@ conventional-changelog-writer@^2.0.1: split "^1.0.0" through2 "^2.0.0" +conventional-changelog-writer@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz#4aecdfef33ff2a53bb0cf3b8071ce21f0e994634" + dependencies: + compare-func "^1.3.1" + conventional-commits-filter "^1.1.6" + dateformat "^3.0.0" + handlebars "^4.0.2" + json-stringify-safe "^5.0.1" + lodash "^4.2.1" + meow "^4.0.0" + semver "^5.5.0" + split "^1.0.0" + through2 "^2.0.0" + conventional-changelog@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-1.1.0.tgz#8ae3fb59feb74bbee0a25833ee1f83dad4a07874" @@ -1502,35 +1570,36 @@ conventional-changelog@1.1.0: conventional-changelog-jshint "^0.1.0" conventional-changelog@^1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-1.1.5.tgz#4c46fb64b2986cab19888d8c4b87ca7c0e431bfd" + version "1.1.24" + resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-1.1.24.tgz#3d94c29c960f5261c002678315b756cdd3d7d1f0" dependencies: - conventional-changelog-angular "^1.5.0" - conventional-changelog-atom "^0.1.1" - conventional-changelog-codemirror "^0.2.0" - conventional-changelog-core "^1.9.1" - conventional-changelog-ember "^0.2.7" - conventional-changelog-eslint "^0.2.0" - conventional-changelog-express "^0.2.0" + conventional-changelog-angular "^1.6.6" + conventional-changelog-atom "^0.2.8" + conventional-changelog-codemirror "^0.3.8" + conventional-changelog-core "^2.0.11" + conventional-changelog-ember "^0.3.12" + conventional-changelog-eslint "^1.0.9" + conventional-changelog-express "^0.3.6" conventional-changelog-jquery "^0.1.0" conventional-changelog-jscs "^0.1.0" - conventional-changelog-jshint "^0.2.0" + conventional-changelog-jshint "^0.3.8" + conventional-changelog-preset-loader "^1.1.8" -conventional-commits-filter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-1.0.0.tgz#6fc2a659372bc3f2339cf9ffff7e1b0344b93039" +conventional-commits-filter@^1.1.1, conventional-commits-filter@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz#4389cd8e58fe89750c0b5fb58f1d7f0cc8ad3831" dependencies: is-subset "^0.1.1" modify-values "^1.0.0" -conventional-commits-parser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-2.0.0.tgz#71d01910cb0a99aeb20c144e50f81f4df3178447" +conventional-commits-parser@^2.1.0, conventional-commits-parser@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz#eca45ed6140d72ba9722ee4132674d639e644e8e" dependencies: JSONStream "^1.0.4" is-text-path "^1.0.0" lodash "^4.2.1" - meow "^3.3.0" + meow "^4.0.0" split2 "^2.0.0" through2 "^2.0.0" trim-off-newlines "^1.0.0" @@ -1558,14 +1627,10 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -core-js@^2.2.0: +core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" -core-js@^2.4.0, core-js@^2.4.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" - core-js@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65" @@ -1593,8 +1658,8 @@ crc@3.3.0: resolved "https://registry.yarnpkg.com/crc/-/crc-3.3.0.tgz#fa622e1bc388bf257309082d6b65200ce67090ba" crc@^3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" + version "3.5.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.5.0.tgz#98b8ba7d489665ba3979f59b21381374101a1964" cross-spawn@^5.0.1: version "5.1.0" @@ -1692,13 +1757,11 @@ dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" -debug@2, debug@2.6.8, debug@^2.2.0: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - dependencies: - ms "2.0.0" +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" -debug@2.6.9, debug@^2.1.2, debug@^2.3.3, debug@~2.6.4, debug@~2.6.6: +debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@~2.6.4, debug@~2.6.6: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1716,10 +1779,21 @@ debug@~2.2.0: dependencies: ms "0.7.1" -decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: +decamelize-keys@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + decompress-zip@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/decompress-zip/-/decompress-zip-0.3.0.tgz#ae3bcb7e34c65879adfe77e19c30f86602b4bdb0" @@ -1732,18 +1806,10 @@ decompress-zip@0.3.0: readable-stream "^1.1.8" touch "0.0.3" -deep-equal@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1758,13 +1824,6 @@ defaults@^1.0.0: dependencies: clone "^1.0.2" -define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" - dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" - define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1784,7 +1843,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0, defined@~1.0.0: +defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -1820,11 +1879,7 @@ depd@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa" -depd@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.1.2: +depd@~1.1.0, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1836,11 +1891,9 @@ destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" -detect-file@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" - dependencies: - fs-exists-sync "^0.1.0" +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" detect-libc@^1.0.2: version "1.0.3" @@ -1859,10 +1912,10 @@ detective@0.1.1: uglify-js "~1.2.5" detective@^4.3.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1" + version "4.7.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" dependencies: - acorn "^4.0.3" + acorn "^5.2.1" defined "^1.0.0" di@^0.0.1: @@ -1873,11 +1926,7 @@ diff@^2.0.2: version "2.2.3" resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" -diff@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" - -diff@^3.5.0: +diff@^3.2.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -1898,8 +1947,8 @@ dom-serialize@^2.2.0: void-elements "^2.0.0" domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" domino@2.0.1: version "2.0.1" @@ -1937,11 +1986,10 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -ecdsa-sig-formatter@1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" dependencies: - base64url "^2.0.0" safe-buffer "^5.0.1" ee-first@1.1.1: @@ -1953,12 +2001,12 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" end-of-stream@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" dependencies: once "^1.4.0" @@ -2030,14 +2078,14 @@ entities@1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" errno@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" dependencies: - prr "~0.0.0" + prr "~1.0.1" -error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" dependencies: is-arrayish "^0.2.1" @@ -2048,42 +2096,25 @@ errorhandler@~1.4.2: accepts "~1.3.0" escape-html "~1.0.3" -es-abstract@^1.5.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.2.tgz#25103263dc4decbda60e0c737ca32313518027ee" +es5-ext@^0.10.12, es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: + version "0.10.45" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.45.tgz#0bfdf7b473da5919d5adf3bd25ceb754fccc3653" dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-to-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" - dependencies: - is-callable "^1.1.1" - is-date-object "^1.0.1" - is-symbol "^1.0.1" - -es5-ext@^0.10.12, es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: - version "0.10.30" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939" - dependencies: - es6-iterator "2" - es6-symbol "~3.1" + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + next-tick "1" es6-error@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.0.0.tgz#f094c7041f662599bb12720da059d6b9c7ff0f40" -es6-iterator@2, es6-iterator@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" +es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" dependencies: d "1" - es5-ext "^0.10.14" - es6-symbol "^3.1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" es6-module-loader@^0.17.4: version "0.17.11" @@ -2092,8 +2123,8 @@ es6-module-loader@^0.17.4: when "^3.7.2" es6-promise@^4.0.3: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" es6-promise@~3.0.2: version "3.0.2" @@ -2105,7 +2136,7 @@ es6-promisify@5.0.0, es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1: +es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" dependencies: @@ -2168,6 +2199,10 @@ estree-walker@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" +estree-walker@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39" + esutils@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375" @@ -2189,7 +2224,7 @@ event-emitter@^0.3.5: event-stream@^3.1.5, event-stream@^3.3.2: version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" dependencies: duplexer "~0.1.1" from "~0" @@ -2199,9 +2234,9 @@ event-stream@^3.1.5, event-stream@^3.3.2: stream-combiner "~0.0.4" through "~2.3.1" -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" +eventemitter3@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" events@^1.0.0: version "1.1.1" @@ -2262,13 +2297,7 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - dependencies: - os-homedir "^1.0.1" - -expand-tilde@^2.0.2: +expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" dependencies: @@ -2324,20 +2353,25 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + fancy-log@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" + version "1.3.2" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" dependencies: - chalk "^1.1.1" + ansi-gray "^0.1.1" + color-support "^1.1.3" time-stamp "^1.0.0" fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -2362,12 +2396,12 @@ filename-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" @@ -2418,14 +2452,14 @@ find-up@^2.0.0: dependencies: locate-path "^2.0.0" -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" +findup-sync@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" + detect-file "^1.0.0" + is-glob "^3.1.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" findup-sync@~0.3.0: version "0.3.0" @@ -2479,9 +2513,9 @@ first-chunk-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" -flagged-respawn@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" +flagged-respawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" follow-redirects@1.0.0: version "1.0.0" @@ -2489,11 +2523,11 @@ follow-redirects@1.0.0: dependencies: debug "^2.2.0" -for-each@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" +follow-redirects@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291" dependencies: - is-function "~1.0.0" + debug "^3.1.0" for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" @@ -2511,10 +2545,6 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -2535,7 +2565,15 @@ form-data@~2.0.0: combined-stream "^1.0.5" mime-types "^2.1.11" -form-data@~2.3.0: +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +form-data@~2.3.0, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: @@ -2543,14 +2581,6 @@ form-data@~2.3.0: combined-stream "1.0.6" mime-types "^2.1.12" -form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -2571,9 +2601,9 @@ fs-access@^1.0.0: dependencies: null-check "^1.0.0" -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" fs-extra@0.30.0, fs-extra@~0.30.0: version "0.30.0" @@ -2616,14 +2646,14 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@1.1.2, fsevents@^1.0.0: +fsevents@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" dependencies: nan "^2.3.0" node-pre-gyp "^0.6.36" -fsevents@^1.2.2: +fsevents@^1.0.0, fsevents@^1.2.2: version "1.2.4" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: @@ -2663,10 +2693,6 @@ ftp@~0.3.10: readable-stream "1.1.x" xregexp "2.0.0" -function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - fx-runner@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fx-runner/-/fx-runner-1.0.5.tgz#713519aca7598a8319e03fa1110b89b97ef1063f" @@ -2752,13 +2778,13 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -git-raw-commits@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-1.2.0.tgz#0f3a8bfd99ae0f2d8b9224d58892975e9a52d03c" +git-raw-commits@^1.3.0, git-raw-commits@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-1.3.6.tgz#27c35a32a67777c1ecd412a239a6c19d71b95aff" dependencies: dargs "^4.0.1" lodash.template "^4.0.2" - meow "^3.3.0" + meow "^4.0.0" split2 "^2.0.0" through2 "^2.0.0" @@ -2769,12 +2795,12 @@ git-remote-origin-url@^2.0.0: gitconfiglocal "^1.0.0" pify "^2.3.0" -git-semver-tags@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-1.2.1.tgz#6ccd2a52e735b736748dc762444fcd9588e27490" +git-semver-tags@^1.2.3, git-semver-tags@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-1.3.6.tgz#357ea01f7280794fe0927f2806bee6414d2caba5" dependencies: - meow "^3.3.0" - semver "^5.0.1" + meow "^4.0.0" + semver "^5.5.0" gitconfiglocal@^1.0.0: version "1.0.0" @@ -2825,7 +2851,7 @@ glob2base@^0.0.12: dependencies: find-index "^0.1.1" -glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.2: +glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -2863,21 +2889,23 @@ glob@~3.1.21: inherits "1" minimatch "~0.2.11" -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" dependencies: - homedir-polyfill "^1.0.0" + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" + is-windows "^1.0.1" + which "^1.2.14" globby@^5.0.0: version "5.0.0" @@ -2899,8 +2927,8 @@ globule@~0.1.0: minimatch "~0.2.11" glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" dependencies: sparkles "^1.0.0" @@ -3031,8 +3059,8 @@ hammerjs@2.0.8: resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" handlebars@^4.0.2: - version "4.0.10" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: async "^1.4.0" optimist "^0.6.1" @@ -3040,6 +3068,10 @@ handlebars@^4.0.2: optionalDependencies: uglify-js "^2.6" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -3053,6 +3085,13 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -3117,19 +3156,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1, has@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" - dependencies: - function-bind "^1.0.2" - "hashish@>=0.0.2 <0.1": version "0.0.4" resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" dependencies: traverse ">=0.2.4" -hawk@~3.1.3: +hawk@3.1.3, hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -3159,18 +3192,18 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: +homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" dependencies: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" http-browserify@^1.3.2: version "1.7.0" @@ -3196,8 +3229,8 @@ http-errors@~1.3.1: statuses "1" http-parser-js@>=0.4.0: - version "0.4.6" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.6.tgz#195273f58704c452d671076be201329dd341dc55" + version "0.4.13" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" http-proxy-agent@^2.1.0: version "2.1.0" @@ -3207,11 +3240,12 @@ http-proxy-agent@^2.1.0: debug "3.1.0" http-proxy@^1.13.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" http-signature@~1.1.0: version "1.1.1" @@ -3279,19 +3313,15 @@ iconv-lite@0.4.15: version "0.4.15" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" -iconv-lite@0.4.23, iconv-lite@^0.4.4: +iconv-lite@0.4.23, iconv-lite@^0.4.4, iconv-lite@^0.4.5: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.4.5: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" ignore-walk@^3.0.1: version "3.0.1" @@ -3313,6 +3343,10 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" +indent-string@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -3344,21 +3378,17 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@^1.2.0: +ini@^1.2.0, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0, ini@~1.3.3: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -ini@^1.3.2, ini@^1.3.4, ini@~1.3.0, ini@~1.3.3: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" - interpret@^0.6.4: version "0.6.6" resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" interpret@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" invert-kv@^1.0.0: version "1.0.0" @@ -3374,12 +3404,12 @@ is-absolute@^0.1.7: dependencies: is-relative "^0.1.0" -is-absolute@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" dependencies: - is-relative "^0.2.1" - is-windows "^0.2.0" + is-relative "^1.0.0" + is-windows "^1.0.1" is-accessor-descriptor@^0.1.6: version "0.1.6" @@ -3404,8 +3434,8 @@ is-binary-path@^1.0.0: binary-extensions "^1.0.0" is-buffer@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" is-builtin-module@^1.0.0: version "1.0.0" @@ -3413,10 +3443,6 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" - is-ci@^1.0.10: version "1.1.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" @@ -3435,10 +3461,6 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -3499,10 +3521,6 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" -is-function@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" - is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -3525,12 +3543,17 @@ is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" jsonpointer "^4.0.0" xtend "^4.0.0" @@ -3550,6 +3573,10 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" @@ -3559,17 +3586,21 @@ is-path-cwd@^1.0.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" dependencies: is-path-inside "^1.0.0" is-path-inside@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" dependencies: path-is-inside "^1.0.1" +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -3592,21 +3623,15 @@ is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - dependencies: - has "^1.0.1" - is-relative@^0.1.0: version "0.1.3" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82" -is-relative@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" dependencies: - is-unc-path "^0.1.1" + is-unc-path "^1.0.0" is-stream@^1.1.0: version "1.1.0" @@ -3616,10 +3641,6 @@ is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" - is-text-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -3630,21 +3651,17 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is-unc-path@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" dependencies: - unc-path-regex "^0.1.0" + unc-path-regex "^0.1.2" is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -is-windows@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - -is-windows@^1.0.2: +is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -3694,9 +3711,9 @@ jasmine-core@^3.1.0, jasmine-core@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.1.0.tgz#a4785e135d5df65024dfc9224953df585bd2766c" -jasmine-core@~2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" +jasmine-core@~2.99.0: + version "2.99.1" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.99.1.tgz#e6400df1e6b56e130b61c4bcd093daa7f6e8ca15" jasmine-diff@^0.1.3: version "0.1.3" @@ -3705,12 +3722,12 @@ jasmine-diff@^0.1.3: diff "^3.2.0" jasmine@^2.5.3: - version "2.8.0" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e" + version "2.99.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.99.0.tgz#8ca72d102e639b867c6489856e0e18a9c7aa42b7" dependencies: exit "^0.1.2" glob "^7.0.6" - jasmine-core "~2.8.0" + jasmine-core "~2.99.0" jasmine@^3.1.0: version "3.1.0" @@ -3720,8 +3737,8 @@ jasmine@^3.1.0: jasmine-core "~3.1.0" jasminewd2@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.1.0.tgz#da595275d1ae631de736ac0a7c7d85c9f73ef652" + version "2.2.0" + resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.2.0.tgz#e37cf0b17f199cce23bea71b2039395246b4ec4e" jetpack-id@0.0.4: version "0.0.4" @@ -3791,6 +3808,10 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -3881,8 +3902,8 @@ jszip@^2.4.0: pako "~1.0.2" jszip@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.4.tgz#fc323fe41bb1730348d20dd022aa4d8b57cbbcf9" + version "3.1.5" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.1.5.tgz#e3c2a6c6d706ac6e603314036d43cd40beefdf37" dependencies: core-js "~2.3.0" es6-promise "~3.0.2" @@ -3890,21 +3911,19 @@ jszip@^3.1.3: pako "~1.0.2" readable-stream "~2.0.6" -jwa@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" dependencies: - base64url "2.0.0" buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.9" + ecdsa-sig-formatter "1.0.10" safe-buffer "^5.0.1" jws@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2" + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" dependencies: - base64url "^2.0.0" - jwa "^1.1.4" + jwa "^1.1.5" safe-buffer "^5.0.1" karma-browserstack-launcher@^1.3.0: @@ -4045,22 +4064,21 @@ lie@~3.1.0: immediate "~3.0.5" liftoff@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" + version "2.5.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" dependencies: extend "^3.0.0" - findup-sync "^0.4.2" + findup-sync "^2.0.0" fined "^1.0.1" - flagged-respawn "^0.3.2" - lodash.isplainobject "^4.0.4" - lodash.isstring "^4.0.1" - lodash.mapvalues "^4.4.0" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" rechoir "^0.6.2" resolve "^1.1.7" livereload-js@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.2.2.tgz#6c87257e648ab475bc24ea257457edcc1f8d0bc2" + version "2.3.0" + resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.3.0.tgz#c3ab22e8aaf5bf3505d80d098cbad67726548c9a" load-json-file@^1.0.0: version "1.1.0" @@ -4081,6 +4099,15 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + loader-utils@^0.2.11: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" @@ -4151,14 +4178,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isplainobject@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -4167,10 +4186,6 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -lodash.mapvalues@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -4221,14 +4236,10 @@ lodash@4.11.1: version "4.11.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.11.1.tgz#a32106eb8e2ec8e82c241611414773c9df15f8bc" -lodash@4.17.10, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.5.0: +lodash@4.17.10, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.5.0, lodash@^4.8.0, lodash@~4.17.2: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" -lodash@^4.0.0, lodash@^4.14.0, lodash@^4.2.1, lodash@^4.8.0, lodash@~4.17.2: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -4291,14 +4302,7 @@ lru-cache@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.5.0.tgz#d82388ae9c960becbea0c73bb9eb79b6c6ce9aeb" -lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^4.1.2: +lru-cache@^4.0.1, lru-cache@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: @@ -4355,6 +4359,12 @@ mailgun-js@^0.18.0: proxy-agent "~3.0.0" tsscmp "~1.0.0" +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + dependencies: + kind-of "^6.0.2" + map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -4363,6 +4373,10 @@ map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" +map-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" + map-stream@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -4384,6 +4398,10 @@ map-visit@^1.0.0: buffers "~0.1.1" readable-stream "~1.0.0" +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4402,8 +4420,8 @@ memoizeasync@^1.1.0: passerror "1.1.1" memoizee@^0.4.3: - version "0.4.11" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.11.tgz#bde9817663c9e40fdb2a4ea1c367296087ae8c8f" + version "0.4.12" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.12.tgz#780e99a219c50c549be6d0fc61765080975c58fb" dependencies: d "1" es5-ext "^0.10.30" @@ -4440,20 +4458,34 @@ meow@^3.3.0: redent "^1.0.0" trim-newlines "^1.0.0" -method-override@~2.3.5: - version "2.3.9" - resolved "https://registry.yarnpkg.com/method-override/-/method-override-2.3.9.tgz#bd151f2ce34cf01a76ca400ab95c012b102d8f71" +meow@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" dependencies: - debug "2.6.8" + camelcase-keys "^4.0.0" + decamelize-keys "^1.0.0" + loud-rejection "^1.0.0" + minimist "^1.1.3" + minimist-options "^3.0.1" + normalize-package-data "^2.3.4" + read-pkg-up "^3.0.0" + redent "^2.0.0" + trim-newlines "^2.0.0" + +method-override@~2.3.5: + version "2.3.10" + resolved "https://registry.yarnpkg.com/method-override/-/method-override-2.3.10.tgz#e3daf8d5dee10dd2dce7d4ae88d62bbee77476b4" + dependencies: + debug "2.6.9" methods "~1.1.2" - parseurl "~1.3.1" - vary "~1.1.1" + parseurl "~1.3.2" + vary "~1.1.2" methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: +micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -4471,7 +4503,7 @@ micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.4: +micromatch@^3.0.4, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -4489,21 +4521,15 @@ micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +"mime-db@>= 1.34.0 < 2": + version "1.34.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.34.0.tgz#452d0ecff5c30346a6dc1e64b1eaee0d3719ff9a" mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.6, mime-types@~2.1.7, mime-types@~2.1.9: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - dependencies: - mime-db "~1.30.0" - -mime-types@~2.1.18: +mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.6, mime-types@~2.1.7, mime-types@~2.1.9: version "2.1.18" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: @@ -4514,12 +4540,12 @@ mime@1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" mime@^1.3.4: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343" + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" "minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" @@ -4546,11 +4572,18 @@ minimatch@~0.2.11: lru-cache "2" sigmund "~1.0.0" +minimist-options@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -4595,12 +4628,12 @@ mkpath@^0.1.0: resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-0.1.0.tgz#7554a6f8d871834cc97b5462b122c4c124d6de91" modify-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2" + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" moment@2.x.x: - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" morgan@~1.6.1: version "1.6.1" @@ -4665,11 +4698,7 @@ mz@2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.3.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" - -nan@^2.9.2: +nan@^2.3.0, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -4690,10 +4719,10 @@ nanomatch@^1.2.9: to-regex "^3.0.1" natives@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31" + version "1.1.4" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.4.tgz#2f0f224fc9a7dd53407c7667c84cf8dbe773de58" -needle@^2.2.0: +needle@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" dependencies: @@ -4746,12 +4775,12 @@ next-tick@1: vm-browserify "0.0.4" node-pre-gyp@^0.10.0: - version "0.10.2" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.2.tgz#e8945c20ef6795a20aac2b44f036eb13cf5146e3" + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" - needle "^2.2.0" + needle "^2.2.1" nopt "^4.0.1" npm-packlist "^1.1.6" npmlog "^4.0.2" @@ -4761,17 +4790,18 @@ node-pre-gyp@^0.10.0: tar "^4" node-pre-gyp@^0.6.36: - version "0.6.37" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz#3c872b236b2e266e4140578fe1ee88f693323a05" + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" mkdirp "^0.5.1" nopt "^4.0.1" npmlog "^4.0.2" rc "^1.1.7" - request "^2.81.0" + request "2.81.0" rimraf "^2.6.1" semver "^5.3.0" - tape "^4.6.3" tar "^2.2.1" tar-pack "^3.4.0" @@ -4952,14 +4982,6 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d" - -object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -4975,6 +4997,13 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -5056,7 +5085,7 @@ os-browserify@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54" -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -5082,20 +5111,13 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@^0.1.0: +osenv@^0.1.0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - "over@>= 0.0.5 < 1": version "0.0.5" resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708" @@ -5105,8 +5127,10 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" p-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + dependencies: + p-try "^1.0.0" p-locate@^2.0.0: version "2.0.0" @@ -5114,6 +5138,10 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + pac-proxy-agent@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896" @@ -5146,10 +5174,10 @@ pako@~1.0.2: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" parse-filepath@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73" + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" dependencies: - is-absolute "^0.2.3" + is-absolute "^1.0.0" map-cache "^0.2.0" path-root "^0.1.1" @@ -5172,6 +5200,13 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -5264,6 +5299,12 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" @@ -5282,6 +5323,10 @@ pegjs@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -5290,6 +5335,10 @@ pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -5321,8 +5370,8 @@ pretty-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" private@^0.1.6, private@~0.1.5: - version "0.1.7" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" process-nextick-args@~1.0.6: version "1.0.7" @@ -5402,9 +5451,9 @@ proxy-from-env@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" -prr@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" pseudomap@^1.0.2: version "1.0.2" @@ -5427,6 +5476,10 @@ punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + q@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/q/-/q-1.0.1.tgz#11872aeedee89268110b10a718448ffb10112a14" @@ -5435,11 +5488,7 @@ q@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" -q@^1.1.2, q@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" - -q@^1.5.0, q@~1.5.0: +q@^1.1.2, q@^1.4.1, q@^1.5.0, q@^1.5.1, q@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -5455,7 +5504,7 @@ qs@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be" -qs@6.5.2: +qs@6.5.2, qs@~6.5.1: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -5467,9 +5516,9 @@ qs@~6.2.0: version "6.2.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" querystring-es3@~0.2.0: version "0.2.1" @@ -5479,16 +5528,21 @@ querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" +quick-lru@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" + random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" +randomatic@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.0.0.tgz#d35490030eb4f7578de292ce6dfb04a91a128923" dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" range-parser@^1.2.0: version "1.2.0" @@ -5515,16 +5569,7 @@ raw-body@~2.1.2, raw-body@~2.1.5: iconv-lite "0.4.13" unpipe "1.0.0" -rc@^1.1.7: - version "1.2.1" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" - dependencies: - deep-extend "~0.4.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -rc@^1.2.7: +rc@^1.1.7, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" dependencies: @@ -5554,6 +5599,13 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + read-pkg@^1.0.0, read-pkg@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -5570,6 +5622,14 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + read@1, read@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" @@ -5585,7 +5645,7 @@ readable-stream@1.1.x, "readable-stream@1.x >=1.1.9", readable-stream@^1.0.27-1, isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@2, readable-stream@^2.3.0: +readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -5606,18 +5666,6 @@ readable-stream@2, readable-stream@^2.3.0: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - readable-stream@~2.0.5, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -5660,6 +5708,13 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + redis-commands@^1.2.0: version "1.3.5" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.5.tgz#4495889414f1e886261180b1442e7295602d83a2" @@ -5677,12 +5732,12 @@ redis@^2.7.1: redis-parser "^2.6.0" reflect-metadata@^0.1.3: - version "0.1.10" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.10.tgz#b4f83704416acad89988c9b15635d47e03b9344a" + version "0.1.12" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" regenerator-runtime@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" regenerator-runtime@^0.9.5: version "0.9.6" @@ -5759,6 +5814,33 @@ request@2.75.0, request@2.75.x: tough-cookie "~2.3.0" tunnel-agent "~0.4.1" +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + request@2.85.0: version "2.85.0" resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" @@ -5786,34 +5868,7 @@ request@2.85.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@2.x, request@^2.78.0, request@^2.81.0: - version "2.82.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.82.0.tgz#2ba8a92cd7ac45660ea2b10a53ae67cd247516ea" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.2" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@^2.0.0, request@^2.74.0: +request@2.x, request@^2.0.0, request@^2.74.0, request@^2.87.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -5881,16 +5936,16 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" -requires-port@1.x.x: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" + expand-tilde "^2.0.0" + global-modules "^1.0.0" resolve-url@^0.2.1: version "0.2.1" @@ -5908,9 +5963,9 @@ resolve@^0.7.1: version "0.7.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.7.4.tgz#395a9ef9e873fbfe12bd14408bd91bb936003d69" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" dependencies: path-parse "^1.0.5" @@ -5921,12 +5976,6 @@ response-time@~2.3.1: depd "~1.1.0" on-headers "~1.0.1" -resumer@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" - dependencies: - through "~2.3.4" - ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -5982,10 +6031,10 @@ rollup-plugin-sourcemaps@0.4.2: source-map-resolve "^0.5.0" rollup-pluginutils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0" + version "2.3.0" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.0.tgz#478ace04bd7f6da2e724356ca798214884738fc4" dependencies: - estree-walker "^0.3.0" + estree-walker "^0.5.2" micromatch "^2.3.11" rollup@0.47.4: @@ -5993,22 +6042,12 @@ rollup@0.47.4: resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.47.4.tgz#e3a55de83a78221d232ce29619a8d68189ae845e" rxjs@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0.tgz#d647e029b5854617f994c82fe57a4c6747b352da" + version "6.2.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" dependencies: tslib "^1.9.0" -rxjs@^6.0.0-beta.3: - version "6.0.0-tactical-rc.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0-tactical-rc.1.tgz#1fe1f1204132d1c71c72f249a487f8e76a5ec1d5" - dependencies: - tslib "^1.9.0" - -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - -safe-buffer@^5.1.2: +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -6018,7 +6057,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6084,17 +6123,17 @@ selenium-webdriver@^2.53.2: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" -"semver@2 || 3 || 4 || 5", semver@5.4.1, semver@^5.0.1, semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" semver@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.0.tgz#85f2cf8550465c4df000cf7d86f6b054106ab9e5" -semver@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +semver@5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" semver@~5.0.1: version "5.0.3" @@ -6215,8 +6254,8 @@ shell-quote@1.6.1: jsonify "~0.0.0" shelljs@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.1.tgz#729e038c413a2254c4078b95ed46e0397154a9f1" + version "0.8.2" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35" dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -6302,8 +6341,8 @@ sntp@1.x.x: hoek "2.x.x" sntp@2.x.x: - version "2.0.2" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b" + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" dependencies: hoek "4.x.x" @@ -6374,10 +6413,11 @@ source-list-map@~0.1.7: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" source-map-resolve@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.0.tgz#fcad0b64b70afb27699e425950cb5ebcd410bc20" + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" dependencies: - atob "^2.0.0" + atob "^2.1.1" + decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" @@ -6395,15 +6435,20 @@ source-map-support@0.4.3: source-map "^0.5.3" source-map-support@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" dependencies: + buffer-from "^1.0.0" source-map "^0.6.0" source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" +source-map@*: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + source-map@0.1.31: version "0.1.31" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.31.tgz#9f704d0d69d9e138a81badf6ebb4fde33d151c61" @@ -6425,8 +6470,8 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" + version "1.0.1" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" spawn-sync@1.0.15: version "1.0.15" @@ -6435,19 +6480,27 @@ spawn-sync@1.0.15: concat-stream "^1.4.7" os-shim "^0.1.2" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -6456,8 +6509,8 @@ split-string@^3.0.1, split-string@^3.0.2: extend-shallow "^3.0.0" split2@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/split2/-/split2-2.1.1.tgz#7a1f551e176a90ecd3345f7246a0cfe175ef4fd0" + version "2.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" dependencies: through2 "^2.0.2" @@ -6478,13 +6531,14 @@ sprintf-js@^1.0.3: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" dashdash "^1.12.0" getpass "^0.1.1" + safer-buffer "^2.0.2" optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" @@ -6498,11 +6552,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -statuses@1, statuses@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" - -"statuses@>= 1.4.0 < 2": +statuses@1, "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -6510,6 +6560,10 @@ statuses@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.2.1.tgz#dded45cc18256d51ed40aec142489d5c61026d28" +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + stream-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-1.0.0.tgz#bf9b4abfb42b274d751479e44e0ff2656b6f1193" @@ -6531,8 +6585,8 @@ stream-combiner@~0.0.4: duplexer "~0.1.1" stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" + version "0.1.1" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" stream-counter@~0.2.0: version "0.2.0" @@ -6567,7 +6621,7 @@ streamroller@0.7.0: mkdirp "^0.5.1" readable-stream "^2.3.0" -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" dependencies: @@ -6575,31 +6629,17 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: +"string-width@^1.0.2 || 2", string-width@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string.prototype.trim@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.0" - function-bind "^1.0.2" - string_decoder@~0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -6607,8 +6647,8 @@ string_decoder@~1.1.1: safe-buffer "~5.1.0" stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" @@ -6678,27 +6718,9 @@ tapable@^0.1.8, tapable@~0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" -tape@^4.6.3: - version "4.8.0" - resolved "https://registry.yarnpkg.com/tape/-/tape-4.8.0.tgz#f6a9fec41cc50a1de50fa33603ab580991f6068e" - dependencies: - deep-equal "~1.0.1" - defined "~1.0.0" - for-each "~0.3.2" - function-bind "~1.1.0" - glob "~7.1.2" - has "~1.0.1" - inherits "~2.0.3" - minimist "~1.2.0" - object-inspect "~1.3.0" - resolve "~1.4.0" - resumer "~0.0.0" - string.prototype.trim "~1.1.2" - through "~2.3.8" - tar-pack@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" dependencies: debug "^2.2.0" fstream "^1.0.10" @@ -6710,12 +6732,15 @@ tar-pack@^3.4.0: uid-number "^0.0.6" tar-stream@^1.5.0: - version "1.5.4" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016" + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" dependencies: bl "^1.0.0" + buffer-alloc "^1.1.0" end-of-stream "^1.0.0" - readable-stream "^2.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" xtend "^4.0.0" tar@^2.2.1: @@ -6743,8 +6768,8 @@ temp@~0.4.0: resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60" text-extensions@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.6.0.tgz#771561b26022783a45f5b6c2e78ad6e7de9fe322" + version "1.7.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39" thenify-all@^1.0.0: version "1.6.0" @@ -6776,7 +6801,7 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@~2.3, through@~2.3.1, through@~2.3.4, through@~2.3.8: +through@2, "through@>=2.2.7 <3", through@~2.3, through@~2.3.1, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -6801,8 +6826,8 @@ timers-browserify@^1.0.1: process "~0.11.0" timers-ext@0.1, timers-ext@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.2.tgz#61cc47a76c1abd3195f14527f978d58ae94c5204" + version "0.1.5" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.5.tgz#77147dd4e76b660c2abb8785db96574cbbd12922" dependencies: es5-ext "~0.10.14" next-tick "1" @@ -6848,6 +6873,10 @@ to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -6882,13 +6911,7 @@ touch@0.0.3: dependencies: nopt "~1.0.10" -tough-cookie@~2.3.0, tough-cookie@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" - dependencies: - punycode "^1.4.1" - -tough-cookie@~2.3.3: +tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: @@ -6906,6 +6929,10 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" +trim-newlines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" + trim-off-newlines@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" @@ -6920,13 +6947,9 @@ tsickle@0.32: source-map "^0.6.0" source-map-support "^0.5.0" -tslib@^1.0.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec" - -tslib@^1.7.1, tslib@^1.8.1, tslib@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" +tslib@^1.0.0, tslib@^1.7.1, tslib@^1.8.1, tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" tslint-eslint-rules@4.1.1: version "4.1.1" @@ -6980,10 +7003,10 @@ tsutils@^1.4.0: resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" tsutils@^2.8.1: - version "2.8.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a" + version "2.27.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7" dependencies: - tslib "^1.7.1" + tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" @@ -7009,14 +7032,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.10, type-is@~1.6.6: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.15" - -type-is@~1.6.16: +type-is@~1.6.10, type-is@~1.6.16, type-is@~1.6.6: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: @@ -7031,9 +7047,9 @@ typescript@2.9.x: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" -typescript@~2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" +"typescript@>=2.6.2 <2.8": + version "2.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" uglify-es@^3.3.9: version "3.3.9" @@ -7100,7 +7116,7 @@ ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" -unc-path-regex@^0.1.0: +unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -7112,8 +7128,8 @@ underscore.string@3.3.4: util-deprecate "^1.0.2" underscore@1.x: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" underscore@~1.7.0: version "1.7.0" @@ -7146,8 +7162,8 @@ universal-analytics@0.4.15: uuid "^3.0.0" universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" @@ -7175,6 +7191,12 @@ upath@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" +uri-js@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-3.0.2.tgz#f90b858507f81dea4dcfbb3c4c3dbfa2b557faaa" + dependencies: + punycode "^2.1.0" + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -7207,12 +7229,18 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, util@~0.10.3: +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: inherits "2.0.1" +util@~0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + dependencies: + inherits "2.0.3" + utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" @@ -7222,8 +7250,8 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" uuid@^3.0.0, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" uws@~9.14.0: version "9.14.0" @@ -7236,19 +7264,19 @@ v8flags@^2.0.2: user-home "^1.1.1" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" vargs@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/vargs/-/vargs-0.1.0.tgz#6b6184da6520cc3204ce1b407cac26d92609ebff" -vary@^1, vary@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" vary@~1.0.1: version "1.0.1" @@ -7294,10 +7322,14 @@ vinyl@^0.5.0: clone-stats "^0.0.1" replace-ext "0.0.1" -vlq@0.2.2, vlq@^0.2.1: +vlq@0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.2.tgz#e316d5257b40b86bb43cb8d5fea5d7f54d6b0ca1" +vlq@^0.2.1: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -7351,17 +7383,17 @@ webdriver-js-extender@^1.0.0: selenium-webdriver "^2.53.2" webdriver-manager@^12.0.6: - version "12.0.6" - resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-12.0.6.tgz#3df1a481977010b4cbf8c9d85c7a577828c0e70b" + version "12.1.0" + resolved "https://registry.yarnpkg.com/webdriver-manager/-/webdriver-manager-12.1.0.tgz#f6601e52de5f0c97fc7024c889eeb2416f2f1d9d" dependencies: - adm-zip "^0.4.7" + adm-zip "^0.4.9" chalk "^1.1.1" del "^2.2.0" glob "^7.0.3" ini "^1.3.4" minimist "^1.2.0" q "^1.4.1" - request "^2.78.0" + request "^2.87.0" rimraf "^2.5.2" semver "^5.3.0" xml2js "^0.4.17" @@ -7401,8 +7433,8 @@ websocket-driver@>=0.5.1: websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.2.tgz#0e18781de629a18308ce1481650f67ffa2693a5d" + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" when@3.7.2: version "3.7.2" @@ -7427,23 +7459,17 @@ which@1.2.4: is-absolute "^0.1.7" isexe "^1.1.1" -which@^1.2.1: +which@^1.2.1, which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" -which@^1.2.12, which@^1.2.9: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" dependencies: - string-width "^1.0.2" + string-width "^1.0.2 || 2" window-size@0.1.0: version "0.1.0" @@ -7485,8 +7511,8 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" ws@^1.0.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61" + version "1.1.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" dependencies: options ">=0.0.5" ultron "1.0.x" @@ -7524,9 +7550,9 @@ xml2js@^0.4.17, xml2js@~0.4.4: sax ">=0.6.0" xmlbuilder "~9.0.1" -xmlbuilder@>=1.0.0, xmlbuilder@~9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" +xmlbuilder@>=1.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.0.0.tgz#c64e52f8ae097fe5fd46d1c38adaade071ee1b55" xmlbuilder@^4.1.0: version "4.2.1" @@ -7534,6 +7560,10 @@ xmlbuilder@^4.1.0: dependencies: lodash "^4.0.0" +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + xmldom@^0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" From dd44f63c73a30e3bf2d1223579f2239fa34bc726 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Fri, 13 Jul 2018 17:27:36 -0700 Subject: [PATCH 580/582] build(bazel): show bazel progress in CircleCI to prevent 10m timeout with no output (#24663) PR Close #24663 --- .circleci/bazel.rc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/bazel.rc b/.circleci/bazel.rc index dc31222442..b63cdfecbd 100644 --- a/.circleci/bazel.rc +++ b/.circleci/bazel.rc @@ -3,7 +3,10 @@ # See remote cache documentation in /docs/BAZEL.md # Don't be spammy in the logs -build --noshow_progress +# TODO(gmagolan): Hide progress again once build performance improves +# Presently, CircleCI can timeout during bazel test ... with the following +# error: Too long with no output (exceeded 10m0s) +# build --noshow_progress # Don't run manual tests test --test_tag_filters=-manual From e265ccd82c509c93000a0363e0b6f69cc2f31ea1 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Mon, 23 Jul 2018 11:39:51 -0700 Subject: [PATCH 581/582] build(bazel): add comment for patch-types work-around (#24663) PR Close #24663 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e40acbb2b4..2f3bcb8861 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "buildifier": "find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier", "preinstall": "node tools/yarn/check-yarn.js", "postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js && yarn patch-types", + "patch-types": "work-around for issue https://github.com/angular/angular/issues/25051", "patch-types": "mkdir -p node_modules/@types/zone.js && cp -f node_modules/zone.js/dist/zone.js.d.ts node_modules/@types/zone.js/index.d.ts", "update-webdriver": "webdriver-manager update --gecko false $CHROMEDRIVER_VERSION_ARG", "check-env": "gulp check-env", From 601064e41de55ed69749cfa9edae9503490dafb2 Mon Sep 17 00:00:00 2001 From: Greg Magolan Date: Mon, 23 Jul 2018 12:46:30 -0700 Subject: [PATCH 582/582] build(bazel): add comment about angular bazel rules API re-export from /index.bzl (#24663) PR Close #24663 --- index.bzl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.bzl b/index.bzl index 5178b8ba4c..e0078cc901 100644 --- a/index.bzl +++ b/index.bzl @@ -3,6 +3,13 @@ # 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 """ Public API surface is re-exported here. + +This API is exported for user's building angular from source in downstream +projects. The rules from packages/bazel are re-exported here as well +as the ng_setup_workspace repository rule needed when building angular +from source downstream. Alternately, this API is available from the +@angular/bazel npm package if the npm distribution of angular is +used in a downstream project. """ load("//packages/bazel:index.bzl",