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
This commit is contained in:
parent
ded7beed32
commit
8c0a92bb45
|
@ -1,3 +1,6 @@
|
|||
/** @codeGenApi */
|
||||
export declare function $ngDeclareDirective(decl: unknown): unknown;
|
||||
|
||||
export declare interface AbstractType<T> extends Function {
|
||||
prototype: T;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileDirectiveFromMetadata, ConstantPool, Expression, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
|
@ -154,19 +154,34 @@ export class DirectiveDecoratorHandler implements
|
|||
compileFull(
|
||||
node: ClassDeclaration, analysis: Readonly<DirectiveHandlerData>,
|
||||
resolution: Readonly<unknown>, pool: ConstantPool): CompileResult[] {
|
||||
const meta = analysis.meta;
|
||||
const res = compileDirectiveFromMetadata(meta, pool, makeBindingParser());
|
||||
const factoryRes = compileNgFactoryDefField(
|
||||
{...meta, injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Directive});
|
||||
const def = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
||||
return this.compileDirective(analysis, def);
|
||||
}
|
||||
|
||||
compilePartial(
|
||||
node: ClassDeclaration, analysis: Readonly<DirectiveHandlerData>,
|
||||
resolution: Readonly<unknown>): CompileResult[] {
|
||||
const def = compileDeclareDirectiveFromMetadata(analysis.meta);
|
||||
return this.compileDirective(analysis, def);
|
||||
}
|
||||
|
||||
private compileDirective(
|
||||
analysis: Readonly<DirectiveHandlerData>,
|
||||
{expression: initializer, type}: R3DirectiveDef): CompileResult[] {
|
||||
const factoryRes = compileNgFactoryDefField({
|
||||
...analysis.meta,
|
||||
injectFn: Identifiers.directiveInject,
|
||||
target: R3FactoryTarget.Directive,
|
||||
});
|
||||
if (analysis.metadataStmt !== null) {
|
||||
factoryRes.statements.push(analysis.metadataStmt);
|
||||
}
|
||||
return [
|
||||
factoryRes, {
|
||||
name: 'ɵdir',
|
||||
initializer: res.expression,
|
||||
initializer,
|
||||
statements: [],
|
||||
type: res.type,
|
||||
type,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -249,7 +249,10 @@ export class ExpressionTranslatorVisitor<TStatement, TExpression> implements o.E
|
|||
|
||||
visitExternalExpr(ast: o.ExternalExpr, _context: Context): TExpression {
|
||||
if (ast.value.name === null) {
|
||||
throw new Error(`Import unknown module or symbol ${ast.value}`);
|
||||
if (ast.value.moduleName === null) {
|
||||
throw new Error('Invalid import without name nor moduleName');
|
||||
}
|
||||
return this.imports.generateNamespaceImport(ast.value.moduleName);
|
||||
}
|
||||
// If a moduleName is specified, this is a normal import. If there's no module name, it's a
|
||||
// reference to a global/ambient symbol.
|
||||
|
|
|
@ -102,6 +102,7 @@ export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compile
|
|||
export {makeBindingParser, ParsedTemplate, parseTemplate, ParseTemplateOptions} from './render3/view/template';
|
||||
export {R3Reference} from './render3/util';
|
||||
export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, ParsedHostBindings, verifyHostBindings} from './render3/view/compiler';
|
||||
export {compileDeclareDirectiveFromMetadata} from './render3/partial/directive';
|
||||
export {publishFacade} from './jit_compiler_facade';
|
||||
// This file only reexports content of the `src` folder. Keep it that way.
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ export class Identifiers {
|
|||
static TRANSFORM_METHOD = 'transform';
|
||||
static PATCH_DEPS = 'patchedDeps';
|
||||
|
||||
static core: o.ExternalReference = {name: null, moduleName: CORE};
|
||||
|
||||
/* Instructions */
|
||||
static namespaceHTML: o.ExternalReference = {name: 'ɵɵnamespaceHTML', moduleName: CORE};
|
||||
|
||||
|
@ -245,10 +247,8 @@ export class Identifiers {
|
|||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineDirective: o.ExternalReference = {
|
||||
name: 'ɵɵdefineDirective',
|
||||
moduleName: CORE,
|
||||
};
|
||||
static defineDirective: o.ExternalReference = {name: 'ɵɵdefineDirective', moduleName: CORE};
|
||||
static declareDirective: o.ExternalReference = {name: '$ngDeclareDirective', moduleName: CORE};
|
||||
|
||||
static DirectiveDefWithMeta: o.ExternalReference = {
|
||||
name: 'ɵɵDirectiveDefWithMeta',
|
||||
|
|
|
@ -86,12 +86,14 @@ export interface R3DirectiveMetadata {
|
|||
};
|
||||
|
||||
/**
|
||||
* A mapping of input field names to the property names.
|
||||
* 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: {[field: string]: string|[string, string]};
|
||||
|
||||
/**
|
||||
* A mapping of output field names to the property names.
|
||||
* 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: {[field: string]: string};
|
||||
|
||||
|
|
|
@ -492,7 +492,7 @@ function stringArrayAsType(arr: ReadonlyArray<string|null>): o.Type {
|
|||
o.NONE_TYPE;
|
||||
}
|
||||
|
||||
function createDirectiveTypeParams(meta: R3DirectiveMetadata): o.Type[] {
|
||||
export function createDirectiveTypeParams(meta: R3DirectiveMetadata): o.Type[] {
|
||||
// On the type side, remove newlines from the selector as it will need to fit into a TypeScript
|
||||
// string literal, which must be on one line.
|
||||
const selectorForType = meta.selector !== null ? meta.selector.replace(/\n/g, '') : null;
|
||||
|
|
|
@ -77,6 +77,8 @@ export {
|
|||
NG_PIPE_DEF as ɵNG_PIPE_DEF,
|
||||
} from './render3/fields';
|
||||
export {
|
||||
|
||||
$ngDeclareDirective,
|
||||
AttributeMarker as ɵAttributeMarker,
|
||||
ComponentDef as ɵComponentDef,
|
||||
ComponentFactory as ɵRender3ComponentFactory,
|
||||
|
|
|
@ -133,6 +133,9 @@ export {
|
|||
AttributeMarker
|
||||
} from './interfaces/node';
|
||||
export {CssSelectorList, ProjectionSlots} from './interfaces/projection';
|
||||
export {
|
||||
$ngDeclareDirective,
|
||||
} from './jit/partial';
|
||||
export {
|
||||
setClassMetadata,
|
||||
} from './metadata';
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ɵɵinject, ɵɵinvalidFactoryDep} from '../../di/injector_compatibility
|
|||
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../../di/interface/defs';
|
||||
import * as sanitization from '../../sanitization/sanitization';
|
||||
import * as r3 from '../index';
|
||||
import * as partial from './partial';
|
||||
|
||||
|
||||
|
||||
|
@ -169,4 +170,6 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵtrustConstantHtml': sanitization.ɵɵtrustConstantHtml,
|
||||
'ɵɵtrustConstantScript': sanitization.ɵɵtrustConstantScript,
|
||||
'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl,
|
||||
|
||||
'$ngDeclareDirective': partial.$ngDeclareDirective,
|
||||
}))();
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compiles a partial directive declaration object into a full directive definition object.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function $ngDeclareDirective(decl: unknown): unknown {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
Loading…
Reference in New Issue