Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projection cleanup #31065

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration/_payload-limits.json
Expand Up @@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime": 1440,
"main": 13498,
"main": 13164,
AndrewKushnir marked this conversation as resolved.
Show resolved Hide resolved
"polyfills": 45340
}
}
Expand All @@ -34,4 +34,4 @@
}
}
}
}
}
32 changes: 17 additions & 15 deletions packages/core/src/render3/i18n.ts
Expand Up @@ -24,11 +24,10 @@ import {RComment, RElement, RText} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {isLContainer} from './interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, TView, T_HOST} from './interfaces/view';
import {appendChild, appendProjectedNodes, createTextNode, nativeRemoveNode} from './node_manipulation';
import {appendChild, applyProjection, 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, load} from './util/view_utils';


Expand Down Expand Up @@ -490,7 +489,7 @@ function i18nStartFirstPass(
}

function appendI18nNode(
tNode: TNode, parentTNode: TNode, previousTNode: TNode | null, viewData: LView): TNode {
tNode: TNode, parentTNode: TNode, previousTNode: TNode | null, lView: LView): TNode {
AndrewKushnir marked this conversation as resolved.
Show resolved Hide resolved
ngDevMode && ngDevMode.rendererMoveNode++;
const nextNode = tNode.next;
if (!previousTNode) {
Expand All @@ -508,7 +507,7 @@ function appendI18nNode(
tNode.next = null;
}

if (parentTNode !== viewData[T_HOST]) {
if (parentTNode !== lView[T_HOST]) {
tNode.parent = parentTNode as TElementNode;
}

Expand All @@ -523,18 +522,16 @@ function appendI18nNode(

// 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));
applyProjection(lView, tNode as TProjectionNode);
return tNode;
}

appendChild(getNativeByTNode(tNode, viewData), tNode, viewData);
appendChild(getNativeByTNode(tNode, lView), tNode, lView);

const slotValue = viewData[tNode.index];
const slotValue = lView[tNode.index];
if (tNode.type !== TNodeType.Container && isLContainer(slotValue)) {
// Nodes that inject ViewContainerRef also have a comment node that should be moved
appendChild(slotValue[NATIVE], tNode, viewData);
appendChild(slotValue[NATIVE], tNode, lView);
}
return tNode;
}
Expand Down Expand Up @@ -687,7 +684,7 @@ function i18nEndFirstPass(tView: TView) {
// Remove deleted nodes
for (let i = rootIndex + 1; i <= lastCreatedNode.index - HEADER_OFFSET; i++) {
if (visitedNodes.indexOf(i) === -1) {
removeNode(i, viewData);
removeNode(i, viewData, /* markAsDetached */ true);
}
}
}
Expand Down Expand Up @@ -862,7 +859,10 @@ function readUpdateOpCodes(
switch (removeOpCode & I18nMutateOpCode.MASK_OPCODE) {
case I18nMutateOpCode.Remove:
const nodeIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
removeNode(nodeIndex, viewData);
// Remove DOM element, but do *not* mark TNode as detached, since we are
// just switching ICU cases (while keeping the same TNode), so a DOM element
// representing a new ICU case will be re-created.
removeNode(nodeIndex, viewData, /* markAsDetached */ false);
break;
case I18nMutateOpCode.RemoveNestedIcu:
const nestedIcuNodeIndex =
Expand Down Expand Up @@ -905,7 +905,7 @@ function readUpdateOpCodes(
}
}

function removeNode(index: number, viewData: LView) {
function removeNode(index: number, viewData: LView, markAsDetached: boolean) {
const removedPhTNode = getTNode(index, viewData);
const removedPhRNode = getNativeByIndex(index, viewData);
if (removedPhRNode) {
Expand All @@ -920,8 +920,10 @@ function removeNode(index: number, viewData: LView) {
}
}

// Define this node as detached so that we don't risk projecting it
removedPhTNode.flags |= TNodeFlags.isDetached;
if (markAsDetached) {
// Define this node as detached to avoid projecting it later
removedPhTNode.flags |= TNodeFlags.isDetached;
}
ngDevMode && ngDevMode.rendererRemoveNode++;
}

Expand Down
83 changes: 52 additions & 31 deletions packages/core/src/render3/instructions/lview_debug.ts
Expand Up @@ -102,6 +102,12 @@ export const TViewConstructor = class TView implements ITView {
public firstChild: TNode|null, //
public schemas: SchemaMetadata[]|null, //
) {}

get template_(): string {
const buf: string[] = [];
processTNodeChildren(this.firstChild, buf);
return buf.join('');
}
};

export const TNodeConstructor = class TNode implements ITNode {
Expand Down Expand Up @@ -161,8 +167,34 @@ export const TNodeConstructor = class TNode implements ITNode {
if (this.flags & TNodeFlags.isProjected) flags.push('TNodeFlags.isProjected');
return flags.join('|');
}

get template_(): string {
const buf: string[] = [];
buf.push('<', this.tagName || this.type_);
if (this.attrs) {
for (let i = 0; i < this.attrs.length;) {
const attrName = this.attrs[i++];
if (typeof attrName == 'number') {
break;
}
const attrValue = this.attrs[i++];
buf.push(' ', attrName as string, '="', attrValue as string, '"');
}
}
buf.push('>');
processTNodeChildren(this.child, buf);
buf.push('</', this.tagName || this.type_, '>');
return buf.join('');
}
};

function processTNodeChildren(tNode: TNode | null, buf: string[]) {
while (tNode) {
buf.push((tNode as any as{template_: string}).template_);
tNode = tNode.next;
}
}

const TViewData = ngDevMode && createNamedArrayType('TViewData');
let TVIEWDATA_EMPTY:
unknown[]; // can't initialize here or it will not be tree shaken, because `LView`
Expand Down Expand Up @@ -226,8 +258,8 @@ function toHtml(value: any, includeChildren: boolean = false): string|null {
if (includeChildren || isTextNode) {
return outerHTML;
} else {
const innerHTML = node.innerHTML;
return outerHTML.split(innerHTML)[0] || null;
const innerHTML = '>' + node.innerHTML + '<';
return (outerHTML.split(innerHTML)[0]) + '>';
}
} else {
return null;
Expand Down Expand Up @@ -257,6 +289,7 @@ export class LViewDebug {
}
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lView[PARENT]); }
get host(): string|null { return toHtml(this._raw_lView[HOST], true); }
get html(): string { return (this.nodes || []).map(node => toHtml(node.native, true)).join(''); }
get context(): {}|null { return this._raw_lView[CONTEXT]; }
/**
* The tree of nodes associated with the current `LView`. The nodes have been normalized into a
Expand All @@ -267,38 +300,30 @@ export class LViewDebug {
const tNode = lView[TVIEW].firstChild;
return toDebugNodes(tNode, lView);
}
/**
* Additional information which is hidden behind a property. The extra level of indirection is
* done so that the debug view would not be cluttered with properties which are only rarely
* relevant to the developer.
*/
get __other__() {
return {
tView: this._raw_lView[TVIEW],
cleanup: this._raw_lView[CLEANUP],
injector: this._raw_lView[INJECTOR],
rendererFactory: this._raw_lView[RENDERER_FACTORY],
renderer: this._raw_lView[RENDERER],
sanitizer: this._raw_lView[SANITIZER],
childHead: toDebug(this._raw_lView[CHILD_HEAD]),
next: toDebug(this._raw_lView[NEXT]),
childTail: toDebug(this._raw_lView[CHILD_TAIL]),
declarationView: toDebug(this._raw_lView[DECLARATION_VIEW]),
queries: null,
tHost: this._raw_lView[T_HOST],
bindingIndex: this._raw_lView[BINDING_INDEX],
};
}

get tView() { return this._raw_lView[TVIEW]; }
get cleanup() { return this._raw_lView[CLEANUP]; }
get injector() { return this._raw_lView[INJECTOR]; }
get rendererFactory() { return this._raw_lView[RENDERER_FACTORY]; }
get renderer() { return this._raw_lView[RENDERER]; }
get sanitizer() { return this._raw_lView[SANITIZER]; }
get childHead() { return toDebug(this._raw_lView[CHILD_HEAD]); }
get next() { return toDebug(this._raw_lView[NEXT]); }
get childTail() { return toDebug(this._raw_lView[CHILD_TAIL]); }
get declarationView() { return toDebug(this._raw_lView[DECLARATION_VIEW]); }
get queries() { return this._raw_lView[QUERIES]; }
get tHost() { return this._raw_lView[T_HOST]; }
get bindingIndex() { return this._raw_lView[BINDING_INDEX]; }

/**
* Normalized view of child views (and containers) attached at this location.
*/
get childViews(): Array<LViewDebug|LContainerDebug> {
const childViews: Array<LViewDebug|LContainerDebug> = [];
let child = this.__other__.childHead;
let child = this.childHead;
while (child) {
childViews.push(child);
child = child.__other__.next;
child = child.next;
}
return childViews;
}
Expand Down Expand Up @@ -359,11 +384,7 @@ export class LContainerDebug {
get movedViews(): LView[]|null { return this._raw_lContainer[MOVED_VIEWS]; }
get host(): RElement|RComment|LView { return this._raw_lContainer[HOST]; }
get native(): RComment { return this._raw_lContainer[NATIVE]; }
get __other__() {
return {
next: toDebug(this._raw_lContainer[NEXT]),
};
}
get next() { return toDebug(this._raw_lContainer[NEXT]); }
}

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/render3/instructions/projection.ts
Expand Up @@ -8,13 +8,15 @@
import {TAttributes, TElementNode, TNode, TNodeType} from '../interfaces/node';
import {ProjectionSlots} from '../interfaces/projection';
import {TVIEW, T_HOST} from '../interfaces/view';
import {appendProjectedNodes} from '../node_manipulation';
import {applyProjection} 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';



/**
* Checks a given node against matching projection slots and returns the
* determined slot index. Returns "null" if no slot matched the given node.
Expand Down Expand Up @@ -134,6 +136,6 @@ export function ɵɵprojection(
// 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));
applyProjection(lView, tProjectionNode);
}
}
1 change: 0 additions & 1 deletion packages/core/src/render3/instructions/shared.ts
Expand Up @@ -235,7 +235,6 @@ function createTNodeAtIndex(
if (index === 0 || !tView.firstChild) {
tView.firstChild = tNode;
}
// Now link ourselves into the tree.
if (previousOrParentTNode) {
if (isParent && previousOrParentTNode.child == null &&
(tNode.parent !== null || previousOrParentTNode.type === TNodeType.View)) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/render3/node_assert.ts
Expand Up @@ -25,6 +25,7 @@ export function assertNodeOfPossibleTypes(tNode: TNode, ...types: TNodeType[]) {
function typeName(type: TNodeType): string {
if (type == TNodeType.Projection) return 'Projection';
if (type == TNodeType.Container) return 'Container';
if (type == TNodeType.IcuContainer) return 'IcuContainer';
if (type == TNodeType.View) return 'View';
if (type == TNodeType.Element) return 'Element';
if (type == TNodeType.ElementContainer) return 'ElementContainer';
Expand Down