From 337b6fe003e33ffe26bb4f735377226078cd7ba7 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Tue, 4 Jun 2019 14:34:44 -0700 Subject: [PATCH 01/16] build: remove unreferenced tsconfig-build.json files (#30858) These are no longer needed since Bazel generates a tsconfig for each compilation PR Close #30858 --- .../browser/testing/tsconfig-build.json | 29 ------------- .../animations/browser/tsconfig-build.json | 28 ------------- packages/animations/tsconfig-build.json | 26 ------------ packages/benchpress/tsconfig-build.json | 21 ---------- .../common/http/testing/tsconfig-build.json | 27 ------------ packages/common/http/tsconfig-build.json | 25 ----------- packages/common/locales/tsconfig-build.json | 27 ------------ packages/common/testing/tsconfig-build.json | 27 ------------ packages/common/tsconfig-build.json | 25 ----------- packages/common/upgrade/tsconfig-build.json | 29 ------------- packages/compiler/testing/tsconfig-build.json | 24 ----------- packages/compiler/tsconfig-build.json | 37 ---------------- packages/core/testing/tsconfig-build.json | 31 -------------- packages/core/tsconfig-build.json | 31 -------------- packages/elements/tsconfig-build.json | 27 ------------ packages/forms/tsconfig-build.json | 31 -------------- packages/http/testing/tsconfig-build.json | 26 ------------ packages/http/tsconfig-build.json | 27 ------------ packages/language-service/tsconfig-build.json | 30 ------------- .../testing/tsconfig-build.json | 34 --------------- .../tsconfig-build.json | 33 --------------- .../animations/tsconfig-build.json | 32 -------------- .../testing/tsconfig-build.json | 31 -------------- packages/platform-browser/tsconfig-build.json | 29 ------------- .../testing/tsconfig-build.json | 42 ------------------- packages/platform-server/tsconfig-build.json | 35 ---------------- .../tsconfig-build.json | 30 ------------- .../platform-webworker/tsconfig-build.json | 27 ------------ packages/router/testing/tsconfig-build.json | 28 ------------- packages/router/tsconfig-build.json | 27 ------------ packages/router/upgrade/tsconfig-build.json | 27 ------------ .../service-worker/config/tsconfig-build.json | 24 ----------- packages/service-worker/tsconfig-build.json | 26 ------------ packages/upgrade/static/tsconfig-build.json | 27 ------------ packages/upgrade/tsconfig-build.json | 29 ------------- 35 files changed, 1009 deletions(-) delete mode 100644 packages/animations/browser/testing/tsconfig-build.json delete mode 100644 packages/animations/browser/tsconfig-build.json delete mode 100644 packages/animations/tsconfig-build.json delete mode 100644 packages/benchpress/tsconfig-build.json delete mode 100644 packages/common/http/testing/tsconfig-build.json delete mode 100644 packages/common/http/tsconfig-build.json delete mode 100644 packages/common/locales/tsconfig-build.json delete mode 100644 packages/common/testing/tsconfig-build.json delete mode 100644 packages/common/tsconfig-build.json delete mode 100644 packages/common/upgrade/tsconfig-build.json delete mode 100644 packages/compiler/testing/tsconfig-build.json delete mode 100644 packages/compiler/tsconfig-build.json delete mode 100644 packages/core/testing/tsconfig-build.json delete mode 100644 packages/core/tsconfig-build.json delete mode 100644 packages/elements/tsconfig-build.json delete mode 100644 packages/forms/tsconfig-build.json delete mode 100644 packages/http/testing/tsconfig-build.json delete mode 100644 packages/http/tsconfig-build.json delete mode 100644 packages/language-service/tsconfig-build.json delete mode 100644 packages/platform-browser-dynamic/testing/tsconfig-build.json delete mode 100644 packages/platform-browser-dynamic/tsconfig-build.json delete mode 100644 packages/platform-browser/animations/tsconfig-build.json delete mode 100644 packages/platform-browser/testing/tsconfig-build.json delete mode 100644 packages/platform-browser/tsconfig-build.json delete mode 100644 packages/platform-server/testing/tsconfig-build.json delete mode 100644 packages/platform-server/tsconfig-build.json delete mode 100644 packages/platform-webworker-dynamic/tsconfig-build.json delete mode 100644 packages/platform-webworker/tsconfig-build.json delete mode 100644 packages/router/testing/tsconfig-build.json delete mode 100644 packages/router/tsconfig-build.json delete mode 100644 packages/router/upgrade/tsconfig-build.json delete mode 100644 packages/service-worker/config/tsconfig-build.json delete mode 100644 packages/service-worker/tsconfig-build.json delete mode 100644 packages/upgrade/static/tsconfig-build.json delete mode 100644 packages/upgrade/tsconfig-build.json diff --git a/packages/animations/browser/testing/tsconfig-build.json b/packages/animations/browser/testing/tsconfig-build.json deleted file mode 100644 index 1b8d6a779b..0000000000 --- a/packages/animations/browser/testing/tsconfig-build.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../../", - "paths": { - "@angular/animations": ["../../../../dist/packages/animations"], - "@angular/animations/browser": ["../../../../dist/packages/animations/browser"], - "@angular/core": ["../../../../dist/packages/core"] - }, - "outDir": "../../../../dist/packages/animations" - }, - - "files": [ - "public_api.ts", - "../../../../node_modules/@types/hammerjs/index.d.ts", - "../../../../node_modules/@types/jasmine/index.d.ts", - "../../../../node_modules/zone.js/dist/zone.js.d.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "testing.js", - "flatModuleId": "@angular/animations/browser/testing" - } -} diff --git a/packages/animations/browser/tsconfig-build.json b/packages/animations/browser/tsconfig-build.json deleted file mode 100644 index 963bc8dfa2..0000000000 --- a/packages/animations/browser/tsconfig-build.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../", - "paths": { - "@angular/animations": ["../../../dist/packages/animations"], - "@angular/core": ["../../../dist/packages/core"] - }, - "outDir": "../../../dist/packages/animations" - }, - - "files": [ - "public_api.ts", - "../../../node_modules/@types/node/index.d.ts", - "../../../node_modules/zone.js/dist/zone.js.d.ts", - "../../system.d.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "browser.js", - "flatModuleId": "@angular/animations/browser" - } -} diff --git a/packages/animations/tsconfig-build.json b/packages/animations/tsconfig-build.json deleted file mode 100644 index f3ecc805f6..0000000000 --- a/packages/animations/tsconfig-build.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": ".", - "paths": { - "@angular/core": ["../../dist/packages/core"] - }, - "outDir": "../../dist/packages/animations" - }, - - "files": [ - "public_api.ts", - "../../node_modules/zone.js/dist/zone.js.d.ts", - "../system.d.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "animations.js", - "flatModuleId": "@angular/animations" - } -} diff --git a/packages/benchpress/tsconfig-build.json b/packages/benchpress/tsconfig-build.json deleted file mode 100644 index 540553953e..0000000000 --- a/packages/benchpress/tsconfig-build.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "module": "commonjs", - "baseUrl": ".", - "rootDir": ".", - "sourceRoot": ".", - "paths": { - "@angular/core": ["../../dist/packages/core"] - }, - "outDir": "../../dist/packages/benchpress" - }, - - "files": [ - "index.ts", - "../../node_modules/@types/node/index.d.ts", - "../../node_modules/@types/jasmine/index.d.ts", - "../../node_modules/zone.js/dist/zone.js.d.ts" - ] -} diff --git a/packages/common/http/testing/tsconfig-build.json b/packages/common/http/testing/tsconfig-build.json deleted file mode 100644 index 9d37f99383..0000000000 --- a/packages/common/http/testing/tsconfig-build.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../../", - "paths": { - "@angular/core": ["../../../../dist/packages/core"], - "@angular/common": ["../../../../dist/packages/common"], - "@angular/common/http": ["../../../../dist/packages/common/http"], - "@angular/platform-browser": ["../../../../dist/packages/platform-browser"] - }, - "outDir": "../../../../dist/packages/common" - }, - - "files": [ - "public_api.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "testing.js", - "flatModuleId": "@angular/common/http/testing" - } -} diff --git a/packages/common/http/tsconfig-build.json b/packages/common/http/tsconfig-build.json deleted file mode 100644 index 39180a20d2..0000000000 --- a/packages/common/http/tsconfig-build.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../", - "paths": { - "@angular/common": ["../../../dist/packages/common"], - "@angular/core": ["../../../dist/packages/core"] - }, - "outDir": "../../../dist/packages/common" - }, - - "files": [ - "public_api.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "http.js", - "flatModuleId": "@angular/common/http" - } -} diff --git a/packages/common/locales/tsconfig-build.json b/packages/common/locales/tsconfig-build.json deleted file mode 100644 index 5a57417b96..0000000000 --- a/packages/common/locales/tsconfig-build.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "declaration": true, - "stripInternal": true, - "experimentalDecorators": true, - "module": "commonjs", - "moduleResolution": "node", - "outDir": "../../../dist/packages/common/locales", - "paths": { - "@angular/common": ["../../../dist/packages/common"], - "@angular/core": ["../../../dist/packages/core"] - }, - "sourceMap": true, - "inlineSources": true, - "target": "es5", - "skipLibCheck": true, - "lib": ["es2015", "dom"] - }, - "exclude": [ - "./closure-locale.ts" - ], - "angularCompilerOptions": { - "skipTemplateCodegen": true, - "skipMetadataEmit": true - } -} diff --git a/packages/common/testing/tsconfig-build.json b/packages/common/testing/tsconfig-build.json deleted file mode 100644 index 9936ed3268..0000000000 --- a/packages/common/testing/tsconfig-build.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../", - "paths": { - "@angular/core": [ - "../../../dist/packages/core" - ], - "@angular/common": [ - "../../../dist/packages/common" - ] - }, - "outDir": "../../../dist/packages/common" - }, - "files": [ - "public_api.ts", - "../../../node_modules/zone.js/dist/zone.js.d.ts" - ], - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "testing.js", - "flatModuleId": "@angular/common/testing" - } -} \ No newline at end of file diff --git a/packages/common/tsconfig-build.json b/packages/common/tsconfig-build.json deleted file mode 100644 index 1cba80a609..0000000000 --- a/packages/common/tsconfig-build.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "rootDir": ".", - "baseUrl": ".", - "paths": { - "@angular/core": ["../../dist/packages/core"] - }, - "outDir": "../../dist/packages/common" - }, - - "files": [ - "public_api.ts", - "../../node_modules/zone.js/dist/zone.js.d.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "common.js", - "flatModuleId": "@angular/common" - } -} diff --git a/packages/common/upgrade/tsconfig-build.json b/packages/common/upgrade/tsconfig-build.json deleted file mode 100644 index 0ea78796f2..0000000000 --- a/packages/common/upgrade/tsconfig-build.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../", - "paths": { - "@angular/common": [ - "../../../dist/packages/common" - ], - "@angular/core": [ - "../../../dist/packages/core" - ], - "@angular/platform-browser": [ - "../../../dist/packages/platform-browser" - ] - }, - "outDir": "../../../dist/packages/common" - }, - "files": [ - "public_api.ts" - ], - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "strictMetadataEmit": false, - "skipTemplateCodegen": true, - "flatModuleOutFile": "upgrade.js", - "flatModuleId": "@angular/common/upgrade" - } -} \ No newline at end of file diff --git a/packages/compiler/testing/tsconfig-build.json b/packages/compiler/testing/tsconfig-build.json deleted file mode 100644 index 48f35ad786..0000000000 --- a/packages/compiler/testing/tsconfig-build.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../tsconfig-build.json", - - "compilerOptions": { - "baseUrl": ".", - "rootDir": "../", - "paths": { - "@angular/compiler": ["../../../dist/packages/compiler"], - "@angular/core": ["../../../dist/packages/core"] - }, - "outDir": "../../../dist/packages/compiler" - }, - - "files": [ - "testing.ts", - "../../../node_modules/zone.js/dist/zone.js.d.ts" - ], - - "angularCompilerOptions": { - "annotateForClosureCompiler": true, - "skipMetadataEmit": true, - "skipTemplateCodegen": true - } -} diff --git a/packages/compiler/tsconfig-build.json b/packages/compiler/tsconfig-build.json deleted file mode 100644 index d59d031f3b..0000000000 --- a/packages/compiler/tsconfig-build.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "declaration": true, - "stripInternal": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "noFallthroughCasesInSwitch": true, - "module": "es2015", - "moduleResolution": "node", - "outDir": "../../dist/packages/compiler", - "paths": { - "@angular/core": ["../../dist/packages/core"] - }, - "rootDir": ".", - "sourceMap": true, - "inlineSources": true, - "target": "es2015", - "skipLibCheck": true, - "lib": ["es2015", "dom"], - // don't auto-discover @types/node, it results in a /// Date: Fri, 31 May 2019 17:11:57 +0200 Subject: [PATCH 02/16] feat(ivy): support `ng-content` in runtime i18n translations (#30782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a new syntax for projections (`¤` will represent `ng-content` nodes) so that we can treat them specifically. When we enter an i18n block with the instruction `i18nStart`, a new `delayProjection` variable is set to true to prevent the instruction `projection` from projecting the nodes. Once we reach the `i18nEnd` instruction and encounter a projection in the translation we will project its nodes. If a projection was removed from a translation, then its nodes won't be projected at all. The variable `delayProjection` is restored to `false` at the end of `i18nEnd` so that it doesn't stop projections outside of i18n blocks. FW-1261 #resolve PR Close #30782 --- .../compiler/src/render3/view/i18n/context.ts | 13 +- .../compiler/src/render3/view/template.ts | 3 + packages/core/src/render3/i18n.ts | 36 +++- .../src/render3/instructions/projection.ts | 13 +- packages/core/test/acceptance/i18n_spec.ts | 167 ++++++++++++++++++ packages/core/test/render3/i18n_spec.ts | 3 +- 6 files changed, 222 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/render3/view/i18n/context.ts b/packages/compiler/src/render3/view/i18n/context.ts index d2573d6f4c..2c770813f3 100644 --- a/packages/compiler/src/render3/view/i18n/context.ts +++ b/packages/compiler/src/render3/view/i18n/context.ts @@ -14,7 +14,8 @@ import {assembleBoundTextPlaceholders, findIndex, getSeqNumberGenerator, updateP enum TagType { ELEMENT, - TEMPLATE + TEMPLATE, + PROJECTION } /** @@ -94,6 +95,12 @@ export class I18nContext { appendElement(node: i18n.AST, index: number, closed?: boolean) { this.appendTag(TagType.ELEMENT, node as i18n.TagPlaceholder, index, closed); } + appendProjection(node: i18n.AST, index: number) { + // add open and close tags at the same time, + // since we process projected content separately + this.appendTag(TagType.PROJECTION, node as i18n.TagPlaceholder, index, false); + this.appendTag(TagType.PROJECTION, node as i18n.TagPlaceholder, index, true); + } /** * Generates an instance of a child context based on the root one, @@ -181,6 +188,7 @@ function findTemplateFn(ctx: number, templateIndex: number | null) { function serializePlaceholderValue(value: any): string { const element = (data: any, closed?: boolean) => wrapTag('#', data, closed); const template = (data: any, closed?: boolean) => wrapTag('*', data, closed); + const projection = (data: any, closed?: boolean) => wrapTag('!', data, closed); switch (value.type) { case TagType.ELEMENT: @@ -198,6 +206,9 @@ function serializePlaceholderValue(value: any): string { case TagType.TEMPLATE: return template(value, value.closed); + case TagType.PROJECTION: + return projection(value, value.closed); + default: return value; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 552dfbd675..d7316a84af 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -483,6 +483,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } this.creationInstruction(ngContent.sourceSpan, R3.projection, parameters); + if (this.i18n) { + this.i18n.appendProjection(ngContent.i18n !, slot); + } } diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index e3291dcfa8..bdf88e081e 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -7,38 +7,42 @@ */ import '../util/ng_i18n_closure_mode'; - import {getPluralCase} from '../i18n/localization'; import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer'; import {InertBodyHelper} from '../sanitization/inert_body'; import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer'; import {addAllToArray} from '../util/array_utils'; import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert'; - import {attachPatchData} from './context_discovery'; -import {elementAttributeInternal, ɵɵload, ɵɵtextBinding} from './instructions/all'; +import {elementAttributeInternal, setDelayProjection, ɵɵload, ɵɵtextBinding} from './instructions/all'; import {attachI18nOpCodesDebug} from './instructions/lview_debug'; import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; -import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node'; +import {TElementNode, TIcuContainerNode, TNode, TNodeType, TProjectionNode} from './interfaces/node'; import {RComment, RElement, RText} from './interfaces/renderer'; import {SanitizerFn} from './interfaces/sanitization'; import {StylingContext} from './interfaces/styling'; import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view'; -import {appendChild, createTextNode, nativeRemoveNode} from './node_manipulation'; +import {appendChild, appendProjectedNodes, createTextNode, nativeRemoveNode} from './node_manipulation'; import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from './state'; import {NO_CHANGE} from './tokens'; import {renderStringify} from './util/misc_utils'; +import {findComponentView} from './util/view_traversal_utils'; import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer} from './util/view_utils'; const MARKER = `�`; const ICU_BLOCK_REGEXP = /^\s*(�\d+:?\d*�)\s*,\s*(select|plural)\s*,/; const SUBTEMPLATE_REGEXP = /�\/?\*(\d+:\d+)�/gi; -const PH_REGEXP = /�(\/?[#*]\d+):?\d*�/gi; +const PH_REGEXP = /�(\/?[#*!]\d+):?\d*�/gi; const BINDING_REGEXP = /�(\d+):?\d*�/gi; const ICU_REGEXP = /({\s*�\d+:?\d*�\s*,\s*\S{6}\s*,[\s\S]*})/gi; +const enum TagType { + ELEMENT = '#', + TEMPLATE = '*', + PROJECTION = '!', +} // i18nPostprocess consts const ROOT_TEMPLATE_ID = 0; @@ -340,6 +344,10 @@ const parentIndexStack: number[] = []; * and end of DOM element that were embedded in the original translation block. The placeholder * `index` points to the element index in the template instructions set. An optional `block` that * matches the sub-template in which it was declared. + * - `�!{index}(:{block})�`/`�/!{index}(:{block})�`: *Projection Placeholder*: Marks the + * beginning and end of that was embedded in the original translation block. + * The placeholder `index` points to the element index in the template instructions set. + * An optional `block` that matches the sub-template in which it was declared. * - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be * split up and translated separately in each angular template function. The `index` points to the * `template` instruction index. A `block` that matches the sub-template in which it was declared. @@ -354,6 +362,8 @@ export function ɵɵi18nStart(index: number, message: string, subTemplateIndex?: const tView = getLView()[TVIEW]; ngDevMode && assertDefined(tView, `tView should be defined`); i18nIndexStack[++i18nIndexStackPointer] = index; + // We need to delay projections until `i18nEnd` + setDelayProjection(true); if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) { i18nStartFirstPass(tView, index, message, subTemplateIndex); } @@ -398,7 +408,7 @@ function i18nStartFirstPass( // Odd indexes are placeholders (elements and sub-templates) if (value.charAt(0) === '/') { // It is a closing tag - if (value.charAt(1) === '#') { + if (value.charAt(1) === TagType.ELEMENT) { const phIndex = parseInt(value.substr(2), 10); parentIndex = parentIndexStack[--parentIndexPointer]; createOpCodes.push(phIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd); @@ -410,7 +420,7 @@ function i18nStartFirstPass( phIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select, parentIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild); - if (value.charAt(0) === '#') { + if (value.charAt(0) === TagType.ELEMENT) { parentIndexStack[++parentIndexPointer] = parentIndex = phIndex; } } @@ -508,6 +518,14 @@ function appendI18nNode(tNode: TNode, parentTNode: TNode, previousTNode: TNode | cursor = cursor.next; } + // If the placeholder to append is a projection, we need to move the projected nodes instead + if (tNode.type === TNodeType.Projection) { + const tProjectionNode = tNode as TProjectionNode; + appendProjectedNodes( + viewData, tProjectionNode, tProjectionNode.projection, findComponentView(viewData)); + return tNode; + } + appendChild(getNativeByTNode(tNode, viewData), tNode, viewData); const slotValue = viewData[tNode.index]; @@ -632,6 +650,8 @@ export function ɵɵi18nEnd(): void { const tView = getLView()[TVIEW]; ngDevMode && assertDefined(tView, `tView should be defined`); i18nEndFirstPass(tView); + // Stop delaying projections + setDelayProjection(false); } /** diff --git a/packages/core/src/render3/instructions/projection.ts b/packages/core/src/render3/instructions/projection.ts index 4a76d6e843..87bfb0628b 100644 --- a/packages/core/src/render3/instructions/projection.ts +++ b/packages/core/src/render3/instructions/projection.ts @@ -12,7 +12,6 @@ import {appendProjectedNodes} from '../node_manipulation'; import {getProjectAsAttrValue, isNodeMatchingSelectorList, isSelectorInSelectorList} from '../node_selector_matcher'; import {getLView, setIsNotParent} from '../state'; import {findComponentView} from '../util/view_traversal_utils'; - import {getOrCreateTNode} from './shared'; @@ -103,6 +102,11 @@ export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void { } } +let delayProjection = false; +export function setDelayProjection(value: boolean) { + delayProjection = value; +} + /** * Inserts previously re-distributed projected nodes. This instruction must be preceded by a call @@ -127,6 +131,9 @@ export function ɵɵprojection( // `` has no content setIsNotParent(); - // re-distribution of projectable nodes is stored on a component's view level - appendProjectedNodes(lView, tProjectionNode, selectorIndex, findComponentView(lView)); + // We might need to delay the projection of nodes if they are in the middle of an i18n block + if (!delayProjection) { + // re-distribution of projectable nodes is stored on a component's view level + appendProjectedNodes(lView, tProjectionNode, selectorIndex, findComponentView(lView)); + } } diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 4e9bc759b5..16c37d6a39 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -9,6 +9,7 @@ import {registerLocaleData} from '@angular/common'; import localeRo from '@angular/common/locales/ro'; import {Component, ContentChild, ContentChildren, Directive, HostBinding, Input, LOCALE_ID, QueryList, TemplateRef, Type, ViewChild, ViewContainerRef, ɵi18nConfigureLocalize} from '@angular/core'; +import {setDelayProjection} from '@angular/core/src/render3/instructions/projection'; import {TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {onlyInIvy} from '@angular/private/testing'; @@ -19,6 +20,8 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { TestBed.configureTestingModule({declarations: [AppComp, DirectiveWithTplRef]}); }); + afterEach(() => { setDelayProjection(false); }); + it('should translate text', () => { ɵi18nConfigureLocalize({translations: {'text': 'texte'}}); const fixture = initWithTemplate(AppComp, `
text
`); @@ -990,6 +993,170 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { expect(fixture.nativeElement.innerHTML) .toEqual('Contenu'); }); + + it('should project content in i18n blocks', () => { + @Component({ + selector: 'child', + template: `
Content projected from
` + }) + class Child { + } + + @Component({selector: 'parent', template: `{{name}}`}) + class Parent { + name: string = 'Parent'; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({ + translations: { + 'Content projected from {$startTagNgContent}{$closeTagNgContent}': + 'Contenu projeté depuis {$startTagNgContent}{$closeTagNgContent}' + } + }); + + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual(`
Contenu projeté depuis Parent
`); + + fixture.componentRef.instance.name = 'Parent component'; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual(`
Contenu projeté depuis Parent component
`); + }); + + it('should project content in i18n blocks with placeholders', () => { + @Component({ + selector: 'child', + template: `
Content projected from
` + }) + class Child { + } + + @Component({selector: 'parent', template: `{{name}}`}) + class Parent { + name: string = 'Parent'; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({ + translations: { + 'Content projected from {$startTagNgContent}{$closeTagNgContent}': + '{$startTagNgContent}{$closeTagNgContent} a projeté le contenu' + } + }); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual(`
Parent a projeté le contenu
`); + }); + + it('should project translated content in i18n blocks', () => { + @Component( + {selector: 'child', template: `
Child content
`}) + class Child { + } + + @Component({selector: 'parent', template: `and projection from {{name}}`}) + class Parent { + name: string = 'Parent'; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({ + translations: { + 'Child content {$startTagNgContent}{$closeTagNgContent}': + 'Contenu enfant {$startTagNgContent}{$closeTagNgContent}', + 'and projection from {$interpolation}': 'et projection depuis {$interpolation}' + } + }); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual(`
Contenu enfant et projection depuis Parent
`); + }); + + it('should project bare ICU expressions', () => { + @Component({selector: 'child', template: '
'}) + class Child { + } + + @Component({ + selector: 'parent', + template: ` + { + value // i18n(ph = "blah"), + plural, + =1 {one} + other {at least {{value}} .} + }` + }) + class Parent { + value = 3; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({translations: {}}); + + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + + expect(fixture.nativeElement.innerHTML).toContain('at least'); + }); + + it('should project ICUs in i18n blocks', () => { + @Component( + {selector: 'child', template: `
Child content
`}) + class Child { + } + + @Component({ + selector: 'parent', + template: + `and projection from {name, select, angular {Angular} other {{{name}}}}` + }) + class Parent { + name: string = 'Parent'; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({ + translations: { + 'Child content {$startTagNgContent}{$closeTagNgContent}': + 'Contenu enfant {$startTagNgContent}{$closeTagNgContent}', + 'and projection from {$icu}': 'et projection depuis {$icu}' + } + }); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual( + `
Contenu enfant et projection depuis Parent
`); + + fixture.componentRef.instance.name = 'angular'; + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML) + .toEqual( + `
Contenu enfant et projection depuis Angular
`); + }); + + it(`shouldn't project deleted projections in i18n blocks`, () => { + @Component( + {selector: 'child', template: `
Child content
`}) + class Child { + } + + @Component({selector: 'parent', template: `and projection from {{name}}`}) + class Parent { + name: string = 'Parent'; + } + TestBed.configureTestingModule({declarations: [Parent, Child]}); + ɵi18nConfigureLocalize({ + translations: { + 'Child content {$startTagNgContent}{$closeTagNgContent}': 'Contenu enfant', + 'and projection from {$interpolation}': 'et projection depuis {$interpolation}' + } + }); + const fixture = TestBed.createComponent(Parent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual(`
Contenu enfant
`); + }); }); describe('queries', () => { diff --git a/packages/core/test/render3/i18n_spec.ts b/packages/core/test/render3/i18n_spec.ts index 154e974cde..0da67146a4 100644 --- a/packages/core/test/render3/i18n_spec.ts +++ b/packages/core/test/render3/i18n_spec.ts @@ -8,13 +8,14 @@ import {noop} from '../../../compiler/src/render3/view/util'; import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n'; -import {ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all'; +import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n'; import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view'; import {getNativeByIndex} from '../../src/render3/util/view_utils'; import {TemplateFixture} from './render_util'; describe('Runtime i18n', () => { + afterEach(() => { setDelayProjection(false); }); describe('getTranslationForTemplate', () => { it('should crop messages for the selected template', () => { let message = `simple text`; From d1df0a94d4b2b31f282500ffcfe1bbbd2e637138 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Fri, 24 May 2019 14:20:41 -0700 Subject: [PATCH 03/16] =?UTF-8?q?refactor(ivy):=20remove=20=C9=B5=C9=B5ele?= =?UTF-8?q?mentProperty=20instruction=20(#30645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removes ɵɵelementProperty instruction - Updates tests that were using it - NOTE: There is one test under `render3/integration_spec.ts` that is commented out, and needs to be reviewed. Basically, I could not find a good why to test what it was doing, because it was doing things that I am not sure we could generate in an acceptance test. PR Close #30645 --- .../r3_view_compiler_binding_spec.ts | 2 +- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 2 +- .../compiler/src/render3/r3_identifiers.ts | 2 - .../core/src/core_render3_private_export.ts | 1 - packages/core/src/render3/VIEW_DATA.md | 6 +- packages/core/src/render3/index.ts | 1 - .../core/src/render3/instructions/property.ts | 26 ----- packages/core/src/render3/jit/environment.ts | 1 - .../test/render3/change_detection_spec.ts | 7 +- packages/core/test/render3/component_spec.ts | 31 +++--- .../core/test/render3/instructions_spec.ts | 65 ++++++++---- .../core/test/render3/integration_spec.ts | 98 ++++++++++--------- packages/core/test/render3/lifecycle_spec.ts | 7 +- .../core/test/render3/pure_function_spec.ts | 11 ++- tools/public_api_guard/core/core.d.ts | 2 - 15 files changed, 141 insertions(+), 121 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts index 025eaada28..3bd84d27c8 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_binding_spec.ts @@ -138,7 +138,7 @@ describe('compiler compliance: bindings', () => { } }; const result = compile(files, angularFiles); - expect(result.source).not.toContain('i0.ɵɵelementProperty'); + expect(result.source).not.toContain('i0.ɵɵproperty'); }); it('should not remap property names whose names do not correspond to their attribute names', diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index afde805218..f6f7d9024c 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -2043,7 +2043,7 @@ describe('ngtsc behavioral tests', () => { `); env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).not.toContain('i0.ɵɵelementProperty'); + expect(jsContents).not.toContain('i0.ɵɵproperty'); }); it('should correctly recognize local symbols', () => { diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 285bb4c7b0..22f1197d38 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -29,8 +29,6 @@ export class Identifiers { static elementEnd: o.ExternalReference = {name: 'ɵɵelementEnd', moduleName: CORE}; - static elementProperty: o.ExternalReference = {name: 'ɵɵelementProperty', moduleName: CORE}; - static select: o.ExternalReference = {name: 'ɵɵselect', moduleName: CORE}; static updateSyntheticHostBinding: diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index b23bc2c35f..aec2b0641e 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -108,7 +108,6 @@ export { ɵɵcontentQuery, ɵɵloadContentQuery, ɵɵelementEnd, - ɵɵelementProperty, ɵɵproperty, ɵɵpropertyInterpolate, ɵɵpropertyInterpolate1, diff --git a/packages/core/src/render3/VIEW_DATA.md b/packages/core/src/render3/VIEW_DATA.md index 4cab860b01..4035bb4e29 100644 --- a/packages/core/src/render3/VIEW_DATA.md +++ b/packages/core/src/render3/VIEW_DATA.md @@ -98,8 +98,10 @@ class MyApp { ɵɵelementEnd(); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'title', ɵɵbind(ctx.name)); - ɵɵtextBinding(1, ɵɵinterpolation1('Hello ', ctx.name, '!')); + ɵɵselect(0); + ɵɵproperty('title', ctx.name); + ɵɵselect(1); + ɵɵtextInterpolate1('Hello ', ctx.name, '!'); } ... } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 723982608a..d37795f76e 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -54,7 +54,6 @@ export { ɵɵelementEnd, ɵɵelementHostAttrs, - ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index 8dd5d615e8..1117455084 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -61,32 +61,6 @@ export function ɵɵbind(value: T): T|NO_CHANGE { return bindingUpdated(lView, bindingIndex, value) ? value : NO_CHANGE; } -/** -* **TODO: Remove this function after `property` is in use** -* Update a property on an element. -* -* If the property name also exists as an input property on one of the element's directives, -* the component property will be set instead of the element property. This check must -* be conducted at runtime so child components that add new @Inputs don't have to be re-compiled. -* -* @param index The index of the element to update in the data array -* @param propName Name of property. Because it is going to DOM, this is not subject to -* renaming as part of minification. -* @param value New value to write. -* @param sanitizer An optional function used to sanitize the value. -* @param nativeOnly Whether or not we should only set native properties and skip input check -* (this is necessary for host property bindings) - * - * @codeGenApi -*/ -export function ɵɵelementProperty( - index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null, - nativeOnly?: boolean): void { - if (value !== NO_CHANGE) { - elementPropertyInternal(index, propName, value, sanitizer, nativeOnly); - } -} - /** * Updates a synthetic host binding (e.g. `[@foo]`) on a component. * diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index d5180a3158..ec3cfad3e5 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -87,7 +87,6 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵlistener': r3.ɵɵlistener, 'ɵɵload': r3.ɵɵload, 'ɵɵprojection': r3.ɵɵprojection, - 'ɵɵelementProperty': r3.ɵɵelementProperty, 'ɵɵupdateSyntheticHostBinding': r3.ɵɵupdateSyntheticHostBinding, 'ɵɵcomponentHostSyntheticListener': r3.ɵɵcomponentHostSyntheticListener, 'ɵɵpipeBind1': r3.ɵɵpipeBind1, diff --git a/packages/core/test/render3/change_detection_spec.ts b/packages/core/test/render3/change_detection_spec.ts index 52629da45b..9e27b42ec1 100644 --- a/packages/core/test/render3/change_detection_spec.ts +++ b/packages/core/test/render3/change_detection_spec.ts @@ -10,8 +10,8 @@ import {withBody} from '@angular/private/testing'; import {ChangeDetectionStrategy, DoCheck} from '../../src/core'; import {whenRendered} from '../../src/render3/component'; -import {LifecycleHooksFeature, getRenderedText, ɵɵdefineComponent, ɵɵgetCurrentView} from '../../src/render3/index'; -import {detectChanges, markDirty, tick, ɵɵbind, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵinterpolation1, ɵɵinterpolation2, ɵɵlistener, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; +import {LifecycleHooksFeature, getRenderedText, ɵɵdefineComponent, ɵɵgetCurrentView, ɵɵproperty, ɵɵselect} from '../../src/render3/index'; +import {detectChanges, markDirty, tick, ɵɵbind, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵinterpolation1, ɵɵinterpolation2, ɵɵlistener, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {Renderer3, RendererFactory3} from '../../src/render3/interfaces/renderer'; import {FLAGS, LViewFlags} from '../../src/render3/interfaces/view'; @@ -186,7 +186,8 @@ describe('change detection', () => { ɵɵelement(0, 'manual-comp'); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'name', ɵɵbind(ctx.name)); + ɵɵselect(0); + ɵɵproperty('name', ctx.name); } }, diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index fbd43b429e..6526e39a43 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -8,8 +8,8 @@ import {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core'; import {createInjector} from '../../src/di/r3_injector'; -import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, getRenderedText, markDirty, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵtemplate} from '../../src/render3/index'; -import {tick, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵnextContext, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; +import {AttributeMarker, ComponentFactory, LifecycleHooksFeature, getRenderedText, markDirty, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵselect, ɵɵtemplate} from '../../src/render3/index'; +import {tick, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵnextContext, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; import {ComponentDef, RenderFlags} from '../../src/render3/interfaces/definition'; import {NgIf} from './common_with_def'; @@ -137,7 +137,8 @@ describe('component', () => { ɵɵelement(4097, 'comp'); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(4097, 'name', ɵɵbind(ctx.name)); + ɵɵselect(4097); + ɵɵproperty('name', ctx.name); } }, 4098, 1, [Comp]); @@ -188,7 +189,8 @@ it('should not invoke renderer destroy method for embedded views', () => { 2, MyComponent_div_Template_2, 2, 0, 'div', [AttributeMarker.Template, 'ngIf']); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(2, 'ngIf', ɵɵbind(ctx.visible)); + ɵɵselect(2); + ɵɵproperty('ngIf', ctx.visible); } } }); @@ -269,7 +271,8 @@ describe('component with a container', () => { ɵɵelement(0, 'wrapper'); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'items', ɵɵbind(ctx.items)); + ɵɵselect(0); + ɵɵproperty('items', ctx.items); } } @@ -339,7 +342,8 @@ describe('recursive components', () => { ɵɵelement(0, 'tree-comp'); } if (rf0 & RenderFlags.Update) { - ɵɵelementProperty(0, 'data', ɵɵbind(ctx.data.left)); + ɵɵselect(0); + ɵɵproperty('data', ctx.data.left); } ɵɵembeddedViewEnd(); } @@ -353,7 +357,8 @@ describe('recursive components', () => { ɵɵelement(0, 'tree-comp'); } if (rf0 & RenderFlags.Update) { - ɵɵelementProperty(0, 'data', ɵɵbind(ctx.data.right)); + ɵɵselect(0); + ɵɵproperty('data', ctx.data.right); } ɵɵembeddedViewEnd(); } @@ -400,8 +405,10 @@ describe('recursive components', () => { } if (rf & RenderFlags.Update) { ɵɵtextBinding(0, ɵɵbind(ctx.data.value)); - ɵɵelementProperty(1, 'ngIf', ɵɵbind(ctx.data.left)); - ɵɵelementProperty(2, 'ngIf', ɵɵbind(ctx.data.right)); + ɵɵselect(1); + ɵɵproperty('ngIf', ɵɵbind(ctx.data.left)); + ɵɵselect(2); + ɵɵproperty('ngIf', ctx.data.right); } }, @@ -416,7 +423,8 @@ describe('recursive components', () => { } if (rf & RenderFlags.Update) { const parent = ɵɵnextContext(); - ɵɵelementProperty(0, 'data', ɵɵbind(parent.data.left)); + ɵɵselect(0); + ɵɵproperty('data', parent.data.left); } } @@ -427,7 +435,8 @@ describe('recursive components', () => { } if (rf & RenderFlags.Update) { const parent = ɵɵnextContext(); - ɵɵelementProperty(0, 'data', ɵɵbind(parent.data.right)); + ɵɵselect(0); + ɵɵproperty('data', parent.data.right); } } diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index cc75beec31..feb6940bbd 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -9,7 +9,7 @@ import {NgForOfContext} from '@angular/common'; import {ɵɵdefineComponent} from '../../src/render3/definition'; -import {RenderFlags, ɵɵbind, ɵɵclassMap, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵinterpolation1, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; +import {RenderFlags, ɵɵbind, ɵɵclassMap, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementStart, ɵɵinterpolation1, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; import {AttributeMarker} from '../../src/render3/interfaces/node'; import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass'; import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization'; @@ -58,10 +58,16 @@ describe('instructions', () => { it('should update bindings when value changes with the correct perf counters', () => { const t = new TemplateFixture(createAnchor, () => {}, 1, 1); - t.update(() => ɵɵelementProperty(0, 'title', ɵɵbind('Hello'))); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('title', 'Hello'); + }); expect(t.html).toEqual(''); - t.update(() => ɵɵelementProperty(0, 'title', ɵɵbind('World'))); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('title', 'World'); + }); expect(t.html).toEqual(''); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, @@ -74,7 +80,10 @@ describe('instructions', () => { it('should not update bindings when value does not change, with the correct perf counters', () => { - const idempotentUpdate = () => ɵɵelementProperty(0, 'title', ɵɵbind('Hello')); + const idempotentUpdate = () => { + ɵɵselect(0); + ɵɵproperty('title', 'Hello'); + }; const t = new TemplateFixture(createAnchor, idempotentUpdate, 1, 1); t.update(); @@ -295,7 +304,8 @@ describe('instructions', () => { } if (rf & RenderFlags.Update) { const row_r2 = ctx0.$implicit; - ɵɵelementProperty(1, 'ngForOf', ɵɵbind(row_r2)); + ɵɵselect(1); + ɵɵproperty('ngForOf', row_r2); } } @@ -331,7 +341,8 @@ describe('instructions', () => { ɵɵtemplate(0, ToDoAppComponent_NgForOf_Template_0, 2, 1, 'ul', _c0); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'ngForOf', ɵɵbind(ctx.rows)); + ɵɵselect(0); + ɵɵproperty('ngForOf', ctx.rows); } }, directives: [NgForOf] @@ -448,66 +459,84 @@ describe('instructions', () => { it('should work for script sanitization', () => { const s = new LocalMockSanitizer(value => `${value} //sanitized`); - const t = new TemplateFixture(createScript, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s); const inputValue = 'fn();'; const outputValue = 'fn(); //sanitized'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeScript)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('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, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s); const inputValue = s.bypassSecurityTrustScript('alert("bar")'); const outputValue = 'alert("bar")'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeScript)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('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, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s); const inputValue = bypassSanitizationTrustScript('alert("bar")'); const outputValue = 'alert("bar")-ivy'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeScript)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('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, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = '
'; const outputValue = '
'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeHtml)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('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, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = s.bypassSecurityTrustHtml('
'); const outputValue = '
'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeHtml)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('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, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = bypassSanitizationTrustHtml('
'); const outputValue = '
-ivy'; - t.update(() => ɵɵelementProperty(0, 'innerHTML', inputValue, ɵɵsanitizeHtml)); + t.update(() => { + ɵɵselect(0); + ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeHtml); + }); expect(t.html).toEqual(`
${outputValue}
`); expect(s.lastSanitizedValue).toBeFalsy(); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 0674229a90..14a53c0ddb 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -8,8 +8,8 @@ import {RendererType2} from '../../src/render/api'; import {getLContext} from '../../src/render3/context_discovery'; -import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index'; -import {ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; +import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index'; +import {ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; @@ -159,8 +159,9 @@ describe('render3 integration test', () => { ɵɵelementEnd(); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'beforeTree', ɵɵbind(ctx.beforeTree)); - ɵɵelementProperty(0, 'afterTree', ɵɵbind(ctx.afterTree)); + ɵɵselect(0); + ɵɵproperty('beforeTree', ctx.beforeTree); + ɵɵproperty('afterTree', ctx.afterTree); ɵɵcontainerRefreshStart(1); { const rf0 = ɵɵembeddedViewStart(0, 3, 0); @@ -335,48 +336,54 @@ describe('component animations', () => { expect(attr).toEqual('@fooAnimation'); }); - it('should allow host binding animations to be picked up and rendered', () => { - class ChildCompWithAnim { - static ngDirectiveDef = ɵɵdefineDirective({ - type: ChildCompWithAnim, - factory: () => new ChildCompWithAnim(), - selectors: [['child-comp-with-anim']], - hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void { - if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, '@fooAnim', ctx.exp); - } - }, - }); + // TODO(benlesh): this test does not seem to be testing anything we could actually generate with + // these instructions. ɵɵbind should be present in the ɵɵelementProperty call in the hostBindings, + // however adding that causes an error because the slot has not been allocated. There is a + // directive called `comp-with-anim`, that seems to want to be a component, but is defined as a + // directive that is looking for a property `@fooAnim` to update. - exp = 'go'; - } + // it('should allow host binding animations to be picked up and rendered', () => { + // class ChildCompWithAnim { + // static ngDirectiveDef = ɵɵdefineDirective({ + // type: ChildCompWithAnim, + // factory: () => new ChildCompWithAnim(), + // selectors: [['child-comp-with-anim']], + // hostBindings: function(rf: RenderFlags, ctx: any, elementIndex: number): void { + // if (rf & RenderFlags.Update) { + // ɵɵelementProperty(0, '@fooAnim', ctx.exp); + // } + // }, + // }); - class ParentComp { - static ngComponentDef = ɵɵdefineComponent({ - type: ParentComp, - consts: 1, - vars: 1, - selectors: [['foo']], - factory: () => new ParentComp(), - template: (rf: RenderFlags, ctx: ParentComp) => { - if (rf & RenderFlags.Create) { - ɵɵelement(0, 'child-comp-with-anim'); - } - }, - directives: [ChildCompWithAnim] - }); - } + // exp = 'go'; + // } - const rendererFactory = new MockRendererFactory(['setProperty']); - const fixture = new ComponentFixture(ParentComp, {rendererFactory}); + // class ParentComp { + // static ngComponentDef = ɵɵdefineComponent({ + // type: ParentComp, + // consts: 1, + // vars: 1, + // selectors: [['foo']], + // factory: () => new ParentComp(), + // template: (rf: RenderFlags, ctx: ParentComp) => { + // if (rf & RenderFlags.Create) { + // ɵɵelement(0, 'child-comp-with-anim'); + // } + // }, + // directives: [ChildCompWithAnim] + // }); + // } - const renderer = rendererFactory.lastRenderer !; - fixture.update(); + // const rendererFactory = new MockRendererFactory(['setProperty']); + // const fixture = new ComponentFixture(ParentComp, {rendererFactory}); - const spy = renderer.spies['setProperty']; - const [elm, attr, value] = spy.calls.mostRecent().args; - expect(attr).toEqual('@fooAnim'); - }); + // const renderer = rendererFactory.lastRenderer !; + // fixture.update(); + + // const spy = renderer.spies['setProperty']; + // const [elm, attr, value] = spy.calls.mostRecent().args; + // expect(attr).toEqual('@fooAnim'); + // }); }); describe('element discovery', () => { @@ -485,7 +492,8 @@ describe('element discovery', () => { ɵɵelementEnd(); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(1, 'ngIf', true); + ɵɵselect(1); + ɵɵproperty('ngIf', true); } } }); @@ -1087,7 +1095,8 @@ describe('sanitization', () => { ɵɵelement(0, 'a'); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'href', ɵɵbind(ctx.href), ɵɵsanitizeUrl); + ɵɵselect(0); + ɵɵproperty('href', ctx.href, ɵɵsanitizeUrl); } } }); @@ -1127,7 +1136,8 @@ describe('sanitization', () => { ɵɵallocHostVars(1); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(elementIndex, 'cite', ɵɵbind(ctx.cite), ɵɵsanitizeUrl, true); + ɵɵselect(elementIndex); + ɵɵproperty('cite', ctx.cite, ɵɵsanitizeUrl, true); } } }); diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index 2c69104aad..c60cd26a34 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -7,8 +7,8 @@ */ import {OnDestroy} from '../../src/core'; -import {AttributeMarker, ComponentTemplate, ɵɵNgOnChangesFeature, ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index'; -import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; +import {AttributeMarker, ComponentTemplate, ɵɵNgOnChangesFeature, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index'; +import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {NgIf} from './common_with_def'; @@ -22,7 +22,8 @@ describe('lifecycles', () => { ɵɵelement(0, name); } if (rf & RenderFlags.Update) { - ɵɵelementProperty(0, 'val', ɵɵbind(ctx.val)); + ɵɵselect(0); + ɵɵproperty('val', ctx.val); } }; } diff --git a/packages/core/test/render3/pure_function_spec.ts b/packages/core/test/render3/pure_function_spec.ts index 42f7a17476..c394358e35 100644 --- a/packages/core/test/render3/pure_function_spec.ts +++ b/packages/core/test/render3/pure_function_spec.ts @@ -5,8 +5,8 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {ɵɵdefineComponent} from '../../src/render3/index'; -import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart} from '../../src/render3/instructions/all'; +import {ɵɵdefineComponent, ɵɵproperty, ɵɵselect} from '../../src/render3/index'; +import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart} from '../../src/render3/instructions/all'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {ɵɵpureFunction2} from '../../src/render3/pure_function'; import {getDirectiveOnNode, renderToHtml} from '../../test/render3/render_util'; @@ -58,9 +58,10 @@ describe('object literals', () => { ɵɵelementEnd(); } if (rf1 & RenderFlags.Update) { - ɵɵelementProperty( - 0, 'config', ɵɵbind(ɵɵpureFunction2( - 1, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration))); + ɵɵselect(0); + ɵɵproperty( + 'config', + ɵɵpureFunction2(1, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration)); } ɵɵembeddedViewEnd(); } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 15d50079a4..1b8f798d56 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -828,8 +828,6 @@ export declare function ɵɵelementEnd(): void; export declare function ɵɵelementHostAttrs(attrs: TAttributes): void; -export declare function ɵɵelementProperty(index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null, nativeOnly?: boolean): void; - export declare function ɵɵelementStart(index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void; export declare function ɵɵembeddedViewEnd(): void; From 8852b793df9c4dd09afebf766cedf0f96efd728b Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 4 Jun 2019 20:42:54 +0200 Subject: [PATCH 04/16] build: unable to run build-packages-dist script on windows (#30853) Currently it's not possible to run the `./scripts/build-packages-dist.sh` script on Windows because `bazel query` returns CRLF line-endings which result on array expansion in Bazel target names that end with a carriage return (e.g. `//packages/core\r`). This then results in a build failure where Bazel complains that target names should not end with a carriage return. In order to fix this, we just strip off the carriage return line-endings from the bazel query stdout. Ideally the script will be ported to a plain Node script eventually, but for now it prevents Windows users from building the release packages and the simple workaround seems reasonable and sufficient. PR Close #30853 --- scripts/package-builder.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/package-builder.sh b/scripts/package-builder.sh index a3f349661a..475196bb8c 100755 --- a/scripts/package-builder.sh +++ b/scripts/package-builder.sh @@ -23,8 +23,10 @@ readonly bazel_bin=$(yarn bin)/bazel readonly bin=$(${bazel_bin} info bazel-bin) function buildTargetPackages() { - # List of targets to build, e.g. core, common, compiler, etc. - targets=$(${bazel_bin} query --output=label 'attr("tags", "\[.*release-with-framework.*\]", //packages/...) intersect kind(".*_package", //packages/...)') + # List of targets to build, e.g. core, common, compiler, etc. Note that we want to + # remove all carriage return ("\r") characters form the query output because otherwise + # the carriage return is part of the bazel target name and bazel will complain. + targets=$(${bazel_bin} query --output=label 'attr("tags", "\[.*release-with-framework.*\]", //packages/...) intersect kind(".*_package", //packages/...)' | tr -d "\r") # Path to the output directory into which we copy the npm packages. dest_path="$1" From 540b01402f53f7cd9bff42ee6d53c7f110a7f2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Wed, 5 Jun 2019 12:27:26 -0700 Subject: [PATCH 05/16] release: cut the v8.1.0-next.1 release --- CHANGELOG.md | 10 ++++++++++ package.json | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66cb186484..d69d7162ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +# [8.1.0-next.1](https://github.com/angular/angular/compare/8.1.0-beta.0...8.1.0-next.1) (2019-06-05) + + +### Bug Fixes + +* **core:** TypeScript related migrations should cater for BOM ([#30719](https://github.com/angular/angular/issues/30719)) ([80394ce](https://github.com/angular/angular/commit/80394ce)), closes [/github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts#L72](https://github.com//github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts/issues/L72) [#30713](https://github.com/angular/angular/issues/30713) + + + # [8.1.0-beta.0](https://github.com/angular/angular/compare/8.0.0...8.1.0-beta.0) (2019-05-30) diff --git a/package.json b/package.json index 3b9e8567af..44337e9493 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "8.1.0-beta.0", + "version": "8.1.0-next.1", "private": true, "description": "Angular - a web framework for modern web apps", "homepage": "https://github.com/angular/angular", @@ -165,4 +165,4 @@ "resolutions": { "natives": "1.1.6" } -} +} \ No newline at end of file From 17bfedd224fda87afe155a0857bdb3503b66f861 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 5 Jun 2019 10:19:28 -0700 Subject: [PATCH 06/16] fix(bazel): Load global stylesheet in dev and prod (#30879) Global stylesheet should be injected as a tag in index.html for both dev and prod app. PR Close #30879 --- .../src/builders/files/src/BUILD.bazel.template | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/bazel/src/builders/files/src/BUILD.bazel.template b/packages/bazel/src/builders/files/src/BUILD.bazel.template index 3059878485..dd553a2948 100644 --- a/packages/bazel/src/builders/files/src/BUILD.bazel.template +++ b/packages/bazel/src/builders/files/src/BUILD.bazel.template @@ -5,11 +5,20 @@ load("@npm_bazel_karma//:index.bzl", "ts_web_test_suite") load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle", "history_server") load("@build_bazel_rules_nodejs//internal/web_package:web_package.bzl", "web_package") load("@npm_bazel_typescript//:index.bzl", "ts_devserver", "ts_library") -load("@io_bazel_rules_sass//:defs.bzl", "multi_sass_binary") +load("@io_bazel_rules_sass//:defs.bzl", "multi_sass_binary", "sass_binary") + +sass_binary( + name = "global_stylesheet", + src = glob(["styles.css", "styles.scss"])[0], + output_name = "global_stylesheet.css", +) multi_sass_binary( name = "styles", - srcs = glob(["**/*.scss"]), + srcs = glob( + include = ["**/*.scss"], + exclude = ["styles.scss"], + ), ) ng_module( @@ -52,6 +61,7 @@ web_package( # do not sort "@npm//node_modules/zone.js:dist/zone.min.js", ":bundle.min.js", + ":global_stylesheet", ], data = [ "favicon.ico", @@ -85,6 +95,7 @@ ts_devserver( ], static_files = [ "@npm//node_modules/zone.js:dist/zone.min.js", + ":global_stylesheet", ], data = [ "favicon.ico", From ea2d453118f3088bd187e0d7cf04f8339772de4d Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 3 Jun 2019 21:39:30 +0300 Subject: [PATCH 07/16] fix(ivy): ngcc - use spaces in overwritten `package.json` content for readability (#30831) When ngcc processes an entrypoint, it updates `package.json` with metadata about the processed format. Previously, it overwrote the `package.json` with the stringified JSON object without spaces. This made the file difficult to read (for example when looking at the file while debugging an ngcc failure). This commit fixes it by using spaces in the new `package.json` content. PR Close #30831 --- integration/ngcc/test.sh | 12 ++++++------ .../compiler-cli/ngcc/src/packages/build_marker.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index 5374cc70fe..6f573e5ed8 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -14,23 +14,23 @@ if [[ $? != 0 ]]; then exit 1; fi # Did it add the appropriate build markers? # - esm2015 - grep '"__processed_by_ivy_ngcc__":[^}]*"esm2015":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"esm2015": "' if [[ $? != 0 ]]; then exit 1; fi # - fesm2015 - grep '"__processed_by_ivy_ngcc__":[^}]*"fesm2015":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"fesm2015": "' if [[ $? != 0 ]]; then exit 1; fi - grep '"__processed_by_ivy_ngcc__":[^}]*"es2015":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"es2015": "' if [[ $? != 0 ]]; then exit 1; fi # - esm5 - grep '"__processed_by_ivy_ngcc__":[^}]*"esm5":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"esm5": "' if [[ $? != 0 ]]; then exit 1; fi # - fesm5 - grep '"__processed_by_ivy_ngcc__":[^}]*"module":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"module": "' if [[ $? != 0 ]]; then exit 1; fi - grep '"__processed_by_ivy_ngcc__":[^}]*"fesm5":"' node_modules/@angular/common/package.json + cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"fesm5": "' if [[ $? != 0 ]]; then exit 1; fi # Did it replace the PRE_R3 markers correctly? diff --git a/packages/compiler-cli/ngcc/src/packages/build_marker.ts b/packages/compiler-cli/ngcc/src/packages/build_marker.ts index c55dbd39c1..8445854e04 100644 --- a/packages/compiler-cli/ngcc/src/packages/build_marker.ts +++ b/packages/compiler-cli/ngcc/src/packages/build_marker.ts @@ -51,5 +51,5 @@ export function markAsProcessed( format: EntryPointJsonProperty) { if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {}; packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION; - fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); + fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } From b51d8dd43876d0bdbcb309015ff876b293027022 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Tue, 4 Jun 2019 22:03:32 +0200 Subject: [PATCH 08/16] fix(ivy): error for empty bindings on ng-template (#30829) Fixes Ivy throwing an error if it runs into an empty property binding on an `ng-template` (e.g. ``) by not generating an update instruction for it. Fixes #30801. This PR resoves FW-1356. PR Close #30829 --- .../directives/ng_template_outlet_spec.ts | 14 ++++++++++++ .../compliance/r3_compiler_compliance_spec.ts | 22 +++++++++++++++++++ .../compiler/src/render3/view/template.ts | 11 ++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/common/test/directives/ng_template_outlet_spec.ts b/packages/common/test/directives/ng_template_outlet_spec.ts index 9226e5acbe..4f349b1776 100644 --- a/packages/common/test/directives/ng_template_outlet_spec.ts +++ b/packages/common/test/directives/ng_template_outlet_spec.ts @@ -204,6 +204,20 @@ describe('NgTemplateOutlet', () => { fixture.componentInstance.value = 'baz'; detectChangesAndExpectText(''); }); + + // https://github.com/angular/angular/issues/30801 + it('should not throw if the context is left blank', () => { + const template = ` + test + + `; + + expect(() => { + fixture = createTestComponent(template); + detectChangesAndExpectText('test'); + }).not.toThrow(); + }); + }); @Injectable() 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 82b4379423..78b52bc5a4 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -2932,6 +2932,28 @@ describe('compiler compliance', () => { expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef'); }); + it('should not throw for empty property bindings on ng-template', () => { + const files = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-app', + template: '' + }) + export class MyComponent { + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {}` + } + }; + + expect(() => compile(files, angularFiles)).not.toThrow(); + }); + + }); describe('inherited base classes', () => { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index d7316a84af..2a742933b4 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -1024,10 +1024,13 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver attrs.forEach(input => { if (input instanceof t.BoundAttribute) { const value = input.value.visit(this._valueConverter); - this.allocateBindingSlots(value); - this.updateInstruction( - templateIndex, template.sourceSpan, R3.property, - () => [o.literal(input.name), this.convertPropertyBinding(context, value, true)]); + + if (value !== undefined) { + this.allocateBindingSlots(value); + this.updateInstruction( + templateIndex, template.sourceSpan, R3.property, + () => [o.literal(input.name), this.convertPropertyBinding(context, value, true)]); + } } }); } From 05a43ca8691004b66ff271dbeb3b0ee2cc3a49e7 Mon Sep 17 00:00:00 2001 From: Jeremy Elbourn Date: Mon, 3 Jun 2019 09:25:17 -0700 Subject: [PATCH 09/16] fix(bazel): exclude components schematics from build (#30825) See https://github.com/angular/components/issues/16189 Without this, bazel attempts to build schematics templates as srcs PR Close #30825 --- integration/bazel/angular-metadata.tsconfig.json | 4 +++- .../ng-add/files/angular-metadata.tsconfig.json.template | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/integration/bazel/angular-metadata.tsconfig.json b/integration/bazel/angular-metadata.tsconfig.json index 7ba3660257..918e261786 100644 --- a/integration/bazel/angular-metadata.tsconfig.json +++ b/integration/bazel/angular-metadata.tsconfig.json @@ -24,6 +24,8 @@ "node_modules/@angular/compiler-cli/**", "node_modules/@angular/**/testing/**", "node_modules/@angular/common/upgrade*", - "node_modules/@angular/router/upgrade*" + "node_modules/@angular/router/upgrade*", + "node_modules/@angular/cdk/schematics*", + "node_modules/@angular/material/schematics*" ] } diff --git a/packages/bazel/src/schematics/ng-add/files/angular-metadata.tsconfig.json.template b/packages/bazel/src/schematics/ng-add/files/angular-metadata.tsconfig.json.template index affaf68154..41cbd63733 100644 --- a/packages/bazel/src/schematics/ng-add/files/angular-metadata.tsconfig.json.template +++ b/packages/bazel/src/schematics/ng-add/files/angular-metadata.tsconfig.json.template @@ -21,6 +21,8 @@ "node_modules/@angular/compiler-cli/**", "node_modules/@angular/**/testing/**", "node_modules/@angular/common/upgrade*", - "node_modules/@angular/router/upgrade*" + "node_modules/@angular/router/upgrade*", + "node_modules/@angular/cdk/schematics*", + "node_modules/@angular/material/schematics*" ] } From 30efb6b8eabc92bc3ece57930133f71f67720f8a Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Wed, 5 Jun 2019 20:47:21 +0200 Subject: [PATCH 10/16] fix(ivy): don't project removed placeholders with runtime i18n (#30783) When translated content was projected, all of the content was reappended, even the placeholders that had been removed in the translation. To avoid that we added a new flag on `TNode` that specifies that a node is detached, in which case it should be ignored by the projection. FW-1319 #resolve PR Close #30783 --- packages/core/src/render3/i18n.ts | 4 +++- packages/core/src/render3/interfaces/node.ts | 13 +++++++----- .../core/src/render3/node_manipulation.ts | 20 ++++++++++--------- packages/core/test/acceptance/i18n_spec.ts | 3 +-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index bdf88e081e..20729712a2 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -19,7 +19,7 @@ import {attachI18nOpCodesDebug} from './instructions/lview_debug'; import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; -import {TElementNode, TIcuContainerNode, TNode, TNodeType, TProjectionNode} from './interfaces/node'; +import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; import {RComment, RElement, RText} from './interfaces/renderer'; import {SanitizerFn} from './interfaces/sanitization'; import {StylingContext} from './interfaces/styling'; @@ -910,6 +910,8 @@ function removeNode(index: number, viewData: LView) { } } + // Define this node as detached so that we don't risk projecting it + removedPhTNode.flags |= TNodeFlags.isDetached; ngDevMode && ngDevMode.rendererRemoveNode++; } diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 4e88949712..3ecb11f346 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -47,19 +47,22 @@ export const enum TNodeType { */ export const enum TNodeFlags { /** This bit is set if the node is a component */ - isComponent = 0b00001, + isComponent = 0b000001, /** This bit is set if the node has been projected */ - isProjected = 0b00010, + isProjected = 0b000010, /** This bit is set if any directive on this node has content queries */ - hasContentQuery = 0b00100, + hasContentQuery = 0b000100, /** This bit is set if the node has any "class" inputs */ - hasClassInput = 0b01000, + hasClassInput = 0b001000, /** This bit is set if the node has any "style" inputs */ - hasStyleInput = 0b10000, + hasStyleInput = 0b010000, + + /** This bit is set if the node has been detached by i18n */ + isDetached = 0b100000, } /** diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 8fc447f32b..50dd8cd1d9 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -784,15 +784,17 @@ export function appendProjectedNodes( appendChild(nodeToProject, tProjectionNode, lView); } else { while (nodeToProject) { - if (nodeToProject.type === TNodeType.Projection) { - appendProjectedNodes( - lView, tProjectionNode, (nodeToProject as TProjectionNode).projection, - findComponentView(projectedView)); - } else { - // This flag must be set now or we won't know that this node is projected - // if the nodes are inserted into a container later. - nodeToProject.flags |= TNodeFlags.isProjected; - appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView); + if (!(nodeToProject.flags & TNodeFlags.isDetached)) { + if (nodeToProject.type === TNodeType.Projection) { + appendProjectedNodes( + lView, tProjectionNode, (nodeToProject as TProjectionNode).projection, + findComponentView(projectedView)); + } else { + // This flag must be set now or we won't know that this node is projected + // if the nodes are inserted into a container later. + nodeToProject.flags |= TNodeFlags.isProjected; + appendProjectedNode(nodeToProject, tProjectionNode, lView, projectedView); + } } nodeToProject = nodeToProject.projectionNext; } diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 16c37d6a39..81e56ac043 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -939,8 +939,7 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { .toEqual('
Bonjour monde!
'); }); - // FW-1319 Runtime i18n should be able to remove projected placeholders - xit('should be able to remove projected placeholders', () => { + it('should be able to remove projected placeholders', () => { @Component({selector: 'grand-child', template: '
'}) class GrandChild { } From 3859bcc70cdf4773cd4bae14b2c0218c273f1540 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Fri, 31 May 2019 10:39:14 -0700 Subject: [PATCH 11/16] =?UTF-8?q?refactor(ivy):=20remove=20=C9=B5=C9=B5ele?= =?UTF-8?q?mentAttribute=20instruction=20(#30640)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR Close #30640 --- .../compiler/src/render3/r3_identifiers.ts | 2 - .../core/src/core_render3_private_export.ts | 1 - packages/core/src/render3/i18n.ts | 12 +-- packages/core/src/render3/index.ts | 1 - .../src/render3/instructions/attribute.ts | 13 ++- .../instructions/attribute_interpolation.ts | 55 +++++++++---- .../core/src/render3/instructions/element.ts | 60 ++------------ .../core/src/render3/instructions/shared.ts | 31 ++++++- packages/core/src/render3/jit/environment.ts | 1 - .../core/test/render3/instructions_spec.ts | 81 +++++++++++++------ .../core/test/render3/integration_spec.ts | 7 +- tools/public_api_guard/core/core.d.ts | 2 - 12 files changed, 151 insertions(+), 115 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 22f1197d38..a17f4ffd12 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -37,8 +37,6 @@ export class Identifiers { static componentHostSyntheticListener: o.ExternalReference = {name: 'ɵɵcomponentHostSyntheticListener', moduleName: CORE}; - static elementAttribute: o.ExternalReference = {name: 'ɵɵelementAttribute', moduleName: CORE}; - static attribute: o.ExternalReference = {name: 'ɵɵattribute', moduleName: CORE}; static attributeInterpolate1: diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index aec2b0641e..e8016d3779 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -126,7 +126,6 @@ export { ɵɵenableBindings, ɵɵdisableBindings, ɵɵallocHostVars, - ɵɵelementAttribute, ɵɵelementContainerStart, ɵɵelementContainerEnd, ɵɵstyling, diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 20729712a2..e04c076d80 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -7,16 +7,18 @@ */ import '../util/ng_i18n_closure_mode'; + import {getPluralCase} from '../i18n/localization'; import {SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS, getTemplateContent} from '../sanitization/html_sanitizer'; import {InertBodyHelper} from '../sanitization/inert_body'; import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer'; import {addAllToArray} from '../util/array_utils'; import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert'; + import {attachPatchData} from './context_discovery'; -import {elementAttributeInternal, setDelayProjection, ɵɵload, ɵɵtextBinding} from './instructions/all'; +import {setDelayProjection, ɵɵload, ɵɵtextBinding} from './instructions/all'; import {attachI18nOpCodesDebug} from './instructions/lview_debug'; -import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared'; +import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; @@ -755,10 +757,9 @@ function readCreateOpCodes( const elementNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF; const attrName = createOpCodes[++i] as string; const attrValue = createOpCodes[++i] as string; - const renderer = viewData[RENDERER]; // This code is used for ICU expressions only, since we don't support // directives/components in ICUs, we don't need to worry about inputs here - elementAttributeInternal(elementNodeIndex, attrName, attrValue, viewData, renderer); + elementAttributeInternal(elementNodeIndex, attrName, attrValue, viewData); break; default: throw new Error(`Unable to determine the type of mutate operation for "${opCode}"`); @@ -988,8 +989,7 @@ function i18nAttributesFirstPass(tView: TView, index: number, values: string[]) generateBindingUpdateOpCodes(value, previousElementIndex, attrName), updateOpCodes); } else { const lView = getLView(); - const renderer = lView[RENDERER]; - elementAttributeInternal(previousElementIndex, attrName, value, lView, renderer); + elementAttributeInternal(previousElementIndex, attrName, value, lView); // Check if that attribute is a directive input const tNode = getTNode(previousElementIndex, lView); const dataValue = tNode.inputs && tNode.inputs[attrName]; diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index d37795f76e..555b6d377a 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -47,7 +47,6 @@ export { ɵɵdirectiveInject, ɵɵelement, - ɵɵelementAttribute, ɵɵelementContainerEnd, ɵɵelementContainerStart, diff --git a/packages/core/src/render3/instructions/attribute.ts b/packages/core/src/render3/instructions/attribute.ts index f5fdc13f8a..8307c5d364 100644 --- a/packages/core/src/render3/instructions/attribute.ts +++ b/packages/core/src/render3/instructions/attribute.ts @@ -6,10 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ import {SanitizerFn} from '../interfaces/sanitization'; -import {getSelectedIndex} from '../state'; +import {getLView, getSelectedIndex} from '../state'; +import {NO_CHANGE} from '../tokens'; -import {ɵɵelementAttribute} from './element'; import {ɵɵbind} from './property'; +import {elementAttributeInternal} from './shared'; + + /** * Updates the value of or removes a bound attribute on an Element. @@ -27,6 +30,10 @@ import {ɵɵbind} from './property'; export function ɵɵattribute( name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string) { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. - return ɵɵelementAttribute(index, name, ɵɵbind(value), sanitizer, namespace); + const bound = ɵɵbind(value); + if (bound !== NO_CHANGE) { + return elementAttributeInternal(index, name, bound, lView, sanitizer, namespace); + } } diff --git a/packages/core/src/render3/instructions/attribute_interpolation.ts b/packages/core/src/render3/instructions/attribute_interpolation.ts index c0505532e9..64dde617d2 100644 --- a/packages/core/src/render3/instructions/attribute_interpolation.ts +++ b/packages/core/src/render3/instructions/attribute_interpolation.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ import {SanitizerFn} from '../interfaces/sanitization'; -import {getSelectedIndex} from '../state'; -import {ɵɵelementAttribute} from './element'; +import {getLView, getSelectedIndex} from '../state'; +import {NO_CHANGE} from '../tokens'; + import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; -import {TsickleIssue1009} from './shared'; +import {TsickleIssue1009, elementAttributeInternal} from './shared'; @@ -41,12 +42,13 @@ export function ɵɵattributeInterpolate1( attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation1(prefix, v0, suffix); - - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); - + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate1; } @@ -80,10 +82,13 @@ export function ɵɵattributeInterpolate2( attrName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation2(prefix, v0, i0, v1, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate2; } @@ -120,10 +125,13 @@ export function ɵɵattributeInterpolate3( attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate3; } @@ -162,10 +170,13 @@ export function ɵɵattributeInterpolate4( attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate4; } @@ -207,10 +218,13 @@ export function ɵɵattributeInterpolate5( v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate5; } @@ -254,10 +268,13 @@ export function ɵɵattributeInterpolate6( v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate6; } @@ -303,10 +320,13 @@ export function ɵɵattributeInterpolate7( v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate7; } @@ -354,10 +374,13 @@ export function ɵɵattributeInterpolate8( v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. const interpolatedValue = ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); - ɵɵelementAttribute(index, attrName, interpolatedValue, sanitizer, namespace); + if (interpolatedValue !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace); + } return ɵɵattributeInterpolate8; } @@ -391,7 +414,11 @@ export function ɵɵattributeInterpolateV( attrName: string, values: any[], sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 { const index = getSelectedIndex(); + const lView = getLView(); // TODO(FW-1340): Refactor to remove the use of other instructions here. - ɵɵelementAttribute(index, attrName, ɵɵinterpolationV(values), sanitizer, namespace); + const interpolated = ɵɵinterpolationV(values); + if (interpolated !== NO_CHANGE) { + elementAttributeInternal(index, attrName, interpolated, lView, sanitizer, namespace); + } return ɵɵattributeInterpolateV; } diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 1cdc428251..219831e4df 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -5,16 +5,14 @@ * 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 {validateAgainstEventAttributes} from '../../sanitization/sanitization'; import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert'; import {assertHasParent} from '../assert'; import {attachPatchData} from '../context_discovery'; import {registerPostOrderHooks} from '../hooks'; import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node'; -import {RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer'; -import {SanitizerFn} from '../interfaces/sanitization'; +import {RElement} from '../interfaces/renderer'; import {StylingContext} from '../interfaces/styling'; -import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {BINDING_INDEX, HEADER_OFFSET, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {assertNodeType} from '../node_assert'; import {appendChild} from '../node_manipulation'; import {applyOnCreateInstructions} from '../node_util'; @@ -23,14 +21,14 @@ import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticCo import {getStylingContextFromLView, hasClassInput, hasStyleInput} from '../styling/util'; import {registerInitialStylingIntoContext} from '../styling_next/instructions'; import {runtimeIsNewStylingInUse} from '../styling_next/state'; -import {NO_CHANGE} from '../tokens'; import {attrsStylingIndexOf, setUpAttributes} from '../util/attrs_utils'; -import {renderStringify} from '../util/misc_utils'; -import {getNativeByIndex, getNativeByTNode, getTNode} from '../util/view_utils'; +import {getNativeByTNode, getTNode} from '../util/view_utils'; + import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, setInputsForProperty, setNodeStylingTemplate} from './shared'; import {getActiveDirectiveStylingIndex} from './styling'; + /** * Create DOM element. The instruction must later be followed by `elementEnd()` call. * @@ -197,54 +195,6 @@ export function ɵɵelement( ɵɵelementEnd(); } - -/** - * Updates the value or removes an attribute on an Element. - * - * @param index The index of the element in the data array - * @param name name The name of the attribute. - * @param value value The attribute is removed when value is `null` or `undefined`. - * Otherwise the attribute value is set to the stringified value. - * @param sanitizer An optional function used to sanitize the value. - * @param namespace Optional namespace to use when setting the attribute. - * - * @codeGenApi - */ -export function ɵɵelementAttribute( - index: number, name: string, value: any, sanitizer?: SanitizerFn | null, - namespace?: string): void { - if (value !== NO_CHANGE) { - const lView = getLView(); - const renderer = lView[RENDERER]; - elementAttributeInternal(index, name, value, lView, renderer, sanitizer, namespace); - } -} - -export function elementAttributeInternal( - index: number, name: string, value: any, lView: LView, renderer: Renderer3, - sanitizer?: SanitizerFn | null, namespace?: string) { - ngDevMode && validateAgainstEventAttributes(name); - const element = getNativeByIndex(index, lView) as RElement; - if (value == null) { - ngDevMode && ngDevMode.rendererRemoveAttribute++; - isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) : - element.removeAttribute(name); - } else { - ngDevMode && ngDevMode.rendererSetAttribute++; - const tNode = getTNode(index, lView); - const strValue = - sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name); - - - if (isProceduralRenderer(renderer)) { - renderer.setAttribute(element, name, strValue, namespace); - } else { - namespace ? element.setAttributeNS(namespace, name, strValue) : - element.setAttribute(name, strValue); - } - } -} - /** * Assign static attribute values to a host element. * diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 6607f8d93b..d787f19107 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -9,7 +9,7 @@ import {Injector} from '../../di'; import {ErrorHandler} from '../../error_handler'; import {Type} from '../../interface/type'; import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema'; -import {validateAgainstEventProperties} from '../../sanitization/sanitization'; +import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization'; import {Sanitizer} from '../../sanitization/security'; import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertLessThan, assertNotEqual, assertNotSame} from '../../util/assert'; import {createNamedArrayType} from '../../util/named_array_type'; @@ -35,7 +35,7 @@ import {initializeStaticContext as initializeStaticStylingContext} from '../styl import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../styling/util'; import {NO_CHANGE} from '../tokens'; import {attrsStylingIndexOf} from '../util/attrs_utils'; -import {INTERPOLATION_DELIMITER, stringifyForError} from '../util/misc_utils'; +import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils'; import {getLViewParent, getRootContext} from '../util/view_traversal_utils'; import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils'; @@ -1303,6 +1303,33 @@ function addComponentLogic( } } +export function elementAttributeInternal( + index: number, name: string, value: any, lView: LView, sanitizer?: SanitizerFn | null, + namespace?: string) { + ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.'); + ngDevMode && validateAgainstEventAttributes(name); + const element = getNativeByIndex(index, lView) as RElement; + const renderer = lView[RENDERER]; + if (value == null) { + ngDevMode && ngDevMode.rendererRemoveAttribute++; + isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) : + element.removeAttribute(name); + } else { + ngDevMode && ngDevMode.rendererSetAttribute++; + const tNode = getTNode(index, lView); + const strValue = + sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name); + + + if (isProceduralRenderer(renderer)) { + renderer.setAttribute(element, name, strValue, namespace); + } else { + namespace ? element.setAttributeNS(namespace, name, strValue) : + element.setAttribute(name, strValue); + } + } +} + /** * Sets initial input properties on directive instances from attribute data * diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index ec3cfad3e5..120ba7195d 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -46,7 +46,6 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵNgOnChangesFeature': r3.ɵɵNgOnChangesFeature, 'ɵɵProvidersFeature': r3.ɵɵProvidersFeature, 'ɵɵInheritDefinitionFeature': r3.ɵɵInheritDefinitionFeature, - 'ɵɵelementAttribute': r3.ɵɵelementAttribute, 'ɵɵbind': r3.ɵɵbind, 'ɵɵcontainer': r3.ɵɵcontainer, 'ɵɵnextContext': r3.ɵɵnextContext, diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index feb6940bbd..ee6e856108 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -9,7 +9,7 @@ import {NgForOfContext} from '@angular/common'; import {ɵɵdefineComponent} from '../../src/render3/definition'; -import {RenderFlags, ɵɵbind, ɵɵclassMap, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementStart, ɵɵinterpolation1, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; +import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵinterpolation1, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/index'; import {AttributeMarker} from '../../src/render3/interfaces/node'; import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass'; import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization'; @@ -119,16 +119,20 @@ describe('instructions', () => { }); }); - describe('elementAttribute', () => { + describe('attribute', () => { it('should use sanitizer function', () => { - const t = new TemplateFixture(createDiv, () => {}, 1); + const t = new TemplateFixture(createDiv, () => {}, 1, 1); - t.update(() => ɵɵelementAttribute(0, 'title', 'javascript:true', ɵɵsanitizeUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('title', 'javascript:true', ɵɵsanitizeUrl); + }); expect(t.html).toEqual('
'); - t.update( - () => ɵɵelementAttribute( - 0, 'title', bypassSanitizationTrustUrl('javascript:true'), ɵɵsanitizeUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('title', bypassSanitizationTrustUrl('javascript:true'), ɵɵsanitizeUrl); + }); expect(t.html).toEqual('
'); expect(ngDevMode).toHaveProperties({ firstTemplatePass: 1, @@ -360,99 +364,126 @@ describe('instructions', () => { describe('sanitization injection compatibility', () => { it('should work for url sanitization', () => { const s = new LocalMockSanitizer(value => `${value}-sanitized`); - const t = new TemplateFixture(createAnchor, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s); const inputValue = 'http://foo'; const outputValue = 'http://foo-sanitized'; - t.update(() => ɵɵelementAttribute(0, 'href', inputValue, ɵɵsanitizeUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); + }); expect(t.html).toEqual(``); expect(s.lastSanitizedValue).toEqual(outputValue); }); it('should bypass url sanitization if marked by the service', () => { const s = new LocalMockSanitizer(value => ''); - const t = new TemplateFixture(createAnchor, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s); const inputValue = s.bypassSecurityTrustUrl('http://foo'); const outputValue = 'http://foo'; - t.update(() => ɵɵelementAttribute(0, 'href', inputValue, ɵɵsanitizeUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); + }); expect(t.html).toEqual(``); expect(s.lastSanitizedValue).toBeFalsy(); }); it('should bypass ivy-level url sanitization if a custom sanitizer is used', () => { const s = new LocalMockSanitizer(value => ''); - const t = new TemplateFixture(createAnchor, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s); const inputValue = bypassSanitizationTrustUrl('http://foo'); const outputValue = 'http://foo-ivy'; - t.update(() => ɵɵelementAttribute(0, 'href', inputValue, ɵɵsanitizeUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); + }); expect(t.html).toEqual(``); expect(s.lastSanitizedValue).toBeFalsy(); }); it('should work for style sanitization', () => { const s = new LocalMockSanitizer(value => `color:blue`); - const t = new TemplateFixture(createDiv, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = 'color:red'; const outputValue = 'color:blue'; - t.update(() => ɵɵelementAttribute(0, 'style', inputValue, ɵɵsanitizeStyle)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); + }); expect(stripStyleWsCharacters(t.html)).toEqual(`
`); expect(s.lastSanitizedValue).toEqual(outputValue); }); it('should bypass style sanitization if marked by the service', () => { const s = new LocalMockSanitizer(value => ''); - const t = new TemplateFixture(createDiv, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = s.bypassSecurityTrustStyle('color:maroon'); const outputValue = 'color:maroon'; - t.update(() => ɵɵelementAttribute(0, 'style', inputValue, ɵɵsanitizeStyle)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); + }); expect(stripStyleWsCharacters(t.html)).toEqual(`
`); expect(s.lastSanitizedValue).toBeFalsy(); }); it('should bypass ivy-level style sanitization if a custom sanitizer is used', () => { const s = new LocalMockSanitizer(value => ''); - const t = new TemplateFixture(createDiv, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s); const inputValue = bypassSanitizationTrustStyle('font-family:foo'); const outputValue = 'font-family:foo-ivy'; - t.update(() => ɵɵelementAttribute(0, 'style', inputValue, ɵɵsanitizeStyle)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); + }); expect(stripStyleWsCharacters(t.html)).toEqual(`
`); expect(s.lastSanitizedValue).toBeFalsy(); }); it('should work for resourceUrl sanitization', () => { const s = new LocalMockSanitizer(value => `${value}-sanitized`); - const t = new TemplateFixture(createScript, undefined, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s); const inputValue = 'http://resource'; const outputValue = 'http://resource-sanitized'; - t.update(() => ɵɵelementAttribute(0, 'src', inputValue, ɵɵsanitizeResourceUrl)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('src', inputValue, ɵɵsanitizeResourceUrl); + }); expect(t.html).toEqual(``); 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, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, 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)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('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, 1, 0, null, null, s); + const t = new TemplateFixture(createScript, undefined, 1, 1, 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)); + t.update(() => { + ɵɵselect(0); + ɵɵattribute('src', inputValue, ɵɵsanitizeResourceUrl); + }); expect(t.html).toEqual(``); expect(s.lastSanitizedValue).toBeFalsy(); }); diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 14a53c0ddb..2bf250c4e7 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -8,8 +8,8 @@ import {RendererType2} from '../../src/render/api'; import {getLContext} from '../../src/render3/context_discovery'; -import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index'; -import {ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementAttribute, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; +import {AttributeMarker, ɵɵattribute, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵproperty} from '../../src/render3/index'; +import {ɵɵallocHostVars, ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵprojection, ɵɵprojectionDef, ɵɵselect, ɵɵstyling, ɵɵstylingApply, ɵɵtemplate, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all'; import {MONKEY_PATCH_KEY_NAME} from '../../src/render3/interfaces/context'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; @@ -286,7 +286,8 @@ describe('component animations', () => { ɵɵelement(0, 'div', [AttributeMarker.Bindings, '@fooAnimation']); } if (rf & RenderFlags.Update) { - ɵɵelementAttribute(0, '@fooAnimation', ɵɵbind(ctx.animationValue)); + ɵɵselect(0); + ɵɵattribute('@fooAnimation', ctx.animationValue); } } }); diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 1b8f798d56..ef6757b785 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -818,8 +818,6 @@ export declare function ɵɵdisableBindings(): void; export declare function ɵɵelement(index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void; -export declare function ɵɵelementAttribute(index: number, name: string, value: any, sanitizer?: SanitizerFn | null, namespace?: string): void; - export declare function ɵɵelementContainerEnd(): void; export declare function ɵɵelementContainerStart(index: number, attrs?: TAttributes | null, localRefs?: string[] | null): void; From f440bd1793218ea64b4f9754e913f971d8f3d8c0 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 6 Jun 2019 09:45:07 +0100 Subject: [PATCH 12/16] build(docs-infra): fix CLI command github links (#30889) The "view" links were broken because the version used to compute the git tag for the GitHub URL included a build SHA. Now we clean that off before using it in the URL. The links are to the JSON schema that defines the documentation for the command. This is accurate but confusing because the content for the long description is stored in a separate markdown file referenced from this schema file. This commit adds a second set of links for the long description, if it exists, which links directly to the markdown file. Closes #30700 PR Close #30889 --- .../transforms/cli-docs-package/index.js | 94 ++++++++++--------- .../cli-docs-package/readers/cli-command.js | 53 +++++++++-- .../readers/cli-command.spec.js | 30 ++++-- .../readers/mocks/commands/add.json | 3 + .../templates/cli/cli-command.template.html | 5 +- 5 files changed, 123 insertions(+), 62 deletions(-) create mode 100644 aio/tools/transforms/cli-docs-package/readers/mocks/commands/add.json diff --git a/aio/tools/transforms/cli-docs-package/index.js b/aio/tools/transforms/cli-docs-package/index.js index 3d4fa95da7..ecefe7d809 100644 --- a/aio/tools/transforms/cli-docs-package/index.js +++ b/aio/tools/transforms/cli-docs-package/index.js @@ -1,4 +1,5 @@ const {resolve} = require('canonical-path'); +const semver = require('semver'); const Package = require('dgeni').Package; const basePackage = require('../angular-base-package'); const contentPackage = require('../content-package'); @@ -8,59 +9,60 @@ const CLI_SOURCE_PATH = resolve(CLI_SOURCE_ROOT, 'node_modules/@angular/cli'); const CLI_SOURCE_HELP_PATH = resolve(CLI_SOURCE_PATH, 'help'); // Define the dgeni package for generating the docs -module.exports = new Package('cli-docs', [basePackage, contentPackage]) +module.exports = + new Package('cli-docs', [basePackage, contentPackage]) -// Register the services and file readers -.factory(require('./readers/cli-command')) + // Register the services and file readers + .factory(require('./readers/cli-command')) -// Register the processors -.processor(require('./processors/processCliContainerDoc')) -.processor(require('./processors/processCliCommands')) -.processor(require('./processors/filterHiddenCommands')) + // Register the processors + .processor(require('./processors/processCliContainerDoc')) + .processor(require('./processors/processCliCommands')) + .processor(require('./processors/filterHiddenCommands')) -// Configure file reading -.config(function(readFilesProcessor, cliCommandFileReader) { - readFilesProcessor.fileReaders.push(cliCommandFileReader); - readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([ - { - basePath: CLI_SOURCE_HELP_PATH, - include: resolve(CLI_SOURCE_HELP_PATH, '*.json'), - fileReader: 'cliCommandFileReader' - }, - { - basePath: CONTENTS_PATH, - include: resolve(CONTENTS_PATH, 'cli/**'), - fileReader: 'contentFileReader' - }, - ]); -}) + // Configure file reading + .config(function(readFilesProcessor, cliCommandFileReader) { + readFilesProcessor.fileReaders.push(cliCommandFileReader); + readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([ + { + basePath: CLI_SOURCE_HELP_PATH, + include: resolve(CLI_SOURCE_HELP_PATH, '*.json'), + fileReader: 'cliCommandFileReader' + }, + { + basePath: CONTENTS_PATH, + include: resolve(CONTENTS_PATH, 'cli/**'), + fileReader: 'contentFileReader' + }, + ]); + }) -.config(function(templateFinder, templateEngine, getInjectables) { - // Where to find the templates for the CLI doc rendering - templateFinder.templateFolders.unshift(resolve(TEMPLATES_PATH, 'cli')); - // Add in templating filters and tags - templateEngine.filters = templateEngine.filters.concat(getInjectables(requireFolder(__dirname, './rendering'))); -}) + .config(function(templateFinder, templateEngine, getInjectables) { + // Where to find the templates for the CLI doc rendering + templateFinder.templateFolders.unshift(resolve(TEMPLATES_PATH, 'cli')); + // Add in templating filters and tags + templateEngine.filters = templateEngine.filters.concat( + getInjectables(requireFolder(__dirname, './rendering'))); + }) -.config(function(renderDocsProcessor) { + .config(function(renderDocsProcessor) { - const cliPackage = require(resolve(CLI_SOURCE_PATH, 'package.json')); - const repoUrlParts = cliPackage.repository.url.replace(/\.git$/, '').split('/'); - const version = `v${cliPackage.version}`; - const repo = repoUrlParts.pop(); - const owner = repoUrlParts.pop(); - const cliVersionInfo = { - gitRepoInfo: { owner, repo }, - currentVersion: { raw: version } - }; + const cliPackage = require(resolve(CLI_SOURCE_PATH, 'package.json')); + const repoUrlParts = cliPackage.repository.url.replace(/\.git$/, '').split('/'); + const version = `v${semver.clean(cliPackage.version)}`; + const repo = repoUrlParts.pop(); + const owner = repoUrlParts.pop(); + const cliVersionInfo = {gitRepoInfo: {owner, repo}, currentVersion: {raw: version}}; - // Add the cli version data to the renderer, for use in things like github links - renderDocsProcessor.extraData.cliVersionInfo = cliVersionInfo; -}) + // Add the cli version data to the renderer, for use in things like github links + renderDocsProcessor.extraData.cliVersionInfo = cliVersionInfo; + }) -.config(function(convertToJsonProcessor, postProcessHtml) { - convertToJsonProcessor.docTypes = convertToJsonProcessor.docTypes.concat(['cli-command', 'cli-overview']); - postProcessHtml.docTypes = postProcessHtml.docTypes.concat(['cli-command', 'cli-overview']); -}); + .config(function(convertToJsonProcessor, postProcessHtml) { + convertToJsonProcessor.docTypes = + convertToJsonProcessor.docTypes.concat(['cli-command', 'cli-overview']); + postProcessHtml.docTypes = + postProcessHtml.docTypes.concat(['cli-command', 'cli-overview']); + }); diff --git a/aio/tools/transforms/cli-docs-package/readers/cli-command.js b/aio/tools/transforms/cli-docs-package/readers/cli-command.js index bca735a8ee..d1203bff48 100644 --- a/aio/tools/transforms/cli-docs-package/readers/cli-command.js +++ b/aio/tools/transforms/cli-docs-package/readers/cli-command.js @@ -26,22 +26,59 @@ module.exports = function cliCommandFileReader(log) { docType: 'cli-command', id: `cli-${doc.name}`, commandAliases: doc.aliases || [], - aliases: computeAliases(doc), - path, + aliases: computeAliases(doc), path, outputPath: `${path}.json`, breadCrumbs: [ - { text: 'CLI', path: 'cli' }, - { text: name, path }, + {text: 'CLI', path: 'cli'}, + {text: name, path}, ] }); + if (doc.longDescription) { + doc.longDescriptionDoc = createLongDescriptionDoc(fileInfo); + } return [result]; } catch (e) { log.warn(`Failed to read cli command file: "${fileInfo.relativePath}" - ${e.message}`); } } }; -}; + function computeAliases(doc) { + return [doc.name].concat(doc.aliases || []).map(alias => `cli-${alias}`); + } -function computeAliases(doc) { - return [doc.name].concat(doc.aliases || []).map(alias => `cli-${alias}`); -} \ No newline at end of file + /** + * Synthesize a doc for the CLI command long description, which is used to generate links + * for viewing and editing the long description in GitHub. + * + * The long description is stored in a markdown file that is referenced from the original + * schema file for the command, via the `$longDescription` field. The field is a relative path + * to the markdown file from the schema file. + * + * This function tries to retrieve that original schema based on the file path of the help JSON + * file, which was passed to the `cliCommandFileReader.getDocs()` method. + */ + function createLongDescriptionDoc(fileInfo) { + try { + const path = require('canonical-path'); + const fs = require('fs'); + const json5 = require('json5'); + + const schemaJsonPath = path.resolve(fileInfo.basePath, '../commands', fileInfo.relativePath); + const schemaJson = fs.readFileSync(schemaJsonPath); + const schema = json5.parse(schemaJson); + if (schema.$longDescription) { + return { + docType: 'content', + startingLine: 0, + fileInfo: { + realProjectRelativePath: + path.join(path.dirname(fileInfo.realProjectRelativePath), schema.$longDescription) + } + }; + } + } catch (e) { + log.warn('Unable to read CLI long description file info', e, fileInfo); + return undefined; + } + } +}; diff --git a/aio/tools/transforms/cli-docs-package/readers/cli-command.spec.js b/aio/tools/transforms/cli-docs-package/readers/cli-command.spec.js index 341c2758af..9c7cfdb3c4 100644 --- a/aio/tools/transforms/cli-docs-package/readers/cli-command.spec.js +++ b/aio/tools/transforms/cli-docs-package/readers/cli-command.spec.js @@ -40,7 +40,12 @@ const content = ` } `; -const fileInfo = {content, baseName: 'add'}; +const fileInfo = { + content, + baseName: 'add', + relativePath: 'add.json', + basePath: __dirname + '/mocks/help', +}; describe('cli-command reader', () => { describe('getDocs', () => { @@ -77,8 +82,8 @@ describe('cli-command reader', () => { it('should compute the bread crumbs', () => { const docs = reader.getDocs(fileInfo); expect(docs[0].breadCrumbs).toEqual([ - { text: 'CLI', path: 'cli' }, - { text: 'add', path: 'cli/add' }, + {text: 'CLI', path: 'cli'}, + {text: 'add', path: 'cli/add'}, ]); }); @@ -89,7 +94,9 @@ describe('cli-command reader', () => { it('should extract the long description', () => { const docs = reader.getDocs(fileInfo); - expect(docs[0].longDescription).toEqual('Add support for a library in your project, for example adding `@angular/pwa` which would configure\nyour project for PWA support.\n'); + expect(docs[0].longDescription) + .toEqual( + 'Add support for a library in your project, for example adding `@angular/pwa` which would configure\nyour project for PWA support.\n'); }); it('should extract the command type', () => { @@ -110,10 +117,19 @@ describe('cli-command reader', () => { it('should extract the options', () => { const docs = reader.getDocs(fileInfo); expect(docs[0].options).toEqual([ - jasmine.objectContaining({ name: 'collection' }), - jasmine.objectContaining({ name: 'help' }), - jasmine.objectContaining({ name: 'helpJson' }), + jasmine.objectContaining({name: 'collection'}), + jasmine.objectContaining({name: 'help'}), + jasmine.objectContaining({name: 'helpJson'}), ]); }); + + it('should extract file info for the long description', () => { + const [doc] = reader.getDocs(fileInfo); + expect(doc.longDescriptionDoc).toEqual({ + docType: 'content', + startingLine: 0, + fileInfo: {realProjectRelativePath: 'packages/angular/cli/commands/add-long.md'} + }); + }); }); }); diff --git a/aio/tools/transforms/cli-docs-package/readers/mocks/commands/add.json b/aio/tools/transforms/cli-docs-package/readers/mocks/commands/add.json new file mode 100644 index 0000000000..84423ec268 --- /dev/null +++ b/aio/tools/transforms/cli-docs-package/readers/mocks/commands/add.json @@ -0,0 +1,3 @@ +{ + "$longDescription": "./add-long.md" +} \ No newline at end of file diff --git a/aio/tools/transforms/templates/cli/cli-command.template.html b/aio/tools/transforms/templates/cli/cli-command.template.html index 0b775a4b7d..5de2178ff9 100644 --- a/aio/tools/transforms/templates/cli/cli-command.template.html +++ b/aio/tools/transforms/templates/cli/cli-command.template.html @@ -14,7 +14,10 @@ {$ cli.renderSyntax(doc) $} {% if doc.longDescription.length %} -

Description

+

+ {$ github.githubLinks(doc.longDescriptionDoc, cliVersionInfo) $} + Description +

{$ doc.longDescription | marked $} {% endif%} From b4b7af86c202cd82b751b3be4881f8a95ce5f6ca Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Wed, 5 Jun 2019 15:38:36 +0200 Subject: [PATCH 13/16] fix(ivy): trigger directive inputs for each template creation (#30870) A directive input that doesn't use a binding is triggered during the creation phase. But this was only executed at the first template pass, and not on subsequent ones. Now only the creation of the update instruction is executed on the first template pass, anything else is executed every time a template is created. FW-1361 #resolve PR Close #30870 --- packages/core/src/render3/i18n.ts | 14 +++++---- packages/core/test/acceptance/i18n_spec.ts | 36 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index e04c076d80..a76f72c7eb 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -958,9 +958,7 @@ export function ɵɵi18n(index: number, message: string, subTemplateIndex?: numb export function ɵɵi18nAttributes(index: number, values: string[]): void { const tView = getLView()[TVIEW]; ngDevMode && assertDefined(tView, `tView should be defined`); - if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) { - i18nAttributesFirstPass(tView, index, values); - } + i18nAttributesFirstPass(tView, index, values); } /** @@ -985,8 +983,10 @@ function i18nAttributesFirstPass(tView: TView, index: number, values: string[]) // Even indexes are text (including bindings) const hasBinding = !!value.match(BINDING_REGEXP); if (hasBinding) { - addAllToArray( - generateBindingUpdateOpCodes(value, previousElementIndex, attrName), updateOpCodes); + if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) { + addAllToArray( + generateBindingUpdateOpCodes(value, previousElementIndex, attrName), updateOpCodes); + } } else { const lView = getLView(); elementAttributeInternal(previousElementIndex, attrName, value, lView); @@ -1001,7 +1001,9 @@ function i18nAttributesFirstPass(tView: TView, index: number, values: string[]) } } - tView.data[index + HEADER_OFFSET] = updateOpCodes; + if (tView.firstTemplatePass && tView.data[index + HEADER_OFFSET] === null) { + tView.data[index + HEADER_OFFSET] = updateOpCodes; + } } let changeMask = 0b0; diff --git a/packages/core/test/acceptance/i18n_spec.ts b/packages/core/test/acceptance/i18n_spec.ts index 81e56ac043..e1a8b742ba 100644 --- a/packages/core/test/acceptance/i18n_spec.ts +++ b/packages/core/test/acceptance/i18n_spec.ts @@ -715,6 +715,42 @@ onlyInIvy('Ivy i18n logic').describe('runtime i18n', () => { const element = fixture.nativeElement.firstChild; expect(element.title).toBe('Bonjour Angular'); }); + + it('should apply i18n attributes during second template pass', () => { + @Directive({ + selector: '[test]', + inputs: ['test'], + exportAs: 'dir', + }) + class Dir { + } + + @Component({ + selector: 'other', + template: `
` + }) + class Other { + } + + @Component({ + selector: 'blah', + template: ` + + + ` + }) + class Cmp { + } + + TestBed.configureTestingModule({ + declarations: [Dir, Cmp, Other], + }); + + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + expect(fixture.debugElement.children[0].children[0].references.ref.test).toBe('Set'); + expect(fixture.debugElement.children[1].children[0].references.ref.test).toBe('Set'); + }); }); it('should work with directives and host bindings', () => { From 04587a33c5eca98fc0aa346d7ce35a5616de83db Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 5 Jun 2019 14:39:01 +0200 Subject: [PATCH 14/16] fix(ivy): DebugNode.attributes not preserving attribute name casing (#30864) In Ivy the `DebugNode.attributes` is populated directly from the DOM, but the problem is that the browser will lowercase all attribute names. These changes preserve the case by first going through the `TNode.attrs`, populating the map with the case-sensitive names and saving a reference to the lower case name. Afterwards when we're going through the attributes from the DOM, we can check whether we've mapped the attribute by its case-sensitive name already. This PR resolves FW-1358. PR Close #30864 --- packages/core/src/debug/debug_node.ts | 45 +++++++++++++++-- packages/core/test/debug/debug_node_spec.ts | 55 ++++++++++++++++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index bc15842731..61f138ee6c 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -276,13 +276,50 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme get attributes(): {[key: string]: string | null;} { const attributes: {[key: string]: string | null;} = {}; const element = this.nativeElement; - if (element) { - const eAttrs = element.attributes; - for (let i = 0; i < eAttrs.length; i++) { - const attr = eAttrs[i]; + + if (!element) { + return attributes; + } + + const context = loadLContext(element); + const lView = context.lView; + const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs; + const lowercaseTNodeAttrs: string[] = []; + + // For debug nodes we take the element's attribute directly from the DOM since it allows us + // to account for ones that weren't set via bindings (e.g. ViewEngine keeps track of the ones + // that are set through `Renderer2`). The problem is that the browser will lowercase all names, + // however since we have the attributes already on the TNode, we can preserve the case by going + // through them once, adding them to the `attributes` map and putting their lower-cased name + // into an array. Afterwards when we're going through the native DOM attributes, we can check + // whether we haven't run into an attribute already through the TNode. + if (tNodeAttrs) { + let i = 0; + while (i < tNodeAttrs.length) { + const attrName = tNodeAttrs[i]; + + // Stop as soon as we hit a marker. We only care about the regular attributes. Everything + // else will be handled below when we read the final attributes off the DOM. + if (typeof attrName !== 'string') break; + + const attrValue = tNodeAttrs[i + 1]; + attributes[attrName] = attrValue as string; + lowercaseTNodeAttrs.push(attrName.toLowerCase()); + + i += 2; + } + } + + const eAttrs = element.attributes; + for (let i = 0; i < eAttrs.length; i++) { + const attr = eAttrs[i]; + // Make sure that we don't assign the same attribute both in its + // case-sensitive form and the lower-cased one from the browser. + if (lowercaseTNodeAttrs.indexOf(attr.name) === -1) { attributes[attr.name] = attr.value; } } + return attributes; } diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index cbec33a9ca..4f318f0b1a 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -7,7 +7,7 @@ */ -import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; +import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -736,5 +736,58 @@ class TestCmptWithPropBindings { ]); }); + it('should preserve the attribute case in DebugNode.attributes', () => { + @Component({selector: 'my-icon', template: ''}) + class Icon { + @Input() svgIcon: any = ''; + } + @Component({template: ``}) + class App { + } + + TestBed.configureTestingModule({declarations: [App, Icon]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const element = fixture.debugElement.children[0]; + + // Assert that the camel-case attribute is correct. + expect(element.attributes.svgIcon).toBe('test'); + + // Make sure that we somehow didn't preserve the native lower-cased value. + expect(element.attributes.svgicon).toBeFalsy(); + }); + + + it('should include namespaced attributes in DebugNode.attributes', () => { + @Component({ + template: `
`, + }) + class Comp { + } + + TestBed.configureTestingModule({declarations: [Comp]}); + const fixture = TestBed.createComponent(Comp); + fixture.detectChanges(); + + expect(fixture.debugElement.query(By.css('div')).attributes['xlink:href']).toBe('foo'); + }); + + it('should include attributes added via Renderer2 in DebugNode.attributes', () => { + @Component({ + template: '
', + }) + class Comp { + constructor(public renderer: Renderer2) {} + } + + TestBed.configureTestingModule({declarations: [Comp]}); + const fixture = TestBed.createComponent(Comp); + const div = fixture.debugElement.query(By.css('div')); + + fixture.componentInstance.renderer.setAttribute(div.nativeElement, 'foo', 'bar'); + + expect(div.attributes.foo).toBe('bar'); + }); + }); } From 11a4454ab37649ee04051f87faf13cbcaaae45f0 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 4 Jun 2019 10:42:43 +0200 Subject: [PATCH 15/16] perf(ivy): remove check for function type in renderStringify (#30838) The `renderStringify` function shows up pretty high in the CPU profiling. Turns out that this function contained unnecessary `typeof` check for function types - the check only makes sense / is used in error messages. The PR also alligns how ivy and view engine stringify functions used in interpolations. PR Close #30838 --- packages/core/src/render3/util/misc_utils.ts | 6 +++--- packages/core/test/acceptance/text_spec.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/core/src/render3/util/misc_utils.ts b/packages/core/src/render3/util/misc_utils.ts index cf45c6f6c4..50db0983e2 100644 --- a/packages/core/src/render3/util/misc_utils.ts +++ b/packages/core/src/render3/util/misc_utils.ts @@ -26,7 +26,6 @@ export function isDifferent(a: any, b: any): boolean { * be extra careful not to introduce megamorphic reads in it. */ export function renderStringify(value: any): string { - if (typeof value === 'function') return value.name || value; if (typeof value === 'string') return value; if (value == null) return ''; return '' + value; @@ -38,9 +37,10 @@ export function renderStringify(value: any): string { * Important! This function contains a megamorphic read and should only be * used for error messages. */ -export function stringifyForError(value: any) { +export function stringifyForError(value: any): string { + if (typeof value === 'function') return value.name || value.toString(); if (typeof value === 'object' && value != null && typeof value.type === 'function') { - return value.type.name || value.type; + return value.type.name || value.type.toString(); } return renderStringify(value); diff --git a/packages/core/test/acceptance/text_spec.ts b/packages/core/test/acceptance/text_spec.ts index 3f3e665f06..87f6d28664 100644 --- a/packages/core/test/acceptance/text_spec.ts +++ b/packages/core/test/acceptance/text_spec.ts @@ -112,4 +112,20 @@ describe('text instructions', () => { expect(div.innerHTML).toBe('<h1>LOL, big text</h1>'); }); + + it('should stringify functions used in bindings', () => { + @Component({ + template: '
{{test}}
', + }) + class App { + test = function foo() {}; + } + + TestBed.configureTestingModule({declarations: [App]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const div = fixture.nativeElement.querySelector('div'); + + expect(div.innerHTML).toBe('function foo() { }'); + }); }); From 383ab8515d11f9657edbb4954991f8b3fff8c5e7 Mon Sep 17 00:00:00 2001 From: Stefanie Fluin Date: Sun, 27 Jan 2019 15:52:53 -0800 Subject: [PATCH 16/16] feat(docs-infra): white background and corresponding figure updates (#28396) PR Close #28396 --- aio/src/styles/0-base/_typography.scss | 7 +++++- aio/src/styles/1-layouts/_footer.scss | 9 +++++--- aio/src/styles/1-layouts/_layout-global.scss | 7 +++--- .../styles/1-layouts/_marketing-layout.scss | 4 ++-- aio/src/styles/1-layouts/_sidenav.scss | 7 +++++- aio/src/styles/1-layouts/_top-menu.scss | 2 +- aio/src/styles/2-modules/_buttons.scss | 2 +- aio/src/styles/2-modules/_card.scss | 2 +- aio/src/styles/2-modules/_contributor.scss | 2 +- aio/src/styles/2-modules/_filetree.scss | 4 ++-- aio/src/styles/2-modules/_images.scss | 23 +++++++++++++++---- aio/src/styles/2-modules/_label.scss | 4 ++-- aio/src/styles/2-modules/_table.scss | 2 +- aio/src/styles/_constants.scss | 8 ++++--- 14 files changed, 56 insertions(+), 27 deletions(-) diff --git a/aio/src/styles/0-base/_typography.scss b/aio/src/styles/0-base/_typography.scss index a3b6610127..00096a9bbc 100755 --- a/aio/src/styles/0-base/_typography.scss +++ b/aio/src/styles/0-base/_typography.scss @@ -7,6 +7,10 @@ body { -moz-osx-font-smoothing: grayscale; } +h1, h2, h3, h4, h5, h6 { + color: $deepgray; +} + h1 { display: inline-block; font-size: 24px; @@ -73,6 +77,7 @@ p, ol, ul, ol, li, input, a { line-height: 24px; letter-spacing: 0.30px; font-weight: 400; + color: $darkgray; & > em { letter-spacing: 0.30px; } @@ -103,7 +108,7 @@ a { .app-toolbar a { font-size: 16px; font-weight: 400; - color: white; + color: $white; font-family: $main-font; text-transform: uppercase; padding: 21px 0; diff --git a/aio/src/styles/1-layouts/_footer.scss b/aio/src/styles/1-layouts/_footer.scss index 55a83b0bb4..c470965570 100644 --- a/aio/src/styles/1-layouts/_footer.scss +++ b/aio/src/styles/1-layouts/_footer.scss @@ -5,12 +5,15 @@ footer { padding: 48px; z-index: 0; background-color: $blue; - color: $offwhite; font-weight: 300; aio-footer { position: relative; z-index: 0; + + & > * { + color: $white; + } } .footer-block { @@ -19,8 +22,7 @@ footer { } a { - color: $offwhite; - font-weight: 300; + color: $white; text-decoration: none; z-index: 20; position: relative; @@ -39,6 +41,7 @@ footer { text-transform: uppercase; font-weight: 400; margin: 0 0 16px; + color: $white; } p { text-align: center; diff --git a/aio/src/styles/1-layouts/_layout-global.scss b/aio/src/styles/1-layouts/_layout-global.scss index 769c5c9ca8..5ff87e7ddf 100644 --- a/aio/src/styles/1-layouts/_layout-global.scss +++ b/aio/src/styles/1-layouts/_layout-global.scss @@ -2,8 +2,9 @@ html, body { height: 100%; } -body { - background-color: $offwhite; +body, +.content { + background-color: $white; } .clearfix { @@ -48,4 +49,4 @@ body { .text-uppercase { text-transform: uppercase; -} \ No newline at end of file +} diff --git a/aio/src/styles/1-layouts/_marketing-layout.scss b/aio/src/styles/1-layouts/_marketing-layout.scss index 2d15939a79..89d579eee0 100644 --- a/aio/src/styles/1-layouts/_marketing-layout.scss +++ b/aio/src/styles/1-layouts/_marketing-layout.scss @@ -42,7 +42,7 @@ section#intro { height: 480px; margin: 0 auto -32px; padding: 48px 0 0; - color: white; + color: $white; @media (max-width: 780px) { flex-direction: column; @@ -426,7 +426,7 @@ div[layout=row]{ text-transform: uppercase; font-size: 24px; font-weight: 300; - color: white; + color: $white; margin: 0; -webkit-margin-before: 0; -webkit-margin-after: 0; diff --git a/aio/src/styles/1-layouts/_sidenav.scss b/aio/src/styles/1-layouts/_sidenav.scss index 437a1abf2a..819ea52558 100644 --- a/aio/src/styles/1-layouts/_sidenav.scss +++ b/aio/src/styles/1-layouts/_sidenav.scss @@ -28,7 +28,7 @@ mat-sidenav.mat-sidenav.sidenav { left: 0; padding: 0; min-width: 260px; - background-color: $offwhite; + background-color: $white; box-shadow: 6px 0 6px rgba(0,0,0,0.10); &.collapsed { @@ -48,6 +48,11 @@ mat-sidenav-container.sidenav-container { } } +mat-sidenav-container.sidenav-container.mat-drawer-container.mat-sidenav-container, +mat-sidenav-container .sidenav-content { + background-color: $white; +} + mat-sidenav-container div.mat-sidenav-content { height: auto; } diff --git a/aio/src/styles/1-layouts/_top-menu.scss b/aio/src/styles/1-layouts/_top-menu.scss index 642085dff9..642ff44142 100644 --- a/aio/src/styles/1-layouts/_top-menu.scss +++ b/aio/src/styles/1-layouts/_top-menu.scss @@ -180,7 +180,7 @@ aio-search-box.search-container { color: $darkgray; border: none; border-radius: 100px; - background-color: $offwhite; + background-color: $white; padding: 5px 16px; margin-left: 8px; width: 180px; diff --git a/aio/src/styles/2-modules/_buttons.scss b/aio/src/styles/2-modules/_buttons.scss index 4aaaa4974e..bf59b91477 100644 --- a/aio/src/styles/2-modules/_buttons.scss +++ b/aio/src/styles/2-modules/_buttons.scss @@ -94,7 +94,7 @@ a.filter-button { &:hover { background-color: $blue; - color: white; + color: $white; } } diff --git a/aio/src/styles/2-modules/_card.scss b/aio/src/styles/2-modules/_card.scss index 294cd7a01d..18552702dc 100644 --- a/aio/src/styles/2-modules/_card.scss +++ b/aio/src/styles/2-modules/_card.scss @@ -35,7 +35,7 @@ section { - color: $darkgray; + color: $deepgray; font-size: 20px; line-height: 24px; margin: 0; diff --git a/aio/src/styles/2-modules/_contributor.scss b/aio/src/styles/2-modules/_contributor.scss index c31d20230c..19db386c19 100644 --- a/aio/src/styles/2-modules/_contributor.scss +++ b/aio/src/styles/2-modules/_contributor.scss @@ -22,7 +22,7 @@ aio-contributor-list { a { &.selected { background-color: $blue; - color: white; + color: $white; } } } diff --git a/aio/src/styles/2-modules/_filetree.scss b/aio/src/styles/2-modules/_filetree.scss index e3b4ae8499..224a21f837 100644 --- a/aio/src/styles/2-modules/_filetree.scss +++ b/aio/src/styles/2-modules/_filetree.scss @@ -1,5 +1,5 @@ .filetree { - background: $offwhite; + background: $white; border: 4px solid $lightgray; border-radius: 4px; margin: 0 0 24px 0; @@ -35,4 +35,4 @@ } } } -} \ No newline at end of file +} diff --git a/aio/src/styles/2-modules/_images.scss b/aio/src/styles/2-modules/_images.scss index b0d1bd0c4a..a6f3b90fe7 100644 --- a/aio/src/styles/2-modules/_images.scss +++ b/aio/src/styles/2-modules/_images.scss @@ -32,15 +32,28 @@ } figure { - border-radius: 4px; + margin: 0; + margin-top: 14px; + margin-bottom: 14px; + border-radius: 1px; background: $white; - padding: 20px; + border: 1px solid $lightgray; + padding: 32px; + box-sizing: border-box; display: inline-block; - box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, .2); - margin: 0 0 14px 0; - img { + &.lightbox { + background-color: $lightboxgray; + width: 100%; + display: flex; + justify-content: center; + } + + div.card { + box-shadow: 0 2px 2px rgba(10, 16, 20, 0.24), 0 0 2px rgba(10, 16, 20, 0.12); border-radius: 4px; + padding: 8px; + background-color: $white; } } } diff --git a/aio/src/styles/2-modules/_label.scss b/aio/src/styles/2-modules/_label.scss index 17b2756af8..1036376355 100644 --- a/aio/src/styles/2-modules/_label.scss +++ b/aio/src/styles/2-modules/_label.scss @@ -3,7 +3,7 @@ label.raised, .api-header label { padding: 4px 16px; display: inline; font-size: 14px; - color: white; + color: $white; margin-right: 8px; font-weight: 500; text-transform: uppercase; @@ -64,4 +64,4 @@ label.raised, .api-header label { } } -} \ No newline at end of file +} diff --git a/aio/src/styles/2-modules/_table.scss b/aio/src/styles/2-modules/_table.scss index a479c0be97..495a10be63 100644 --- a/aio/src/styles/2-modules/_table.scss +++ b/aio/src/styles/2-modules/_table.scss @@ -2,7 +2,7 @@ table { margin: 24px 0px; box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12); border-radius: 2px; - background: $offwhite; + background: $white; &.is-full-width { width: 100%; diff --git a/aio/src/styles/_constants.scss b/aio/src/styles/_constants.scss index df2d4ea994..b886c94a04 100755 --- a/aio/src/styles/_constants.scss +++ b/aio/src/styles/_constants.scss @@ -18,9 +18,11 @@ $white: #FFFFFF; $offwhite: #FAFAFA; $backgroundgray: #F1F1F1; $lightgray: #DBDBDB; +$lightboxgray: #EBEBEB; $mist: #ECEFF1; -$mediumgray: #6e6e6e; -$darkgray: #333; +$mediumgray: #6E6E6E; +$darkgray: #444444; +$deepgray: #333333; $black: #0A1014; $orange: #FF9800; $darkorange: #940; @@ -119,4 +121,4 @@ $api-symbols: ( // OTHER $small-breakpoint-width: 840px; $phone-breakpoint: 480px; -$tablet-breakpoint: 800px; \ No newline at end of file +$tablet-breakpoint: 800px;