feat(ngcc): support for new APF where `module` points to esm2015 output (#36944)

As of version 10, libraries following the APF will no longer contain
ESM5 output. Hence, tests in ngcc need to be updated as they currently
rely on the release output of `@angular/core`.

Additionally, we'd need to support in ngcc that the `module`
property of entry-points no longer necessarily refers to
`esm5` output, but instead can also target `esm2015`.

We currently achieve this by checking the path the `module`
property points to. We can do this because as per APF, the
folder name is known for the esm2015 output. Long-term for
more coverage, we want to sniff the format by looking for
known ES2015 constructs in the file `module` refers to.

PR Close #36944
This commit is contained in:
Paul Gschwendtner 2020-05-06 16:54:44 +02:00 committed by Alex Rickabaugh
parent d5293d2aa3
commit c98a4d6ddd
9 changed files with 231 additions and 112 deletions

View File

@ -64,42 +64,32 @@ assertSucceeded "Expected 'ngcc' to log 'Compiling'."
cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"fesm2015": "'
assertSucceeded "Expected 'ngcc' to add build marker for 'fesm2015' in '@angular/common'."
# `es2015` is an alias of `fesm2015`.
cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"es2015": "'
assertSucceeded "Expected 'ngcc' to add build marker for 'es2015' in '@angular/common'."
# - esm5
cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"esm5": "'
assertSucceeded "Expected 'ngcc' to add build marker for 'esm5' in '@angular/common'."
# - fesm5
# `module` is an alias of `fesm2015`
cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"module": "'
assertSucceeded "Expected 'ngcc' to add build marker for 'module' in '@angular/common'."
cat node_modules/@angular/common/package.json | awk 'ORS=" "' | grep '"__processed_by_ivy_ngcc__":[^}]*"fesm5": "'
assertSucceeded "Expected 'ngcc' to add build marker for 'fesm5' in '@angular/common'."
# Did it replace the PRE_R3 markers correctly?
grep "= SWITCH_COMPILE_COMPONENT__POST_R3__" node_modules/@angular/core/fesm2015/core.js
assertSucceeded "Expected 'ngcc' to replace 'SWITCH_COMPILE_COMPONENT__PRE_R3__' in '@angular/core' (fesm2015)."
grep "= SWITCH_COMPILE_COMPONENT__POST_R3__" node_modules/@angular/core/fesm5/core.js
assertSucceeded "Expected 'ngcc' to replace 'SWITCH_COMPILE_COMPONENT__PRE_R3__' in '@angular/core' (fesm5)."
grep "= SWITCH_COMPILE_COMPONENT__POST_R3__" node_modules/@angular/core/bundles/core.umd.js
assertSucceeded "Expected 'ngcc' to replace 'SWITCH_COMPILE_COMPONENT__PRE_R3__' in '@angular/core' (main)."
# Did it compile @angular/core/ApplicationModule correctly?
grep "ApplicationModule.ɵmod = ɵɵdefineNgModule" node_modules/@angular/core/fesm2015/core.js
assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (fesm2015)."
grep "ApplicationModule.ɵmod = ɵɵdefineNgModule" node_modules/@angular/core/fesm5/core.js
assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (fesm5)."
grep "ApplicationModule.ɵmod = ɵɵdefineNgModule" node_modules/@angular/core/bundles/core.umd.js
assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (main)."
grep "ApplicationModule.ɵmod = ɵngcc0.ɵɵdefineNgModule" node_modules/@angular/core/esm2015/src/application_module.js
assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (esm2015)."
grep "ApplicationModule.ɵmod = ɵngcc0.ɵɵdefineNgModule" node_modules/@angular/core/esm5/src/application_module.js
assertSucceeded "Expected 'ngcc' to correctly compile 'ApplicationModule' in '@angular/core' (esm5)."
# Did it place the `setClassMetadata` call correctly?
cat node_modules/@angular/core/fesm2015/core.js | awk 'ORS=" "' | grep "ApplicationRef.ctorParameters.*setClassMetadata(ApplicationRef"
assertSucceeded "Expected 'ngcc' to place 'setClassMetadata' after static properties like 'ctorParameters' in '@angular/core' (fesm2015)."

View File

@ -45,6 +45,7 @@
"@angular-devkit/build-optimizer": "0.901.0",
"@angular-devkit/core": "9.1.0",
"@angular-devkit/schematics": "9.1.0",
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.6",
"@babel/generator": "^7.8.6",
"@babel/template": "^7.8.6",

View File

@ -27,7 +27,7 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
alias: 'properties',
array: true,
describe:
'An array of names of properties in package.json to compile (e.g. `module` or `es2015`)\n' +
'An array of names of properties in package.json to compile (e.g. `module` or `main`)\n' +
'Each of these properties should hold the path to a bundle-format.\n' +
'If provided, only the specified properties are considered for processing.\n' +
'If not provided, all the supported format properties (e.g. fesm2015, fesm5, es2015, esm2015, esm5, main, module) in the package.json are considered.'
@ -136,4 +136,4 @@ export function parseCommandLineOptions(args: string[]): NgccOptions {
errorOnFailedEntryPoint,
tsConfigPath
};
}
}

