forked from angular/angular
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf(ngcc): introduce cache for sharing data across entry-points
ngcc creates typically two `ts.Program` instances for each entry-point, one for processing sources and another one for processing the typings. The creation of these programs is somewhat expensive, as it concerns module resolution and parsing of source files. This commit implements several layers of caching to optimize the creation of programs: 1. A shared module resolution cache across all entry-points within a single invocation of ngcc. Both the sources and typings program benefit from this cache. 2. Sharing the parsed `ts.SourceFile` for a single entry-point between the sources and typings program. 3. Sharing parsed `ts.SourceFile`s of TypeScript's default libraries across all entry-points within a single invocation. Some of these default library typings are large and therefore expensive to parse, so sharing the parsed source files across all entry-points offers a significant performance improvement. Using a bare CLI app created using `ng new` + `ng add @angular/material`, the above changes offer a ~3x improvement in ngcc's running time.
- Loading branch information
Showing
7 changed files
with
164 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
packages/compiler-cli/ngcc/src/packages/transform_cache.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* @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 * as ts from 'typescript'; | ||
import {AbsoluteFsPath, basename, FileSystem} from '../../../src/ngtsc/file_system'; | ||
|
||
/** | ||
* A cache that holds on to data that can be shared for processing all entry-points in a single | ||
* invocation of ngcc. In particular, the default library files are cached as parsed `ts.SourceFile` | ||
* as these files are used in each entry-point and some are expensive to parse, especially | ||
* `lib.es5.d.ts` and `lib.dom.d.ts`. Additionally, a `ts.ModuleResolutionCache` is exposed for | ||
* all module resolution operations to use, such that all entry-points can leverage a single module | ||
* resolution cache. | ||
* | ||
* The lifecycle of this cache corresponds with a single invocation of ngcc. Separate invocations, | ||
* e.g. the CLI's synchronous module resolution fallback will therefore all have their own cache. | ||
* This is because module resolution results cannot be assumed to be valid across invocations, as | ||
* modifications of the file-system may have invalidated earlier results. | ||
*/ | ||
export class TransformCache { | ||
private defaultLibCache = new Map<AbsoluteFsPath, ts.SourceFile>(); | ||
readonly moduleResolutionCache: ts.ModuleResolutionCache; | ||
|
||
constructor(private fs: FileSystem) { | ||
this.moduleResolutionCache = ts.createModuleResolutionCache(fs.pwd(), fileName => { | ||
return fs.isCaseSensitive() ? fileName : fileName.toLowerCase(); | ||
}); | ||
} | ||
|
||
getCachedSourceFile(fileName: string): ts.SourceFile|undefined { | ||
if (/^lib\..+\.d\.ts$/.test(basename(fileName))) { | ||
return this.getDefaultLibFileCached(fileName); | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
|
||
private getDefaultLibFileCached(fileName: string): ts.SourceFile|undefined { | ||
const absPath = this.fs.resolve(fileName); | ||
if (!this.defaultLibCache.has(absPath)) { | ||
const content = readFile(absPath, this.fs); | ||
if (content === undefined) { | ||
return undefined; | ||
} | ||
const sf = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES2015); | ||
this.defaultLibCache.set(absPath, sf); | ||
} | ||
return this.defaultLibCache.get(absPath)!; | ||
} | ||
} | ||
|
||
/** | ||
* A cache for processing a single entry-point. This exists to share `ts.SourceFile`s between the | ||
* source and typing programs that are created for a single program. Additionally, it leverages the | ||
* transform cache for module resolution and sharing of default library files. | ||
*/ | ||
export class EntryPointCache { | ||
private readonly sfCache = new Map<AbsoluteFsPath, ts.SourceFile>(); | ||
|
||
constructor(private fs: FileSystem, private transformCache: TransformCache) {} | ||
|
||
get moduleResolutionCache(): ts.ModuleResolutionCache { | ||
return this.transformCache.moduleResolutionCache; | ||
} | ||
|
||
getCachedSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined { | ||
const staticSf = this.transformCache.getCachedSourceFile(fileName); | ||
if (staticSf !== undefined) { | ||
return staticSf; | ||
} | ||
|
||
const absPath = this.fs.resolve(fileName); | ||
if (this.sfCache.has(absPath)) { | ||
return this.sfCache.get(absPath); | ||
} | ||
|
||
const content = readFile(absPath, this.fs); | ||
if (content === undefined) { | ||
return undefined; | ||
} | ||
const sf = ts.createSourceFile(fileName, content, languageVersion); | ||
this.sfCache.set(absPath, sf); | ||
return sf; | ||
} | ||
} | ||
|
||
function readFile(absPath: AbsoluteFsPath, fs: FileSystem): string|undefined { | ||
if (!fs.exists(absPath) || !fs.stat(absPath).isFile()) { | ||
return undefined; | ||
} | ||
return fs.readFile(absPath); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters