feat(ivy): JIT renders the TODO app (#24138)

This commit builds out enough of the JIT compiler to render
//packages/core/test/bundling/todo, and allows the tests to run in
JIT mode.

To play with the app, run:

bazel run --define=compile=jit //packages/core/test/bundling/todo:prodserver

PR Close #24138
This commit is contained in:
Alex Rickabaugh
2018-05-21 08:15:19 -07:00
committed by Victor Berchet
parent 24e5c5b425
commit 646b42a113
22 changed files with 348 additions and 194 deletions

View File

@ -81,9 +81,9 @@ export {getParseErrors, isSyntaxError, syntaxError, Version} from './util';
export {SourceMap} from './output/source_map';
export * from './injectable_compiler_2';
export * from './render3/view/api';
export {jitPatchDefinition} from './render3/r3_jit';
export {jitExpression} from './render3/r3_jit';
export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory';
export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler';
export {makeBindingParser, parseTemplate} from './render3/view/template';
export {compileComponent, compileDirective} from './render3/view/compiler';
export {compileComponentFromMetadata, compileDirectiveFromMetadata} from './render3/view/compiler';
// This file only reexports content of the `src` folder. Keep it that way.

View File

@ -49,7 +49,7 @@ export function replaceNgsp(value: string): string {
* whitespace removal. The default option for whitespace removal will be revisited in Angular 6
* and might be changed to "on" by default.
*/
class WhitespaceVisitor implements html.Visitor {
export class WhitespaceVisitor implements html.Visitor {
visitElement(element: html.Element, context: any): any {
if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
// don't descent into elements where we need to preserve whitespaces

View File

@ -50,19 +50,17 @@ class R3JitReflector implements CompileReflector {
}
/**
* JIT compiles an expression and monkey-patches the result of executing the expression onto a given
* type.
* JIT compiles an expression and returns the result of executing that expression.
*
* @param type the type which will receive the monkey-patched result
* @param field name of the field on the type to monkey-patch
* @param def the definition which will be compiled and executed to get the value to patch
* @param context an object map of @angular/core symbol names to symbols which will be available in
* the context of the compiled expression
* @param sourceUrl a URL to use for the source map of the compiled expression
* @param constantPool an optional `ConstantPool` which contains constants used in the expression
*/
export function jitPatchDefinition(
type: any, field: string, def: o.Expression, context: {[key: string]: any},
constantPool?: ConstantPool): void {
export function jitExpression(
def: o.Expression, context: {[key: string]: any}, sourceUrl: string,
constantPool?: ConstantPool): any {
// The ConstantPool may contain Statements which declare variables used in the final expression.
// Therefore, its statements need to precede the actual JIT operation. The final statement is a
// declaration of $def which is set to the expression being compiled.
@ -71,8 +69,6 @@ export function jitPatchDefinition(
new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]),
];
// Monkey patch the field on the given type with the result of compilation.
// TODO(alxhub): consider a better source url.
type[field] = jitStatements(
`ng://${type && type.name}/${field}`, statements, new R3JitReflector(context), false)['$def'];
const res = jitStatements(sourceUrl, statements, new R3JitReflector(context), false);
return res['$def'];
}

View File

@ -66,6 +66,17 @@ export interface R3DirectiveMetadata {
properties: {[key: string]: string};
};
/**
* Information about usage of specific lifecycle events which require special treatment in the
* code generator.
*/
lifecycle: {
/**
* Whether the directive uses NgOnChanges.
*/
usesOnChanges: boolean;
};
/**
* A mapping of input field names to the property names.
*/
@ -101,17 +112,6 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
ngContentSelectors: string[];
};
/**
* Information about usage of specific lifecycle events which require special treatment in the
* code generator.
*/
lifecycle: {
/**
* Whether the component uses NgOnChanges.
*/
usesOnChanges: boolean;
};
/**
* Information about the view queries made by the component.
*/

View File

@ -65,13 +65,22 @@ function baseDirectiveFields(
// e.g 'outputs: {a: 'a'}`
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
const features: o.Expression[] = [];
if (meta.lifecycle.usesOnChanges) {
features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type]));
}
if (features.length) {
definitionMap.set('features', o.literalArr(features));
}
return definitionMap;
}
/**
* Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
*/
export function compileDirective(
export function compileDirectiveFromMetadata(
meta: R3DirectiveMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3DirectiveDef {
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
@ -84,7 +93,7 @@ export function compileDirective(
/**
* Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
*/
export function compileComponent(
export function compileComponentFromMetadata(
meta: R3ComponentMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3ComponentDef {
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
@ -143,15 +152,6 @@ export function compileComponent(
definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed)));
}
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
const features: o.Expression[] = [];
if (meta.lifecycle.usesOnChanges) {
features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type]));
}
if (features.length) {
definitionMap.set('features', o.literalArr(features));
}
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
const type =
new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)]));
@ -175,7 +175,7 @@ export function compileDirectiveFromRender2(
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive);
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
const res = compileDirective(meta, outputCtx.constantPool, bindingParser);
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
// Create the partial class to be merged with the actual class.
outputCtx.statements.push(new o.ClassStmt(
@ -211,15 +211,11 @@ export function compileComponentFromRender2(
hasNgContent: render3Ast.hasNgContent,
ngContentSelectors: render3Ast.ngContentSelectors,
},
lifecycle: {
usesOnChanges:
component.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
},
directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx),
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
};
const res = compileComponent(meta, outputCtx.constantPool, bindingParser);
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
// Create the partial class to be merged with the actual class.
outputCtx.statements.push(new o.ClassStmt(
@ -251,6 +247,10 @@ function directiveMetadataFromGlobalMetadata(
listeners: summary.hostListeners,
properties: summary.hostProperties,
},
lifecycle: {
usesOnChanges:
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
},
inputs: directive.inputs,
outputs: directive.outputs,
};

View File

@ -16,6 +16,7 @@ import {Lexer} from '../../expression_parser/lexer';
import {Parser} from '../../expression_parser/parser';
import * as html from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
import * as o from '../../output/output_ast';
import {ParseError, ParseSourceSpan} from '../../parse_util';
@ -777,16 +778,24 @@ function interpolate(args: o.Expression[]): o.Expression {
* @param template text of the template to parse
* @param templateUrl URL to use for source mapping of the parsed template
*/
export function parseTemplate(template: string, templateUrl: string):
export function parseTemplate(
template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}):
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
const bindingParser = makeBindingParser();
const htmlParser = new HtmlParser();
const parseResult = htmlParser.parse(template, templateUrl);
if (parseResult.errors && parseResult.errors.length > 0) {
return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
}
let rootNodes: html.Node[] = parseResult.rootNodes;
if (!options.preserveWhitespace) {
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
}
const {nodes, hasNgContent, ngContentSelectors, errors} =
htmlAstToRender3Ast(parseResult.rootNodes, bindingParser);
htmlAstToRender3Ast(rootNodes, bindingParser);
if (errors && errors.length > 0) {
return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
}

View File

@ -1078,8 +1078,8 @@ describe('compiler compliance', () => {
selectors: [['lifecycle-comp']],
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
inputs: {nameMin: 'name'},
template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {},
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)],
template: function LifecycleComp_Template(rf: IDENT, ctx: IDENT) {}
});`;
const SimpleLayoutDefinition = `