Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2eaf420

Browse files
alan-agius4matsko
authored andcommitted
perf(ngcc): reduce directory traversing (#35756)
This reduces the time that `findEntryPoints` takes from 9701.143ms to 4177.278ms, by reducing the file operations done. Reference: #35717 PR Close #35756
1 parent 07efe2a commit 2eaf420

File tree

6 files changed

+251
-97
lines changed

6 files changed

+251
-97
lines changed

‎packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file
99
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
1010
import {Logger} from '../logging/logger';
1111
import {NgccConfiguration} from '../packages/configuration';
12-
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
12+
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
1313
import {PathMappings} from '../utils';
14+
import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer';
1415
import {EntryPointFinder} from './interface';
1516
import {getBasePaths} from './utils';
1617

@@ -40,10 +41,24 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
4041
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
4142
* @param sourceDirectory An absolute path to the root directory where searching begins.
4243
*/
43-
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
44+
walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
4445
const entryPoints = this.getEntryPointsForPackage(sourceDirectory);
46+
if (entryPoints === null) {
47+
return [];
48+
}
49+
4550
if (entryPoints.length > 0) {
46-
// The `sourceDirectory` is an entry-point itself so no need to search its sub-directories.
51+
// The `sourceDirectory` is an entry point itself so no need to search its sub-directories.
52+
// Also check for any nested node_modules in this package but only if it was compiled by
53+
// Angular.
54+
// It is unlikely that a non Angular entry point has a dependency on an Angular library.
55+
if (entryPoints.some(e => e.compiledByAngular)) {
56+
const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules');
57+
if (this.fs.exists(nestedNodeModulesPath)) {
58+
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
59+
}
60+
}
61+
4762
return entryPoints;
4863
}
4964

@@ -52,54 +67,57 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
5267
// Not interested in hidden files
5368
.filter(p => !p.startsWith('.'))
5469
// Ignore node_modules
55-
.filter(p => p !== 'node_modules')
70+
.filter(p => p !== 'node_modules' && p !== NGCC_DIRECTORY)
5671
// Only interested in directories (and only those that are not symlinks)
5772
.filter(p => {
5873
const stat = this.fs.lstat(resolve(sourceDirectory, p));
5974
return stat.isDirectory() && !stat.isSymbolicLink();
6075
})
6176
.forEach(p => {
62-
// Either the directory is a potential package or a namespace containing packages (e.g
63-
// `@angular`).
77+
// Package is a potential namespace containing packages (e.g `@angular`).
6478
const packagePath = join(sourceDirectory, p);
6579
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
66-
67-
// Also check for any nested node_modules in this package
68-
const nestedNodeModulesPath = join(packagePath, 'node_modules');
69-
if (this.fs.exists(nestedNodeModulesPath)) {
70-
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
71-
}
7280
});
7381
return entryPoints;
7482
}
7583

7684
/**
7785
* Recurse the folder structure looking for all the entry points
7886
* @param packagePath The absolute path to an npm package that may contain entry points
79-
* @returns An array of entry points that were discovered.
87+
* @returns An array of entry points that were discovered or null when it's not a valid entrypoint
8088
*/
81-
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
89+
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[]|null {
8290
const entryPoints: EntryPoint[] = [];
8391

8492
// Try to get an entry point from the top level package directory
8593
const topLevelEntryPoint =
8694
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, packagePath);
8795

