Skip to content

Commit f7cfc3b

Browse files
pmvaldpkozlowski-opensource
authored andcommitted
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
1 parent e7ea016 commit f7cfc3b

File tree

4 files changed

+168
-15
lines changed

4 files changed

+168
-15
lines changed

packages/core/src/render3/interfaces/definition.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -517,21 +517,34 @@ export type PipeTypeList =
517517
export const unusedValueExportToPlacateAjd = 1;
518518

519519
/**
520-
* NgModule scope info as provided by NgModule decorator.
520+
* NgModule scope info as provided by AoT compiler
521+
*
522+
* In full compilation Ivy resolved all the "module with providers" and forward refs the whole array
523+
* if at least one element is forward refed. So we end up with type `Type<any>[]|(() =>
524+
* Type<any>[])`.
525+
*
526+
* In local mode the compiler passes the raw info as they are to the runtime functions as it is not
527+
* possible to resolve them any further due to limited info at compile time. So we end up with type
528+
* `RawScopeInfoFromDecorator[]`.
521529
*/
522530
export interface NgModuleScopeInfoFromDecorator {
523531
/** List of components, directives, and pipes declared by this module. */
524-
declarations?: Type<any>[]|(() => Type<any>[]);
532+
declarations?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
525533

526-
/** List of modules or `ModuleWithProviders` imported by this module. */
527-
imports?: Type<any>[]|(() => Type<any>[]);
534+
/** List of modules or `ModuleWithProviders` or standalone components imported by this module. */
535+
imports?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
528536

529537
/**
530538
* List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this
531539
* module.
532540
*/
533-
exports?: Type<any>[]|(() => Type<any>[]);
541+
exports?: Type<any>[]|(() => Type<any>[])|RawScopeInfoFromDecorator[];
534542
}
535543

544+
/**
545+
* The array element type passed to:
546+
* - NgModule's annotation imports/exports/declarations fields
547+
* - standalone component annotation imports field
548+
*/
536549
export type RawScopeInfoFromDecorator =
537550
Type<any>|ModuleWithProviders<any>|(() => Type<any>)|(() => ModuleWithProviders<any>);

packages/core/src/render3/local_compilation.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Type} from '../interface/type';
10-
11-
import {DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition';
9+
import {depsTracker} from './deps_tracker/deps_tracker';
10+
import {ComponentType, DependencyTypeList, RawScopeInfoFromDecorator} from './interfaces/definition';
1211

1312
export function ɵɵgetComponentDepsFactory(
14-
type: Type<any>, rawImports?: RawScopeInfoFromDecorator): () => DependencyTypeList {
15-
// TODO(pmvald): Implement this runtime using deps tracker.
16-
return () => [];
13+
type: ComponentType<any>, rawImports?: RawScopeInfoFromDecorator[]): () => DependencyTypeList {
14+
return () => {
15+
return depsTracker.getComponentDependencies(type, rawImports).dependencies;
16+
};
1717
}

packages/core/src/render3/scope.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
910
import {Type} from '../interface/type';
1011
import {noSideEffects} from '../util/closure';
1112
import {EMPTY_ARRAY} from '../util/empty';
1213

1314
import {extractDefListOrFactory, getNgModuleDef} from './definition';
14-
import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator} from './interfaces/definition';
15+
import {depsTracker} from './deps_tracker/deps_tracker';
16+
import {ComponentDef, ComponentType, NgModuleScopeInfoFromDecorator, RawScopeInfoFromDecorator} from './interfaces/definition';
17+
import {isModuleWithProviders} from './jit/util';
1518

