-
Notifications
You must be signed in to change notification settings - Fork 27.2k
perf(ivy): ngcc - exit early if the targeted package has been compiled #29740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,14 @@ export function mainNgcc({basePath, targetEntryPointPath, | |
| const absoluteTargetEntryPointPath = targetEntryPointPath ? | ||
| AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) : | ||
| undefined; | ||
|
|
||
| if (absoluteTargetEntryPointPath && | ||
| hasProcessedTargetEntryPoint( | ||
| absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) { | ||
| logger.info('The target entry-point has already been processed'); | ||
| return; | ||
| } | ||
|
|
||
| const {entryPoints} = | ||
| finder.findEntryPoints(AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath); | ||
|
|
||
|
|
@@ -167,3 +175,28 @@ export function mainNgcc({basePath, targetEntryPointPath, | |
| function getFileWriter(createNewEntryPointFormats: boolean): FileWriter { | ||
| return createNewEntryPointFormats ? new NewEntryPointFileWriter() : new InPlaceFileWriter(); | ||
| } | ||
|
|
||
| function hasProcessedTargetEntryPoint( | ||
| targetPath: AbsoluteFsPath, propertiesToConsider: string[], compileAllFormats: boolean) { | ||
| const packageJsonPath = AbsoluteFsPath.from(resolve(targetPath, 'package.json')); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you are concerned with speed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll fix that up next time I am touching this code. |
||
| const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); | ||
|
|
||
| for (const property of propertiesToConsider) { | ||
| if (packageJson[property]) { | ||
| // Here is a property that should be processed | ||
| if (hasBeenProcessed(packageJson, property as EntryPointJsonProperty)) { | ||
| if (!compileAllFormats) { | ||
| // It has been processed and we only need one, so we are done. | ||
| return true; | ||
| } | ||
| } else { | ||
| // It has not been processed but we need all of them, so we are done. | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
| // Either all formats need to be compiled and there were none that were unprocessed, | ||
| // Or only the one matching format needs to be compiled but there was at least one matching | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or --> or
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😱 |
||
| // property before the first processed format that was unprocessed. | ||
| return true; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| load("//tools:defaults.bzl", "ts_library") | ||
|
|
||
| package(default_visibility = ["//packages/compiler-cli/ngcc:__subpackages__"]) | ||
|
|
||
| ts_library( | ||
| name = "helpers", | ||
| testonly = True, | ||
| srcs = glob([ | ||
| "*.ts", | ||
| ]), | ||
| deps = [ | ||
| "//packages/compiler-cli/ngcc", | ||
| "//packages/compiler-cli/src/ngtsc/path", | ||
| "//packages/compiler-cli/src/ngtsc/testing", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,15 +6,19 @@ | |
| * found in the LICENSE file at https://angular.io/license | ||
| */ | ||
|
|
||
| import {existsSync, readFileSync, readdirSync, statSync} from 'fs'; | ||
| import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path'; | ||
| import {existsSync, readFileSync, readdirSync, statSync, writeFileSync} from 'fs'; | ||
| import * as mockFs from 'mock-fs'; | ||
| import {join} from 'path'; | ||
| const Module = require('module'); | ||
|
|
||
| import {mainNgcc} from '../../src/main'; | ||
| import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; | ||
| import {EntryPointPackageJson} from '../../src/packages/entry_point'; | ||
| import {Logger} from '../../src/logging/logger'; | ||
| import {mainNgcc} from '../../src/main'; | ||
| import {markAsProcessed} from '../../src/packages/build_marker'; | ||
| import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; | ||
| import {MockLogger} from '../helpers/mock_logger'; | ||
|
|
||
| const _ = AbsoluteFsPath.from; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why naming this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In unit tests I have to create I realise that in this file I only use it once so perhaps that isn't necessary. :-)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I typically associate _ with lodash or underscore 😜.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's so 1990s :-P Check out https://github.com/angular/angular/blob/master/packages/compiler-cli/ngcc/test/packages/dependency_host_spec.ts for an example of this being actually useful. |
||
|
|
||
| describe('ngcc main()', () => { | ||
| beforeEach(createMockFileSystem); | ||
|
|
@@ -26,7 +30,11 @@ describe('ngcc main()', () => { | |
| }); | ||
|
|
||
| it('should run ngcc without errors for esm5', () => { | ||
| expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm5']})) | ||
| expect(() => mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['esm5'], | ||
| logger: new MockLogger(), | ||
| })) | ||
| .not.toThrow(); | ||
| }); | ||
|
|
||
|
|
@@ -82,12 +90,86 @@ describe('ngcc main()', () => { | |
| }); | ||
| }); | ||
|
|
||
| describe('early skipping of target entry-point', () => { | ||
| describe('[compileAllFormats === true]', () => { | ||
| it('should skip all processing if all the properties are marked as processed', () => { | ||
| const logger = new MockLogger(); | ||
| markPropertiesAsProcessed('@angular/common/http/testing', SUPPORTED_FORMAT_PROPERTIES); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| targetEntryPointPath: '@angular/common/http/testing', logger, | ||
| }); | ||
| expect(logger.logs.info).toContain(['The target entry-point has already been processed']); | ||
| }); | ||
|
|
||
| it('should process the target if any `propertyToConsider` is not marked as processed', () => { | ||
| const logger = new MockLogger(); | ||
| markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015', 'fesm2015']); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| targetEntryPointPath: '@angular/common/http/testing', | ||
| propertiesToConsider: ['fesm2015', 'esm5', 'esm2015'], logger, | ||
| }); | ||
| expect(logger.logs.info).not.toContain([ | ||
| 'The target entry-point has already been processed' | ||
| ]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('[compileAllFormats === false]', () => { | ||
| it('should process the target if the first matching `propertyToConsider` is not marked as processed', | ||
| () => { | ||
| const logger = new MockLogger(); | ||
| markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| targetEntryPointPath: '@angular/common/http/testing', | ||
| propertiesToConsider: ['esm5', 'esm2015'], | ||
| compileAllFormats: false, logger, | ||
| }); | ||
|
|
||
| expect(logger.logs.info).not.toContain([ | ||
| 'The target entry-point has already been processed' | ||
| ]); | ||
| }); | ||
|
|
||
| it('should skip all processing if the first matching `propertyToConsider` is marked as processed', | ||
| () => { | ||
| const logger = new MockLogger(); | ||
| markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| targetEntryPointPath: '@angular/common/http/testing', | ||
| // Simulate a property that does not exist on the package.json and will be ignored. | ||
| propertiesToConsider: ['missing', 'esm2015', 'esm5'], | ||
| compileAllFormats: false, logger, | ||
| }); | ||
|
|
||
| expect(logger.logs.info).toContain([ | ||
| 'The target entry-point has already been processed' | ||
| ]); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| function markPropertiesAsProcessed(packagePath: string, properties: EntryPointJsonProperty[]) { | ||
| const basePath = '/node_modules'; | ||
| const targetPackageJsonPath = _(join(basePath, packagePath, 'package.json')); | ||
| const targetPackage = loadPackage(packagePath); | ||
| markAsProcessed(targetPackage, targetPackageJsonPath, 'typings'); | ||
| properties.forEach(property => markAsProcessed(targetPackage, targetPackageJsonPath, property)); | ||
| } | ||
|
|
||
|
|
||
| describe('with propertiesToConsider', () => { | ||
| it('should only compile the entry-point formats given in the `propertiesToConsider` list', | ||
| () => { | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'] | ||
| propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'], | ||
| logger: new MockLogger(), | ||
|
|
||
| }); | ||
|
|
||
| // * the `main` property is UMD, which is not yet supported. | ||
|
|
@@ -125,7 +207,9 @@ describe('ngcc main()', () => { | |
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['main', 'module', 'fesm5', 'esm5'], | ||
| compileAllFormats: false | ||
| compileAllFormats: false, | ||
| logger: new MockLogger(), | ||
|
|
||
| }); | ||
| // * 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, | ||
|
|
@@ -158,15 +242,21 @@ describe('ngcc main()', () => { | |
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['module'], | ||
| compileAllFormats: false | ||
| compileAllFormats: false, | ||
| logger: new MockLogger(), | ||
|
|
||
| }); | ||
| expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ | ||
| module: '0.0.0-PLACEHOLDER', | ||
| typings: '0.0.0-PLACEHOLDER', | ||
| }); | ||
| // If ngcc tries to write out the typings files again, this will throw an exception. | ||
| mainNgcc( | ||
| {basePath: '/node_modules', propertiesToConsider: ['esm5'], compileAllFormats: false}); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['esm5'], | ||
| compileAllFormats: false, | ||
| logger: new MockLogger(), | ||
| }); | ||
| expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ | ||
| esm5: '0.0.0-PLACEHOLDER', | ||
| module: '0.0.0-PLACEHOLDER', | ||
|
|
@@ -181,7 +271,9 @@ describe('ngcc main()', () => { | |
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| createNewEntryPointFormats: true, | ||
| propertiesToConsider: ['esm5'] | ||
| propertiesToConsider: ['esm5'], | ||
| logger: new MockLogger(), | ||
|
|
||
| }); | ||
|
|
||
| // Updates the package.json | ||
|
|
@@ -222,9 +314,12 @@ describe('ngcc main()', () => { | |
| }); | ||
|
|
||
| it('should use a custom logger if provided', () => { | ||
| const logger: Logger = jasmine.createSpyObj(['debug', 'info', 'warn', 'error']); | ||
| mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015'], logger}); | ||
| expect(logger.info).toHaveBeenCalled(); | ||
| const logger = new MockLogger(); | ||
| mainNgcc({ | ||
| basePath: '/node_modules', | ||
| propertiesToConsider: ['esm2015'], logger, | ||
| }); | ||
| expect(logger.logs.info).toContain(['Compiling @angular/common/http : esm2015 as esm2015']); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good