refactor(compiler-cli): implement ɵɵngDeclareFactory (#41231)

This commit changes the partial compilation so that it outputs declaration
calls rather than compiled factory functions.

The JIT compiler and the linker are updated to be able to handle these
new declarations.

PR Close #41231
This commit is contained in:
Pete Bacon Darwin
2021-03-14 21:03:59 +00:00
committed by Alex Rickabaugh
parent 9c9fd2d074
commit cf4f74aad0
76 changed files with 1433 additions and 957 deletions
@@ -0,0 +1,83 @@
/**
* @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 {compileFactoryFunction, ConstantPool, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3FactoryTarget, R3PartialDeclaration, R3ResolvedDependencyType} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstObject} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
import {PartialLinker} from './partial_linker';
import {parseEnum, wrapReference} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
*/
export class PartialFactoryLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
const meta = toR3FactoryMeta(metaObj);
const def = compileFactoryFunction(meta);
return def.expression;
}
}
/**
* Derives the `R3FactoryMetadata` structure from the AST object.
*/
export function toR3FactoryMeta<TExpression>(
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>): R3FactoryMetadata {
const typeExpr = metaObj.getValue('type');
const typeName = typeExpr.getSymbolName();
if (typeName === null) {
throw new FatalLinkerError(
typeExpr.expression, 'Unsupported type, its name could not be determined');
}
const deps = getDeps(metaObj, 'deps');
const meta: R3FactoryMetadata = {
name: typeName,
type: wrapReference(typeExpr.getOpaque()),
internalType: metaObj.getOpaque('type'),
typeArgumentCount: 0,
target: parseEnum(metaObj.getValue('target'), R3FactoryTarget),
deps,
};
return meta;
}
function getDeps<TExpression>(
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
if (!metaObj.has(propName)) {
return null;
}
const deps = metaObj.getValue(propName);
if (deps.isArray()) {
return deps.getArray().map(dep => getDep(dep.getObject()));
}
if (deps.isString()) {
return 'invalid';
}
return null;
}
function getDep<TExpression>(dep: AstObject<R3DependencyMetadata, TExpression>):
R3DependencyMetadata {
return {
token: dep.getOpaque('token'),
attribute: null,
resolved: parseEnum(dep.getValue('resolved'), R3ResolvedDependencyType),
host: dep.has('host') && dep.getBoolean('host'),
optional: dep.has('optional') && dep.getBoolean('optional'),
self: dep.has('self') && dep.getBoolean('self'),
skipSelf: dep.has('skipSelf') && dep.getBoolean('skipSelf'),
};
}
@@ -13,6 +13,7 @@ import {LinkerEnvironment} from '../linker_environment';
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1';
import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
@@ -20,12 +21,13 @@ import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareInjector, ɵɵngDeclareNgModule,
ɵɵngDeclarePipe
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjector,
ɵɵngDeclareNgModule, ɵɵngDeclarePipe
];
interface LinkerRange<TExpression> {
@@ -90,6 +92,7 @@ export class PartialLinkerSelector<TStatement, TExpression> {
const partialComponentLinkerVersion1 = new PartialComponentLinkerVersion1(
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
code);
const partialFactoryLinkerVersion1 = new PartialFactoryLinkerVersion1();
const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1();
const partialNgModuleLinkerVersion1 =
new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode);
@@ -104,6 +107,10 @@ export class PartialLinkerSelector<TStatement, TExpression> {
{range: '0.0.0-PLACEHOLDER', linker: partialComponentLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialComponentLinkerVersion1},
]);
linkers.set(ɵɵngDeclareFactory, [
{range: '0.0.0-PLACEHOLDER', linker: partialFactoryLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialFactoryLinkerVersion1},
]);
linkers.set(ɵɵngDeclareInjector, [
{range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialInjectorLinkerVersion1},
@@ -7,7 +7,25 @@
*/
import {R3Reference} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
return {value: wrapped, type: wrapped};
}
/**
* Parses the value of an enum from the AST value's symbol name.
*/
export function parseEnum<TExpression, TEnum>(
value: AstValue<unknown, TExpression>, Enum: TEnum): TEnum[keyof TEnum] {
const symbolName = value.getSymbolName();
if (symbolName === null) {
throw new FatalLinkerError(value.expression, 'Expected value to have a symbol name');
}
const enumValue = Enum[symbolName as keyof typeof Enum];
if (enumValue === undefined) {
throw new FatalLinkerError(value.expression, `Unsupported enum value for ${Enum}`);
}
return enumValue;
}
@@ -16,6 +16,7 @@ import {TypeScriptAstHost} from '../../../src/ast/typescript/typescript_ast_host
import {LinkerEnvironment} from '../../../src/file_linker/linker_environment';
import {PartialComponentLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_directive_linker_1';
import {PartialFactoryLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_factory_linker_1';
import {PartialInjectorLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_injector_linker_1';
import {PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector';
import {PartialNgModuleLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_ng_module_linker_1';
@@ -41,6 +42,9 @@ describe('PartialLinkerSelector', () => {
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
expect(selector.supportsDeclaration('ɵɵngDeclareDirective')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareComponent')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareFactory')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareInjector')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclareNgModule')).toBe(true);
expect(selector.supportsDeclaration('ɵɵngDeclarePipe')).toBe(true);
expect(selector.supportsDeclaration('$foo')).toBe(false);
});
@@ -60,12 +64,14 @@ describe('PartialLinkerSelector', () => {
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareComponent', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialComponentLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclarePipe', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialPipeLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareFactory', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialFactoryLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectorLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareNgModule', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialNgModuleLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclarePipe', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialPipeLinkerVersion1);
});
it('should return the linker that matches the name and valid full version', () => {