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[];
|
||||
/** 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?
|
||||
|
|
|
@ -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<EntryPoint>();
|
||||
|
@ -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<string>();
|
||||
const missing = new Set<string>();
|
||||
const deepImports = new Set<string>();
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -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[]};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue