Skip to content

Commit 0a1a989

Browse files
AndrewKushnirmhevery
authored andcommitted
perf(core): avoid recursive scope recalculation when TestBed.overrideModule is used (angular#35454)
Currently if TestBed detects that TestBed.overrideModule was used for module X, transitive scopes are recalculated recursively for all modules that X imports and previously calculated data (stored in cache) is ignored. This behavior was introduced in angular#33787 to fix stale transitive scopes issue (cache was not updated if module overrides are present). The perf issue comes from a "diamond" problem, where module X is overridden which imports modules A and B, which both import module C. Under previous logic, module C gets its transitive deps recomputed multiple times, during the recompute for both A and B. For deep graphs and big common/shared modules this can be super costly. This commit updates the logic to recalculate ransitive scopes for the overridden module, while keeping previously calculated scopes of other modules untouched. PR Close angular#35454
1 parent c414f45 commit 0a1a989

File tree

3 files changed

+27
-24
lines changed

3 files changed

+27
-24
lines changed

packages/core/src/render3/jit/module.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -427,25 +427,19 @@ export function patchComponentDefWithScope<C>(
427427
/**
428428
* Compute the pair of transitive scopes (compilation scope and exported scope) for a given module.
429429
*
430-
* By default this operation is memoized and the result is cached on the module's definition. You
431-
* can avoid memoization and previously stored results (if available) by providing the second
432-
* argument with the `true` value (forcing transitive scopes recalculation).
433-
*
434-
* This function can be called on modules with components that have not fully compiled yet, but the
435-
* result should not be used until they have.
430+
* This operation is memoized and the result is cached on the module's definition. This function can
431+
* be called on modules with components that have not fully compiled yet, but the result should not
432+
* be used until they have.
436433
*
437434
* @param moduleType module that transitive scope should be calculated for.
438-
* @param forceRecalc flag that indicates whether previously calculated and memoized values should
439-
* be ignored and transitive scope to be fully recalculated.
440435
*/
441-
export function transitiveScopesFor<T>(
442-
moduleType: Type<T>, forceRecalc: boolean = false): NgModuleTransitiveScopes {
436+
export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveScopes {
443437
if (!isNgModule(moduleType)) {
444438
throw new Error(`${moduleType.name} does not have a module def (ɵmod property)`);
445439
}
446440
const def = getNgModuleDef(moduleType) !;
447441

448-
if (!forceRecalc && def.transitiveCompileScopes !== null) {
442+
if (def.transitiveCompileScopes !== null) {
449443
return def.transitiveCompileScopes;
450444
}
451445

@@ -486,7 +480,7 @@ export function transitiveScopesFor<T>(
486480

487481
// When this module imports another, the imported module's exported directives and pipes are
488482
// added to the compilation scope of this module.
489-
const importedScope = transitiveScopesFor(importedType, forceRecalc);
483+
const importedScope = transitiveScopesFor(importedType);
490484
importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry));
491485
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
492486
});
@@ -505,7 +499,7 @@ export function transitiveScopesFor<T>(
505499
if (isNgModule(exportedType)) {
506500
// When this module exports another, the exported module's exported directives and pipes are
507501
// added to both the compilation and exported scopes of this module.
508-
const exportedScope = transitiveScopesFor(exportedType, forceRecalc);
502+
const exportedScope = transitiveScopesFor(exportedType);
509503
exportedScope.exported.directives.forEach(entry => {
510504
scopes.compilation.directives.add(entry);
511505
scopes.exported.directives.add(entry);
@@ -521,9 +515,7 @@ export function transitiveScopesFor<T>(
521515
}
522516
});
523517

524-
if (!forceRecalc) {
525-
def.transitiveCompileScopes = scopes;
526-
}
518+
def.transitiveCompileScopes = scopes;
527519
return scopes;
528520
}
529521

packages/core/test/test_bed_spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -889,15 +889,23 @@ describe('TestBed', () => {
889889
{set: {template: `<span someDirective>{{'hello' | somePipe}}</span>`}});
890890
TestBed.createComponent(SomeComponent);
891891

892-
const defBeforeReset = (SomeComponent as any).ɵcmp;
893-
expect(defBeforeReset.pipeDefs().length).toEqual(1);
894-
expect(defBeforeReset.directiveDefs().length).toEqual(2); // directive + component
892+
const cmpDefBeforeReset = (SomeComponent as any).ɵcmp;
893+
expect(cmpDefBeforeReset.pipeDefs().length).toEqual(1);
894+
expect(cmpDefBeforeReset.directiveDefs().length).toEqual(2); // directive + component
895+
896+
const modDefBeforeReset = (SomeModule as any).ɵmod;
897+
const transitiveScope = modDefBeforeReset.transitiveCompileScopes.compilation;
898+
expect(transitiveScope.pipes.size).toEqual(1);
899+
expect(transitiveScope.directives.size).toEqual(2);
895900

896901
TestBed.resetTestingModule();
897902

898-
const defAfterReset = (SomeComponent as any).ɵcmp;
899-
expect(defAfterReset.pipeDefs).toBe(null);
900-
expect(defAfterReset.directiveDefs).toBe(null);
903+
const cmpDefAfterReset = (SomeComponent as any).ɵcmp;
904+
expect(cmpDefAfterReset.pipeDefs).toBe(null);
905+
expect(cmpDefAfterReset.directiveDefs).toBe(null);
906+
907+
const modDefAfterReset = (SomeModule as any).ɵmod;
908+
expect(modDefAfterReset.transitiveCompileScopes).toBe(null);
901909
});
902910

903911
it('should cleanup ng defs for classes with no ng annotations (in case of inheritance)',

packages/core/testing/src/r3_test_bed_compiler.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,11 @@ export class R3TestBedCompiler {
355355
// are present, always re-calculate transitive scopes to have the most up-to-date
356356
// information available. The `moduleToScope` map avoids repeated re-calculation of
357357
// scopes for the same module.
358-
const forceRecalc = !isTestingModule && this.hasModuleOverrides;
359-
moduleToScope.set(moduleType, transitiveScopesFor(realType, forceRecalc));
358+
if (!isTestingModule && this.hasModuleOverrides) {
359+
this.storeFieldOfDefOnType(moduleType as any, NG_MOD_DEF, 'transitiveCompileScopes');
360+
(moduleType as any)[NG_MOD_DEF].transitiveCompileScopes = null;
361+
}
362+
moduleToScope.set(moduleType, transitiveScopesFor(realType));
360363
}
361364
return moduleToScope.get(moduleType) !;
362365
};

0 commit comments

Comments
 (0)