Skip to content

Commit

Permalink
feat(compiler-cli): JIT compilation of directive declarations (#40101)
Browse files Browse the repository at this point in the history
The `ɵɵngDeclareDirective` calls are designed to be translated to fully
AOT compiled code during a build transform, but in cases this is not
done it is still possible to compile the declaration object in the
browser using the JIT compiler. This commit adds a runtime
implementation of `ɵɵngDeclareDirective` which invokes the JIT compiler
using the declaration object, such that a compiled directive definition
is made available to the Ivy runtime.

PR Close #40101
  • Loading branch information
JoostK authored and josephperrott committed Dec 23, 2020
1 parent e54261b commit 9186f1f
Show file tree
Hide file tree
Showing 11 changed files with 486 additions and 16 deletions.
34 changes: 34 additions & 0 deletions packages/compiler/src/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export interface CompilerFacade {
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3NgModuleMetadataFacade): any;
compileDirective(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadataFacade): any;
compileDirectiveDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
declaration: R3DeclareDirectiveFacade): any;
compileComponent(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
compileFactory(
Expand Down Expand Up @@ -162,6 +165,28 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
changeDetection?: ChangeDetectionStrategy;
}

export type OpaqueValue = unknown;

export interface R3DeclareDirectiveFacade {
selector?: string;
type: Function;
inputs?: {[classPropertyName: string]: string|[string, string]};
outputs?: {[classPropertyName: string]: string};
host?: {
attributes?: {[key: string]: OpaqueValue};
listeners?: {[key: string]: string};
properties?: {[key: string]: string};
classAttribute?: string;
styleAttribute?: string;
};
queries?: R3DeclareQueryMetadataFacade[];
viewQueries?: R3DeclareQueryMetadataFacade[];
providers?: OpaqueValue;
exportAs?: string[];
usesInheritance?: boolean;
usesOnChanges?: boolean;
}

export interface R3UsedDirectiveMetadata {
selector: string;
inputs: string[];
Expand Down Expand Up @@ -197,6 +222,15 @@ export interface R3QueryMetadataFacade {
static: boolean;
}

export interface R3DeclareQueryMetadataFacade {
propertyName: string;
first?: boolean;
predicate: OpaqueValue|string[];
descendants?: boolean;
read?: OpaqueValue;
static?: boolean;
}

export interface ParseSourceSpan {
start: any;
end: any;
Expand Down
80 changes: 76 additions & 4 deletions packages/compiler/src/jit_compiler_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/


import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, R3ComponentMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, OpaqueValue, R3ComponentMetadataFacade, R3DeclareDirectiveFacade, R3DeclareQueryMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
import {ConstantPool} from './constant_pool';
import {HostBinding, HostListener, Input, Output, Type} from './core';
import {Identifiers} from './identifiers';
Expand All @@ -21,7 +21,7 @@ import {R3JitReflector} from './render3/r3_jit';
import {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler';
import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
import {R3Reference} from './render3/util';
import {R3ComponentMetadata, R3DirectiveMetadata, R3QueryMetadata} from './render3/view/api';
import {R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './render3/view/api';
import {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler';
import {makeBindingParser, parseTemplate} from './render3/view/template';
import {ResourceLoader} from './resource_loader';
Expand Down Expand Up @@ -107,10 +107,23 @@ export class CompilerFacadeImpl implements CompilerFacade {
compileDirective(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
facade: R3DirectiveMetadataFacade): any {
const meta: R3DirectiveMetadata = convertDirectiveFacadeToMetadata(facade);
return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
}

compileDirectiveDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
declaration: R3DeclareDirectiveFacade): any {
const typeSourceSpan =
this.createParseSourceSpan('Directive', declaration.type.name, sourceMapUrl);
const meta = convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan);
return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
}

private compileDirectiveFromMeta(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadata): any {
const constantPool = new ConstantPool();
const bindingParser = makeBindingParser();

const meta: R3DirectiveMetadata = convertDirectiveFacadeToMetadata(facade);
const res = compileDirectiveFromMetadata(meta, constantPool, bindingParser);
return this.jitExpression(
res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
Expand Down Expand Up @@ -230,6 +243,19 @@ function convertToR3QueryMetadata(facade: R3QueryMetadataFacade): R3QueryMetadat
};
}

function convertQueryDeclarationToMetadata(declaration: R3DeclareQueryMetadataFacade):
R3QueryMetadata {
return {
propertyName: declaration.propertyName,
first: declaration.first ?? false,
predicate: Array.isArray(declaration.predicate) ? declaration.predicate :
new WrappedNodeExpr(declaration.predicate),
descendants: declaration.descendants ?? false,
read: declaration.read ? new WrappedNodeExpr(declaration.read) : null,
static: declaration.static ?? false,
};
}

function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3DirectiveMetadata {
const inputsFromMetadata = parseInputOutputs(facade.inputs || []);
const outputsFromMetadata = parseInputOutputs(facade.outputs || []);
Expand Down Expand Up @@ -265,6 +291,52 @@ function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3
};
}

function convertDeclareDirectiveFacadeToMetadata(
declaration: R3DeclareDirectiveFacade, typeSourceSpan: ParseSourceSpan): R3DirectiveMetadata {
return {
name: declaration.type.name,
type: wrapReference(declaration.type),
typeSourceSpan,
internalType: new WrappedNodeExpr(declaration.type),
selector: declaration.selector ?? null,
inputs: declaration.inputs ?? {},
outputs: declaration.outputs ?? {},
host: convertHostDeclarationToMetadata(declaration.host),
queries: (declaration.queries ?? []).map(convertQueryDeclarationToMetadata),
viewQueries: (declaration.viewQueries ?? []).map(convertQueryDeclarationToMetadata),
providers: declaration.providers !== undefined ? new WrappedNodeExpr(declaration.providers) :
null,
exportAs: declaration.exportAs ?? null,
usesInheritance: declaration.usesInheritance ?? false,
lifecycle: {usesOnChanges: declaration.usesOnChanges ?? false},
deps: null,
typeArgumentCount: 0,
fullInheritance: false,
};
}

function convertHostDeclarationToMetadata(host: R3DeclareDirectiveFacade['host'] = {}):
R3HostMetadata {
return {
attributes: convertOpaqueValuesToExpressions(host.attributes ?? {}),
listeners: host.listeners ?? {},
properties: host.properties ?? {},
specialAttributes: {
classAttr: host.classAttribute,
styleAttr: host.styleAttribute,
},
};
}

function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}):
{[key: string]: WrappedNodeExpr<unknown>} {
const result: {[key: string]: WrappedNodeExpr<unknown>} = {};
for (const key of Object.keys(obj)) {
result[key] = new WrappedNodeExpr(obj[key]);
}
return result;
}

// This seems to be needed to placate TS v3.0 only
type R3DirectiveMetadataFacadeNoPropAndWhitespace =
Pick<R3DirectiveMetadataFacade, Exclude<keyof R3DirectiveMetadataFacade, 'propMetadata'>>;
Expand Down
3 changes: 1 addition & 2 deletions packages/compiler/src/render3/partial/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ export interface R3DeclareDirectiveMetadata extends R3PartialDeclaration {
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.
* A mapping of outputs from class property names to binding property names.
*/
outputs?: {[classPropertyName: string]: string};

Expand Down
11 changes: 9 additions & 2 deletions packages/compiler/src/render3/view/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,24 @@ function mapToExpression(
let declaredName: string;
let publicName: string;
let minifiedName: string;
let needsDeclaredName: boolean;
if (Array.isArray(value)) {
[publicName, declaredName] = value;
minifiedName = key;
needsDeclaredName = publicName !== declaredName;
} else {
[declaredName, publicName] = splitAtColon(key, [key, value]);
minifiedName = declaredName;
// Only include the declared name if extracted from the key, i.e. the key contains a colon.
// Otherwise the declared name should be omitted even if it is different from the public name,
// as it may have already been minified.
needsDeclaredName = publicName !== declaredName && key.includes(':');
}
minifiedName = declaredName;
return {
key: minifiedName,
// put quotes around keys that contain potentially unsafe characters
quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(minifiedName),
value: (keepDeclared && publicName !== declaredName) ?
value: (keepDeclared && needsDeclaredName) ?
o.literalArr([asLiteral(publicName), asLiteral(declaredName)]) :
asLiteral(publicName)
};
Expand Down
10 changes: 10 additions & 0 deletions packages/compiler/test/compiler_facade_interface_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,20 @@ const coreR3ComponentMetadataFacade: core.R3ComponentMetadataFacade =
const compilerR3ComponentMetadataFacade: compiler.R3ComponentMetadataFacade =
null! as core.R3ComponentMetadataFacade;

const coreR3DeclareDirectiveFacade: core.R3DeclareDirectiveFacade =
null! as compiler.R3DeclareDirectiveFacade;
const compilerR3DeclareDirectiveFacade: compiler.R3DeclareDirectiveFacade =
null! as core.R3DeclareDirectiveFacade;

const coreViewEncapsulation: core.ViewEncapsulation = null! as compiler.ViewEncapsulation;
const compilerViewEncapsulation: compiler.ViewEncapsulation = null! as core.ViewEncapsulation;

const coreR3QueryMetadataFacade: core.R3QueryMetadataFacade =
null! as compiler.R3QueryMetadataFacade;
const compilerR3QueryMetadataFacade: compiler.R3QueryMetadataFacade =
null! as core.R3QueryMetadataFacade;

const coreR3DeclareQueryMetadataFacade: core.R3DeclareQueryMetadataFacade =
null! as compiler.R3DeclareQueryMetadataFacade;
const compilerR3DeclareQueryMetadataFacade: compiler.R3DeclareQueryMetadataFacade =
null! as core.R3DeclareQueryMetadataFacade;
34 changes: 34 additions & 0 deletions packages/core/src/compiler/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export interface CompilerFacade {
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3NgModuleMetadataFacade): any;
compileDirective(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3DirectiveMetadataFacade): any;
compileDirectiveDeclaration(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
declaration: R3DeclareDirectiveFacade): any;
compileComponent(
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
compileFactory(
Expand Down Expand Up @@ -162,6 +165,28 @@ export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade {
changeDetection?: ChangeDetectionStrategy;
}

export type OpaqueValue = unknown;

export interface R3DeclareDirectiveFacade {
selector?: string;
type: Function;
inputs?: {[classPropertyName: string]: string|[string, string]};
outputs?: {[classPropertyName: string]: string};
host?: {
attributes?: {[key: string]: OpaqueValue};
listeners?: {[key: string]: string};
properties?: {[key: string]: string};
classAttribute?: string;
styleAttribute?: string;
};
queries?: R3DeclareQueryMetadataFacade[];
viewQueries?: R3DeclareQueryMetadataFacade[];
providers?: OpaqueValue;
exportAs?: string[];
usesInheritance?: boolean;
usesOnChanges?: boolean;
}

export interface R3UsedDirectiveMetadata {
selector: string;
inputs: string[];
Expand Down Expand Up @@ -197,6 +222,15 @@ export interface R3QueryMetadataFacade {
static: boolean;
}

export interface R3DeclareQueryMetadataFacade {
propertyName: string;
first?: boolean;
predicate: OpaqueValue|string[];
descendants?: boolean;
read?: OpaqueValue;
static?: boolean;
}

export interface ParseSourceSpan {
start: any;
end: any;
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/core_render3_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ export {
ɵɵnamespaceMathML,
ɵɵnamespaceSVG,
ɵɵnextContext,
ɵɵngDeclareComponent,
ɵɵngDeclareDirective,
ɵɵNgOnChangesFeature,
ɵɵpipe,
ɵɵpipeBind1,
Expand Down Expand Up @@ -274,6 +272,10 @@ export {
resetCompiledComponents as ɵresetCompiledComponents,
transitiveScopesFor as ɵtransitiveScopesFor,
} from './render3/jit/module';
export {
ɵɵngDeclareComponent,
ɵɵngDeclareDirective,
} from './render3/jit/partial';
export {
compilePipe as ɵcompilePipe,
} from './render3/jit/pipe';
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/render3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,6 @@ export {
AttributeMarker
} from './interfaces/node';
export {CssSelectorList, ProjectionSlots} from './interfaces/projection';
export {
ɵɵngDeclareComponent,
ɵɵngDeclareDirective,
} from './jit/partial';
export {
setClassMetadata,
} from './metadata';
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/render3/jit/partial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/

import {getCompilerFacade, R3DeclareDirectiveFacade} from '../../compiler/compiler_facade';
import {angularCoreEnv} from './environment';

/**
* 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');
export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown {
const compiler = getCompilerFacade();
return compiler.compileDirectiveDeclaration(
angularCoreEnv, `ng:///${decl.type.name}/ɵfac.js`, decl);
}

/**
Expand Down

0 comments on commit 9186f1f

Please sign in to comment.