refactor(compiler-cli): implement ɵɵngDeclareNgModule and ɵɵngDeclareInjector (#41080)

This commit changes the partial compilation so that it outputs declaration
calls rather than definition calls for NgModules and Injectors.

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

PR Close #41080
This commit is contained in:
Pete Bacon Darwin
2021-03-04 20:55:25 +00:00
committed by Jessica Janiuk
parent 8a33842cca
commit 5565810bd6
85 changed files with 2330 additions and 827 deletions
+1 -1
View File
@@ -11,5 +11,5 @@ export {FatalLinkerError, isFatalLinkerError} from './src/fatal_linker_error';
export {DeclarationScope} from './src/file_linker/declaration_scope';
export {FileLinker} from './src/file_linker/file_linker';
export {LinkerEnvironment} from './src/file_linker/linker_environment';
export {LinkerOptions} from './src/file_linker/linker_options';
export {DEFAULT_LINKER_OPTIONS, LinkerOptions} from './src/file_linker/linker_options';
export {needsLinking} from './src/file_linker/needs_linking';
@@ -34,7 +34,8 @@ export class LinkerEnvironment<TStatement, TExpression> {
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs ??
DEFAULT_LINKER_OPTIONS.i18nNormalizeLineEndingsInICUs,
i18nUseExternalIds: options.i18nUseExternalIds ?? DEFAULT_LINKER_OPTIONS.i18nUseExternalIds,
sourceMapping: options.sourceMapping ?? DEFAULT_LINKER_OPTIONS.sourceMapping
sourceMapping: options.sourceMapping ?? DEFAULT_LINKER_OPTIONS.sourceMapping,
linkerJitMode: options.linkerJitMode ?? DEFAULT_LINKER_OPTIONS.linkerJitMode,
});
}
}
@@ -33,6 +33,14 @@ export interface LinkerOptions {
* The default is `true`.
*/
sourceMapping: boolean;
/**
* This option tells the linker to generate information used by a downstream JIT compiler.
*
* Specifically, in JIT mode, NgModule definitions must describe the `declarations`, `imports`,
* `exports`, etc, which are otherwise not needed.
*/
linkerJitMode: boolean;
}
/**
@@ -43,4 +51,5 @@ export const DEFAULT_LINKER_OPTIONS: LinkerOptions = {
i18nNormalizeLineEndingsInICUs: false,
i18nUseExternalIds: false,
sourceMapping: true,
linkerJitMode: false,
};
@@ -0,0 +1,49 @@
/**
* @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 {compileInjector, ConstantPool, R3DeclareInjectorMetadata, R3InjectorMetadata, R3PartialDeclaration} 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 {wrapReference} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareInjector()` call expressions.
*/
export class PartialInjectorLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
const meta = toR3InjectorMeta(metaObj);
const def = compileInjector(meta);
return def.expression;
}
}
/**
* Derives the `R3InjectorMetadata` structure from the AST object.
*/
export function toR3InjectorMeta<TExpression>(
metaObj: AstObject<R3DeclareInjectorMetadata, TExpression>): R3InjectorMetadata {
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');
}
return {
name: typeName,
type: wrapReference(typeExpr.getOpaque()),
internalType: metaObj.getOpaque('type'),
providers: metaObj.has('providers') ? metaObj.getOpaque('providers') : null,
imports: metaObj.has('imports') ? metaObj.getArray('imports').map(i => i.getOpaque()) : [],
};
}
@@ -13,13 +13,20 @@ import {LinkerEnvironment} from '../linker_environment';
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
import {PartialLinker} from './partial_linker';
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const declarationFunctions = [ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclarePipe];
export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareInjector, ɵɵngDeclareNgModule,
ɵɵngDeclarePipe
];
interface LinkerRange<TExpression> {
range: string;
@@ -83,6 +90,9 @@ export class PartialLinkerSelector<TStatement, TExpression> {
const partialComponentLinkerVersion1 = new PartialComponentLinkerVersion1(
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
code);
const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1();
const partialNgModuleLinkerVersion1 =
new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode);
const partialPipeLinkerVersion1 = new PartialPipeLinkerVersion1();
const linkers = new Map<string, LinkerRange<TExpression>[]>();
@@ -94,6 +104,14 @@ export class PartialLinkerSelector<TStatement, TExpression> {
{range: '0.0.0-PLACEHOLDER', linker: partialComponentLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialComponentLinkerVersion1},
]);
linkers.set(ɵɵngDeclareInjector, [
{range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialInjectorLinkerVersion1},
]);
linkers.set(ɵɵngDeclareNgModule, [
{range: '0.0.0-PLACEHOLDER', linker: partialNgModuleLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialNgModuleLinkerVersion1},
]);
linkers.set(ɵɵngDeclarePipe, [
{range: '0.0.0-PLACEHOLDER', linker: partialPipeLinkerVersion1},
{range: '>=11.1.0-next.1', linker: partialPipeLinkerVersion1},
@@ -0,0 +1,128 @@
/**
* @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 {compileNgModule, ConstantPool, R3DeclareNgModuleMetadata, R3NgModuleMetadata, R3PartialDeclaration, R3Reference} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast';
import {AstObject, AstValue} from '../../ast/ast_value';
import {PartialLinker} from './partial_linker';
import {wrapReference} from './util';
/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareNgModule()` call expressions.
*/
export class PartialNgModuleLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
constructor(
/**
* If true then emit the additional declarations, imports, exports, etc in the NgModule
* definition. These are only used by JIT compilation.
*/
private emitInline: boolean) {}
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
const meta = toR3NgModuleMeta(metaObj, this.emitInline);
const def = compileNgModule(meta);
return def.expression;
}
}
/**
* Derives the `R3NgModuleMetadata` structure from the AST object.
*/
export function toR3NgModuleMeta<TExpression>(
metaObj: AstObject<R3DeclareNgModuleMetadata, TExpression>,
emitInline: boolean): R3NgModuleMetadata {
const wrappedType = metaObj.getOpaque('type');
const meta: R3NgModuleMetadata = {
type: wrapReference(wrappedType),
internalType: wrappedType,
adjacentType: wrappedType,
bootstrap: [],
declarations: [],
imports: [],
exports: [],
emitInline,
containsForwardDecls: false,
schemas: [],
id: metaObj.has('id') ? metaObj.getOpaque('id') : null,
};
// Each of `bootstrap`, `declarations`, `imports` and `exports` are normally an array. But if any
// of the references are not yet declared, then the arrays must be wrapped in a function to
// prevent errors at runtime when accessing the values.
// The following blocks of code will unwrap the arrays from such functions, because
// `R3NgModuleMetadata` expects arrays of `R3Reference` objects.
// Further, since the `ɵdefineNgModule()` will also suffer from the forward declaration problem,
// we must update the `containsForwardDecls` property if a function wrapper was found.
if (metaObj.has('bootstrap')) {
const bootstrap: AstValue<unknown, TExpression> = metaObj.getValue('bootstrap');
if (bootstrap.isFunction()) {
meta.containsForwardDecls = true;
meta.bootstrap = wrapReferences(unwrapForwardRefs(bootstrap));
} else
meta.bootstrap = wrapReferences(bootstrap);
}
if (metaObj.has('declarations')) {
const declarations: AstValue<unknown, TExpression> = metaObj.getValue('declarations');
if (declarations.isFunction()) {
meta.containsForwardDecls = true;
meta.declarations = wrapReferences(unwrapForwardRefs(declarations));
} else
meta.declarations = wrapReferences(declarations);
}
if (metaObj.has('imports')) {
const imports: AstValue<unknown, TExpression> = metaObj.getValue('imports');
if (imports.isFunction()) {
meta.containsForwardDecls = true;
meta.imports = wrapReferences(unwrapForwardRefs(imports));
} else
meta.imports = wrapReferences(imports);
}
if (metaObj.has('exports')) {
const exports: AstValue<unknown, TExpression> = metaObj.getValue('exports');
if (exports.isFunction()) {
meta.containsForwardDecls = true;
meta.exports = wrapReferences(unwrapForwardRefs(exports));
} else
meta.exports = wrapReferences(exports);
}
if (metaObj.has('schemas')) {
const schemas: AstValue<unknown, TExpression> = metaObj.getValue('schemas');
meta.schemas = wrapReferences(schemas);
}
return meta;
}
/**
* Extract an array from the body of the function.
*
* If `field` is `function() { return [exp1, exp2, exp3]; }` then we return `[exp1, exp2, exp3]`.
*
*/
function unwrapForwardRefs<TExpression>(field: AstValue<unknown, TExpression>):
AstValue<TExpression[], TExpression> {
return (field as AstValue<Function, TExpression>).getFunctionReturnValue();
}
/**
* Wrap the array of expressions into an array of R3 references.
*/
function wrapReferences<TExpression>(values: AstValue<TExpression[], TExpression>): R3Reference[] {
return values.getArray().map(i => wrapReference(i.getOpaque()));
}
@@ -7,7 +7,7 @@
*/
import * as ts from 'typescript';
import {LinkerOptions} from '../../..';
import {DEFAULT_LINKER_OPTIONS, LinkerOptions} from '../../..';
import {FileSystem} from '../../../../src/ngtsc/file_system';
import {MockFileSystemNative} from '../../../../src/ngtsc/file_system/testing';
import {MockLogger} from '../../../../src/ngtsc/logging/testing';
@@ -16,16 +16,13 @@ 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 {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';
import {PartialPipeLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_pipe_linker_1';
describe('PartialLinkerSelector', () => {
const options: LinkerOptions = {
i18nNormalizeLineEndingsInICUs: true,
enableI18nLegacyMessageIdFormat: false,
i18nUseExternalIds: false,
sourceMapping: false,
};
const options: LinkerOptions = DEFAULT_LINKER_OPTIONS;
let environment: LinkerEnvironment<ts.Statement, ts.Expression>;
let fs: FileSystem;
@@ -65,6 +62,10 @@ describe('PartialLinkerSelector', () => {
.toBeInstanceOf(PartialComponentLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclarePipe', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialPipeLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialInjectorLinkerVersion1);
expect(selector.getLinker('ɵɵngDeclareNgModule', '0.0.0-PLACEHOLDER'))
.toBeInstanceOf(PartialNgModuleLinkerVersion1);
});
it('should return the linker that matches the name and valid full version', () => {