View File

@ -207,6 +207,15 @@ export function getEntryPointFormat(
}
return sniffModuleFormat(fs, join(entryPoint.path, mainFile));
case 'module':
const moduleFilePath = entryPoint.packageJson['module'];
// As of version 10, the `module` property in `package.json` should point to
// the ESM2015 format output as per Angular Package format specification. This
// means that the `module` property captures multiple formats, as old libraries
// built with the old APF can still be processed. We detect the format by checking
// the paths that should be used as per APF specification.
if (typeof moduleFilePath === 'string' && moduleFilePath.includes('esm2015')) {
return `esm2015`;
}
return 'esm5';
default:
return undefined;

View File

@ -1,3 +1,4 @@
load("@npm//@babel/cli:index.bzl", "babel")
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
package(default_visibility = ["//visibility:public"])
@ -62,11 +63,33 @@ ts_library(
],
)
# As of version 10, the release packages do not contain esm2015 output anymore. The ngcc
# integration tests intend to test ES5 features though, so we downlevel the flat esm2015
# file to ES5 using Babel. We can then link that into the mock file system as if the Angular
# core package is still built with previous APF versions where esm5 output was shipped. This
# allows us to ensure that ngcc properly processes libraries with esm5 output. **Note**: We are
# using Babel instead of `tsc` as TypeScript does not allow us to downlevel the file without
# setting the module resolution to either `amd` or `system`. We want to preserve ES modules.
babel(
name = "fesm5_angular_core",
outs = ["fesm5_angular_core.js"],
args = [
"$(execpath //packages/core:npm_package)/fesm2015/core.js",
"--presets @babel/preset-env",
"--out-file $(execpath fesm5_angular_core.js)",
],
data = [
"//packages/core:npm_package",
"@npm//@babel/preset-env",
],
)
jasmine_node_test(
name = "integration",
timeout = "long",
bootstrap = ["//tools/testing:node_no_angular_es5"],
data = [
":fesm5_angular_core",
"//packages/common:npm_package",
"//packages/core:npm_package",
"@npm//rxjs",

View File

@ -7,6 +7,7 @@
*/
/// <reference types="node" />
import {readFileSync} from 'fs';
import * as os from 'os';
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, join} from '../../../src/ngtsc/file_system';
@ -21,7 +22,7 @@ import {Transformer} from '../../src/packages/transformer';
import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater';
import {MockLogger} from '../helpers/mock_logger';
import {compileIntoApf, compileIntoFlatEs5Package} from './util';
import {compileIntoApf, compileIntoFlatEs2015Package, compileIntoFlatEs5Package} from './util';
const testFiles = loadStandardTestFiles({fakeCore: false, rxjs: true});
@ -41,18 +42,44 @@ runInEachFileSystem(() => {
spyOn(os, 'cpus').and.returnValue([{model: 'Mock CPU'} as any]);
});
/**
* Sets up the esm5 format in the Angular core package. By default, package output
* no longer contains esm5 output, so we process the fesm2015 file into ES5 and
* link it as if its the ESM5 output.
*/
function setupAngularCoreEsm5() {
const pkgPath = _('/node_modules/@angular/core');
const pkgJsonPath = fs.join(pkgPath, 'package.json');
const pkgJson = JSON.parse(fs.readFile(pkgJsonPath));
fs.ensureDir(fs.join(pkgPath, 'fesm5'));
fs.writeFile(
fs.join(pkgPath, 'fesm5/core.js'),
readFileSync(require.resolve('../fesm5_angular_core.js'), 'utf8'));
pkgJson.esm5 = './fesm5/core.js';
pkgJson.fesm5 = './fesm5/core.js';
fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
}
it('should run ngcc without errors for esm2015', () => {
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']}))
.not.toThrow();
});
it('should run ngcc without errors for esm5', () => {
setupAngularCoreEsm5();
expect(() => mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['esm5'],
targetEntryPointPath: '@angular/core',
logger: new MockLogger(),
}))
.not.toThrow();
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeDefined();
});
it('should run ngcc without errors when "main" property is not present', () => {
@ -114,6 +141,7 @@ runInEachFileSystem(() => {
});
it('should generate correct metadata for decorated getter/setter properties', () => {
setupAngularCoreEsm5();
compileIntoFlatEs5Package('test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
@ -134,7 +162,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm5'],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`)).replace(/\s+/g, ' ');
@ -150,6 +178,7 @@ runInEachFileSystem(() => {
it(`should be able to process spread operator inside objects for ${
target} format (imported helpers)`,
() => {
setupAngularCoreEsm5();
compileIntoApf(
'test-package', {
'/index.ts': `
@ -190,6 +219,7 @@ runInEachFileSystem(() => {
it(`should be able to process emitted spread operator inside objects for ${
target} format (emitted helpers)`,
() => {
setupAngularCoreEsm5();
compileIntoApf(
'test-package', {
'/index.ts': `
@ -225,6 +255,7 @@ runInEachFileSystem(() => {
});
it('should not add `const` in ES5 generated code', () => {
setupAngularCoreEsm5();
compileIntoFlatEs5Package('test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
@ -246,7 +277,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm5'],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
@ -280,7 +311,7 @@ runInEachFileSystem(() => {
`
});
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
import * as lib from 'lib';
@ -308,7 +339,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
@ -358,7 +389,7 @@ runInEachFileSystem(() => {
});
it('should add ɵfac but not duplicate ɵprov properties on injectables', () => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Injectable, ɵɵdefineInjectable} from '@angular/core';
export const TestClassToken = 'TestClassToken';
@ -374,7 +405,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
const after = fs.readFile(_(`/node_modules/test-package/index.js`));
@ -389,7 +420,7 @@ runInEachFileSystem(() => {
// This is necessary to ensure XPipeDef.fac is defined when delegated from injectable def
it('should always generate factory def (fac) before injectable def (prov)', () => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Injectable, Pipe, PipeTransform} from '@angular/core';
@ -406,7 +437,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
@ -508,6 +539,7 @@ runInEachFileSystem(() => {
});
it('should use `$localize` calls rather than tagged templates in ES5 generated code', () => {
setupAngularCoreEsm5();
compileIntoFlatEs5Package('test-package', {
'/index.ts': `
import {Component, Input, NgModule} from '@angular/core';
@ -529,7 +561,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm5'],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/index.js`));
@ -611,9 +643,7 @@ runInEachFileSystem(() => {
main: '0.0.0-PLACEHOLDER',
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',
typings: '0.0.0-PLACEHOLDER',
};
@ -710,7 +740,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: '@angular/common/http/testing',
propertiesToConsider: ['fesm2015', 'esm5', 'esm2015'],
propertiesToConsider: ['fesm2015', 'main', 'esm2015'],
logger,
});
expect(logger.logs.debug).not.toContain([
@ -727,7 +757,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: '@angular/common/http/testing',
propertiesToConsider: ['esm5', 'esm2015'],
propertiesToConsider: ['main', 'esm2015'],
compileAllFormats: false,
logger,
});
@ -784,7 +814,7 @@ runInEachFileSystem(() => {
}
it('should clean up outdated artifacts', () => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'index.ts': `
import {Directive} from '@angular/core';
@ -795,7 +825,7 @@ runInEachFileSystem(() => {
});
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
logger: new MockLogger(),
});
@ -811,12 +841,12 @@ runInEachFileSystem(() => {
// Now run ngcc again to see that it cleans out the outdated artifacts
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
logger: new MockLogger(),
});
const newPackageJson = loadPackage('test-package', _('/node_modules'));
expect(newPackageJson.__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
esm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(newPackageJson.module_ivy_ngcc).toBeUndefined();
@ -843,38 +873,41 @@ runInEachFileSystem(() => {
() => {
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'],
propertiesToConsider: ['main', 'module'],
logger: new MockLogger(),
});
// The ES2015 formats are not compiled as they are not in `propertiesToConsider`.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
// `module` and `es2015` are aliases of `fesm2015`.
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
// `module` and `es2015` are aliases of `fesm2015`.
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
// `module` and `es2015` are aliases for `fesm2015`.
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
// `module` and `es2015` are aliases for `fesm2015`.
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -893,6 +926,8 @@ runInEachFileSystem(() => {
expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']);
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
// `module` and `es2015` are aliases of `fesm2015`.
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
@ -913,30 +948,34 @@ runInEachFileSystem(() => {
it('should only compile the first matching format', () => {
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['module', 'fesm5', 'esm5'],
propertiesToConsider: ['module', 'fesm2015', 'main'],
compileAllFormats: false,
logger: new MockLogger(),
});
// * 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.
// * In the Angular packages fesm2015, module and `es2015` have the same
// underlying format, so both are marked as compiled.
// * The `main` is not compiled because we stopped after the `fesm2015` format.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
@ -946,27 +985,25 @@ runInEachFileSystem(() => {
() => {
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['module'],
propertiesToConsider: ['main'],
compileAllFormats: false,
logger: new MockLogger(),
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
main: '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'],
propertiesToConsider: ['esm2015'],
compileAllFormats: false,
logger: new MockLogger(),
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
esm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -978,34 +1015,33 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
createNewEntryPointFormats: true,
propertiesToConsider: ['esm5'],
propertiesToConsider: ['esm2015'],
logger: new MockLogger(),
});
// Updates the package.json
expect(loadPackage('@angular/common').esm5).toEqual('./esm5/common.js');
expect((loadPackage('@angular/common') as any).esm5_ivy_ngcc)
.toEqual('__ivy_ngcc__/esm5/common.js');
expect(loadPackage('@angular/common').esm2015).toEqual('./esm2015/common.js');
expect((loadPackage('@angular/common') as any).esm2015_ivy_ngcc)
.toEqual('__ivy_ngcc__/esm2015/common.js');
// Doesn't touch original files
expect(fs.readFile(_(`/node_modules/@angular/common/esm5/src/common_module.js`)))
expect(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/common_module.js`)))
.not.toMatch(ANGULAR_CORE_IMPORT_REGEX);
// Or create a backup of the original
expect(
fs.exists(_(`/node_modules/@angular/common/esm5/src/common_module.js.__ivy_ngcc_bak`)))
expect(fs.exists(
_(`/node_modules/@angular/common/esm2015/src/common_module.js.__ivy_ngcc_bak`)))
.toBe(false);
// Creates new files
expect(
fs.readFile(_(`/node_modules/@angular/common/__ivy_ngcc__/esm5/src/common_module.js`)))
expect(fs.readFile(
_(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/common_module.js`)))
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
// Copies over files (unchanged) that did not need compiling
expect(fs.exists(_(`/node_modules/@angular/common/__ivy_ngcc__/esm5/src/version.js`)))
expect(fs.exists(_(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/version.js`)))
.toBeTrue();
expect(fs.readFile(_(`/node_modules/@angular/common/__ivy_ngcc__/esm5/src/version.js`)))
.toEqual(fs.readFile(_(`/node_modules/@angular/common/esm5/src/version.js`)));
expect(fs.readFile(_(`/node_modules/@angular/common/__ivy_ngcc__/esm2015/src/version.js`)))
.toEqual(fs.readFile(_(`/node_modules/@angular/common/esm2015/src/version.js`)));
// Overwrites .d.ts files (as usual)
expect(fs.readFile(_(`/node_modules/@angular/common/common.d.ts`)))
@ -1017,69 +1053,71 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules/@angular/core',
createNewEntryPointFormats: true,
propertiesToConsider: ['fesm2015', 'fesm5'],
propertiesToConsider: ['fesm2015', 'main'],
});
const pkg: any = loadPackage('@angular/core');
// `es2015` is an alias of `fesm2015`.
// `es2015` and `module` are aliases of `fesm2015`.
expect(pkg.fesm2015).toEqual('./fesm2015/core.js');
expect(pkg.es2015).toEqual('./fesm2015/core.js');
expect(pkg.module).toEqual('./fesm2015/core.js');
expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
// `module` is an alias of `fesm5`.
expect(pkg.fesm5).toEqual('./fesm5/core.js');
expect(pkg.module).toEqual('./fesm5/core.js');
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
expect(pkg.main).toEqual('./bundles/core.umd.js');
expect(pkg.main_ivy_ngcc).toEqual('__ivy_ngcc__/bundles/core.umd.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, 'main_ivy_ngcc');
expectNotToHaveProp(pkg, 'esm2015_ivy_ngcc');
expectNotToHaveProp(pkg, 'fesm2015_ivy_ngcc');
expectNotToHaveProp(pkg, 'fesm5_ivy_ngcc');
expectNotToHaveProp(pkg, 'module_ivy_ngcc');
expectNotToHaveProp(pkg, '__processed_by_ivy_ngcc__');
// Process `fesm2015` and update `package.json`.
pkg = processFormatAndUpdatePackageJson('fesm2015');
expectNotToHaveProp(pkg, 'esm5_ivy_ngcc');
expectNotToHaveProp(pkg, 'main_ivy_ngcc');
expectNotToHaveProp(pkg, 'esm2015_ivy_ngcc');
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
expectNotToHaveProp(pkg, 'fesm5_ivy_ngcc');
expectToHaveProp(pkg, 'module_ivy_ngcc');
expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'fesm2015');
// Process `fesm5` and update `package.json`.
pkg = processFormatAndUpdatePackageJson('fesm5');
expectNotToHaveProp(pkg, 'esm5_ivy_ngcc');
// Process `esm2015` and update `package.json`.
pkg = processFormatAndUpdatePackageJson('esm2015');
expectNotToHaveProp(pkg, 'main_ivy_ngcc');
expectToHaveProp(pkg, 'esm2015_ivy_ngcc');
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
expectToHaveProp(pkg, 'fesm5_ivy_ngcc');
expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'fesm5');
expectToHaveProp(pkg, 'module_ivy_ngcc');
expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'esm2015');
// Process `esm5` and update `package.json`.
pkg = processFormatAndUpdatePackageJson('esm5');
expectToHaveProp(pkg, 'esm5_ivy_ngcc');
// Process `main` and update `package.json`.
pkg = processFormatAndUpdatePackageJson('main');
expectToHaveProp(pkg, 'main_ivy_ngcc');
expectToHaveProp(pkg, 'esm2015_ivy_ngcc');
expectToHaveProp(pkg, 'fesm2015_ivy_ngcc');
expectToHaveProp(pkg, 'fesm5_ivy_ngcc');
expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'esm5');
expectToHaveProp(pkg, 'module_ivy_ngcc');
expectToHaveProp(pkg.__processed_by_ivy_ngcc__!, 'main');
// 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('|main_ivy_ngcc|main|');
expect(pkgKeys).toContain('|fesm2015_ivy_ngcc|fesm2015|');
expect(pkgKeys).toContain('|fesm5_ivy_ngcc|fesm5|');
expect(pkgKeys).toContain('|esm2015_ivy_ngcc|esm2015|');
// 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`
// - `fesm2015` <=> `module <=> es2015`
expect(stringifyKeys(pkg.__processed_by_ivy_ngcc__!))
.toBe('|es2015|esm5|fesm2015|fesm5|module|typings|');
.toBe('|es2015|esm2015|fesm2015|main|module|typings|');
// Helpers
function expectNotToHaveProp(obj: object, prop: string) {
@ -1118,11 +1156,11 @@ runInEachFileSystem(() => {
fs.writeFile(_('/yarn.lock'), 'DUMMY YARN LOCK FILE');
// Populate the manifest file
mainNgcc(
{basePath: '/node_modules', propertiesToConsider: ['esm5'], logger: new MockLogger()});
{basePath: '/node_modules', propertiesToConsider: ['main'], logger: new MockLogger()});
// Check that common/testing ES5 was processed
let commonTesting =
JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json')));
expect(hasBeenProcessed(commonTesting, 'esm5')).toBe(true);
expect(hasBeenProcessed(commonTesting, 'main')).toBe(true);
expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(false);
// Modify the manifest to test that is has no effect
let manifest: EntryPointManifestFile =
@ -1141,7 +1179,7 @@ runInEachFileSystem(() => {
// Check that common/testing ES2015 is now processed, despite the manifest not listing it
commonTesting =
JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json')));
expect(hasBeenProcessed(commonTesting, 'esm5')).toBe(true);
expect(hasBeenProcessed(commonTesting, 'main')).toBe(true);
expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(true);
// Check that the newly computed manifest has written to disk, containing the path that we
// had removed earlier.
@ -1383,6 +1421,8 @@ runInEachFileSystem(() => {
logger
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
// `module` and `es2015` are aliases for `fesm2015`.
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
@ -1523,6 +1563,7 @@ runInEachFileSystem(() => {
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
@ -1550,6 +1591,7 @@ runInEachFileSystem(() => {
mainNgcc({basePath: '/node_modules', propertiesToConsider: ['es2015']});
// We process core but not core/testing.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
@ -1558,6 +1600,7 @@ runInEachFileSystem(() => {
// We do not compile common but we do compile its sub-entry-points.
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
@ -1629,7 +1672,7 @@ runInEachFileSystem(() => {
describe('undecorated child class migration', () => {
it('should generate a directive definition with CopyDefinitionFeature for an undecorated child directive',
() => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Directive, NgModule} from '@angular/core';
@ -1651,7 +1694,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
@ -1670,7 +1713,7 @@ runInEachFileSystem(() => {
it('should generate a component definition with CopyDefinitionFeature for an undecorated child component',
() => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Component, NgModule} from '@angular/core';
@ -1692,7 +1735,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
@ -1710,7 +1753,7 @@ runInEachFileSystem(() => {
it('should generate directive definitions with CopyDefinitionFeature for undecorated child directives in a long inheritance chain',
() => {
compileIntoFlatEs5Package('test-package', {
compileIntoFlatEs2015Package('test-package', {
'/index.ts': `
import {Directive, NgModule} from '@angular/core';
@ -1735,7 +1778,7 @@ runInEachFileSystem(() => {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: ['module'],
propertiesToConsider: ['esm2015'],
});
const dtsContents = fs.readFile(_(`/node_modules/test-package/index.d.ts`));

View File

@ -33,7 +33,28 @@ export function compileIntoFlatEs5Package(pkgName: string, sources: PackageSourc
compileIntoFlatPackage(pkgName, sources, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.ESNext,
formatProperty: 'module',
formatProperty: 'esm5',
});
}
/**
* Instead of writing packaged code by hand, and manually describing the layout of the package,
* this function transpiles the TypeScript sources into a flat file structure using the ES2015
* format. In this package layout, all compiled sources are at the root of the package, with
* `.d.ts` files next to the `.js` files. Each `.js` also has a corresponding `.metadata.js`
* file alongside with it.
*
* All generated code is written into the `node_modules` in the top-level filesystem, ready for use
* in testing ngcc.
*
* @param pkgName The name of the package to compile.
* @param sources The TypeScript sources to compile.
*/
export function compileIntoFlatEs2015Package(pkgName: string, sources: PackageSources): void {
compileIntoFlatPackage(pkgName, sources, {
target: ts.ScriptTarget.ES2015,
module: ts.ModuleKind.ESNext,
formatProperty: 'esm2015',
});
}
@ -184,7 +205,7 @@ export function compileIntoApf(
version: '0.0.1',
esm5: './esm5/index.js',
esm2015: './esm2015/index.js',
module: './esm5/index.js',
module: './esm2015/index.js',
typings: './index.d.ts',
};

View File

@ -429,6 +429,12 @@ runInEachFileSystem(() => {
expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm5');
});
it('should return `esm2015` format for `module` property if it points to esm2015 output',
() => {
entryPoint.packageJson['module'] = '../fesm2015/valid-entry-point.js';
expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm2015');
});
(['browser', 'main'] as EntryPointJsonProperty[]).forEach(browserOrMain => {
it('should return `esm5` for `' + browserOrMain +
'` if the file contains import or export statements',

View File

@ -153,6 +153,22 @@
universal-analytics "0.4.20"
uuid "7.0.2"
"@babel/cli@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c"
integrity sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==
dependencies:
commander "^4.0.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.1.0"
glob "^7.0.0"
lodash "^4.17.13"
make-dir "^2.1.0"
slash "^2.0.0"
source-map "^0.5.0"
optionalDependencies:
chokidar "^2.1.8"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
@ -3984,7 +4000,7 @@ conventional-commits-parser@^3.0.3, conventional-commits-parser@^3.0.8:
through2 "^3.0.0"
trim-off-newlines "^1.0.0"
convert-source-map@^1.5.1, convert-source-map@^1.7.0:
convert-source-map@^1.1.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
@ -6039,6 +6055,11 @@ fs-promise@0.3.1:
dependencies:
any-promise "~0.1.0"
fs-readdir-recursive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@ -12626,6 +12647,11 @@ slash@^1.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"