diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index bcb9b6a470..c5077cb3fa 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -116,7 +116,13 @@ export class NgCompiler { const moduleResolutionCache = ts.createModuleResolutionCache( this.adapter.getCurrentDirectory(), - fileName => this.adapter.getCanonicalFileName(fileName)); + // Note: this used to be an arrow-function closure. However, JS engines like v8 have some + // strange behaviors with retaining the lexical scope of the closure. Even if this function + // doesn't retain a reference to `this`, if other closures in the constructor here reference + // `this` internally then a closure created here would retain them. This can cause major + // memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its + // way into all kinds of places inside TS internal objects. + this.adapter.getCanonicalFileName.bind(this.adapter)); this.moduleResolver = new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache); this.resourceManager = new AdapterResourceLoader(adapter, this.options); diff --git a/packages/compiler-cli/src/ngtsc/incremental/src/strategy.ts b/packages/compiler-cli/src/ngtsc/incremental/src/strategy.ts index 8e8b3379f6..b7d60b9856 100644 --- a/packages/compiler-cli/src/ngtsc/incremental/src/strategy.ts +++ b/packages/compiler-cli/src/ngtsc/incremental/src/strategy.ts @@ -42,20 +42,22 @@ export class NoopIncrementalBuildStrategy implements IncrementalBuildStrategy { * Tracks an `IncrementalDriver` within the strategy itself. */ export class TrackedIncrementalBuildStrategy implements IncrementalBuildStrategy { - private previous: IncrementalDriver|null = null; - private next: IncrementalDriver|null = null; + private driver: IncrementalDriver|null = null; + private isSet: boolean = false; getIncrementalDriver(): IncrementalDriver|null { - return this.next !== null ? this.next : this.previous; + return this.driver; } setIncrementalDriver(driver: IncrementalDriver): void { - this.next = driver; + this.driver = driver; + this.isSet = true; } toNextBuildStrategy(): TrackedIncrementalBuildStrategy { const strategy = new TrackedIncrementalBuildStrategy(); - strategy.previous = this.next; + // Only reuse a driver that was explicitly set via `setIncrementalDriver`. + strategy.driver = this.isSet ? this.driver : null; return strategy; } }