-
Notifications
You must be signed in to change notification settings - Fork 24.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(compiler-cli): partial compilation of directives (#39518)
This commit implements partial code generation for directives, which will be transformed by the linker plugin to fully AOT compiled code in follow-up work. PR Close #39518
- Loading branch information
1 parent
ded7bee
commit 8c0a92b
Showing
13 changed files
with
363 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* 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 * as o from '../../output/output_ast'; | ||
|
||
/** | ||
* This interface describes the shape of the object that partial directive declarations are compiled | ||
* into. This serves only as documentation, as conformance of this interface is not enforced during | ||
* the generation of the partial declaration, nor when the linker applies full compilation from the | ||
* partial declaration. | ||
*/ | ||
export interface R3DeclareDirectiveMetadata { | ||
/** | ||
* Version number of the metadata format. This is used to evolve the metadata | ||
* interface later - the linker will be able to detect which version a library | ||
* is using and interpret its metadata accordingly. | ||
*/ | ||
version: 1; | ||
|
||
/** | ||
* Unparsed selector of the directive. | ||
*/ | ||
selector?: string; | ||
|
||
/** | ||
* Reference to the directive class itself. | ||
*/ | ||
type: o.Expression; | ||
|
||
/** | ||
* A mapping of inputs from class property names to binding property names, or to a tuple of | ||
* binding property name and class property name if the names are different. | ||
*/ | ||
inputs?: {[classPropertyName: string]: string|[string, string]}; | ||
|
||
/** | ||
* A mapping of outputs from class property names to binding property names, or to a tuple of | ||
* binding property name and class property name if the names are different. | ||
*/ | ||
outputs?: {[classPropertyName: string]: string}; | ||
|
||
/** | ||
* Information about host bindings present on the component. | ||
*/ | ||
host?: { | ||
/** | ||
* A mapping of attribute names to their value expression. | ||
*/ | ||
attributes?: {[key: string]: o.Expression}; | ||
|
||
/** | ||
* A mapping of event names to their unparsed event handler expression. | ||
*/ | ||
listeners: {[key: string]: string}; | ||
|
||
/** | ||
* A mapping of bound properties to their unparsed binding expression. | ||
*/ | ||
properties?: {[key: string]: string}; | ||
|
||
/** | ||
* The value of the class attribute, if present. This is stored outside of `attributes` as its | ||
* string value must be known statically. | ||
*/ | ||
classAttribute?: string; | ||
|
||
/** | ||
* The value of the style attribute, if present. This is stored outside of `attributes` as its | ||
* string value must be known statically. | ||
*/ | ||
styleAttribute?: string; | ||
}; | ||
|
||
/** | ||
* Information about the content queries made by the directive. | ||
*/ | ||
queries?: R3DeclareQueryMetadata[]; | ||
|
||
/** | ||
* Information about the view queries made by the directive. | ||
*/ | ||
viewQueries?: R3DeclareQueryMetadata[]; | ||
|
||
/** | ||
* The list of providers provided by the directive. | ||
*/ | ||
providers?: o.Expression; | ||
|
||
/** | ||
* The names by which the directive is exported. | ||
*/ | ||
exportAs?: string[]; | ||
|
||
/** | ||
* Whether the directive has an inheritance clause. Defaults to false. | ||
*/ | ||
usesInheritance?: boolean; | ||
|
||
/** | ||
* Whether the directive implements the `ngOnChanges` hook. Defaults to false. | ||
*/ | ||
usesOnChanges?: boolean; | ||
|
||
/** | ||
* A reference to the `@angular/core` ES module, which allows access | ||
* to all Angular exports, including Ivy instructions. | ||
*/ | ||
ngImport: o.Expression; | ||
} | ||
|
||
export interface R3DeclareQueryMetadata { | ||
/** | ||
* Name of the property on the class to update with query results. | ||
*/ | ||
propertyName: string; | ||
|
||
/** | ||
* Whether to read only the first matching result, or an array of results. Defaults to false. | ||
*/ | ||
first?: boolean; | ||
|
||
/** | ||
* Either an expression representing a type or `InjectionToken` for the query | ||
* predicate, or a set of string selectors. | ||
*/ | ||
predicate: o.Expression|string[]; | ||
|
||
/** | ||
* Whether to include only direct children or all descendants. Defaults to false. | ||
*/ | ||
descendants?: boolean; | ||
|
||
/** | ||
* An expression representing a type to read from each matched node, or null if the default value | ||
* for a given node is to be returned. | ||
*/ | ||
read?: o.Expression; | ||
|
||
/** | ||
* Whether or not this query should collect only static results. Defaults to false. | ||
* | ||
* If static is true, the query's results will be set on the component after nodes are created, | ||
* but before change detection runs. This means that any results that relied upon change detection | ||
* to run (e.g. results inside *ngIf or *ngFor views) will not be collected. Query results are | ||
* available in the ngOnInit hook. | ||
* | ||
* If static is false, the query's results will be set on the component after change detection | ||
* runs. This means that the query results can contain nodes inside *ngIf or *ngFor views, but | ||
* the results will not be available in the ngOnInit hook (only in the ngAfterContentInit for | ||
* content hooks and ngAfterViewInit for view hooks). | ||
*/ | ||
static?: boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* 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 * as o from '../../output/output_ast'; | ||
import {Identifiers as R3} from '../r3_identifiers'; | ||
import {R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from '../view/api'; | ||
import {createDirectiveTypeParams} from '../view/compiler'; | ||
import {asLiteral, conditionallyCreateMapObjectLiteral, DefinitionMap} from '../view/util'; | ||
|
||
|
||
/** | ||
* Compile a directive declaration defined by the `R3DirectiveMetadata`. | ||
*/ | ||
export function compileDeclareDirectiveFromMetadata(meta: R3DirectiveMetadata): R3DirectiveDef { | ||
const definitionMap = createDirectiveDefinitionMap(meta); | ||
|
||
const expression = o.importExpr(R3.declareDirective).callFn([definitionMap.toLiteralMap()]); | ||
|
||
const typeParams = createDirectiveTypeParams(meta); | ||
const type = o.expressionType(o.importExpr(R3.DirectiveDefWithMeta, typeParams)); | ||
|
||
return {expression, type}; | ||
} | ||
|
||
/** | ||
* Gathers the declaration fields for a directive into a `DefinitionMap`. This allows for reusing | ||
* this logic for components, as they extend the directive metadata. | ||
*/ | ||
export function createDirectiveDefinitionMap(meta: R3DirectiveMetadata): DefinitionMap { | ||
const definitionMap = new DefinitionMap(); | ||
|
||
definitionMap.set('version', o.literal(1)); | ||
|
||
// e.g. `type: MyDirective` | ||
definitionMap.set('type', meta.internalType); | ||
|
||
// e.g. `selector: 'some-dir'` | ||
if (meta.selector !== null) { | ||
definitionMap.set('selector', o.literal(meta.selector)); | ||
} | ||
|
||
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true)); | ||
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs)); | ||
|
||
definitionMap.set('host', compileHostMetadata(meta.host)); | ||
|
||
definitionMap.set('providers', meta.providers); | ||
|
||
if (meta.queries.length > 0) { | ||
definitionMap.set('queries', o.literalArr(meta.queries.map(compileQuery))); | ||
} | ||
if (meta.viewQueries.length > 0) { | ||
definitionMap.set('viewQueries', o.literalArr(meta.viewQueries.map(compileQuery))); | ||
} | ||
|
||
if (meta.exportAs !== null) { | ||
definitionMap.set('exportAs', asLiteral(meta.exportAs)); | ||
} | ||
|
||
if (meta.usesInheritance) { | ||
definitionMap.set('usesInheritance', o.literal(true)); | ||
} | ||
if (meta.lifecycle.usesOnChanges) { | ||
definitionMap.set('usesOnChanges', o.literal(true)); | ||
} | ||
|
||
definitionMap.set('ngImport', o.importExpr(R3.core)); | ||
|
||
return definitionMap; | ||
} | ||
|
||
/** | ||
* Compiles the metadata of a single query into its partial declaration form as declared | ||
* by `R3DeclareQueryMetadata`. | ||
*/ | ||
function compileQuery(query: R3QueryMetadata): o.LiteralMapExpr { | ||
const meta = new DefinitionMap(); | ||
meta.set('propertyName', o.literal(query.propertyName)); | ||
if (query.first) { | ||
meta.set('first', o.literal(true)); | ||
} | ||
meta.set( | ||
'predicate', Array.isArray(query.predicate) ? asLiteral(query.predicate) : query.predicate); | ||
if (query.descendants) { | ||
meta.set('descendants', o.literal(true)); | ||
} | ||
meta.set('read', query.read); | ||
if (query.static) { | ||
meta.set('static', o.literal(true)); | ||
} | ||
return meta.toLiteralMap(); | ||
} | ||
|
||
/** | ||
* Compiles the host metadata into its partial declaration form as declared | ||
* in `R3DeclareDirectiveMetadata['host']` | ||
*/ | ||
function compileHostMetadata(meta: R3HostMetadata): o.LiteralMapExpr|null { | ||
const hostMetadata = new DefinitionMap(); | ||
hostMetadata.set('attributes', toOptionalLiteralMap(meta.attributes, expression => expression)); | ||
hostMetadata.set('listeners', toOptionalLiteralMap(meta.listeners, o.literal)); | ||
hostMetadata.set('properties', toOptionalLiteralMap(meta.properties, o.literal)); | ||
|
||
if (meta.specialAttributes.styleAttr) { | ||
hostMetadata.set('styleAttribute', o.literal(meta.specialAttributes.styleAttr)); | ||
} | ||
if (meta.specialAttributes.classAttr) { | ||
hostMetadata.set('classAttribute', o.literal(meta.specialAttributes.classAttr)); | ||
} | ||
|
||
if (hostMetadata.values.length > 0) { | ||
return hostMetadata.toLiteralMap(); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
/** | ||
* Creates an object literal expression from the given object, mapping all values to an expression | ||
* using the provided mapping function. If the object has no keys, then null is returned. | ||
* | ||
* @param object The object to transfer into an object literal expression. | ||
* @param mapper The logic to use for creating an expression for the object's values. | ||
* @returns An object literal expression representing `object`, or null if `object` does not have | ||
* any keys. | ||
*/ | ||
function toOptionalLiteralMap<T>( | ||
object: {[key: string]: T}, mapper: (value: T) => o.Expression): o.LiteralMapExpr|null { | ||
const entries = Object.keys(object).map(key => { | ||
const value = object[key]; | ||
return {key, value: mapper(value), quoted: true}; | ||
}); | ||
|
||
if (entries.length > 0) { | ||
return o.literalMap(entries); | ||
} else { | ||
return null; | ||
} | ||
} |
Oops, something went wrong.