1619
/**
1720
* Generated next to NgModules to monkey-patch directive and pipe references onto a component's
@@ -43,8 +46,27 @@ export function ɵɵsetComponentScope(
4346
export function ɵɵsetNgModuleScope(type: any, scope: NgModuleScopeInfoFromDecorator): unknown {
4447
return noSideEffects(() => {
4548
const ngModuleDef = getNgModuleDef(type, true);
46-
ngModuleDef.declarations = scope.declarations || EMPTY_ARRAY;
47-
ngModuleDef.imports = scope.imports || EMPTY_ARRAY;
48-
ngModuleDef.exports = scope.exports || EMPTY_ARRAY;
49+
ngModuleDef.declarations = convertToTypeArray(scope.declarations || EMPTY_ARRAY);
50+
ngModuleDef.imports = convertToTypeArray(scope.imports || EMPTY_ARRAY);
51+
ngModuleDef.exports = convertToTypeArray(scope.exports || EMPTY_ARRAY);
52+
53+
depsTracker.registerNgModule(type, scope);
4954
});
5055
}
56+
57+
function convertToTypeArray(values: Type<any>[]|(() => Type<any>[])|
58+
RawScopeInfoFromDecorator[]): Type<any>[]|(() => Type<any>[]) {
59+
if (typeof values === 'function') {
60+
return values;
61+
}
62+
63+
if (values.some(isForwardRef)) {
64+
return () => values.map(resolveForwardRef).map(maybeUnwrapModuleWithProviders);
65+
} else {
66+
return values.map(maybeUnwrapModuleWithProviders);
67+
}
68+
}
69+
70+
function maybeUnwrapModuleWithProviders(value: any): Type<any> {
71+
return isModuleWithProviders(value) ? value.ngModule : value as Type<any>;
72+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Component, forwardRef, ɵɵdefineNgModule, ɵɵgetComponentDepsFactory, ɵɵsetNgModuleScope} from '@angular/core';
10+
import {ComponentType} from '@angular/core/src/render3';
11+
12+
describe('component dependencies in local compilation', () => {
13+
it('should compute correct set of dependencies when importing ng-modules directly', () => {
14+
@Component({selector: 'sub'})
15+
class SubComponent {
16+
}
17+
18+
class SubModule {
19+
static ɵmod = ɵɵdefineNgModule({type: SubModule});
20+
}
21+
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
22+
23+
@Component({})
24+
class MainComponent {
25+
}
26+
27+
class MainModule {
28+
static ɵmod = ɵɵdefineNgModule({type: MainModule});
29+
}
30+
ɵɵsetNgModuleScope(MainModule, {imports: [SubModule], declarations: [MainComponent]});
31+
32+
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
33+
34+
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
35+
});
36+
37+
it('should compute correct set of dependencies when importing ng-modules with providers', () => {
38+
@Component({selector: 'sub'})
39+
class SubComponent {
40+
}
41+
42+
class SubModule {
43+
static ɵmod = ɵɵdefineNgModule({type: SubModule});
44+
}
45+
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
46+
47+
@Component({})
48+
class MainComponent {
49+
}
50+
51+
class MainModule {
52+
static ɵmod = ɵɵdefineNgModule({type: MainModule});
53+
}
54+
ɵɵsetNgModuleScope(
55+
MainModule,
56+
{imports: [{ngModule: SubModule, providers: []}], declarations: [MainComponent]});
57+
58+
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
59+
60+
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
61+
});
62+
63+
it('should compute correct set of dependencies when importing ng-modules using forward ref',
64+
() => {
65+
@Component({selector: 'sub'})
66+
class SubComponent {
67+
}
68+
69+
class SubModule {
70+
static ɵmod = ɵɵdefineNgModule({type: SubModule});
71+
}
72+
ɵɵsetNgModuleScope(SubModule, {exports: [forwardRef(() => SubComponent)]});
73+
74+
@Component({})
75+
class MainComponent {
76+
}
77+
78+
class MainModule {
79+
static ɵmod = ɵɵdefineNgModule({type: MainModule});
80+
}
81+
ɵɵsetNgModuleScope(MainModule, {
82+
imports: [forwardRef(() => SubModule)],
83+
declarations: [forwardRef(() => MainComponent)]
84+
});
85+
86+
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
87+
88+
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
89+
});
90+
91+
it('should compute correct set of dependencies when importing ng-modules with providers using forward ref',
92+
() => {
93+
@Component({selector: 'sub'})
94+
class SubComponent {
95+
}
96+
97+
class SubModule {
98+
static ɵmod = ɵɵdefineNgModule({type: SubModule});
99+
}
100+
ɵɵsetNgModuleScope(SubModule, {exports: [SubComponent]});
101+
102+
@Component({})
103+
class MainComponent {
104+
}
105+
106+
class MainModule {
107+
static ɵmod = ɵɵdefineNgModule({type: MainModule});
108+
}
109+
ɵɵsetNgModuleScope(MainModule, {
110+
imports: [forwardRef(() => ({ngModule: SubModule, providers: []}))],
111+
declarations: [MainComponent]
112+
});
113+
114+
const deps = ɵɵgetComponentDepsFactory(MainComponent as ComponentType<any>)();
115+
116+
expect(deps).toEqual(jasmine.arrayWithExactContents([SubComponent, MainComponent]));
117+
});
118+
});

0 commit comments

Comments
 (0)