Skip to content

Commit

Permalink
perf: short-circuit ngcc processing across entry-points
Browse files Browse the repository at this point in the history
This commit introduces a cache of which entry-points have already been
processed by ngcc that is used across entry-points, to avoid the overhead
of invoking ngcc repeatedly for all entry-points.
  • Loading branch information
JoostK committed Oct 10, 2020
1 parent b6379c2 commit c358a4d
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/lib/ng-package/entry-point/compile-ngc.transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export const compileNgcTransform: Transform = transformFromPromise(async graph =

// Compile TypeScript sources
const { esm2015, declarations } = entryPoint.data.destinationFiles;
const { moduleResolutionCache } = entryPoint.cache;
const { moduleResolutionCache, ngccCache } = entryPoint.cache;
const { basePath, cssUrl, styleIncludePaths } = entryPoint.data.entryPoint;
const stylesheetProcessor = new StylesheetProcessor(basePath, cssUrl, styleIncludePaths);

const ngccProcessor = tsConfig.options.enableIvy
? new NgccProcessor(tsConfig.project, tsConfig.options, entryPoints)
? new NgccProcessor(ngccCache, tsConfig.project, tsConfig.options, entryPoints)
: undefined;

await compileSourceFiles(
Expand Down
25 changes: 25 additions & 0 deletions src/lib/ng-package/ngcc-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Registers the paths to package.json files of libraries that have been processed by ngcc. This
* cache is reused across all entry-points of a package, so module requests across the entry-points
* can determine whether invoking ngcc is necessary.
*
* The cost of invoking ngcc for an entry-point that has already been invoked is limited due to
* a fast path in ngcc, however even in this fast-path does ngcc scan the entry-point to determine
* if all dependencies have been processed. This cache allows to avoid that work, as entry-points
* are processed in batches during which the `node_modules` directory is not mutated.
*/
export class NgccProcessingCache {
private readonly processedModules = new Set<string>();

hasProcessed(packageJsonPath: string): boolean {
return this.processedModules.has(packageJsonPath);
}

markProcessed(packageJsonPath: string): void {
this.processedModules.add(packageJsonPath);
}

clear(): void {
this.processedModules.clear();
}
}
5 changes: 5 additions & 0 deletions src/lib/ng-package/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ParsedConfiguration, Program } from '@angular/compiler-cli';
import { Node } from '../graph/node';
import { by, isInProgress, isDirty } from '../graph/select';
import { NgEntryPoint, DestinationFiles } from './entry-point/entry-point';
import { NgccProcessingCache } from './ngcc-cache';
import { NgPackage } from './package';
import { FileCache } from '../file-system/file-cache';
import { ComplexPredicate } from '../graph/build-graph';
Expand Down Expand Up @@ -63,12 +64,14 @@ export class EntryPointNode extends Node {
constructor(
public readonly url: string,
sourcesFileCache: FileCache,
ngccCache: NgccProcessingCache,
moduleResolutionCache: ts.ModuleResolutionCache,
) {
super(url);

this.cache = {
sourcesFileCache,
ngccCache,
analysesSourcesFileCache: new FileCache(),
moduleResolutionCache,
};
Expand All @@ -77,6 +80,7 @@ export class EntryPointNode extends Node {
cache: {
oldPrograms?: Record<ts.ScriptTarget | 'analysis', Program | ts.Program>;
sourcesFileCache: FileCache;
ngccCache: NgccProcessingCache;
analysesSourcesFileCache: FileCache;
moduleResolutionCache: ts.ModuleResolutionCache;
rollupFESMCache?: RollupCache;
Expand All @@ -97,6 +101,7 @@ export class PackageNode extends Node {
cache = {
globCache: {} as GlobCache,
sourcesFileCache: new FileCache(),
ngccCache: new NgccProcessingCache(),
moduleResolutionCache: ts.createModuleResolutionCache(process.cwd(), s => s),
};
}
5 changes: 4 additions & 1 deletion src/lib/ng-package/package.transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const packageTransformFactory = (
const node = new EntryPointNode(
ngUrl(moduleId),
ngPkg.cache.sourcesFileCache,
ngPkg.cache.ngccCache,
ngPkg.cache.moduleResolutionCache,
);
node.data = { entryPoint, destinationFiles };
Expand Down Expand Up @@ -138,9 +139,11 @@ const watchTransformFactory = (
return createFileWatch(data.src, [data.dest]).pipe(
tap(fileChange => {
const { filePath, event } = fileChange;
const { sourcesFileCache } = cache;
const { sourcesFileCache, ngccCache } = cache;
const cachedSourceFile = sourcesFileCache.get(filePath);

ngccCache.clear();

if (!cachedSourceFile) {
if (event === 'unlink' || event === 'add') {
cache.globCache = regenerateGlobCache(sourcesFileCache);
Expand Down
12 changes: 10 additions & 2 deletions src/lib/ngc/ngcc-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ import { Logger, process as mainNgcc, LogLevel } from '@angular/compiler-cli/ngc
import { existsSync, constants, accessSync } from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import { NgccProcessingCache } from '../ng-package/ngcc-cache';
import * as log from '../utils/log';
import { EntryPointNode, ngUrl } from '../ng-package/nodes';

// Transform a package and its typings when NGTSC is resolving a module.
export class NgccProcessor {
/**
* Tracks the absolute module names that have already been processed by this instance. This is in
* addition to the `NgccProcessingCache` which is shared across entry-points, to avoid the need to
* resolve the package.json path for the module name which is required for `NgccProcessingCache`.
*/
private _processedModules = new Set<string>();
private _logger: NgccLogger;
private _nodeModulesDirectory: string;
private _entryPointsUrl: string[];

constructor(
private projectPath: string,
private readonly ngccCache: NgccProcessingCache,
private readonly projectPath: string,
private readonly compilerOptions: ts.CompilerOptions,
private readonly entryPoints: EntryPointNode[],
) {
Expand All @@ -36,7 +43,7 @@ export class NgccProcessor {
}

const packageJsonPath = this.tryResolvePackage(moduleName, resolvedFileName);
if (!packageJsonPath) {
if (!packageJsonPath || this.ngccCache.hasProcessed(packageJsonPath)) {
// add it to processed so the second time round we skip this.
this._processedModules.add(moduleName);

Expand Down Expand Up @@ -65,6 +72,7 @@ export class NgccProcessor {
});

this._processedModules.add(moduleName);
this.ngccCache.markProcessed(packageJsonPath);
}

/**
Expand Down

0 comments on commit c358a4d

Please sign in to comment.