feat(ivy): ngcc - support targeting a start entry-point (#29092)
You can now, programmatically, specify an entry-point where the ngcc compilation will occur. Only this entry-point and its dependencies will be compiled. FW-1119 PR Close #29092
This commit is contained in:
parent
66239b9d09
commit
4bb0259bc0
|
@ -24,6 +24,11 @@ export interface NgccOptions {
|
||||||
formats: EntryPointFormat[];
|
formats: EntryPointFormat[];
|
||||||
/** The path to the node_modules folder where modified files should be written. */
|
/** The path to the node_modules folder where modified files should be written. */
|
||||||
baseTargetPath?: string;
|
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.
|
* @param options The options telling ngcc what to compile and how.
|
||||||
*/
|
*/
|
||||||
export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePath}: NgccOptions):
|
export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePath,
|
||||||
void {
|
targetEntryPointPath}: NgccOptions): void {
|
||||||
const transformer = new Transformer(baseSourcePath, baseTargetPath);
|
const transformer = new Transformer(baseSourcePath, baseTargetPath);
|
||||||
const host = new DependencyHost();
|
const host = new DependencyHost();
|
||||||
const resolver = new DependencyResolver(host);
|
const resolver = new DependencyResolver(host);
|
||||||
const finder = new EntryPointFinder(resolver);
|
const finder = new EntryPointFinder(resolver);
|
||||||
|
|
||||||
const {entryPoints} = finder.findEntryPoints(baseSourcePath);
|
const {entryPoints} = finder.findEntryPoints(baseSourcePath, targetEntryPointPath);
|
||||||
entryPoints.forEach(entryPoint => {
|
entryPoints.forEach(entryPoint => {
|
||||||
|
|
||||||
// Are we compiling the Angular core?
|
// Are we compiling the Angular core?
|
||||||
|
|
|
@ -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
|
* The `entryPoints` array will be ordered so that no entry point depends upon an entry point that
|
||||||
* appears later in the array.
|
* 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
|
* Sort the array of entry points so that the dependant entry points always come later than
|
||||||
* their dependencies in the array.
|
* their dependencies in the array.
|
||||||
* @param entryPoints An array entry points to sort.
|
* @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 invalidEntryPoints: InvalidEntryPoint[] = [];
|
||||||
const ignoredDependencies: IgnoredDependency[] = [];
|
const ignoredDependencies: IgnoredDependency[] = [];
|
||||||
const graph = new DepGraph<EntryPoint>();
|
const graph = new DepGraph<EntryPoint>();
|
||||||
|
@ -79,15 +105,10 @@ export class DependencyResolver {
|
||||||
|
|
||||||
// Now add the dependencies between them
|
// Now add the dependencies between them
|
||||||
entryPoints.forEach(entryPoint => {
|
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<string>();
|
const dependencies = new Set<string>();
|
||||||
const missing = new Set<string>();
|
const missing = new Set<string>();
|
||||||
const deepImports = new Set<string>();
|
const deepImports = new Set<string>();
|
||||||
|
const entryPointPath = getEntryPointPath(entryPoint);
|
||||||
this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports);
|
this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports);
|
||||||
|
|
||||||
if (missing.size > 0) {
|
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
|
return {invalidEntryPoints, ignoredDependencies, graph};
|
||||||
// (direct and transitive) all exist.
|
|
||||||
return {
|
|
||||||
entryPoints: graph.overallOrder().map(path => graph.getNodeData(path)),
|
|
||||||
invalidEntryPoints,
|
|
||||||
ignoredDependencies
|
|
||||||
};
|
|
||||||
|
|
||||||
function removeNodes(entryPoint: EntryPoint, missingDependencies: string[]) {
|
function removeNodes(entryPoint: EntryPoint, missingDependencies: string[]) {
|
||||||
const nodesToRemove = [entryPoint.path, ...graph.dependantsOf(entryPoint.path)];
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -18,9 +18,14 @@ export class EntryPointFinder {
|
||||||
* Search the given directory, and sub-directories, for Angular package entry points.
|
* 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.
|
* @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);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,25 @@ describe('ngcc main()', () => {
|
||||||
it('should run ngcc without errors for esm5', () => {
|
it('should run ngcc without errors for esm5', () => {
|
||||||
expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['esm5']})).not.toThrow();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
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';
|
import {EntryPoint} from '../../src/packages/entry_point';
|
||||||
|
|
||||||
describe('DependencyResolver', () => {
|
describe('DependencyResolver', () => {
|
||||||
|
@ -83,7 +83,7 @@ describe('DependencyResolver', () => {
|
||||||
it('should error if the entry point does not have either the fesm2015 nor esm2015 formats',
|
it('should error if the entry point does not have either the fesm2015 nor esm2015 formats',
|
||||||
() => {
|
() => {
|
||||||
expect(() => resolver.sortEntryPointsByDependency([{ path: 'first' } as EntryPoint]))
|
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', () => {
|
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 {
|
interface DepMap {
|
||||||
[path: string]: {resolved: string[], missing: string[]};
|
[path: string]: {resolved: string[], missing: string[]};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue