fix(ngcc): update package.json
deterministically (#34870)
Ngcc adds properties to the `package.json` files of the entry-points it processes to mark them as processed for a format and point to the created Ivy entry-points (in case of `--create-ivy-entry-points`). When running ngcc in parallel mode (which is the default for the standalone ngcc command), multiple formats can be processed simultaneously for the same entry-point and the order of completion is not deterministic. Previously, ngcc would append new properties at the end of the target object in `package.json` as soon as the format processing was completed. As a result, the order of properties in the resulting `package.json` (when processing multiple formats for an entry-point in parallel) was not deterministic. For tools that use file hashes for caching purposes (such as Bazel), this lead to a high probability of cache misses. This commit fixes the problem by ensuring that the position of properties added to `package.json` files is deterministic and independent of the order in which each format is processed. Jira issue: [FW-1801](https://angular-team.atlassian.net/browse/FW-1801) Fixes #34635 PR Close #34870
This commit is contained in:

committed by
Andrew Kushnir

parent
8215b345e3
commit
a10d2a8dc4
@ -760,6 +760,79 @@ runInEachFileSystem(() => {
|
||||
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
|
||||
});
|
||||
|
||||
it('should update `package.json` deterministically (regardless of entry-point processing order)',
|
||||
() => {
|
||||
// Ensure formats are not marked as processed in `package.json` at the beginning.
|
||||
let pkg = loadPackage('@angular/core');
|
||||
expectNotToHaveProp(pkg, 'esm5_ivy_ngcc');
|
||||
expectNotToHaveProp(pkg, 'fesm2015_ivy_ngcc');
|
||||
expectNotToHaveProp(pkg, 'fesm5_ivy_ngcc');
|
||||
expectNotToHaveProp(pkg, '__processed_by_ivy_ngcc__');
|
||||
|
||||
// Process `fesm2015` and update `package.json`.
|
||||
pkg = processFormatAndUpdatePackageJson('fesm2015');
|
||||
expectNotToHaveProp(pkg, 'esm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
|
||||
expectNotToHaveProp(pkg, 'fesm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg.__processed_by_ivy_ngcc__ !, 'fesm2015');
|
||||
|
||||
// Process `fesm5` and update `package.json`.
|
||||
pkg = processFormatAndUpdatePackageJson('fesm5');
|
||||
expectNotToHaveProp(pkg, 'esm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
|
||||
expectToHaveProp(pkg, 'fesm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg.__processed_by_ivy_ngcc__ !, 'fesm5');
|
||||
|
||||
// Process `esm5` and update `package.json`.
|
||||
pkg = processFormatAndUpdatePackageJson('esm5');
|
||||
expectToHaveProp(pkg, 'esm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
|
||||
expectToHaveProp(pkg, 'fesm5_ivy_ngcc');
|
||||
expectToHaveProp(pkg.__processed_by_ivy_ngcc__ !, 'esm5');
|
||||
|
||||
// Ensure the properties are in deterministic order (regardless of processing order).
|
||||
const pkgKeys = stringifyKeys(pkg);
|
||||
expect(pkgKeys).toContain('|esm5_ivy_ngcc|esm5|');
|
||||
expect(pkgKeys).toContain('|fesm2015_ivy_ngcc|fesm2015|');
|
||||
expect(pkgKeys).toContain('|fesm5_ivy_ngcc|fesm5|');
|
||||
|
||||
// NOTE:
|
||||
// Along with the first format that is processed, the typings are processed as well.
|
||||
// Also, once a property has been processed, alias properties as also marked as
|
||||
// processed. Aliases properties are properties that point to the same entry-point file.
|
||||
// For example:
|
||||
// - `fesm2015` <=> `es2015`
|
||||
// - `fesm5` <=> `module`
|
||||
expect(stringifyKeys(pkg.__processed_by_ivy_ngcc__ !))
|
||||
.toBe('|es2015|esm5|fesm2015|fesm5|module|typings|');
|
||||
|
||||
// Helpers
|
||||
function expectNotToHaveProp(obj: object, prop: string) {
|
||||
expect(obj.hasOwnProperty(prop))
|
||||
.toBe(
|
||||
false,
|
||||
`Expected object not to have property '${prop}': ${JSON.stringify(obj, null, 2)}`);
|
||||
}
|
||||
|
||||
function expectToHaveProp(obj: object, prop: string) {
|
||||
expect(obj.hasOwnProperty(prop))
|
||||
.toBe(
|
||||
true,
|
||||
`Expected object to have property '${prop}': ${JSON.stringify(obj, null, 2)}`);
|
||||
}
|
||||
|
||||
function processFormatAndUpdatePackageJson(formatProp: string) {
|
||||
mainNgcc({
|
||||
basePath: '/node_modules/@angular/core',
|
||||
createNewEntryPointFormats: true,
|
||||
propertiesToConsider: [formatProp],
|
||||
});
|
||||
return loadPackage('@angular/core');
|
||||
}
|
||||
|
||||
function stringifyKeys(obj: object) { return `|${Object.keys(obj).join('|')}|`; }
|
||||
});
|
||||
});
|
||||
|
||||
describe('diagnostics', () => {
|
||||
|
Reference in New Issue
Block a user