Skip to content

Commit 80a5934

Browse files
crisbetomhevery
authored andcommitted
fix(ivy): support schemas at runtime (angular#28637)
Accounts for schemas in when validating properties in Ivy. This PR resolves FW-819. A couple of notes: * I had to rework the test slightly, in order to have it fail when we expect it to. The one in master is passing since Ivy's validation runs during the update phase, rather than creation. * I had to deviate from the design in FW-819 and not add an `enableSchema` instruction, because the schema is part of the `NgModule` scope, however the scope is only assigned to a component once all of the module's declarations have been resolved and some of them can be async. Instead, I opted to have the `schemas` on the component definition. PR Close angular#28637
1 parent 7cbc36f commit 80a5934

File tree

18 files changed

+164
-78
lines changed

18 files changed

+164
-78
lines changed

packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
134134
exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)),
135135
imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)),
136136
emitInline: false,
137+
// TODO: to be implemented as a part of FW-1004.
138+
schemas: [],
137139
};
138140

139141
const providers: Expression = ngModule.has('providers') ?

packages/compiler/src/compiler_facade_interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade {
9797
imports: Function[];
9898
exports: Function[];
9999
emitInline: boolean;
100+
schemas: {name: string}[]|null;
100101
}
101102

102103
export interface R3InjectorMetadataFacade {
@@ -155,4 +156,4 @@ export interface ParseSourceSpan {
155156
start: any;
156157
end: any;
157158
details: any;
158-
}
159+
}

packages/compiler/src/jit_compiler_facade.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
8686
imports: facade.imports.map(wrapReference),
8787
exports: facade.exports.map(wrapReference),
8888
emitInline: true,
89+
schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
8990
};
9091
const res = compileNgModule(meta);
9192
return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);

packages/compiler/src/render3/r3_module_compiler.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,27 @@ export interface R3NgModuleMetadata {
5757
* does not allow components to be tree-shaken, but is useful for JIT mode.
5858
*/
5959
emitInline: boolean;
60+
61+
/**
62+
* The set of schemas that declare elements to be allowed in the NgModule.
63+
*/
64+
schemas: R3Reference[]|null;
6065
}
6166

6267
/**
6368
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
6469
*/
6570
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
66-
const {type: moduleType, bootstrap, declarations, imports, exports} = meta;
71+
const {type: moduleType, bootstrap, declarations, imports, exports, schemas} = meta;
6772
const definitionMap = {
6873
type: moduleType
6974
} as{
7075
type: o.Expression,
7176
bootstrap: o.LiteralArrayExpr,
7277
declarations: o.LiteralArrayExpr,
7378
imports: o.LiteralArrayExpr,
74-
exports: o.LiteralArrayExpr
79+
exports: o.LiteralArrayExpr,
80+
schemas: o.LiteralArrayExpr
7581
};
7682

7783
// Only generate the keys in the metadata if the arrays have values.
@@ -91,6 +97,10 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
9197
definitionMap.exports = o.literalArr(exports.map(ref => ref.value));
9298
}
9399

