From 229f03596976f0ad327d1fe681976e689b6d9372 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 20 Mar 2019 13:47:58 +0000 Subject: [PATCH] feat(ivy): ngcc - support only compiling the first format property to match (#29092) By default ngcc will compile all the format properties specified. With this change you can configure ngcc so that it will stop compiling an entry-point after the first property that matches the `propertiesToConsider`. PR Close #29092 --- packages/compiler-cli/ngcc/main-ngcc.ts | 8 +- packages/compiler-cli/ngcc/src/main.ts | 15 +- .../ngcc/test/integration/ngcc_spec.ts | 147 ++++++++++++------ 3 files changed, 119 insertions(+), 51 deletions(-) diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts index 1d6fffff70..0f42a84a0a 100644 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ b/packages/compiler-cli/ngcc/main-ngcc.ts @@ -38,6 +38,11 @@ if (require.main === module) { alias: 'target', describe: 'A path to a single entry-point to compile (plus its dependencies).', }) + .option('first-only', { + describe: + 'If specified then only the first matching package.json property will be compiled', + type: 'boolean' + }) .help() .parse(args); @@ -50,8 +55,9 @@ if (require.main === module) { const propertiesToConsider: EntryPointJsonProperty[] = options['p']; const targetEntryPointPath = options['t'] ? AbsoluteFsPath.from(path.resolve(options['t'])) : undefined; + const compileAllFormats = !options['first-only']; try { - mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath}); + mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath, compileAllFormats}); process.exitCode = 0; } catch (e) { console.error(e.stack || e.message); diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index 8f510da12d..8fd1ee005c 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -31,6 +31,11 @@ export interface NgccOptions { * Each of properties contain a path to particular bundle format for a given entry-point. */ propertiesToConsider?: EntryPointJsonProperty[]; + /** + * Whether to compile all specified formats or to stop compiling this entry-point at the first + * matching format. Defaults to `true`. + */ + compileAllFormats?: boolean; } const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015']; @@ -43,8 +48,8 @@ const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015']; * * @param options The options telling ngcc what to compile and how. */ -export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToConsider}: NgccOptions): - void { +export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToConsider, + compileAllFormats = true}: NgccOptions): void { const transformer = new Transformer(baseSourcePath, baseSourcePath); const host = new DependencyHost(); const resolver = new DependencyResolver(host); @@ -74,7 +79,9 @@ export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToCons continue; } - if (!compiledFormats.has(formatPath)) { + // We don't break if this if statement fails because we still want to mark + // the property as processed even if its underlying format has been built already. + if (!compiledFormats.has(formatPath) && (compileAllFormats || compiledFormats.size === 0)) { const bundle = makeEntryPointBundle( entryPoint.path, formatPath, entryPoint.typings, isCore, format, compiledFormats.size === 0); @@ -86,7 +93,7 @@ export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToCons console.warn( `Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`); } - } else { + } else if (!compileAllFormats) { console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`); } diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index f4f5cf5d40..810d0a9464 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -31,57 +31,106 @@ describe('ngcc main()', () => { .not.toThrow(); }); - it('should only compile the given package entry-point (and its dependencies)', () => { - mainNgcc({ - baseSourcePath: NODE_MODULES, - targetEntryPointPath: AbsoluteFsPath.from(`${NODE_MODULES}/@angular/common/http`) - }); + describe('with targetEntryPointPath', () => { + it('should only compile the given package entry-point (and its dependencies).', () => { + mainNgcc({ + baseSourcePath: NODE_MODULES, + targetEntryPointPath: AbsoluteFsPath.from('/node_modules/@angular/common/http') + }); - expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - esm5: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - fesm5: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER' + expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ + module: '0.0.0-PLACEHOLDER', + es2015: '0.0.0-PLACEHOLDER', + esm5: '0.0.0-PLACEHOLDER', + esm2015: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + fesm2015: '0.0.0-PLACEHOLDER', + }); + // * `common` is a dependency of `common/http`, so is compiled. + expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ + module: '0.0.0-PLACEHOLDER', + es2015: '0.0.0-PLACEHOLDER', + esm5: '0.0.0-PLACEHOLDER', + esm2015: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + fesm2015: '0.0.0-PLACEHOLDER', + }); + // * `core` is a dependency of `common`, so is compiled. + expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ + module: '0.0.0-PLACEHOLDER', + es2015: '0.0.0-PLACEHOLDER', + esm5: '0.0.0-PLACEHOLDER', + esm2015: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + fesm2015: '0.0.0-PLACEHOLDER', + }); + + // * `common/testing` is not a dependency of `common/http` so is not compiled. + expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toBeUndefined(); }); - expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - esm5: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - fesm5: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER' - }); - expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ - module: '0.0.0-PLACEHOLDER', - es2015: '0.0.0-PLACEHOLDER', - esm5: '0.0.0-PLACEHOLDER', - esm2015: '0.0.0-PLACEHOLDER', - fesm5: '0.0.0-PLACEHOLDER', - fesm2015: '0.0.0-PLACEHOLDER' - }); - expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toBeUndefined(); }); - it('should only build the format properties specified for each entry-point', () => { - mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['main', 'esm5', 'module']}); + describe('with propertiesToConsider', () => { + it('should only compile the entry-point formats given in the `propertiesToConsider` list', + () => { + mainNgcc({ + baseSourcePath: NODE_MODULES, + propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'] + }); - expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ - esm5: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ - esm5: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toEqual({ - esm5: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', - }); - expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ - esm5: '0.0.0-PLACEHOLDER', - module: '0.0.0-PLACEHOLDER', + // * the `main` property is UMD, which is not yet supported. + // * none of the ES2015 formats are compiled as they are not on the `propertiesToConsider` + // list. + expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ + esm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ + esm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toEqual({ + esm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ + esm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + fesm5: '0.0.0-PLACEHOLDER', + }); + }); + }); + + describe('with compileAllFormats set to false', () => { + it('should only compile the first matching format', () => { + mainNgcc({ + baseSourcePath: NODE_MODULES, + propertiesToConsider: ['main', 'module', 'fesm5', 'esm5'], + compileAllFormats: false + }); + // * The `main` is UMD, which is not yet supported, and so is not compiled. + // * In the Angular packages fesm5 and module have the same underlying format, + // so both are marked as compiled. + // * The `esm5` is not compiled because we stopped after the `fesm5` format. + expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ + fesm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({ + fesm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toEqual({ + fesm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + }); + expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ + fesm5: '0.0.0-PLACEHOLDER', + module: '0.0.0-PLACEHOLDER', + }); }); }); }); @@ -135,6 +184,12 @@ interface Directory { [pathSegment: string]: string|Directory; } +/** + * A mock implementation of the node.js Module._resolveFilename function, + * which we are spying on to support mocking out the file-system in these tests. + * + * @param request the path to a module that needs resolving. + */ function mockResolve(request: string): string|null { if (existsSync(request)) { const stat = statSync(request);