diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts index c753df274f..cd368582de 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/directory_walker_entry_point_finder.ts @@ -5,16 +5,14 @@ * 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 {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; import {Logger} from '../../../src/ngtsc/logging'; import {EntryPointWithDependencies} from '../dependencies/dependency_host'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; -import {NgccConfiguration} from '../packages/configuration'; -import {getEntryPointInfo, IGNORED_ENTRY_POINT, INCOMPATIBLE_ENTRY_POINT, isEntryPoint, NO_ENTRY_POINT} from '../packages/entry_point'; import {EntryPointManifest} from '../packages/entry_point_manifest'; import {PathMappings} from '../path_mappings'; -import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer'; +import {EntryPointCollector} from './entry_point_collector'; import {EntryPointFinder} from './interface'; import {getBasePaths, trackDuration} from './utils'; @@ -25,9 +23,11 @@ import {getBasePaths, trackDuration} from './utils'; export class DirectoryWalkerEntryPointFinder implements EntryPointFinder { private basePaths = getBasePaths(this.logger, this.sourceDirectory, this.pathMappings); constructor( - private fs: FileSystem, private config: NgccConfiguration, private logger: Logger, - private resolver: DependencyResolver, private entryPointManifest: EntryPointManifest, - private sourceDirectory: AbsoluteFsPath, private pathMappings: PathMappings|undefined) {} + private logger: Logger, private resolver: DependencyResolver, + private entryPointCollector: EntryPointCollector, + private entryPointManifest: EntryPointManifest, private sourceDirectory: AbsoluteFsPath, + private pathMappings: PathMappings|undefined) {} + /** * Search the `sourceDirectory`, and sub-directories, using `pathMappings` as necessary, to find * all package entry-points. @@ -45,145 +45,16 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder { /** * Search the `basePath` for possible Angular packages and entry-points. * - * @param basePath The path at which to start the search + * @param basePath The path at which to start the search. * @returns an array of `EntryPoint`s that were found within `basePath`. */ walkBasePathForPackages(basePath: AbsoluteFsPath): EntryPointWithDependencies[] { this.logger.debug( `No manifest found for ${basePath} so walking the directories for entry-points.`); const entryPoints = trackDuration( - () => this.walkDirectoryForPackages(basePath), + () => this.entryPointCollector.walkDirectoryForPackages(basePath), duration => this.logger.debug(`Walking ${basePath} for entry-points took ${duration}s.`)); this.entryPointManifest.writeEntryPointManifest(basePath, entryPoints); return entryPoints; } - - /** - * Look for Angular packages that need to be compiled, starting at the source directory. - * The function will recurse into directories that start with `@...`, e.g. `@angular/...`. - * - * @param sourceDirectory An absolute path to the root directory where searching begins. - * @returns an array of `EntryPoint`s that were found within `sourceDirectory`. - */ - walkDirectoryForPackages(sourceDirectory: AbsoluteFsPath): EntryPointWithDependencies[] { - // Try to get a primary entry point from this directory - const primaryEntryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, sourceDirectory, sourceDirectory); - - // If there is an entry-point but it is not compatible with ngcc (it has a bad package.json or - // invalid typings) then exit. It is unlikely that such an entry point has a dependency on an - // Angular library. - if (primaryEntryPoint === INCOMPATIBLE_ENTRY_POINT) { - return []; - } - - const entryPoints: EntryPointWithDependencies[] = []; - if (primaryEntryPoint !== NO_ENTRY_POINT) { - if (primaryEntryPoint !== IGNORED_ENTRY_POINT) { - entryPoints.push(this.resolver.getEntryPointWithDependencies(primaryEntryPoint)); - } - this.collectSecondaryEntryPoints( - entryPoints, sourceDirectory, sourceDirectory, this.fs.readdir(sourceDirectory)); - - // Also check for any nested node_modules in this package but only if at least one of the - // entry-points was compiled by Angular. - if (entryPoints.some(e => e.entryPoint.compiledByAngular)) { - const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules'); - if (this.fs.exists(nestedNodeModulesPath)) { - entryPoints.push(...this.walkDirectoryForPackages(nestedNodeModulesPath)); - } - } - - return entryPoints; - } - - // The `sourceDirectory` was not a package (i.e. there was no package.json) - // So search its sub-directories for Angular packages and entry-points - for (const path of this.fs.readdir(sourceDirectory)) { - if (isIgnorablePath(path)) { - // Ignore hidden files, node_modules and ngcc directory - continue; - } - - const absolutePath = this.fs.resolve(sourceDirectory, path); - const stat = this.fs.lstat(absolutePath); - if (stat.isSymbolicLink() || !stat.isDirectory()) { - // Ignore symbolic links and non-directories - continue; - } - - entryPoints.push(...this.walkDirectoryForPackages(this.fs.join(sourceDirectory, path))); - } - - return entryPoints; - } - - /** - * Search the `directory` looking for any secondary entry-points for a package, adding any that - * are found to the `entryPoints` array. - * - * @param entryPoints An array where we will add any entry-points found in this directory - * @param packagePath The absolute path to the package that may contain entry-points - * @param directory The current directory being searched - * @param paths The paths contained in the current `directory`. - */ - private collectSecondaryEntryPoints( - entryPoints: EntryPointWithDependencies[], packagePath: AbsoluteFsPath, - directory: AbsoluteFsPath, paths: PathSegment[]): void { - for (const path of paths) { - if (isIgnorablePath(path)) { - // Ignore hidden files, node_modules and ngcc directory - continue; - } - - const absolutePath = this.fs.resolve(directory, path); - const stat = this.fs.lstat(absolutePath); - if (stat.isSymbolicLink()) { - // Ignore symbolic links - continue; - } - - const isDirectory = stat.isDirectory(); - if (!path.endsWith('.js') && !isDirectory) { - // Ignore files that do not end in `.js` - continue; - } - - // If the path is a JS file then strip its extension and see if we can match an - // entry-point (even if it is an ignored one). - const possibleEntryPointPath = isDirectory ? absolutePath : stripJsExtension(absolutePath); - const subEntryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath); - if (isEntryPoint(subEntryPoint)) { - entryPoints.push(this.resolver.getEntryPointWithDependencies(subEntryPoint)); - } - - if (!isDirectory) { - // This path is not a directory so we are done. - continue; - } - - // If not an entry-point itself, this directory may contain entry-points of its own. - const canContainEntryPoints = - subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INCOMPATIBLE_ENTRY_POINT; - const childPaths = this.fs.readdir(absolutePath); - if (canContainEntryPoints && - childPaths.some( - childPath => childPath.endsWith('.js') && - this.fs.stat(this.fs.resolve(absolutePath, childPath)).isFile())) { - // We do not consider non-entry-point directories that contain JS files as they are very - // unlikely to be containers for sub-entry-points. - continue; - } - this.collectSecondaryEntryPoints(entryPoints, packagePath, absolutePath, childPaths); - } - } -} - -function stripJsExtension(filePath: T): T { - return filePath.replace(/\.js$/, '') as T; -} - -function isIgnorablePath(path: PathSegment): boolean { - return path.startsWith('.') || path === 'node_modules' || path === NGCC_DIRECTORY; } diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts new file mode 100644 index 0000000000..638c9f9249 --- /dev/null +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/entry_point_collector.ts @@ -0,0 +1,154 @@ +/** + * @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 {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; +import {Logger} from '../../../src/ngtsc/logging'; + +import {EntryPointWithDependencies} from '../dependencies/dependency_host'; +import {DependencyResolver} from '../dependencies/dependency_resolver'; +import {NgccConfiguration} from '../packages/configuration'; +import {getEntryPointInfo, IGNORED_ENTRY_POINT, INCOMPATIBLE_ENTRY_POINT, isEntryPoint, NO_ENTRY_POINT} from '../packages/entry_point'; +import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer'; + +/** + * A class that traverses a file-tree, starting at a given path, looking for all entry-points, + * also capturing the dependencies of each entry-point that is found. + */ +export class EntryPointCollector { + constructor( + private fs: FileSystem, private config: NgccConfiguration, private logger: Logger, + private resolver: DependencyResolver) {} + + /** + * Look for Angular packages that need to be compiled, starting at the source directory. + * The function will recurse into directories that start with `@...`, e.g. `@angular/...`. + * + * @param sourceDirectory An absolute path to the root directory where searching begins. + * @returns an array of `EntryPoint`s that were found within `sourceDirectory`. + */ + walkDirectoryForPackages(sourceDirectory: AbsoluteFsPath): EntryPointWithDependencies[] { + // Try to get a primary entry point from this directory + const primaryEntryPoint = + getEntryPointInfo(this.fs, this.config, this.logger, sourceDirectory, sourceDirectory); + + // If there is an entry-point but it is not compatible with ngcc (it has a bad package.json or + // invalid typings) then exit. It is unlikely that such an entry point has a dependency on an + // Angular library. + if (primaryEntryPoint === INCOMPATIBLE_ENTRY_POINT) { + return []; + } + + const entryPoints: EntryPointWithDependencies[] = []; + if (primaryEntryPoint !== NO_ENTRY_POINT) { + if (primaryEntryPoint !== IGNORED_ENTRY_POINT) { + entryPoints.push(this.resolver.getEntryPointWithDependencies(primaryEntryPoint)); + } + this.collectSecondaryEntryPoints( + entryPoints, sourceDirectory, sourceDirectory, this.fs.readdir(sourceDirectory)); + + // Also check for any nested node_modules in this package but only if at least one of the + // entry-points was compiled by Angular. + if (entryPoints.some(e => e.entryPoint.compiledByAngular)) { + const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules'); + if (this.fs.exists(nestedNodeModulesPath)) { + entryPoints.push(...this.walkDirectoryForPackages(nestedNodeModulesPath)); + } + } + + return entryPoints; + } + + // The `sourceDirectory` was not a package (i.e. there was no package.json) + // So search its sub-directories for Angular packages and entry-points + for (const path of this.fs.readdir(sourceDirectory)) { + if (isIgnorablePath(path)) { + // Ignore hidden files, node_modules and ngcc directory + continue; + } + + const absolutePath = this.fs.resolve(sourceDirectory, path); + const stat = this.fs.lstat(absolutePath); + if (stat.isSymbolicLink() || !stat.isDirectory()) { + // Ignore symbolic links and non-directories + continue; + } + + entryPoints.push(...this.walkDirectoryForPackages(this.fs.join(sourceDirectory, path))); + } + + return entryPoints; + } + + /** + * Search the `directory` looking for any secondary entry-points for a package, adding any that + * are found to the `entryPoints` array. + * + * @param entryPoints An array where we will add any entry-points found in this directory. + * @param packagePath The absolute path to the package that may contain entry-points. + * @param directory The current directory being searched. + * @param paths The paths contained in the current `directory`. + */ + private collectSecondaryEntryPoints( + entryPoints: EntryPointWithDependencies[], packagePath: AbsoluteFsPath, + directory: AbsoluteFsPath, paths: PathSegment[]): void { + for (const path of paths) { + if (isIgnorablePath(path)) { + // Ignore hidden files, node_modules and ngcc directory + continue; + } + + const absolutePath = this.fs.resolve(directory, path); + const stat = this.fs.lstat(absolutePath); + if (stat.isSymbolicLink()) { + // Ignore symbolic links + continue; + } + + const isDirectory = stat.isDirectory(); + if (!path.endsWith('.js') && !isDirectory) { + // Ignore files that do not end in `.js` + continue; + } + + // If the path is a JS file then strip its extension and see if we can match an + // entry-point (even if it is an ignored one). + const possibleEntryPointPath = isDirectory ? absolutePath : stripJsExtension(absolutePath); + const subEntryPoint = + getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath); + if (isEntryPoint(subEntryPoint)) { + entryPoints.push(this.resolver.getEntryPointWithDependencies(subEntryPoint)); + } + + if (!isDirectory) { + // This path is not a directory so we are done. + continue; + } + + // If not an entry-point itself, this directory may contain entry-points of its own. + const canContainEntryPoints = + subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INCOMPATIBLE_ENTRY_POINT; + const childPaths = this.fs.readdir(absolutePath); + if (canContainEntryPoints && + childPaths.some( + childPath => childPath.endsWith('.js') && + this.fs.stat(this.fs.resolve(absolutePath, childPath)).isFile())) { + // We do not consider non-entry-point directories that contain JS files as they are very + // unlikely to be containers for sub-entry-points. + continue; + } + this.collectSecondaryEntryPoints(entryPoints, packagePath, absolutePath, childPaths); + } + } +} + +function stripJsExtension(filePath: T): T { + return filePath.replace(/\.js$/, '') as T; +} + +function isIgnorablePath(path: PathSegment): boolean { + return path.startsWith('.') || path === 'node_modules' || path === NGCC_DIRECTORY; +} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts index c57c99ea0a..35bf766afa 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/program_based_entry_point_finder.ts @@ -9,14 +9,17 @@ import {AbsoluteFsPath, FileSystem} from '../../../src/ngtsc/file_system'; import {Logger} from '../../../src/ngtsc/logging'; import {ParsedConfiguration} from '../../../src/perform_compile'; -import {createDependencyInfo} from '../dependencies/dependency_host'; +import {createDependencyInfo, EntryPointWithDependencies} from '../dependencies/dependency_host'; import {DependencyResolver} from '../dependencies/dependency_resolver'; import {EsmDependencyHost} from '../dependencies/esm_dependency_host'; import {ModuleResolver} from '../dependencies/module_resolver'; import {NgccConfiguration} from '../packages/configuration'; +import {EntryPointManifest} from '../packages/entry_point_manifest'; import {getPathMappingsFromTsConfig} from '../path_mappings'; +import {EntryPointCollector} from './entry_point_collector'; import {TracingEntryPointFinder} from './tracing_entry_point_finder'; +import {trackDuration} from './utils'; /** * An EntryPointFinder that starts from the files in the program defined by the given tsconfig.json @@ -26,14 +29,21 @@ import {TracingEntryPointFinder} from './tracing_entry_point_finder'; * and is used primarily by the CLI integration. */ export class ProgramBasedEntryPointFinder extends TracingEntryPointFinder { + private entryPointsWithDependencies: Map|null = null; + constructor( fs: FileSystem, config: NgccConfiguration, logger: Logger, resolver: DependencyResolver, - basePath: AbsoluteFsPath, private tsConfig: ParsedConfiguration, - projectPath: AbsoluteFsPath) { + private entryPointCollector: EntryPointCollector, + private entryPointManifest: EntryPointManifest, basePath: AbsoluteFsPath, + private tsConfig: ParsedConfiguration, projectPath: AbsoluteFsPath) { super( fs, config, logger, resolver, basePath, getPathMappingsFromTsConfig(tsConfig, projectPath)); } + /** + * Return an array containing the external import paths that were extracted from the source-files + * of the program defined by the tsconfig.json. + */ protected getInitialEntryPointPaths(): AbsoluteFsPath[] { const moduleResolver = new ModuleResolver(this.fs, this.pathMappings, ['', '.ts', '/index.ts']); const host = new EsmDependencyHost(this.fs, moduleResolver); @@ -48,4 +58,64 @@ export class ProgramBasedEntryPointFinder extends TracingEntryPointFinder { }); return Array.from(dependencies.dependencies); } -} \ No newline at end of file + + /** + * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including + * paths to other entry-points that this entry-point depends upon. + * + * In this entry-point finder, we use the `EntryPointManifest` to avoid computing each + * entry-point's dependencies in the case that this had been done previously. + * + * @param entryPointPath the path to the entry-point whose information and dependencies are to be + * retrieved or computed. + * + * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by + * Angular or cannot be determined. + */ + protected getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): EntryPointWithDependencies|null { + const entryPoints = this.findOrLoadEntryPoints(); + if (!entryPoints.has(entryPointPath)) { + return null; + } + const entryPointWithDeps = entryPoints.get(entryPointPath)!; + if (!entryPointWithDeps.entryPoint.compiledByAngular) { + return null; + } + return entryPointWithDeps; + } + + /** + * Walk the base paths looking for entry-points or load this information from an entry-point + * manifest, if available. + */ + private findOrLoadEntryPoints(): Map { + if (this.entryPointsWithDependencies === null) { + const entryPointsWithDependencies = this.entryPointsWithDependencies = + new Map(); + for (const basePath of this.getBasePaths()) { + const entryPoints = this.entryPointManifest.readEntryPointsUsingManifest(basePath) || + this.walkBasePathForPackages(basePath); + for (const e of entryPoints) { + entryPointsWithDependencies.set(e.entryPoint.path, e); + } + } + } + return this.entryPointsWithDependencies; + } + + /** + * Search the `basePath` for possible Angular packages and entry-points. + * + * @param basePath The path at which to start the search. + * @returns an array of `EntryPoint`s that were found within `basePath`. + */ + walkBasePathForPackages(basePath: AbsoluteFsPath): EntryPointWithDependencies[] { + this.logger.debug( + `No manifest found for ${basePath} so walking the directories for entry-points.`); + const entryPoints = trackDuration( + () => this.entryPointCollector.walkDirectoryForPackages(basePath), + duration => this.logger.debug(`Walking ${basePath} for entry-points took ${duration}s.`)); + this.entryPointManifest.writeEntryPointManifest(basePath, entryPoints); + return entryPoints; + } +} diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts index d0dcaa9e8d..d39f65d78c 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/targeted_entry_point_finder.ts @@ -5,12 +5,13 @@ * 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 {AbsoluteFsPath, FileSystem} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath, FileSystem, join, PathSegment, relative, relativeFrom} from '../../../src/ngtsc/file_system'; import {Logger} from '../../../src/ngtsc/logging'; +import {EntryPointWithDependencies} from '../dependencies/dependency_host'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; import {hasBeenProcessed} from '../packages/build_marker'; import {NgccConfiguration} from '../packages/configuration'; -import {EntryPointJsonProperty} from '../packages/entry_point'; +import {EntryPointJsonProperty, getEntryPointInfo, isEntryPoint} from '../packages/entry_point'; import {PathMappings} from '../path_mappings'; import {TracingEntryPointFinder} from './tracing_entry_point_finder'; @@ -30,6 +31,10 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder { super(fs, config, logger, resolver, basePath, pathMappings); } + /** + * Search for Angular entry-points that can be reached from the entry-point specified by the given + * `targetPath`. + */ findEntryPoints(): SortedEntryPointsInfo { const entryPoints = super.findEntryPoints(); @@ -43,17 +48,25 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder { return entryPoints; } + /** + * Determine whether the entry-point at the given `targetPath` needs to be processed. + * + * @param propertiesToConsider the package.json properties that should be considered for + * processing. + * @param compileAllFormats true if all formats need to be processed, or false if it is enough for + * one of the formats covered by the `propertiesToConsider` is processed. + */ targetNeedsProcessingOrCleaning( propertiesToConsider: EntryPointJsonProperty[], compileAllFormats: boolean): boolean { - const entryPoint = this.getEntryPoint(this.targetPath); - if (entryPoint === null || !entryPoint.compiledByAngular) { + const entryPointWithDeps = this.getEntryPointWithDeps(this.targetPath); + if (entryPointWithDeps === null) { return false; } for (const property of propertiesToConsider) { - if (entryPoint.packageJson[property]) { + if (entryPointWithDeps.entryPoint.packageJson[property]) { // Here is a property that should be processed. - if (!hasBeenProcessed(entryPoint.packageJson, property)) { + if (!hasBeenProcessed(entryPointWithDeps.entryPoint.packageJson, property)) { return true; } if (!compileAllFormats) { @@ -67,7 +80,160 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder { return false; } + /** + * Return an array containing the `targetPath` from which to start the trace. + */ protected getInitialEntryPointPaths(): AbsoluteFsPath[] { return [this.targetPath]; } + + /** + * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including + * paths to other entry-points that this entry-point depends upon. + * + * @param entryPointPath the path to the entry-point whose information and dependencies are to be + * retrieved or computed. + * + * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by + * Angular or cannot be determined. + */ + protected getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): EntryPointWithDependencies|null { + const packagePath = this.computePackagePath(entryPointPath); + const entryPoint = + getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath); + if (!isEntryPoint(entryPoint) || !entryPoint.compiledByAngular) { + return null; + } + return this.resolver.getEntryPointWithDependencies(entryPoint); + } + + /** + * Compute the path to the package that contains the given entry-point. + * + * In this entry-point finder it is not trivial to find the containing package, since it is + * possible that this entry-point is not directly below the directory containing the package. + * Moreover, the import path could be affected by path-mapping. + * + * @param entryPointPath the path to the entry-point, whose package path we want to compute. + */ + private computePackagePath(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { + // First try the main basePath, to avoid having to compute the other basePaths from the paths + // mappings, which can be computationally intensive. + if (entryPointPath.startsWith(this.basePath)) { + const packagePath = this.computePackagePathFromContainingPath(entryPointPath, this.basePath); + if (packagePath !== null) { + return packagePath; + } + } + + // The main `basePath` didn't work out so now we try the `basePaths` computed from the paths + // mappings in `tsconfig.json`. + for (const basePath of this.getBasePaths()) { + if (entryPointPath.startsWith(basePath)) { + const packagePath = this.computePackagePathFromContainingPath(entryPointPath, basePath); + if (packagePath !== null) { + return packagePath; + } + // If we got here then we couldn't find a `packagePath` for the current `basePath`. + // Since `basePath`s are guaranteed not to be a sub-directory of each other then no other + // `basePath` will match either. + break; + } + } + + // Finally, if we couldn't find a `packagePath` using `basePaths` then try to find the nearest + // `node_modules` that contains the `entryPointPath`, if there is one, and use it as a + // `basePath`. + return this.computePackagePathFromNearestNodeModules(entryPointPath); + } + + + /** + * Search down to the `entryPointPath` from the `containingPath` for the first `package.json` that + * we come to. This is the path to the entry-point's containing package. For example if + * `containingPath` is `/a/b/c` and `entryPointPath` is `/a/b/c/d/e` and there exists + * `/a/b/c/d/package.json` and `/a/b/c/d/e/package.json`, then we will return `/a/b/c/d`. + * + * To account for nested `node_modules` we actually start the search at the last `node_modules` in + * the `entryPointPath` that is below the `containingPath`. E.g. if `containingPath` is `/a/b/c` + * and `entryPointPath` is `/a/b/c/d/node_modules/x/y/z`, we start the search at + * `/a/b/c/d/node_modules`. + */ + private computePackagePathFromContainingPath( + entryPointPath: AbsoluteFsPath, containingPath: AbsoluteFsPath): AbsoluteFsPath|null { + let packagePath = containingPath; + const segments = this.splitPath(relative(containingPath, entryPointPath)); + let nodeModulesIndex = segments.lastIndexOf(relativeFrom('node_modules')); + + // If there are no `node_modules` in the relative path between the `basePath` and the + // `entryPointPath` then just try the `basePath` as the `packagePath`. + // (This can be the case with path-mapped entry-points.) + if (nodeModulesIndex === -1) { + if (this.fs.exists(join(packagePath, 'package.json'))) { + return packagePath; + } + } + + // Start the search at the deepest nested `node_modules` folder that is below the `basePath` + // but above the `entryPointPath`, if there are any. + while (nodeModulesIndex >= 0) { + packagePath = join(packagePath, segments.shift()!); + nodeModulesIndex--; + } + + // Note that we start at the folder below the current candidate `packagePath` because the + // initial candidate `packagePath` is either a `node_modules` folder or the `basePath` with + // no `package.json`. + for (const segment of segments) { + packagePath = join(packagePath, segment); + if (this.fs.exists(join(packagePath, 'package.json'))) { + return packagePath; + } + } + return null; + } + + /** + * Search up the directory tree from the `entryPointPath` looking for a `node_modules` directory + * that we can use as a potential starting point for computing the package path. + */ + private computePackagePathFromNearestNodeModules(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { + let packagePath = entryPointPath; + let scopedPackagePath = packagePath; + let containerPath = this.fs.dirname(packagePath); + while (!this.fs.isRoot(containerPath) && !containerPath.endsWith('node_modules')) { + scopedPackagePath = packagePath; + packagePath = containerPath; + containerPath = this.fs.dirname(containerPath); + } + + if (this.fs.exists(join(packagePath, 'package.json'))) { + // The directory directly below `node_modules` is a package - use it + return packagePath; + } else if ( + this.fs.basename(packagePath).startsWith('@') && + this.fs.exists(join(scopedPackagePath, 'package.json'))) { + // The directory directly below the `node_modules` is a scope and the directory directly + // below that is a scoped package - use it + return scopedPackagePath; + } else { + // If we get here then none of the `basePaths` contained the `entryPointPath` and the + // `entryPointPath` contains no `node_modules` that contains a package or a scoped + // package. All we can do is assume that this entry-point is a primary entry-point to a + // package. + return entryPointPath; + } + } + + /** + * Split the given `path` into path segments using an FS independent algorithm. + */ + private splitPath(path: PathSegment) { + const segments = []; + while (path !== '.') { + segments.unshift(this.fs.basename(path)); + path = this.fs.dirname(path); + } + return segments; + } } diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts index 18132db1af..578b184247 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/tracing_entry_point_finder.ts @@ -5,13 +5,12 @@ * 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 {AbsoluteFsPath, FileSystem, join, PathSegment, relative, relativeFrom} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath, FileSystem} from '../../../src/ngtsc/file_system'; import {Logger} from '../../../src/ngtsc/logging'; import {EntryPointWithDependencies} from '../dependencies/dependency_host'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; import {NgccConfiguration} from '../packages/configuration'; -import {EntryPoint, getEntryPointInfo, isEntryPoint} from '../packages/entry_point'; import {PathMappings} from '../path_mappings'; import {EntryPointFinder} from './interface'; @@ -21,18 +20,19 @@ import {getBasePaths} from './utils'; * An EntryPointFinder that starts from a set of initial files and only returns entry-points that * are dependencies of these files. * - * This is faster than searching the entire file-system for all the entry-points, - * and is used primarily by the CLI integration. + * This is faster than processing all entry-points in the entire file-system, and is used primarily + * by the CLI integration. * * There are two concrete implementations of this class. * - * * `TargetEntryPointFinder` - is given a single entry-point as the initial entry-point - * * `ProgramBasedEntryPointFinder` - computes the initial entry-points from program files given by - * a `tsconfig.json` file. + * * `TargetEntryPointFinder` - is given a single entry-point as the initial entry-point. This can + * be used in the synchronous CLI integration where the build tool has identified an external + * import to one of the source files being built. + * * `ProgramBasedEntryPointFinder` - computes the initial entry-points from the source files + * computed from a `tsconfig.json` file. This can be used in the asynchronous CLI integration + * where the `tsconfig.json` to be used to do the build is known. */ export abstract class TracingEntryPointFinder implements EntryPointFinder { - protected unprocessedPaths: AbsoluteFsPath[] = []; - protected unsortedEntryPoints = new Map(); private basePaths: AbsoluteFsPath[]|null = null; constructor( @@ -40,165 +40,59 @@ export abstract class TracingEntryPointFinder implements EntryPointFinder { protected resolver: DependencyResolver, protected basePath: AbsoluteFsPath, protected pathMappings: PathMappings|undefined) {} + /** + * Search for Angular package entry-points. + */ + findEntryPoints(): SortedEntryPointsInfo { + const unsortedEntryPoints = new Map(); + const unprocessedPaths = this.getInitialEntryPointPaths(); + while (unprocessedPaths.length > 0) { + const path = unprocessedPaths.shift()!; + const entryPointWithDeps = this.getEntryPointWithDeps(path); + if (entryPointWithDeps === null) { + continue; + } + unsortedEntryPoints.set(entryPointWithDeps.entryPoint.path, entryPointWithDeps); + entryPointWithDeps.depInfo.dependencies.forEach(dep => { + if (!unsortedEntryPoints.has(dep)) { + unprocessedPaths.push(dep); + } + }); + } + return this.resolver.sortEntryPointsByDependency(Array.from(unsortedEntryPoints.values())); + } + + + /** + * Return an array of entry-point paths from which to start the trace. + */ + protected abstract getInitialEntryPointPaths(): AbsoluteFsPath[]; + + /** + * For the given `entryPointPath`, compute, or retrieve, the entry-point information, including + * paths to other entry-points that this entry-point depends upon. + * + * @param entryPointPath the path to the entry-point whose information and dependencies are to be + * retrieved or computed. + * + * @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by + * Angular or cannot be determined. + */ + protected abstract getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): + EntryPointWithDependencies|null; + + + /** + * Parse the path-mappings to compute the base-paths that need to be considered when finding + * entry-points. + * + * This processing can be time-consuming if the path-mappings are complex or extensive. + * So the result is cached locally once computed. + */ protected getBasePaths() { if (this.basePaths === null) { this.basePaths = getBasePaths(this.logger, this.basePath, this.pathMappings); } return this.basePaths; } - - findEntryPoints(): SortedEntryPointsInfo { - this.unprocessedPaths = this.getInitialEntryPointPaths(); - while (this.unprocessedPaths.length > 0) { - this.processNextPath(); - } - return this.resolver.sortEntryPointsByDependency(Array.from(this.unsortedEntryPoints.values())); - } - - protected abstract getInitialEntryPointPaths(): AbsoluteFsPath[]; - - protected getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null { - const packagePath = this.computePackagePath(entryPointPath); - const entryPoint = - getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath); - - return isEntryPoint(entryPoint) ? entryPoint : null; - } - - private processNextPath(): void { - const path = this.unprocessedPaths.shift()!; - const entryPoint = this.getEntryPoint(path); - if (entryPoint === null || !entryPoint.compiledByAngular) { - return; - } - const entryPointWithDeps = this.resolver.getEntryPointWithDependencies(entryPoint); - this.unsortedEntryPoints.set(entryPoint.path, entryPointWithDeps); - entryPointWithDeps.depInfo.dependencies.forEach(dep => { - if (!this.unsortedEntryPoints.has(dep)) { - this.unprocessedPaths.push(dep); - } - }); - } - - private computePackagePath(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { - // First try the main basePath, to avoid having to compute the other basePaths from the paths - // mappings, which can be computationally intensive. - if (entryPointPath.startsWith(this.basePath)) { - const packagePath = this.computePackagePathFromContainingPath(entryPointPath, this.basePath); - if (packagePath !== null) { - return packagePath; - } - } - - // The main `basePath` didn't work out so now we try the `basePaths` computed from the paths - // mappings in `tsconfig.json`. - for (const basePath of this.getBasePaths()) { - if (entryPointPath.startsWith(basePath)) { - const packagePath = this.computePackagePathFromContainingPath(entryPointPath, basePath); - if (packagePath !== null) { - return packagePath; - } - // If we got here then we couldn't find a `packagePath` for the current `basePath`. - // Since `basePath`s are guaranteed not to be a sub-directory of each other then no other - // `basePath` will match either. - break; - } - } - - // Finally, if we couldn't find a `packagePath` using `basePaths` then try to find the nearest - // `node_modules` that contains the `entryPointPath`, if there is one, and use it as a - // `basePath`. - return this.computePackagePathFromNearestNodeModules(entryPointPath); - } - - - /** - * Search down to the `entryPointPath` from the `containingPath` for the first `package.json` that - * we come to. This is the path to the entry-point's containing package. For example if - * `containingPath` is `/a/b/c` and `entryPointPath` is `/a/b/c/d/e` and there exists - * `/a/b/c/d/package.json` and `/a/b/c/d/e/package.json`, then we will return `/a/b/c/d`. - * - * To account for nested `node_modules` we actually start the search at the last `node_modules` in - * the `entryPointPath` that is below the `containingPath`. E.g. if `containingPath` is `/a/b/c` - * and `entryPointPath` is `/a/b/c/d/node_modules/x/y/z`, we start the search at - * `/a/b/c/d/node_modules`. - */ - private computePackagePathFromContainingPath( - entryPointPath: AbsoluteFsPath, containingPath: AbsoluteFsPath): AbsoluteFsPath|null { - let packagePath = containingPath; - const segments = this.splitPath(relative(containingPath, entryPointPath)); - let nodeModulesIndex = segments.lastIndexOf(relativeFrom('node_modules')); - - // If there are no `node_modules` in the relative path between the `basePath` and the - // `entryPointPath` then just try the `basePath` as the `packagePath`. - // (This can be the case with path-mapped entry-points.) - if (nodeModulesIndex === -1) { - if (this.fs.exists(join(packagePath, 'package.json'))) { - return packagePath; - } - } - - // Start the search at the deepest nested `node_modules` folder that is below the `basePath` - // but above the `entryPointPath`, if there are any. - while (nodeModulesIndex >= 0) { - packagePath = join(packagePath, segments.shift()!); - nodeModulesIndex--; - } - - // Note that we start at the folder below the current candidate `packagePath` because the - // initial candidate `packagePath` is either a `node_modules` folder or the `basePath` with - // no `package.json`. - for (const segment of segments) { - packagePath = join(packagePath, segment); - if (this.fs.exists(join(packagePath, 'package.json'))) { - return packagePath; - } - } - return null; - } - - /** - * Search up the directory tree from the `entryPointPath` looking for a `node_modules` directory - * that we can use as a potential starting point for computing the package path. - */ - private computePackagePathFromNearestNodeModules(entryPointPath: AbsoluteFsPath): AbsoluteFsPath { - let packagePath = entryPointPath; - let scopedPackagePath = packagePath; - let containerPath = this.fs.dirname(packagePath); - while (!this.fs.isRoot(containerPath) && !containerPath.endsWith('node_modules')) { - scopedPackagePath = packagePath; - packagePath = containerPath; - containerPath = this.fs.dirname(containerPath); - } - - if (this.fs.exists(join(packagePath, 'package.json'))) { - // The directory directly below `node_modules` is a package - use it - return packagePath; - } else if ( - this.fs.basename(packagePath).startsWith('@') && - this.fs.exists(join(scopedPackagePath, 'package.json'))) { - // The directory directly below the `node_modules` is a scope and the directory directly - // below that is a scoped package - use it - return scopedPackagePath; - } else { - // If we get here then none of the `basePaths` contained the `entryPointPath` and the - // `entryPointPath` contains no `node_modules` that contains a package or a scoped - // package. All we can do is assume that this entry-point is a primary entry-point to a - // package. - return entryPointPath; - } - } - - /** - * Split the given `path` into path segments using an FS independent algorithm. - * @param path The path to split. - */ - private splitPath(path: PathSegment) { - const segments = []; - while (path !== '.') { - segments.unshift(this.fs.basename(path)); - path = this.fs.dirname(path); - } - return segments; - } } diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts index 6cb446a649..37099f1e98 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts @@ -82,8 +82,8 @@ function extractPathPrefix(path: string) { /** * Run a task and track how long it takes. * - * @param task The task whose duration we are tracking - * @param log The function to call with the duration of the task + * @param task The task whose duration we are tracking. + * @param log The function to call with the duration of the task. * @returns The result of calling `task`. */ export function trackDuration(task: () => T extends Promise? never : T, @@ -136,7 +136,7 @@ function addPath(root: Node, path: AbsoluteFsPath): void { } /** - * Flatten the tree of nodes back into an array of absolute paths + * Flatten the tree of nodes back into an array of absolute paths. */ function flattenTree(root: Node): AbsoluteFsPath[] { const paths: AbsoluteFsPath[] = []; diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index c46cb9b8a6..d87bd556cd 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -21,6 +21,7 @@ import {EsmDependencyHost} from './dependencies/esm_dependency_host'; import {ModuleResolver} from './dependencies/module_resolver'; import {UmdDependencyHost} from './dependencies/umd_dependency_host'; import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder'; +import {EntryPointCollector} from './entry_point_finder/entry_point_collector'; import {EntryPointFinder} from './entry_point_finder/interface'; import {ProgramBasedEntryPointFinder} from './entry_point_finder/program_based_entry_point_finder'; import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder'; @@ -203,10 +204,15 @@ function getEntryPointFinder( if (absoluteTargetEntryPointPath !== null) { return new TargetedEntryPointFinder( fs, config, logger, resolver, basePath, pathMappings, absoluteTargetEntryPointPath); - } else if (tsConfig !== null) { - return new ProgramBasedEntryPointFinder( - fs, config, logger, resolver, basePath, tsConfig, projectPath); + } else { + const entryPointCollector = new EntryPointCollector(fs, config, logger, resolver); + if (tsConfig !== null) { + return new ProgramBasedEntryPointFinder( + fs, config, logger, resolver, entryPointCollector, entryPointManifest, basePath, tsConfig, + projectPath); + } else { + return new DirectoryWalkerEntryPointFinder( + logger, resolver, entryPointCollector, entryPointManifest, basePath, pathMappings); + } } - return new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, entryPointManifest, basePath, pathMappings); } diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts index 0127aa8891..a3ecc66be6 100644 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts @@ -14,6 +14,7 @@ import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {DirectoryWalkerEntryPointFinder} from '../../src/entry_point_finder/directory_walker_entry_point_finder'; +import {EntryPointCollector} from '../../src/entry_point_finder/entry_point_collector'; import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPointManifest, EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; @@ -25,6 +26,7 @@ runInEachFileSystem(() => { let resolver: DependencyResolver; let logger: MockLogger; let config: NgccConfiguration; + let collector: EntryPointCollector; let manifest: EntryPointManifest; let _Abs: typeof absoluteFrom; @@ -37,6 +39,7 @@ runInEachFileSystem(() => { config = new NgccConfiguration(fs, _Abs('/')); resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); manifest = new EntryPointManifest(fs, config, logger); + collector = new EntryPointCollector(fs, config, logger, resolver); }); describe('findEntryPoints()', () => { @@ -50,7 +53,7 @@ runInEachFileSystem(() => { ...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']), ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ ['common', 'common'], @@ -71,7 +74,7 @@ runInEachFileSystem(() => { ...createPackage(fs.resolve(basePath, '@angular/common'), 'testing', ['@angular/common']), ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ ['@angular/common', '@angular/common'], @@ -84,7 +87,7 @@ runInEachFileSystem(() => { it('should return an empty array if there are no packages', () => { fs.ensureDir(_Abs('/no_packages/node_modules/should_not_be_found')); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/no_packages/node_modules'), undefined); + logger, resolver, collector, manifest, _Abs('/no_packages/node_modules'), undefined); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); }); @@ -97,7 +100,7 @@ runInEachFileSystem(() => { }, ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/no_valid_entry_points/node_modules'), + logger, resolver, collector, manifest, _Abs('/no_valid_entry_points/node_modules'), undefined); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); @@ -106,7 +109,7 @@ runInEachFileSystem(() => { it('should not include ignored entry-points', () => { const basePath = _Abs('/project/node_modules'); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); loadTestFiles(createPackage(basePath, 'some-package')); spyOn(config, 'getPackageConfig') @@ -124,7 +127,7 @@ runInEachFileSystem(() => { it('should look for sub-entry-points even if a containing entry-point is ignored', () => { const basePath = _Abs('/project/node_modules'); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); loadTestFiles([ ...createPackage(basePath, 'some-package'), @@ -161,7 +164,7 @@ runInEachFileSystem(() => { spyOn(manifest, 'readEntryPointsUsingManifest').and.callThrough(); spyOn(manifest, 'writeEntryPointManifest').and.callThrough(); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); finder.findEntryPoints(); expect(manifest.readEntryPointsUsingManifest).toHaveBeenCalled(); expect(manifest.writeEntryPointManifest).toHaveBeenCalled(); @@ -182,7 +185,7 @@ runInEachFileSystem(() => { spyOn(manifest, 'readEntryPointsUsingManifest').and.callThrough(); spyOn(manifest, 'writeEntryPointManifest').and.callThrough(); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); finder.findEntryPoints(); expect(manifest.readEntryPointsUsingManifest).toHaveBeenCalled(); expect(manifest.writeEntryPointManifest).toHaveBeenCalled(); @@ -200,7 +203,7 @@ runInEachFileSystem(() => { {name: _Abs('/sub_entry_points/yarn.lock'), contents: 'MOCK LOCK FILE'}, ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); // Prime the manifest by calling findEntryPoints() once. finder.findEntryPoints(); @@ -229,8 +232,7 @@ runInEachFileSystem(() => { ...createPackage(_Abs('/dotted_folders/node_modules/'), '.common'), ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/dotted_folders/node_modules'), - undefined); + logger, resolver, collector, manifest, _Abs('/dotted_folders/node_modules'), undefined); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); }); @@ -241,7 +243,7 @@ runInEachFileSystem(() => { _Abs('/external/node_modules/common'), _Abs('/symlinked_folders/node_modules/common')); loadTestFiles(createPackage(_Abs('/external/node_modules'), 'common')); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/symlinked_folders/node_modules'), + logger, resolver, collector, manifest, _Abs('/symlinked_folders/node_modules'), undefined); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); @@ -253,7 +255,7 @@ runInEachFileSystem(() => { ...createPackage(_Abs('/nested_node_modules/node_modules/outer/node_modules'), 'inner'), ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/nested_node_modules/node_modules'), + logger, resolver, collector, manifest, _Abs('/nested_node_modules/node_modules'), undefined); const {entryPoints} = finder.findEntryPoints(); // Note that the `inner` entry-point is not part of the `outer` package @@ -272,9 +274,9 @@ runInEachFileSystem(() => { ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, _Abs('/nested_node_modules/node_modules'), + logger, resolver, collector, manifest, _Abs('/nested_node_modules/node_modules'), undefined); - const spy = spyOn(finder, 'walkDirectoryForPackages').and.callThrough(); + const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); const {entryPoints} = finder.findEntryPoints(); expect(spy.calls.allArgs()).toEqual([ [_Abs(basePath)], @@ -295,8 +297,8 @@ runInEachFileSystem(() => { ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); - const spy = spyOn(finder, 'walkDirectoryForPackages').and.callThrough(); + logger, resolver, collector, manifest, basePath, undefined); + const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); const {entryPoints} = finder.findEntryPoints(); expect(spy.calls.allArgs()).toEqual([ [_Abs(basePath)], @@ -319,8 +321,8 @@ runInEachFileSystem(() => { ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); - const spy = spyOn(finder, 'walkDirectoryForPackages').and.callThrough(); + logger, resolver, collector, manifest, basePath, undefined); + const spy = spyOn(collector, 'walkDirectoryForPackages').and.callThrough(); const {entryPoints} = finder.findEntryPoints(); expect(spy.calls.allArgs()).toEqual([ [_Abs(basePath)], @@ -339,7 +341,7 @@ runInEachFileSystem(() => { ...createPackage(fs.resolve(basePath, 'package/container'), 'entry-point-1'), ]); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, undefined); + logger, resolver, collector, manifest, basePath, undefined); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ ['package', 'package'], @@ -367,8 +369,9 @@ runInEachFileSystem(() => { const srcHost = new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings)); const dtsHost = new DtsDependencyHost(fs, pathMappings); resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); + collector = new EntryPointCollector(fs, config, logger, resolver); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, pathMappings); + logger, resolver, collector, manifest, basePath, pathMappings); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ ['pkg1', 'pkg1'], @@ -396,7 +399,7 @@ runInEachFileSystem(() => { const dtsHost = new DtsDependencyHost(fs, pathMappings); resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); const finder = new DirectoryWalkerEntryPointFinder( - fs, config, logger, resolver, manifest, basePath, pathMappings); + logger, resolver, collector, manifest, basePath, pathMappings); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ ['test', 'test'], diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts index 202fc952dd..d48d7cf703 100644 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/entry_point_finder/program_based_entry_point_finder_spec.ts @@ -15,6 +15,7 @@ import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; +import {EntryPointCollector} from '../../src/entry_point_finder/entry_point_collector'; import {ProgramBasedEntryPointFinder} from '../../src/entry_point_finder/program_based_entry_point_finder'; import {NgccConfiguration} from '../../src/packages/configuration'; import {EntryPoint} from '../../src/packages/entry_point'; @@ -68,9 +69,10 @@ runInEachFileSystem(() => { const dtsHost = new DtsDependencyHost(fs); const config = new NgccConfiguration(fs, projectPath); const resolver = new DependencyResolver(fs, logger, config, {esm2015: srcHost}, dtsHost); + const collector = new EntryPointCollector(fs, config, logger, resolver); const manifest = new EntryPointManifest(fs, config, logger); return new ProgramBasedEntryPointFinder( - fs, config, logger, resolver, basePath, tsConfig, projectPath); + fs, config, logger, resolver, collector, manifest, basePath, tsConfig, projectPath); } function createProgram(projectPath: AbsoluteFsPath): TestFile[] {