feat(ivy): implement unknown element detection in jit mode (#33419)

In ViewEngine we used to throw an error if we encountered an unknown element while rendering. We have this already for Ivy in AoT, but we didn't in JiT. These changes implement the error for JiT mode.

PR Close #33419
This commit is contained in:
crisbeto
2019-10-26 12:44:03 +02:00
committed by atscott
parent ac9d044cad
commit c83f5013bf
6 changed files with 132 additions and 12 deletions

View File

@ -10,7 +10,7 @@ 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 {TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement} from '../interfaces/renderer';
import {StylingMapArray, TStylingContext} from '../interfaces/styling';
import {isContentQueryHost, isDirectiveHost} from '../interfaces/type_checks';
@ -22,7 +22,7 @@ import {setUpAttributes} from '../util/attrs_utils';
import {getInitialStylingValue, hasClassInput, hasStyleInput, selectClassBasedInputName} from '../util/styling_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
import {createDirectivesInstances, elementCreate, executeContentQueries, getOrCreateTNode, matchingSchemas, renderInitialStyling, resolveDirectives, saveResolvedLocalsInData, setInputsForProperty} from './shared';
import {registerInitialStylingOnTNode} from './styling';
@ -84,7 +84,8 @@ export function ɵɵelementStart(
// and `[class]` bindings work for multiple directives.)
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tNode, localRefs || null);
const hasDirectives = resolveDirectives(tView, lView, tNode, localRefs || null);
ngDevMode && validateElement(lView, native, tNode, hasDirectives);
if (tView.queries !== null) {
tView.queries.elementStart(tView, tNode);
@ -241,3 +242,33 @@ function setDirectiveStylingInput(
// be (Jira Issue = FW-1467).
setInputsForProperty(lView, stylingInputs, value);
}
function validateElement(
hostView: LView, element: RElement, tNode: TNode, hasDirectives: boolean): void {
const tagName = tNode.tagName;
// If the element matches any directive, it's considered as valid.
if (!hasDirectives && tagName !== null) {
// The element is unknown if it's an instance of HTMLUnknownElement or it isn't registered
// as a custom element. Note that unknown elements with a dash in their name won't be instances
// of HTMLUnknownElement in browsers that support web components.
const isUnknown =
(typeof HTMLUnknownElement === 'function' && element instanceof HTMLUnknownElement) ||
(typeof customElements !== 'undefined' && tagName.indexOf('-') > -1 &&
!customElements.get(tagName));
if (isUnknown && !matchingSchemas(hostView, tagName)) {
let errorMessage = `'${tagName}' is not a known element:\n`;
errorMessage +=
`1. If '${tagName}' is an Angular component, then verify that it is part of this module.\n`;
if (tagName && tagName.indexOf('-') > -1) {
errorMessage +=
`2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`;
} else {
errorMessage +=
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
}
throw new Error(errorMessage);
}
}
}

View File

@ -989,7 +989,7 @@ function validateProperty(
isAnimationProp(propName) || typeof Node !== 'function' || !(element instanceof Node);
}
function matchingSchemas(hostView: LView, tagName: string | null): boolean {
export function matchingSchemas(hostView: LView, tagName: string | null): boolean {
const schemas = hostView[TVIEW].schemas;
if (schemas !== null) {
@ -1040,17 +1040,19 @@ export function instantiateRootComponent<T>(tView: TView, lView: LView, def: Com
*/
export function resolveDirectives(
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
localRefs: string[] | null): void {
localRefs: string[] | null): boolean {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle.
ngDevMode && assertFirstTemplatePass(tView);
if (!getBindingsEnabled()) return;
if (!getBindingsEnabled()) return false;
const directives: DirectiveDef<any>[]|null = findDirectiveMatches(tView, lView, tNode);
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
let hasDirectives = false;
if (directives !== null) {
hasDirectives = true;
initNodeFlags(tNode, tView.data.length, directives.length);
// When the same token is provided by several directives on the same node, some rules apply in
// the viewEngine:
@ -1088,6 +1090,7 @@ export function resolveDirectives(
initializeInputAndOutputAliases(tView, tNode);
}
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
return hasDirectives;
}
/**