fix(ivy): prevent unknown element check for AOT-compiled components (#34024)
Prior to this commit, the unknown element can happen twice for AOT-compiled components: once during compilation and once again at runtime. Due to the fact that `schemas` information is not present on Component and NgModule defs after AOT compilation, the second check (at runtime) may fail, even though the same check was successful at compile time. This commit updates the code to avoid the second check for AOT-compiled components by checking whether `schemas` information is present in a logic that executes the unknown element check. PR Close #34024
This commit is contained in:
parent
8c33f91529
commit
658087be7e
|
@ -3009,6 +3009,44 @@ runInEachFileSystem(os => {
|
||||||
`/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(Service, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })();`);
|
`/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(Service, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })();`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not include `schemas` in component and module defs', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '<custom-el></custom-el>',
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
})
|
||||||
|
class MyComp {}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [MyComp],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
})
|
||||||
|
class MyModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
env.driveMain();
|
||||||
|
const jsContents = trim(env.getContents('test.js'));
|
||||||
|
expect(jsContents).toContain(trim(`
|
||||||
|
MyComp.ɵcmp = i0.ɵɵdefineComponent({
|
||||||
|
type: MyComp,
|
||||||
|
selectors: [["comp"]],
|
||||||
|
decls: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: function MyComp_Template(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
i0.ɵɵelement(0, "custom-el");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
encapsulation: 2
|
||||||
|
});
|
||||||
|
`));
|
||||||
|
expect(jsContents)
|
||||||
|
.toContain(trim('MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule });'));
|
||||||
|
});
|
||||||
|
|
||||||
it('should emit setClassMetadata calls for all types', () => {
|
it('should emit setClassMetadata calls for all types', () => {
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {Component, Directive, Injectable, NgModule, Pipe} from '@angular/core';
|
import {Component, Directive, Injectable, NgModule, Pipe} from '@angular/core';
|
||||||
|
|
|
@ -255,6 +255,14 @@ function setDirectiveStylingInput(
|
||||||
|
|
||||||
function validateElement(
|
function validateElement(
|
||||||
hostView: LView, element: RElement, tNode: TNode, hasDirectives: boolean): void {
|
hostView: LView, element: RElement, tNode: TNode, hasDirectives: boolean): void {
|
||||||
|
const schemas = hostView[TVIEW].schemas;
|
||||||
|
|
||||||
|
// If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
|
||||||
|
// mode where this check happens at compile time. In JIT mode, `schemas` is always present and
|
||||||
|
// defined as an array (as an empty array in case `schemas` field is not defined) and we should
|
||||||
|
// execute the check below.
|
||||||
|
if (schemas === null) return;
|
||||||
|
|
||||||
const tagName = tNode.tagName;
|
const tagName = tNode.tagName;
|
||||||
|
|
||||||
// If the element matches any directive, it's considered as valid.
|
// If the element matches any directive, it's considered as valid.
|
||||||
|
|
|
@ -128,6 +128,13 @@ export function compileNgModuleDefs(
|
||||||
schemas: ngModule.schemas ? flatten(ngModule.schemas) : null,
|
schemas: ngModule.schemas ? flatten(ngModule.schemas) : null,
|
||||||
id: ngModule.id || null,
|
id: ngModule.id || null,
|
||||||
});
|
});
|
||||||
|
// Set `schemas` on ngModuleDef to an empty array in JIT mode to indicate that runtime
|
||||||
|
// should verify that there are no unknown elements in a template. In AOT mode, that check
|
||||||
|
// happens at compile time and `schemas` information is not present on Component and Module
|
||||||
|
// defs after compilation (so the check doesn't happen the second time at runtime).
|
||||||
|
if (!ngModuleDef.schemas) {
|
||||||
|
ngModuleDef.schemas = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ngModuleDef;
|
return ngModuleDef;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {CUSTOM_ELEMENTS_SCHEMA, Component, NO_ERRORS_SCHEMA, NgModule} from '@angular/core';
|
import {CUSTOM_ELEMENTS_SCHEMA, Component, NO_ERRORS_SCHEMA, NgModule, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
|
||||||
|
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
|
@ -193,6 +194,71 @@ describe('NgModule', () => {
|
||||||
}).toThrowError(/'custom' is not a known element/);
|
}).toThrowError(/'custom' is not a known element/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onlyInIvy('test relies on Ivy-specific AOT format')
|
||||||
|
.it('should not throw unknown element error for AOT-compiled components', () => {
|
||||||
|
/*
|
||||||
|
* @Component({
|
||||||
|
* selector: 'comp',
|
||||||
|
* template: '<custom-el></custom-el>',
|
||||||
|
* })
|
||||||
|
* class MyComp {}
|
||||||
|
*/
|
||||||
|
class MyComp {
|
||||||
|
static ɵfac = () => new MyComp();
|
||||||
|
static ɵcmp = defineComponent({
|
||||||
|
type: MyComp,
|
||||||
|
selectors: [['comp']],
|
||||||
|
decls: 1,
|
||||||
|
vars: 0,
|
||||||
|
template: function MyComp_Template(rf, ctx) {
|
||||||
|
if (rf & 1) {
|
||||||
|
element(0, 'custom-el');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
encapsulation: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setClassMetadata(
|
||||||
|
MyComp, [{
|
||||||
|
type: Component,
|
||||||
|
args: [{
|
||||||
|
selector: 'comp',
|
||||||
|
template: '<custom-el></custom-el>',
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
null, null);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @NgModule({
|
||||||
|
* declarations: [MyComp],
|
||||||
|
* schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
* })
|
||||||
|
* class MyModule {}
|
||||||
|
*/
|
||||||
|
class MyModule {
|
||||||
|
static ɵmod = defineNgModule({type: MyModule});
|
||||||
|
static ɵinj = defineInjector({factory: () => new MyModule()});
|
||||||
|
}
|
||||||
|
setClassMetadata(
|
||||||
|
MyModule, [{
|
||||||
|
type: NgModule,
|
||||||
|
args: [{
|
||||||
|
declarations: [MyComp],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
null, null);
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [MyModule],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not throw unknown element error with CUSTOM_ELEMENTS_SCHEMA', () => {
|
it('should not throw unknown element error with CUSTOM_ELEMENTS_SCHEMA', () => {
|
||||||
@Component({template: `<custom-el></custom-el>`})
|
@Component({template: `<custom-el></custom-el>`})
|
||||||
class MyComp {
|
class MyComp {
|
||||||
|
|
Loading…
Reference in New Issue