diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index 1bde0dddd9..4c38d55471 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -24,6 +24,11 @@ export interface NgccOptions { formats: EntryPointFormat[]; /** The path to the node_modules folder where modified files should be written. */ baseTargetPath?: string; + /** + * The path, relative to `baseSourcePath` of the primary package to be compiled. + * All its dependencies will need to be compiled too. + */ + targetEntryPointPath?: string; } /** @@ -34,14 +39,14 @@ export interface NgccOptions { * * @param options The options telling ngcc what to compile and how. */ -export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePath}: NgccOptions): - void { +export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePath, + targetEntryPointPath}: NgccOptions): void { const transformer = new Transformer(baseSourcePath, baseTargetPath); const host = new DependencyHost(); const resolver = new DependencyResolver(host); const finder = new EntryPointFinder(resolver); - const {entryPoints} = finder.findEntryPoints(baseSourcePath); + const {entryPoints} = finder.findEntryPoints(baseSourcePath, targetEntryPointPath); entryPoints.forEach(entryPoint => { // Are we compiling the Angular core? diff --git a/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts index 8fffb6e90e..a7a372d7ba 100644 --- a/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts +++ b/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts @@ -44,7 +44,7 @@ export interface IgnoredDependency { } /** - * The result of sorting the entry-points by their dependencies. + * A list of entry-points, sorted by their dependencies. * * The `entryPoints` array will be ordered so that no entry point depends upon an entry point that * appears later in the array. @@ -67,9 +67,35 @@ export class DependencyResolver { * Sort the array of entry points so that the dependant entry points always come later than * their dependencies in the array. * @param entryPoints An array entry points to sort. - * @returns the result of sorting the entry points. + * @param target If provided, only return entry-points depended on by this entry-point. + * @returns the result of sorting the entry points by dependency. */ - sortEntryPointsByDependency(entryPoints: EntryPoint[]): SortedEntryPointsInfo { + sortEntryPointsByDependency(entryPoints: EntryPoint[], target?: EntryPoint): + SortedEntryPointsInfo { + const {invalidEntryPoints, ignoredDependencies, graph} = this.createDependencyInfo(entryPoints); + + let sortedEntryPointNodes: string[]; + if (target) { + sortedEntryPointNodes = graph.dependenciesOf(target.path); + sortedEntryPointNodes.push(target.path); + } else { + sortedEntryPointNodes = graph.overallOrder(); + } + + return { + entryPoints: sortedEntryPointNodes.map(path => graph.getNodeData(path)), + invalidEntryPoints, + ignoredDependencies, + }; + } + + /** + * Computes a dependency graph of the given entry-points. + * + * The graph only holds entry-points that ngcc cares about and whose dependencies + * (direct and transitive) all exist. + */ + private createDependencyInfo(entryPoints: EntryPoint[]) { const invalidEntryPoints: InvalidEntryPoint[] = []; const ignoredDependencies: IgnoredDependency[] = []; const graph = new DepGraph(); @@ -79,15 +105,10 @@ export class DependencyResolver { // Now add the dependencies between them entryPoints.forEach(entryPoint => { - const entryPointPath = entryPoint.fesm2015 || entryPoint.esm2015; - if (!entryPointPath) { - throw new Error( - `ESM2015 format (flat and non-flat) missing in '${entryPoint.path}' entry-point.`); - } - const dependencies = new Set(); const missing = new Set(); const deepImports = new Set(); + const entryPointPath = getEntryPointPath(entryPoint); this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports); if (missing.size > 0) { @@ -119,13 +140,7 @@ export class DependencyResolver { } }); - // The map now only holds entry-points that ngcc cares about and whose dependencies - // (direct and transitive) all exist. - return { - entryPoints: graph.overallOrder().map(path => graph.getNodeData(path)), - invalidEntryPoints, - ignoredDependencies - }; + return {invalidEntryPoints, ignoredDependencies, graph}; function removeNodes(entryPoint: EntryPoint, missingDependencies: string[]) { const nodesToRemove = [entryPoint.path, ...graph.dependantsOf(entryPoint.path)]; @@ -136,3 +151,13 @@ export class DependencyResolver { } } } + +function getEntryPointPath(entryPoint: EntryPoint): string { + const entryPointPath = + entryPoint.fesm2015 || entryPoint.fesm5 || entryPoint.esm2015 || entryPoint.esm5; + if (!entryPointPath) { + throw new Error( + `There is no format with import statements in '${entryPoint.path}' entry-point.`); + } + return entryPointPath; +} diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts index 32668ee2f6..cda95dbd91 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts @@ -18,9 +18,14 @@ export class EntryPointFinder { * Search the given directory, and sub-directories, for Angular package entry points. * @param sourceDirectory An absolute path to the directory to search for entry points. */ - findEntryPoints(sourceDirectory: string): SortedEntryPointsInfo { + findEntryPoints(sourceDirectory: string, targetEntryPointPath?: string): SortedEntryPointsInfo { const unsortedEntryPoints = walkDirectoryForEntryPoints(sourceDirectory); - return this.resolver.sortEntryPointsByDependency(unsortedEntryPoints); + targetEntryPointPath = + targetEntryPointPath && path.resolve(sourceDirectory, targetEntryPointPath); + const targetEntryPoint = targetEntryPointPath ? + unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) : + undefined; + return this.resolver.sortEntryPointsByDependency(unsortedEntryPoints, targetEntryPoint); } } diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index e5a8d401cd..790de7ec69 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -33,6 +33,25 @@ describe('ngcc main()', () => { it('should run ngcc without errors for esm5', () => { expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['esm5']})).not.toThrow(); }); + + it('should only compile the given package entry-point (and its dependencies)', () => { + mainNgcc({ + baseSourcePath: '/node_modules', + formats: ['esm2015'], + targetEntryPointPath: '@angular/common' + }); + + expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ + esm2015: '0.0.0-PLACEHOLDER', + es2015: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ + esm2015: '0.0.0-PLACEHOLDER', + es2015: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toBeUndefined(); + expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toBeUndefined(); + }); }); diff --git a/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts index 7570f32311..ee01076dc4 100644 --- a/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts @@ -7,7 +7,7 @@ */ import {DependencyHost} from '../../src/packages/dependency_host'; -import {DependencyResolver} from '../../src/packages/dependency_resolver'; +import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver'; import {EntryPoint} from '../../src/packages/entry_point'; describe('DependencyResolver', () => { @@ -83,7 +83,7 @@ describe('DependencyResolver', () => { it('should error if the entry point does not have either the fesm2015 nor esm2015 formats', () => { expect(() => resolver.sortEntryPointsByDependency([{ path: 'first' } as EntryPoint])) - .toThrowError(`ESM2015 format (flat and non-flat) missing in 'first' entry-point.`); + .toThrowError(`There is no format with import statements in 'first' entry-point.`); }); it('should capture any dependencies that were ignored', () => { @@ -95,6 +95,23 @@ describe('DependencyResolver', () => { ]); }); + it('should only return dependencies of the target, if provided', () => { + spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies)); + const entryPoints = [fifth, first, fourth, second, third]; + let sorted: SortedEntryPointsInfo; + + sorted = resolver.sortEntryPointsByDependency(entryPoints, first); + expect(sorted.entryPoints).toEqual([fifth, fourth, third, second, first]); + sorted = resolver.sortEntryPointsByDependency(entryPoints, second); + expect(sorted.entryPoints).toEqual([fifth, fourth, third, second]); + sorted = resolver.sortEntryPointsByDependency(entryPoints, third); + expect(sorted.entryPoints).toEqual([fifth, fourth, third]); + sorted = resolver.sortEntryPointsByDependency(entryPoints, fourth); + expect(sorted.entryPoints).toEqual([fifth, fourth]); + sorted = resolver.sortEntryPointsByDependency(entryPoints, fifth); + expect(sorted.entryPoints).toEqual([fifth]); + }); + interface DepMap { [path: string]: {resolved: string[], missing: string[]}; }