Skip to content

Commit

Permalink
perf(ngcc): cache parsed tsconfig between runs (angular#37417)
Browse files Browse the repository at this point in the history
This commit will store a cached copy of the parsed tsconfig
that can be reused if the tsconfig path is the same.

This will improve the ngcc "noop" case, where there is no processing
to do, when the entry-points have already been processed.
Previously we were parsing this config every time we checked for
entry-points to process, which can take up to seconds in some
cases.

Resolves angular#36882

PR Close angular#37417
  • Loading branch information
petebacondarwin authored and atscott committed Jun 4, 2020
1 parent 2021ad1 commit 6e7bd93
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 2 deletions.
2 changes: 1 addition & 1 deletion packages/compiler-cli/ngcc/index.ts
Expand Up @@ -12,7 +12,7 @@ import {AsyncNgccOptions, SyncNgccOptions} from './src/ngcc_options';

export {ConsoleLogger} from './src/logging/console_logger';
export {Logger, LogLevel} from './src/logging/logger';
export {AsyncNgccOptions, NgccOptions, SyncNgccOptions} from './src/ngcc_options';
export {AsyncNgccOptions, clearTsConfigCache, NgccOptions, SyncNgccOptions} from './src/ngcc_options';
export {PathMappings} from './src/path_mappings';

export function process<T extends AsyncNgccOptions|SyncNgccOptions>(options: T):
Expand Down
27 changes: 26 additions & 1 deletion packages/compiler-cli/ngcc/src/ngcc_options.ts
Expand Up @@ -156,7 +156,7 @@ export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOp
const absBasePath = absoluteFrom(options.basePath);
const projectPath = fileSystem.dirname(absBasePath);
const tsConfig =
options.tsConfigPath !== null ? readConfiguration(options.tsConfigPath || projectPath) : null;
options.tsConfigPath !== null ? getTsConfig(options.tsConfigPath || projectPath) : null;

let {
basePath,
Expand Down Expand Up @@ -200,3 +200,28 @@ export function getSharedSetup(options: NgccOptions): SharedSetup&RequiredNgccOp
new InPlaceFileWriter(fileSystem, logger, errorOnFailedEntryPoint),
};
}

let tsConfigCache: ParsedConfiguration|null = null;
let tsConfigPathCache: string|null = null;

/**
* Get the parsed configuration object for the given `tsConfigPath`.
*
* This function will cache the previous parsed configuration object to avoid unnecessary processing
* of the tsconfig.json in the case that it is requested repeatedly.
*
* This makes the assumption, which is true as of writing, that the contents of tsconfig.json and
* its dependencies will not change during the life of the process running ngcc.
*/
function getTsConfig(tsConfigPath: string): ParsedConfiguration|null {
if (tsConfigPath !== tsConfigPathCache) {
tsConfigPathCache = tsConfigPath;
tsConfigCache = readConfiguration(tsConfigPath);
}
return tsConfigCache;
}

export function clearTsConfigCache() {
tsConfigPathCache = null;
tsConfigCache = null;
}
5 changes: 5 additions & 0 deletions packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts
Expand Up @@ -15,6 +15,7 @@ import {Folder, MockFileSystem, runInEachFileSystem, TestFile} from '../../../sr
import {loadStandardTestFiles, loadTestFiles} from '../../../test/helpers';
import {getLockFilePath} from '../../src/locking/lock_file';
import {mainNgcc} from '../../src/main';
import {clearTsConfigCache} from '../../src/ngcc_options';
import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker';
import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point';
import {EntryPointManifestFile} from '../../src/packages/entry_point_manifest';
Expand Down Expand Up @@ -42,6 +43,10 @@ runInEachFileSystem(() => {
spyOn(os, 'cpus').and.returnValue([{model: 'Mock CPU'} as any]);
});

afterEach(() => {
clearTsConfigCache();
});

/**
* Sets up the esm5 format in the Angular core package. By default, package output
* no longer contains esm5 output, so we process the fesm2015 file into ES5 and
Expand Down
78 changes: 78 additions & 0 deletions packages/compiler-cli/ngcc/test/ngcc_options_spec.ts
@@ -0,0 +1,78 @@
/**
* @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 {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';

import {clearTsConfigCache, getSharedSetup, NgccOptions} from '../src/ngcc_options';

import {MockLogger} from './helpers/mock_logger';


runInEachFileSystem(() => {
let fs: FileSystem;
let _abs: typeof absoluteFrom;
let projectPath: AbsoluteFsPath;

beforeEach(() => {
fs = getFileSystem();
_abs = absoluteFrom;
projectPath = _abs('/project');
});

describe('getSharedSetup()', () => {
let pathToProjectTsConfig: AbsoluteFsPath;
let pathToCustomTsConfig: AbsoluteFsPath;

beforeEach(() => {
clearTsConfigCache();
pathToProjectTsConfig = fs.resolve(projectPath, 'tsconfig.json');
fs.ensureDir(fs.dirname(pathToProjectTsConfig));
fs.writeFile(pathToProjectTsConfig, '{"files": ["src/index.ts"]}');
pathToCustomTsConfig = _abs('/path/to/tsconfig.json');
fs.ensureDir(fs.dirname(pathToCustomTsConfig));
fs.writeFile(pathToCustomTsConfig, '{"files": ["custom/index.ts"]}');
});

it('should load the tsconfig.json at the project root if tsConfigPath is `undefined`', () => {
const setup = getSharedSetup({...createOptions()});
expect(setup.tsConfigPath).toBeUndefined();
expect(setup.tsConfig?.rootNames).toEqual([fs.resolve(projectPath, 'src/index.ts')]);
});

it('should load a specific tsconfig.json if tsConfigPath is a string', () => {
const setup = getSharedSetup({...createOptions(), tsConfigPath: pathToCustomTsConfig});
expect(setup.tsConfigPath).toEqual(pathToCustomTsConfig);
expect(setup.tsConfig?.rootNames).toEqual([_abs('/path/to/custom/index.ts')]);
});

it('should not load a tsconfig.json if tsConfigPath is `null`', () => {
const setup = getSharedSetup({...createOptions(), tsConfigPath: null});
expect(setup.tsConfigPath).toBe(null);
expect(setup.tsConfig).toBe(null);
});
});

/**
* This function creates an object that contains the minimal required properties for NgccOptions.
*/
function createOptions(): NgccOptions {
return {
async: false,
basePath: fs.resolve(projectPath, 'node_modules'),
propertiesToConsider: ['es2015'],
compileAllFormats: false,
createNewEntryPointFormats: false,
logger: new MockLogger(),
fileSystem: getFileSystem(),
errorOnFailedEntryPoint: true,
enableI18nLegacyMessageIdFormat: true,
invalidateEntryPointManifest: false,
};
}
});

0 comments on commit 6e7bd93

Please sign in to comment.