8896
// If there is no primary entry-point then exit
89-
if (topLevelEntryPoint === null) {
97+
if (topLevelEntryPoint === NO_ENTRY_POINT) {
9098
return [];
9199
}
92100

101+
if (topLevelEntryPoint === INVALID_ENTRY_POINT) {
102+
return null;
103+
}
104+
93105
// Otherwise store it and search for secondary entry-points
94106
entryPoints.push(topLevelEntryPoint);
95107
this.walkDirectory(packagePath, packagePath, (path, isDirectory) => {
108+
if (!path.endsWith('.js') && !isDirectory) {
109+
return false;
110+
}
111+
96112
// If the path is a JS file then strip its extension and see if we can match an entry-point.
97113
const possibleEntryPointPath = isDirectory ? path : stripJsExtension(path);
98114
const subEntryPoint =
99115
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath);
100-
if (subEntryPoint !== null) {
101-
entryPoints.push(subEntryPoint);
116+
if (subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INVALID_ENTRY_POINT) {
117+
return false;
102118
}
119+
entryPoints.push(subEntryPoint);
120+
return true;
103121
});
104122

105123
return entryPoints;
@@ -113,26 +131,25 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
113131
*/
114132
private walkDirectory(
115133
packagePath: AbsoluteFsPath, dir: AbsoluteFsPath,
116-
fn: (path: AbsoluteFsPath, isDirectory: boolean) => void) {
134+
fn: (path: AbsoluteFsPath, isDirectory: boolean) => boolean) {
117135
return this.fs
118136
.readdir(dir)
119137
// Not interested in hidden files
120138
.filter(path => !path.startsWith('.'))
121139
// Ignore node_modules
122-
.filter(path => path !== 'node_modules')
123-
.map(path => resolve(dir, path))
140+
.filter(path => path !== 'node_modules' && path !== NGCC_DIRECTORY)
124141
.forEach(path => {
125-
const stat = this.fs.lstat(path);
142+
const absolutePath = resolve(dir, path);
143+
const stat = this.fs.lstat(absolutePath);
126144

127145
if (stat.isSymbolicLink()) {
128146
// We are not interested in symbolic links
129147
return;
130148
}
131149

132-
fn(path, stat.isDirectory());
133-
134-
if (stat.isDirectory()) {
135-
this.walkDirectory(packagePath, path, fn);
150+
const containsEntryPoint = fn(absolutePath, stat.isDirectory());
151+
if (containsEntryPoint) {
152+
this.walkDirectory(packagePath, absolutePath, fn);
136153
}
137154
});
138155
}

‎packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/depende
1010
import {Logger} from '../logging/logger';
1111
import {hasBeenProcessed} from '../packages/build_marker';
1212
import {NgccConfiguration} from '../packages/configuration';
13-
import {EntryPoint, EntryPointJsonProperty, getEntryPointInfo} from '../packages/entry_point';
13+
import {EntryPoint, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
1414
import {PathMappings} from '../utils';
1515
import {EntryPointFinder} from './interface';
1616
import {getBasePaths} from './utils';
@@ -78,20 +78,26 @@ export class TargetedEntryPointFinder implements EntryPointFinder {
7878
private processNextPath(): void {
7979
const path = this.unprocessedPaths.shift() !;
8080
const entryPoint = this.getEntryPoint(path);
81-
if (entryPoint !== null && entryPoint.compiledByAngular) {
82-
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
83-
const deps = this.resolver.getEntryPointDependencies(entryPoint);
84-
deps.dependencies.forEach(dep => {
85-
if (!this.unsortedEntryPoints.has(dep)) {
86-
this.unprocessedPaths.push(dep);
87-
}
88-
});
81+
if (entryPoint === null || !entryPoint.compiledByAngular) {
82+
return;
8983
}
84+
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
85+
const deps = this.resolver.getEntryPointDependencies(entryPoint);
86+
deps.dependencies.forEach(dep => {
87+
if (!this.unsortedEntryPoints.has(dep)) {
88+
this.unprocessedPaths.push(dep);
89+
}
90+
});
9091
}
9192

9293
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
9394
const packagePath = this.computePackagePath(entryPointPath);
94-
return getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
95+
const entryPoint =
96+
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
97+
if (entryPoint === NO_ENTRY_POINT || entryPoint === INVALID_ENTRY_POINT) {
98+
return null;
99+
}
100+
return entryPoint;
95101
}
96102

97103
/**

‎packages/compiler-cli/ngcc/src/packages/entry_point.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -75,41 +75,77 @@ export type EntryPointJsonProperty = Exclude<PackageJsonFormatProperties, 'types
7575
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
7676
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
7777

78+
79+
/**
80+
* The path does not represent an entry-point:
81+
* * there is no package.json at the path and there is no config to force an entry-point
82+
* * or the entrypoint is `ignored` by a config.
83+
*/
84+
export const NO_ENTRY_POINT = 'no-entry-point';
85+
86+
/**
87+
* The path has a package.json, but it is not a valid entry-point for ngcc processing.
88+
*/
89+
export const INVALID_ENTRY_POINT = 'invalid-entry-point';
90+
91+
/**
92+
* The result of calling `getEntryPointInfo()`.
93+
*
94+
* This will be an `EntryPoint` object if an Angular entry-point was identified;
95+
* Otherwise it will be a flag indicating one of:
96+
* * NO_ENTRY_POINT - the path is not an entry-point or ngcc is configured to ignore it
97+
* * INVALID_ENTRY_POINT - the path was a non-processable entry-point that should be searched
98+
* for sub-entry-points
99+
*/
100+
export type GetEntryPointResult = EntryPoint | typeof INVALID_ENTRY_POINT | typeof NO_ENTRY_POINT;
101+
102+
78103
/**
79104
* Try to create an entry-point from the given paths and properties.
80105
*
81106
* @param packagePath the absolute path to the containing npm package
82107
* @param entryPointPath the absolute path to the potential entry-point.
83-
* @returns An entry-point if it is valid, `null` otherwise.
108+
* @returns
109+
* - An entry-point if it is valid.
110+
* - `undefined` when there is no package.json at the path and there is no config to force an
111+
* entry-point or the entrypoint is `ignored`.
112+
* - `null` there is a package.json but it is not a valid Angular compiled entry-point.
84113
*/
85114
export function getEntryPointInfo(
86115
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
87-
entryPointPath: AbsoluteFsPath): EntryPoint|null {
116+
entryPointPath: AbsoluteFsPath): GetEntryPointResult {
88117
const packageJsonPath = resolve(entryPointPath, 'package.json');
89118
const packageVersion = getPackageVersion(fs, packageJsonPath);
90119
const entryPointConfig =
91120
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
92-
if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) {
93-
return null;
121+
const hasConfig = entryPointConfig !== undefined;
122+
123+
if (!hasConfig && !fs.exists(packageJsonPath)) {
124+
// No package.json and no config
125+
return NO_ENTRY_POINT;
94126
}
95127

96-
if (entryPointConfig !== undefined && entryPointConfig.ignore === true) {
97-
return null;
128+
if (hasConfig && entryPointConfig.ignore === true) {
129+
// Explicitly ignored
130+
return NO_ENTRY_POINT;
98131
}
99132

100-
const loadedEntryPointPackageJson =
101-
loadEntryPointPackage(fs, logger, packageJsonPath, entryPointConfig !== undefined);
102-
const entryPointPackageJson = mergeConfigAndPackageJson(
103-
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath);
133+
const loadedEntryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath, hasConfig);
134+
const entryPointPackageJson = hasConfig ?
135+
mergeConfigAndPackageJson(
136+
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath) :
137+
loadedEntryPointPackageJson;
138+
104139
if (entryPointPackageJson === null) {
105-
return null;
140+
// package.json exists but could not be parsed and there was no redeeming config
141+
return INVALID_ENTRY_POINT;
106142
}
107143

108-
// We must have a typings property
109144
const typings = entryPointPackageJson.typings || entryPointPackageJson.types ||
110145
guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson);
111146
if (typeof typings !== 'string') {
112-
return null;
147+
// Missing the required `typings` property
148+
return INVALID_ENTRY_POINT;
113149
}
114150

115151
// An entry-point is assumed to be compiled by Angular if there is either:
@@ -198,22 +234,13 @@ function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean {
198234
}
199235

200236
function mergeConfigAndPackageJson(
201-
entryPointPackageJson: EntryPointPackageJson | null,
202-
entryPointConfig: NgccEntryPointConfig | undefined, packagePath: AbsoluteFsPath,
203-
entryPointPath: AbsoluteFsPath): EntryPointPackageJson|null {
237+
entryPointPackageJson: EntryPointPackageJson | null, entryPointConfig: NgccEntryPointConfig,
238+
packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPointPackageJson {
204239
if (entryPointPackageJson !== null) {
205-
if (entryPointConfig === undefined) {
206-
return entryPointPackageJson;
207-
} else {
208-
return {...entryPointPackageJson, ...entryPointConfig.override};
209-
}
240+
return {...entryPointPackageJson, ...entryPointConfig.override};
210241
} else {
211-
if (entryPointConfig === undefined) {
212-
return null;
213-
} else {
214-
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
215-
return {name, ...entryPointConfig.override};
216-
}
242+
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
243+
return {name, ...entryPointConfig.override};
217244
}
218245
}
219246

0 commit comments

Comments
 (0)