Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {Logger} from '../logging/logger';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
import {DependencyHost, DependencyInfo} from './dependency_host';

const builtinNodeJsModules = new Set<string>(require('module').builtinModules);

/**
* Holds information about entry points that are removed because
* they have dependencies that are missing (directly or transitively).
Expand Down Expand Up @@ -81,7 +83,7 @@ export class DependencyResolver {

let sortedEntryPointNodes: string[];
if (target) {
if (target.compiledByAngular) {
if (target.compiledByAngular && graph.hasNode(target.path)) {
Comment thread
JoostK marked this conversation as resolved.
Resolved
sortedEntryPointNodes = graph.dependenciesOf(target.path);
sortedEntryPointNodes.push(target.path);
} else {
Expand Down Expand Up @@ -128,10 +130,12 @@ export class DependencyResolver {
angularEntryPoints.forEach(entryPoint => {
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);

if (missing.size > 0) {
const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep));

if (missingDependencies.length > 0) {
// This entry point has dependencies that are missing
// so remove it from the graph.
removeNodes(entryPoint, Array.from(missing));
removeNodes(entryPoint, missingDependencies);
} else {
dependencies.forEach(dependencyPath => {
if (!graph.hasNode(entryPoint.path)) {
Expand Down
7 changes: 7 additions & 0 deletions packages/compiler-cli/ngcc/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ function getTargetedEntryPoints(
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
const entryPointInfo = finder.findEntryPoints();
const invalidTarget = entryPointInfo.invalidEntryPoints.find(
i => i.entryPoint.path === absoluteTargetEntryPointPath);
if (invalidTarget !== undefined) {
throw new Error(
`The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` +
invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`));
}
if (entryPointInfo.entryPoints.length === 0) {
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,32 @@ runInEachFileSystem(() => {
expect(sorted.entryPoints).toEqual([fifth]);
});

it('should not process the provided target if it has missing dependencies', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;

sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([]);
expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first);
expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual(['/missing']);
});

it('should not consider builtin NodeJS modules as missing dependency', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['fs']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;

sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([first]);
expect(sorted.invalidEntryPoints).toEqual([]);
expect(sorted.ignoredDependencies).toEqual([]);
});

it('should use the appropriate DependencyHost for each entry-point', () => {
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);
Expand Down
32 changes: 32 additions & 0 deletions packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ runInEachFileSystem(() => {
// was not processed.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
});

it('should report an error if a dependency of the target does not exist', () => {
expect(() => {
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'});
})
.toThrowError(
'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n');
});
});

describe('early skipping of target entry-point', () => {
Expand Down Expand Up @@ -482,6 +490,30 @@ runInEachFileSystem(() => {
contents: `export declare class AppComponent {};`
},
]);

// An Angular package that has a missing dependency
loadTestFiles([
{
name: _('/node_modules/invalid-package/package.json'),
contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}'
},
{
name: _('/node_modules/invalid-package/index.js'),
contents: `
import {AppModule} from "@angular/missing";
import {Component} from '@angular/core';
export class AppComponent {};
AppComponent.decorators = [
{ type: Component, args: [{selector: 'app', template: '<h2>Hello</h2>'}] }
];
`
},
{
name: _('/node_modules/invalid-package/index.d.ts'),
contents: `export declare class AppComponent {}`
},
{name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'},
]);
}
});
});