100+
if (schemas && schemas.length) {
101+
definitionMap.schemas = o.literalArr(schemas.map(ref => ref.value));
102+
}
103+
94104
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression(definitionMap)]);
95105
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDefWithMeta, [
96106
new o.ExpressionType(moduleType), tupleTypeOf(declarations), tupleTypeOf(imports),

packages/core/src/compiler/compiler_facade_interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade {
9797
imports: Function[];
9898
exports: Function[];
9999
emitInline: boolean;
100+
schemas: {name: string}[]|null;
100101
}
101102

102103
export interface R3InjectorMetadataFacade {
@@ -155,4 +156,4 @@ export interface ParseSourceSpan {
155156
start: any;
156157
end: any;
157158
details: any;
158-
}
159+
}

packages/core/src/metadata.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
import {Attribute} from './di';
1515
import {ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
1616
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
17-
import {DoBootstrap, ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
17+
import {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module';
18+
import {SchemaMetadata} from './metadata/schema';
1819
import {ViewEncapsulation} from './metadata/view';
1920

2021
export {Attribute} from './di';
2122
export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './interface/lifecycle_hooks';
2223
export {ANALYZE_FOR_ENTRY_COMPONENTS, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
2324
export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
24-
export {CUSTOM_ELEMENTS_SCHEMA, DoBootstrap, ModuleWithProviders, NO_ERRORS_SCHEMA, NgModule, SchemaMetadata} from './metadata/ng_module';
25+
export {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module';
26+
export {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from './metadata/schema';
2527
export {ViewEncapsulation} from './metadata/view';

packages/core/src/metadata/ng_module.ts

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {InjectorType, defineInjector} from '../di/interface/defs';
1111
import {Provider} from '../di/interface/provider';
1212
import {convertInjectableProviderToFactory} from '../di/util';
1313
import {Type} from '../interface/type';
14+
import {SchemaMetadata} from '../metadata/schema';
1415
import {NgModuleType} from '../render3';
1516
import {compileNgModule as render3CompileNgModule} from '../render3/jit/module';
1617
import {TypeDecorator, makeDecorator} from '../util/decorators';
@@ -28,6 +29,7 @@ import {TypeDecorator, makeDecorator} from '../util/decorators';
2829
export interface NgModuleTransitiveScopes {
2930
compilation: {directives: Set<any>; pipes: Set<any>;};
3031
exported: {directives: Set<any>; pipes: Set<any>;};
32+
schemas: SchemaMetadata[]|null;
3133
}
3234

3335
export type NgModuleDefWithMeta<T, Declarations, Imports, Exports> = NgModuleDef<T>;
@@ -67,6 +69,9 @@ export interface NgModuleDef<T> {
6769
* This should never be read directly, but accessed via `transitiveScopesFor`.
6870
*/
6971
transitiveCompileScopes: NgModuleTransitiveScopes|null;
72+
73+
/** The set of schemas that declare elements to be allowed in the NgModule. */
74+
schemas: SchemaMetadata[]|null;
7075
}
7176

7277
/**
@@ -83,38 +88,6 @@ export interface ModuleWithProviders<
8388
providers?: Provider[];
8489
}
8590

86-
/**
87-
* A schema definition associated with an NgModule.
88-
*
89-
* @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA`
90-
*
91-
* @param name The name of a defined schema.
92-
*
93-
* @publicApi
94-
*/
95-
export interface SchemaMetadata { name: string; }
96-
97-
/**
98-
* Defines a schema that allows an NgModule to contain the following:
99-
* - Non-Angular elements named with dash case (`-`).
100-
* - Element properties named with dash case (`-`).
101-
* Dash case is the naming convention for custom elements.
102-
*
103-
* @publicApi
104-
*/
105-
export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
106-
name: 'custom-elements'
107-
};
108-
109-
/**
110-
* Defines a schema that allows any property on any element.
111-
*
112-
* @publicApi
113-
*/
114-
export const NO_ERRORS_SCHEMA: SchemaMetadata = {
115-
name: 'no-errors-schema'
116-
};
117-
11891

11992
/**
12093
* Type of the NgModule decorator / constructor function.

packages/core/src/metadata/schema.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
10+
/**
11+
* A schema definition associated with an NgModule.
12+
*
13+
* @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA`
14+
*
15+
* @param name The name of a defined schema.
16+
*
17+
* @publicApi
18+
*/
19+
export interface SchemaMetadata { name: string; }
20+
21+
/**
22+
* Defines a schema that allows an NgModule to contain the following:
23+
* - Non-Angular elements named with dash case (`-`).
24+
* - Element properties named with dash case (`-`).
25+
* Dash case is the naming convention for custom elements.
26+
*
27+
* @publicApi
28+
*/
29+
export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
30+
name: 'custom-elements'
31+
};
32+
33+
/**
34+
* Defines a schema that allows any property on any element.
35+
*
36+
* @publicApi
37+
*/
38+
export const NO_ERRORS_SCHEMA: SchemaMetadata = {
39+
name: 'no-errors-schema'
40+
};

packages/core/src/render3/component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function renderComponent<T>(
122122

123123
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
124124
const rootView: LView = createLView(
125-
null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null,
125+
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null, null,
126126
rendererFactory, renderer, undefined, opts.injector || null);
127127

128128
const oldView = enterView(rootView, null);
@@ -165,9 +165,9 @@ export function createRootComponentView(
165165
const tView = rootView[TVIEW];
166166
const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null);
167167
const componentView = createLView(
168-
rootView,
169-
getOrCreateTView(
170-
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery),
168+
rootView, getOrCreateTView(
169+
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs,
170+
def.viewQuery, def.schemas),
171171
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
172172
rendererFactory, renderer, sanitizer);
173173

packages/core/src/render3/component_ref.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
162162

163163
// Create the root view. Uses empty TView and ContentTemplate.
164164
const rootLView = createLView(
165-
null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null,
166-
rendererFactory, renderer, sanitizer, rootViewInjector);
165+
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null,
166+
null, rendererFactory, renderer, sanitizer, rootViewInjector);
167167

168168
// rootView is the parent when bootstrapping
169169
const oldLView = enterView(rootLView, null);

packages/core/src/render3/definition.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../util/ng_dev_mode';
1111
import {ChangeDetectionStrategy} from '../change_detection/constants';
1212
import {Mutable, Type} from '../interface/type';
1313
import {NgModuleDef} from '../metadata/ng_module';
14+
import {SchemaMetadata} from '../metadata/schema';
1415
import {ViewEncapsulation} from '../metadata/view';
1516
import {noSideEffects} from '../util/closure';
1617
import {stringify} from '../util/stringify';
@@ -234,6 +235,11 @@ export function defineComponent<T>(componentDefinition: {
234235
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
235236
*/
236237
pipes?: PipeTypesOrFactory | null;
238+
239+
/**
240+
* The set of schemas that declare elements to be allowed in the component's template.
241+
*/
242+
schemas?: SchemaMetadata[] | null;
237243
}): never {
238244
const type = componentDefinition.type;
239245
const typePrototype = type.prototype;
@@ -274,6 +280,7 @@ export function defineComponent<T>(componentDefinition: {
274280
styles: componentDefinition.styles || EMPTY_ARRAY,
275281
_: null as never,
276282
setInput: null,
283+
schemas: componentDefinition.schemas || null,
277284
};
278285
def._ = noSideEffects(() => {
279286
const directiveTypes = componentDefinition.directives !;
@@ -326,6 +333,7 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
326333
imports: def.imports || EMPTY_ARRAY,
327334
exports: def.exports || EMPTY_ARRAY,
328335
transitiveCompileScopes: null,
336+
schemas: def.schemas || null,
329337
};
330338
return res as never;
331339
}

0 commit comments

Comments
 (0)