refactor(core): implement runtime logic to compute component dependencies in local compilation mode (#51309)

The runtime `ɵɵsetNgModuleScope` is modified to accept raw scope info as passed to it in local compilation mode. The runtime further registers the ng-module in the deps tracker. Then the runtime `ɵɵgetComponentDepsFactory` is implemented to use the deps tracker to get the component dependencies which leads to a valid and working Angular code.

PR Close #51309
This commit is contained in:
Payam Valadkhan 2023-08-09 15:25:59 -04:00 committed by Pawel Kozlowski
parent e7ea016e00
commit f7cfc3b8fa
4 changed files with 168 additions and 15 deletions

View file

@ -517,21 +517,34 @@ export type PipeTypeList =
export const unusedValueExportToPlacateAjd = 1;
/**
* NgModule scope info as provided by NgModule decorator.
* NgModule scope info as provided by AoT compiler
*
* In full compilation Ivy resolved all the "module with providers" and forward refs the whole array
* if at least one element is forward refed. So we end up with type `Type<any>[]|(() =>
* Type<any>[])`.
*
* In local mode the compiler passes the raw info as they are to the runtime functions as it is not
* possible to resolve them any further due to limited info at compile time. So we end up with type
* `RawScopeInfoFromDecorator[]`.
*/
export interface NgModuleScopeInfoFromDecorator {
/** List of components, directives, and pipes declared by this module. */
declarations?: Type<any>[]|(() => Type<any>[]);
declarations?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
/** List of modules or `ModuleWithProviders` imported by this module. */
imports?: Type<any>[]|(() => Type<any>[]);
/** List of modules or `ModuleWithProviders` or standalone components imported by this module. */
imports?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
/**
* List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this
* module.
*/
exports?: Type<any>[]|(() => Type<any>[]);
exports?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
}
/**
* The array element type passed to:
* - NgModule's annotation imports/exports/declarations fields
* - standalone component annotation imports field
*/
export type RawScopeInfoFromDecorator =
Type<any>|ModuleWithProviders<any>|(() => Type<any>)|(() => ModuleWithProviders<any>);

View file

@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../interface/type';
import {DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition';
import {depsTracker} from './deps_tracker/deps_tracker';
import {ComponentType, DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition';
export function ɵɵgetComponentDepsFactory(
type: Type<any>, rawImports?: RawScopeInfoFromDecorator): () => DependencyTypeList {
// TODO(pmvald): Implement this runtime using deps tracker.
return () => [];
type: ComponentType<any>, rawImports?: RawScopeInfoFromDecorator[]): () => DependencyTypeList {
return () => {
return depsTracker.getComponentDependencies(type, rawImports).dependencies;
};
}

View file

@ -6,12 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
import {Type} from '../interface/type';
import {noSideEffects} from '../util/closure';
import {EMPTY_ARRAY} from '../util/empty';
import {extractDefListOrFactory, getNgModuleDef} from './definition';
import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator} from './interfaces/definition';
import {depsTracker} from './deps_tracker/deps_tracker';
import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator, RawScopeInfoFromDecorator} from './interfaces/definition';
import {isModuleWithProviders} from './jit/util';
/**
* Generated next to NgModules to monkey-patch directive and pipe references onto a component's
@ -43,8 +46,27 @@ export function ɵɵsetComponentScope(
export function ɵɵsetNgModuleScope(type: any, scope: NgModuleScopeInfoFromDecorator): unknown {
return noSideEffects(() => {
const ngModuleDef = getNgModuleDef(type, true);
ngModuleDef.declarations = scope.declarations || EMPTY_ARRAY;
ngModuleDef.imports = scope.imports || EMPTY_ARRAY;
ngModuleDef.exports = scope.exports || EMPTY_ARRAY;
ngModuleDef.declarations = convertToTypeArray(scope.declarations || EMPTY_ARRAY);
ngModuleDef.imports = convertToTypeArray(scope.imports || EMPTY_ARRAY);
ngModuleDef.exports = convertToTypeArray(scope.exports || EMPTY_ARRAY);
depsTracker.registerNgModule(type, scope);
});
}
function convertToTypeArray(values: Type<any>[]|(() => Type<any>[])|
RawScopeInfoFromDecorator[]): Type<any>[]|(() => Type<any>[]) {
if (typeof values === 'function') {
return values;
}
if (values.some(isForwardRef)) {
return () => values.map(resolveForwardRef).map(maybeUnwrapModuleWithProviders);
} else {
return values.map(maybeUnwrapModuleWithProviders);
}
}
function maybeUnwrapModuleWithProviders(value: any): Type<any> {
return isModuleWithProviders(value) ? value.ngModule : value as Type<any>;
}

View file

@ -0,0 +1,118 @@
/**
* @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 {Component, forwardRef, ɵɵdefineNgModule, ɵɵgetComponentDepsFactory, ɵɵsetNgModuleScope} from '@angular/core';
import {ComponentType} from '@angular/core/src/render3';
describe('component dependencies in local compilation', () => {
it('should compute correct set of dependencies when importing ng-modules directly', () => {
@Component({selector: 'sub'})
class SubComponent {
}
class SubModule {
static ɵmod = ɵɵdefineNgModule({type: SubModule});
}
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
@Component({})
class MainComponent {
}
class MainModule {
static ɵmod = ɵɵdefineNgModule({type: MainModule});
}
ɵɵsetNgModuleScope(MainModule, {imports: [SubModule], declarations: [MainComponent]});
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
});
it('should compute correct set of dependencies when importing ng-modules with providers', () => {
@Component({selector: 'sub'})
class SubComponent {
}
class SubModule {
static ɵmod = ɵɵdefineNgModule({type: SubModule});
}
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
@Component({})
class MainComponent {
}
class MainModule {
static ɵmod = ɵɵdefineNgModule({type: MainModule});
}
ɵɵsetNgModuleScope(
MainModule,
{imports: [{ngModule: SubModule, providers: []}], declarations: [MainComponent]});
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
});
it('should compute correct set of dependencies when importing ng-modules using forward ref',
() => {
@Component({selector: 'sub'})
class SubComponent {
}
class SubModule {
static ɵmod = ɵɵdefineNgModule({type: SubModule});
}
ɵɵsetNgModuleScope(SubModule, {exports: [forwardRef(() => SubComponent)]});
@Component({})
class MainComponent {
}
class MainModule {
static ɵmod = ɵɵdefineNgModule({type: MainModule});
}
ɵɵsetNgModuleScope(MainModule, {
imports: [forwardRef(() => SubModule)],
declarations: [forwardRef(() => MainComponent)]
});
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
});
it('should compute correct set of dependencies when importing ng-modules with providers using forward ref',
() => {
@Component({selector: 'sub'})
class SubComponent {
}
class SubModule {
static ɵmod = ɵɵdefineNgModule({type: SubModule});
}
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
@Component({})
class MainComponent {
}
class MainModule {
static ɵmod = ɵɵdefineNgModule({type: MainModule});
}
ɵɵsetNgModuleScope(MainModule, {
imports: [forwardRef(() => ({ngModule: SubModule, providers: []}))],
declarations: [MainComponent]
});
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
});
});