From 4da2dda64751775c803b724587437e44a306df46 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 2 Oct 2019 10:32:57 +0300 Subject: [PATCH] feat(ngcc): support ignoreMissingDependencies in ngcc config (#33192) Normally, when ngcc encounters a package with missing dependencies while attempting to determine a compilation ordering, it will ignore that package. This commit adds a configuration for a flag to tell ngcc to compile the package anyway, regardless of any missing dependencies. FW-1931 #resolve PR Close #33192 --- .../src/dependencies/dependency_resolver.ts | 2 +- .../ngcc/src/packages/configuration.ts | 7 +++ .../ngcc/src/packages/entry_point.ts | 4 ++ .../dependencies/dependency_resolver_spec.ts | 47 +++++++++++++++++-- .../compiler-cli/ngcc/test/helpers/utils.ts | 1 + .../test/packages/entry_point_bundle_spec.ts | 3 ++ .../ngcc/test/packages/entry_point_spec.ts | 8 ++++ 7 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts index 4f4d3357ab..d910dbfe88 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts @@ -148,7 +148,7 @@ export class DependencyResolver { const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep)); - if (missingDependencies.length > 0) { + if (missingDependencies.length > 0 && !entryPoint.ignoreMissingDependencies) { // This entry point has dependencies that are missing // so remove it from the graph. removeNodes(entryPoint, missingDependencies); diff --git a/packages/compiler-cli/ngcc/src/packages/configuration.ts b/packages/compiler-cli/ngcc/src/packages/configuration.ts index 87d6274fdc..7f869a15a6 100644 --- a/packages/compiler-cli/ngcc/src/packages/configuration.ts +++ b/packages/compiler-cli/ngcc/src/packages/configuration.ts @@ -43,6 +43,13 @@ export interface NgccEntryPointConfig { * entry-point's package.json file. */ override?: PackageJsonFormatPropertiesMap; + + /** + * Normally, ngcc will skip compilation of entrypoints that contain imports that can't be resolved + * or understood. If this option is specified, ngcc will proceed with compiling the entrypoint + * even in the face of such missing dependencies. + */ + ignoreMissingDependencies?: boolean; } /** diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index 2f9cd800ed..7df6257634 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -36,6 +36,8 @@ export interface EntryPoint extends JsonObject { typings: AbsoluteFsPath; /** Is this EntryPoint compiled with the Angular View Engine compiler? */ compiledByAngular: boolean; + /** Should ngcc ignore missing dependencies and process this entrypoint anyway? */ + ignoreMissingDependencies: boolean; } export type JsonPrimitive = string | number | boolean | null; @@ -120,6 +122,8 @@ export function getEntryPointInfo( package: packagePath, path: entryPointPath, typings: resolve(entryPointPath, typings), compiledByAngular, + ignoreMissingDependencies: + entryPointConfig !== undefined ? !!entryPointConfig.ignoreMissingDependencies : false, }; return entryPointInfo; diff --git a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts index 5709512f53..f9fa7ff444 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts @@ -43,33 +43,46 @@ runInEachFileSystem(() => { let third: EntryPoint; let fourth: EntryPoint; let fifth: EntryPoint; + let sixthIgnoreMissing: EntryPoint; let dependencies: DepMap; beforeEach(() => { first = { path: _('/first'), packageJson: {esm5: './index.js'}, - compiledByAngular: true + compiledByAngular: true, + ignoreMissingDependencies: false, } as EntryPoint; second = { path: _('/second'), packageJson: {esm2015: './sub/index.js'}, - compiledByAngular: true + compiledByAngular: true, + ignoreMissingDependencies: false, } as EntryPoint; third = { path: _('/third'), packageJson: {fesm5: './index.js'}, - compiledByAngular: true + compiledByAngular: true, + ignoreMissingDependencies: false, } as EntryPoint; fourth = { path: _('/fourth'), packageJson: {fesm2015: './sub2/index.js'}, - compiledByAngular: true + compiledByAngular: true, + ignoreMissingDependencies: false, } as EntryPoint; fifth = { path: _('/fifth'), packageJson: {module: './index.js'}, - compiledByAngular: true + compiledByAngular: true, + ignoreMissingDependencies: false, + } as EntryPoint; + + sixthIgnoreMissing = { + path: _('/sixth'), + packageJson: {module: './index.js'}, + compiledByAngular: true, + ignoreMissingDependencies: true, } as EntryPoint; dependencies = { @@ -129,6 +142,30 @@ runInEachFileSystem(() => { ]); }); + it('should cope with entry points that will depend upon an invalid entry-point, when told to ignore missing dependencies', + () => { + spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({ + [_('/first/index.js')]: {resolved: [sixthIgnoreMissing.path], missing: []}, + [_('/sixth/index.js')]: {resolved: [], missing: ['/missing']}, + })); + // Note that we will process `first` after `second`, which has the missing dependency. + const result = resolver.sortEntryPointsByDependency([sixthIgnoreMissing, first]); + expect(result.entryPoints).toEqual([sixthIgnoreMissing, first]); + expect(result.invalidEntryPoints).toEqual([]); + }); + + it('should not transitively ignore missing dependencies', () => { + spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({ + [_('/first/index.js')]: {resolved: [], missing: ['/missing']}, + [_('/second/sub/index.js')]: {resolved: [first.path], missing: []}, + [_('/sixth/index.js')]: {resolved: [second.path], missing: []}, + })); + const result = resolver.sortEntryPointsByDependency([first, second, sixthIgnoreMissing]); + // sixth has no missing dependencies, but it has _invalid_ dependencies, so it's not + // compiled. + expect(result.entryPoints).toEqual([]); + }); + it('should cope with entry points having multiple indirect missing dependencies', () => { spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({ [_('/first/index.js')]: {resolved: [], missing: ['/missing1']}, diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts index d520878ded..108fb3ad04 100644 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/ngcc/test/helpers/utils.ts @@ -22,6 +22,7 @@ export function makeTestEntryPoint( path: absoluteFrom(`/node_modules/${entryPointName}`), typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`), compiledByAngular: true, + ignoreMissingDependencies: false, }; } diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts index bc3b1e3102..d7b65e040d 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_bundle_spec.ts @@ -143,6 +143,7 @@ runInEachFileSystem(() => { path: absoluteFrom('/node_modules/test'), typings: absoluteFrom('/node_modules/test/index.d.ts'), compiledByAngular: true, + ignoreMissingDependencies: false, }; const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true); @@ -189,6 +190,7 @@ runInEachFileSystem(() => { path: absoluteFrom('/node_modules/test'), typings: absoluteFrom('/node_modules/test/index.d.ts'), compiledByAngular: true, + ignoreMissingDependencies: false, }; const esm5bundle = makeEntryPointBundle( fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true, @@ -210,6 +212,7 @@ runInEachFileSystem(() => { path: absoluteFrom('/node_modules/test'), typings: absoluteFrom('/node_modules/test/index.d.ts'), compiledByAngular: true, + ignoreMissingDependencies: false, }; const esm5bundle = makeEntryPointBundle( fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true, diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts index 968c3acc03..56ab9158ff 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts @@ -50,6 +50,7 @@ runInEachFileSystem(() => { _(`/project/node_modules/some_package/valid_entry_point/valid_entry_point.d.ts`), packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'), compiledByAngular: true, + ignoreMissingDependencies: false, }); }); @@ -109,6 +110,7 @@ runInEachFileSystem(() => { typings: _('/project/node_modules/some_package/valid_entry_point/some_other.d.ts'), packageJson: overriddenPackageJson, compiledByAngular: true, + ignoreMissingDependencies: false, }); }); @@ -155,6 +157,7 @@ runInEachFileSystem(() => { '/project/node_modules/some_package/missing_package_json/missing_package_json.d.ts'), packageJson: {name: 'some_package/missing_package_json', ...override}, compiledByAngular: true, + ignoreMissingDependencies: false, }); }); @@ -211,6 +214,7 @@ runInEachFileSystem(() => { typings: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`), packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'), compiledByAngular: true, + ignoreMissingDependencies: false, }); }); } @@ -234,6 +238,7 @@ runInEachFileSystem(() => { typings: _(`/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts`), packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'), compiledByAngular: false, + ignoreMissingDependencies: false, }); }); @@ -260,6 +265,7 @@ runInEachFileSystem(() => { typings: _('/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts'), packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'), compiledByAngular: true, + ignoreMissingDependencies: false, }); }); @@ -288,6 +294,7 @@ runInEachFileSystem(() => { packageJson: loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'), compiledByAngular: true, + ignoreMissingDependencies: false, }); }); @@ -319,6 +326,7 @@ runInEachFileSystem(() => { typings: _(`/project/node_modules/some_package/material_style/material_style.d.ts`), packageJson: loadPackageJson(fs, '/project/node_modules/some_package/material_style'), compiledByAngular: true, + ignoreMissingDependencies: false, }); });