feat(ivy): ngcc - compile only specified package.json format properties (#29092)

You can now specify a list of properties in the package.json that
should be considered (in order) to find the path to the format to compile.

The build marker system has been updated to store the markers in
the package.json rather than an additional external file.
Also instead of tracking the underlying bundle format that was compiled,
it now tracks the package.json property.

BREAKING CHANGE:

The `proertiesToConsider` option replaces the previous `formats` option,
which specified the final bundle format, rather than the property in the
package.json.
If you were using this option to compile only specific bundle formats,
you must now modify your usage to pass in the properties in the package.json
that map to the format that you wish to compile.

In the CLI, the `--formats` is no longer available. Instead use the
`--properties` option.

FW-1120

PR Close #29092
This commit is contained in:
Pete Bacon Darwin 2019-03-20 13:47:58 +00:00 committed by Matias Niemelä
parent 4bb0259bc0
commit cd449021c1
11 changed files with 355 additions and 270 deletions

View File

@ -17,7 +17,7 @@
"build": "yarn ~~build",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build",
"prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc --formats fesm2015 fesm5",
"prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc",
"build-with-ivy": "node scripts/build-with-ivy",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js cafa558cf",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",

View File

@ -12,80 +12,36 @@ const EXAMPLES_BASE_PATH = path.resolve(__dirname, '../../content/examples');
const BOILERPLATE_PATHS = {
cli: [
'src/environments/environment.prod.ts',
'src/environments/environment.ts',
'src/assets/.gitkeep',
'src/browserslist',
'src/favicon.ico',
'src/karma.conf.js',
'src/polyfills.ts',
'src/test.ts',
'src/tsconfig.app.json',
'src/tsconfig.spec.json',
'src/tslint.json',
'e2e/src/app.po.ts',
'e2e/protractor.conf.js',
'e2e/tsconfig.e2e.json',
'.editorconfig',
'angular.json',
'package.json',
'tsconfig.json',
'tslint.json'
'src/environments/environment.prod.ts', 'src/environments/environment.ts',
'src/assets/.gitkeep', 'src/browserslist', 'src/favicon.ico', 'src/karma.conf.js',
'src/polyfills.ts', 'src/test.ts', 'src/tsconfig.app.json', 'src/tsconfig.spec.json',
'src/tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor.conf.js', 'e2e/tsconfig.e2e.json',
'.editorconfig', 'angular.json', 'package.json', 'tsconfig.json', 'tslint.json'
],
systemjs: [
'src/systemjs-angular-loader.js',
'src/systemjs.config.js',
'src/tsconfig.json',
'bs-config.json',
'bs-config.e2e.json',
'package.json',
'tslint.json'
'src/systemjs-angular-loader.js', 'src/systemjs.config.js', 'src/tsconfig.json',
'bs-config.json', 'bs-config.e2e.json', 'package.json', 'tslint.json'
],
common: [
'src/styles.css'
]
common: ['src/styles.css']
};
// All paths in this tool are relative to the current boilerplate folder, i.e boilerplate/i18n
// This maps the CLI files that exists in a parent folder
const cliRelativePath = BOILERPLATE_PATHS.cli.map(file => `../cli/${file}`);
BOILERPLATE_PATHS.elements = [
...cliRelativePath,
'tsconfig.json'
];
BOILERPLATE_PATHS.elements = [...cliRelativePath, 'tsconfig.json'];
BOILERPLATE_PATHS.i18n = [
...cliRelativePath,
'angular.json',
'package.json'
];
BOILERPLATE_PATHS.i18n = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS['service-worker'] = [
...cliRelativePath,
'angular.json',
'package.json'
];
BOILERPLATE_PATHS['service-worker'] = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS.testing = [
...cliRelativePath,
'angular.json'
];
BOILERPLATE_PATHS.testing = [...cliRelativePath, 'angular.json'];
BOILERPLATE_PATHS.universal = [
...cliRelativePath,
'angular.json',
'package.json'
];
BOILERPLATE_PATHS.universal = [...cliRelativePath, 'angular.json', 'package.json'];
BOILERPLATE_PATHS.ivy = {
systemjs: [
'rollup-config.js',
'tsconfig-aot.json'
],
cli: [
'src/tsconfig.app.json'
]
systemjs: ['rollup-config.js', 'tsconfig-aot.json'],
cli: ['src/tsconfig.app.json']
};
BOILERPLATE_PATHS.schematics = [
@ -101,11 +57,13 @@ class ExampleBoilerPlate {
*/
add(ivy = false) {
// Get all the examples folders, indicated by those that contain a `example-config.json` file
const exampleFolders = this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
const exampleFolders =
this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
if (!fs.existsSync(SHARED_NODE_MODULES_PATH)) {
throw new Error(`The shared node_modules folder for the examples (${SHARED_NODE_MODULES_PATH}) is missing.\n` +
`Perhaps you need to run "yarn example-use-npm" or "yarn example-use-local" to install the dependencies?`);
throw new Error(
`The shared node_modules folder for the examples (${SHARED_NODE_MODULES_PATH}) is missing.\n` +
`Perhaps you need to run "yarn example-use-npm" or "yarn example-use-local" to install the dependencies?`);
}
if (ivy) {
@ -114,7 +72,7 @@ class ExampleBoilerPlate {
// the module typings if we specified an "es2015" format. This means that
// we also need to build with "fesm2015" in order to get updated typings
// which are needed for compilation.
shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc --formats fesm2015 fesm5`);
shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc`);
}
exampleFolders.forEach(exampleFolder => {
@ -128,16 +86,20 @@ class ExampleBoilerPlate {
const boilerPlateBasePath = path.resolve(BOILERPLATE_BASE_PATH, boilerPlateType);
// Copy the boilerplate specific files
BOILERPLATE_PATHS[boilerPlateType].forEach(filePath => this.copyFile(boilerPlateBasePath, exampleFolder, filePath));
BOILERPLATE_PATHS[boilerPlateType].forEach(
filePath => this.copyFile(boilerPlateBasePath, exampleFolder, filePath));
// Copy the boilerplate common files
BOILERPLATE_PATHS.common.forEach(filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath));
BOILERPLATE_PATHS.common.forEach(
filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath));
// Copy Ivy specific files
if (ivy) {
const ivyBoilerPlateType = boilerPlateType === 'systemjs' ? 'systemjs' : 'cli';
const ivyBoilerPlateBasePath = path.resolve(BOILERPLATE_BASE_PATH, 'ivy', ivyBoilerPlateType);
BOILERPLATE_PATHS.ivy[ivyBoilerPlateType].forEach(filePath => this.copyFile(ivyBoilerPlateBasePath, exampleFolder, filePath));
const ivyBoilerPlateBasePath =
path.resolve(BOILERPLATE_BASE_PATH, 'ivy', ivyBoilerPlateType);
BOILERPLATE_PATHS.ivy[ivyBoilerPlateType].forEach(
filePath => this.copyFile(ivyBoilerPlateBasePath, exampleFolder, filePath));
}
});
}
@ -145,23 +107,20 @@ class ExampleBoilerPlate {
/**
* Remove all the boilerplate files from all the examples
*/
remove() {
shelljs.exec('git clean -xdfq', { cwd: EXAMPLES_BASE_PATH });
}
remove() { shelljs.exec('git clean -xdfq', {cwd: EXAMPLES_BASE_PATH}); }
main() {
yargs
.usage('$0 <cmd> [args]')
.command('add', 'add the boilerplate to each example', (yrgs) => this.add(yrgs.argv.ivy))
.command('remove', 'remove the boilerplate from each example', () => this.remove())
.demandCommand(1, 'Please supply a command from the list above')
.argv;
yargs.usage('$0 <cmd> [args]')
.command('add', 'add the boilerplate to each example', (yrgs) => this.add(yrgs.argv.ivy))
.command('remove', 'remove the boilerplate from each example', () => this.remove())
.demandCommand(1, 'Please supply a command from the list above')
.argv;
}
getFoldersContaining(basePath, filename, ignore) {
const pattern = path.resolve(basePath, '**', filename);
const ignorePattern = path.resolve(basePath, '**', ignore, '**');
return glob.sync(pattern, { ignore: [ignorePattern] }).map(file => path.dirname(file));
return glob.sync(pattern, {ignore: [ignorePattern]}).map(file => path.dirname(file));
}
copyFile(sourceFolder, destinationFolder, filePath) {
@ -171,13 +130,11 @@ class ExampleBoilerPlate {
filePath = this.normalizePath(filePath);
const destinationPath = path.resolve(destinationFolder, filePath);
fs.copySync(sourcePath, destinationPath, { overwrite: true });
fs.copySync(sourcePath, destinationPath, {overwrite: true});
fs.chmodSync(destinationPath, 444);
}
loadJsonFile(filePath) {
return fs.readJsonSync(filePath, { throws: false }) || {};
}
loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; }
normalizePath(filePath) {
// transform for example ../cli/src/tsconfig.app.json to src/tsconfig.app.json

View File

@ -10,11 +10,25 @@ ivy-ngcc --help
ivy-ngcc
# Did it add the appropriate build markers?
# - fesm2015
ls node_modules/@angular/common | grep __modified_by_ngcc_for_fesm2015
if [[ $? != 0 ]]; then exit 1; fi
# - esm2015
ls node_modules/@angular/common | grep __modified_by_ngcc_for_esm2015
grep '"__modified_by_ngcc__":[^}]*"esm2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - fesm2015
grep '"__modified_by_ngcc__":[^}]*"fesm2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
grep '"__modified_by_ngcc__":[^}]*"es2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - esm5
grep '"__modified_by_ngcc__":[^}]*"esm5":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - fesm5
grep '"__modified_by_ngcc__":[^}]*"module":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
grep '"__modified_by_ngcc__":[^}]*"fesm5":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# Did it replace the PRE_R3 markers correctly?
@ -48,6 +62,9 @@ ivy-ngcc
# Can it be safely run again (as a noop)?
ivy-ngcc
# Does running it with --formats fail?
ivy-ngcc --formats fesm2015 && exit 1
# Now try compiling the app using the ngcc compiled libraries
ngc -p tsconfig-app.json

View File

@ -10,7 +10,7 @@ import * as path from 'canonical-path';
import * as yargs from 'yargs';
import {mainNgcc} from './src/main';
import {EntryPointFormat} from './src/packages/entry_point';
import {EntryPointJsonProperty} from './src/packages/entry_point';
// CLI entry point
if (require.main === module) {
@ -22,11 +22,15 @@ if (require.main === module) {
describe: 'A path to the root folder to compile.',
default: './node_modules'
})
.option('f', {
alias: 'formats',
.option('f', {alias: 'formats', hidden: true, array: true})
.option('p', {
alias: 'properties',
array: true,
describe: 'An array of formats to compile.',
default: ['fesm2015', 'esm2015', 'fesm5', 'esm5']
describe:
'An array of names of properties in package.json (e.g. `module` or `es2015`)\n' +
'These properties should hold a path to a bundle-format to compile.\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.'
})
.option('t', {
alias: 'target',
@ -36,11 +40,16 @@ if (require.main === module) {
.help()
.parse(args);
if (options['f'] && options['f'].length) {
console.error(
'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.');
process.exit(1);
}
const baseSourcePath: string = path.resolve(options['s']);
const formats: EntryPointFormat[] = options['f'];
const propertiesToConsider: EntryPointJsonProperty[] = options['p'];
const baseTargetPath: string = options['t'];
try {
mainNgcc({baseSourcePath, formats, baseTargetPath});
mainNgcc({baseSourcePath, propertiesToConsider, baseTargetPath});
process.exitCode = 0;
} catch (e) {
console.error(e.stack || e.message);

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {checkMarkerFile, writeMarkerFile} from './packages/build_marker';
import {checkMarker, writeMarker} from './packages/build_marker';
import {DependencyHost} from './packages/dependency_host';
import {DependencyResolver} from './packages/dependency_resolver';
import {EntryPointFormat} from './packages/entry_point';
import {EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from './packages/entry_point';
import {makeEntryPointBundle} from './packages/entry_point_bundle';
import {EntryPointFinder} from './packages/entry_point_finder';
import {Transformer} from './packages/transformer';
@ -20,8 +20,6 @@ import {Transformer} from './packages/transformer';
export interface NgccOptions {
/** The path to the node_modules folder that contains the packages to compile. */
baseSourcePath: string;
/** A list of JavaScript bundle formats that should be compiled. */
formats: EntryPointFormat[];
/** The path to the node_modules folder where modified files should be written. */
baseTargetPath?: string;
/**
@ -29,8 +27,15 @@ export interface NgccOptions {
* All its dependencies will need to be compiled too.
*/
targetEntryPointPath?: string;
/**
* Which entry-point properties in the package.json to consider when compiling.
* Each of properties contain a path to particular bundle format for a given entry-point.
*/
propertiesToConsider?: EntryPointJsonProperty[];
}
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'fesm5', 'fesm2015'];
/**
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
*
@ -39,8 +44,8 @@ export interface NgccOptions {
*
* @param options The options telling ngcc what to compile and how.
*/
export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePath,
targetEntryPointPath}: NgccOptions): void {
export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targetEntryPointPath,
propertiesToConsider}: NgccOptions): void {
const transformer = new Transformer(baseSourcePath, baseTargetPath);
const host = new DependencyHost();
const resolver = new DependencyResolver(host);
@ -52,28 +57,51 @@ export function mainNgcc({baseSourcePath, formats, baseTargetPath = baseSourcePa
// Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core';
// We transform the d.ts typings files while transforming one of the formats.
// This variable decides with which of the available formats to do this transform.
// It is marginally faster to process via the flat file if available.
const dtsTransformFormat: EntryPointFormat = entryPoint.fesm2015 ? 'fesm2015' : 'esm2015';
let dtsTransformFormat: EntryPointFormat|undefined;
formats.forEach(format => {
if (checkMarkerFile(entryPoint, format)) {
console.warn(`Skipping ${entryPoint.name} : ${format} (already built).`);
return;
const propertiesToCompile =
propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[];
const compiledFormats = new Set<EntryPointFormat>();
for (let i = 0; i < propertiesToCompile.length; i++) {
const property = propertiesToCompile[i];
const format = getEntryPointFormat(entryPoint.packageJson, property);
// No format then this property is not supposed to be compiled.
if (!format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
// We don't want to compile a format more than once.
// This could happen if there are multiple properties that map to the same format...
// E.g. `fesm5` and `module` both can point to the flat ESM5 format.
if (!compiledFormats.has(format)) {
compiledFormats.add(format);
// Use the first format found for typings transformation.
dtsTransformFormat = dtsTransformFormat || format;
if (checkMarker(entryPoint, property)) {
const bundle =
makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat);
if (bundle) {
transformer.transform(entryPoint, isCore, bundle);
} else {
console.warn(
`Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`);
}
} else {
console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
}
}
// Write the built-with-ngcc marker.
writeMarker(entryPoint, property);
}
const bundle =
makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat);
if (bundle === null) {
console.warn(
`Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`);
} else {
transformer.transform(entryPoint, isCore, bundle);
}
// Write the built-with-ngcc marker
writeMarkerFile(entryPoint, format);
});
if (!dtsTransformFormat) {
throw new Error(
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`);
}
});
}
export {NGCC_VERSION} from './packages/build_marker';

View File

@ -7,35 +7,42 @@
*/
import {resolve} from 'canonical-path';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {EntryPoint, EntryPointFormat} from './entry_point';
import {writeFileSync} from 'fs';
import {EntryPoint, EntryPointJsonProperty} from './entry_point';
export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
function getMarkerPath(entryPointPath: string, format: EntryPointFormat) {
return resolve(entryPointPath, `__modified_by_ngcc_for_${format}__`);
/**
* Check whether there is a build marker for the given entry-point and format property.
* @param entryPoint the entry-point to check for a marker.
* @param format the property in the package.json of the format for which we are checking for a
* marker.
* @returns true if the entry-point and format have already been built with this ngcc version.
* @throws Error if the entry-point and format have already been built with a different ngcc
* version.
*/
export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty): boolean {
const pkg = entryPoint.packageJson;
const compiledVersion = pkg.__modified_by_ngcc__ && pkg.__modified_by_ngcc__[format];
if (compiledVersion && compiledVersion !== NGCC_VERSION) {
throw new Error(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
}
return !!compiledVersion;
}
/**
* Check whether there is a build marker for the given entry point and format.
* @param entryPoint the entry point to check for a marker.
* @param format the format for which we are checking for a marker.
* Write a build marker for the given entry-point and format property, to indicate that it has
* been compiled by this version of ngcc.
*
* @param entryPoint the entry-point to write a marker.
* @param format the property in the package.json of the format for which we are writing the marker.
*/
export function checkMarkerFile(entryPoint: EntryPoint, format: EntryPointFormat): boolean {
const markerPath = getMarkerPath(entryPoint.path, format);
const markerExists = existsSync(markerPath);
if (markerExists) {
const previousVersion = readFileSync(markerPath, 'utf8');
if (previousVersion !== NGCC_VERSION) {
throw new Error(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
}
}
return markerExists;
}
export function writeMarkerFile(entryPoint: EntryPoint, format: EntryPointFormat) {
const markerPath = getMarkerPath(entryPoint.path, format);
writeFileSync(markerPath, NGCC_VERSION, 'utf8');
export function writeMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty) {
const pkg = entryPoint.packageJson;
if (!pkg.__modified_by_ngcc__) pkg.__modified_by_ngcc__ = {};
pkg.__modified_by_ngcc__[format] = NGCC_VERSION;
writeFileSync(resolve(entryPoint.path, 'package.json'), JSON.stringify(pkg), 'utf8');
}

View File

@ -8,6 +8,7 @@
import * as path from 'canonical-path';
import * as fs from 'fs';
import {isDefined} from '../utils';
/**
@ -33,6 +34,8 @@ export type EntryPointFormat = keyof(EntryPointPaths);
export interface EntryPoint extends EntryPointPaths {
/** The name of the package (e.g. `@angular/core`). */
name: string;
/** The parsed package.json file for this entry-point. */
packageJson: EntryPointPackageJson;
/** The path to the package that contains this entry-point. */
package: string;
/** The path to this entry point. */
@ -41,11 +44,7 @@ export interface EntryPoint extends EntryPointPaths {
typings: string;
}
/**
* The properties that may be loaded from the `package.json` file.
*/
interface EntryPointPackageJson {
name: string;
interface PackageJsonFormatProperties {
fesm2015?: string;
fesm5?: string;
es2015?: string; // if exists then it is actually FESM2015
@ -57,6 +56,103 @@ interface EntryPointPackageJson {
typings?: string; // TypeScript .d.ts files
}
/**
* The properties that may be loaded from the `package.json` file.
*/
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
name: string;
__modified_by_ngcc__?: {[key: string]: string};
}
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
/**
* Try to create an entry-point from the given paths and properties.
*
* @param packagePath the absolute path to the containing npm package
* @param entryPointPath the absolute path to the potential entry-point.
* @returns An entry-point if it is valid, `null` otherwise.
*/
export function getEntryPointInfo(packagePath: string, entryPointPath: string): EntryPoint|null {
const packageJsonPath = path.resolve(entryPointPath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return null;
}
const entryPointPackageJson = loadEntryPointPackage(packageJsonPath);
if (!entryPointPackageJson) {
return null;
}
// We must have a typings property
const typings = entryPointPackageJson.typings || entryPointPackageJson.types;
if (!typings) {
return null;
}
// Also there must exist a `metadata.json` file next to the typings entry-point.
const metadataPath =
path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
if (!fs.existsSync(metadataPath)) {
return null;
}
const formats = Object.keys(entryPointPackageJson)
.map((property: EntryPointJsonProperty) => {
const format = getEntryPointFormat(entryPointPackageJson, property);
return format ? {property, format} : undefined;
})
.filter(isDefined);
const entryPointInfo: EntryPoint = {
name: entryPointPackageJson.name,
packageJson: entryPointPackageJson,
package: packagePath,
path: entryPointPath,
typings: path.resolve(entryPointPath, typings)
};
// Add the formats to the entry-point info object.
formats.forEach(
item => entryPointInfo[item.format] =
path.resolve(entryPointPath, entryPointPackageJson[item.property] !));
return entryPointInfo;
}
/**
* Convert a package.json property into an entry-point format.
*
* The actual format is dependent not only on the property itself but also
* on what other properties exist in the package.json.
*
* @param entryPointProperties The package.json that contains the properties.
* @param property The property to convert to a format.
* @returns An entry-point format or `undefined` if none match the given property.
*/
export function getEntryPointFormat(
entryPointProperties: EntryPointPackageJson, property: string): EntryPointFormat|undefined {
switch (property) {
case 'fesm2015':
return 'fesm2015';
case 'fesm5':
return 'fesm5';
case 'es2015':
return !entryPointProperties.fesm2015 ? 'fesm2015' : 'esm2015';
case 'esm2015':
return 'esm2015';
case 'esm5':
return 'esm5';
case 'main':
return 'umd';
case 'module':
return !entryPointProperties.fesm5 ? 'fesm5' : 'esm5';
default:
return undefined;
}
}
/**
* Parses the JSON from a package.json file.
* @param packageJsonPath the absolute path to the package.json file.
@ -71,72 +167,3 @@ function loadEntryPointPackage(packageJsonPath: string): EntryPointPackageJson|n
return null;
}
}
/**
* Try to get an entry point from the given path.
* @param packagePath the absolute path to the containing npm package
* @param entryPointPath the absolute path to the potential entry point.
* @returns Info about the entry point if it is valid, `null` otherwise.
*/
export function getEntryPointInfo(packagePath: string, entryPointPath: string): EntryPoint|null {
const packageJsonPath = path.resolve(entryPointPath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return null;
}
const entryPointPackageJson = loadEntryPointPackage(packageJsonPath);
if (!entryPointPackageJson) {
return null;
}
// If there is `esm2015` then `es2015` will be FESM2015, otherwise ESM2015.
// If there is `esm5` then `module` will be FESM5, otherwise it will be ESM5.
const {
name,
module: modulePath,
types,
typings = types, // synonymous
es2015,
fesm2015 = es2015, // synonymous
fesm5 = modulePath, // synonymous
esm2015,
esm5,
main
} = entryPointPackageJson;
// Minimum requirement is that we have typings and one of esm2015 or fesm2015 formats.
if (!typings || !(fesm2015 || esm2015)) {
return null;
}
// Also there must exist a `metadata.json` file next to the typings entry-point.
const metadataPath =
path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
if (!fs.existsSync(metadataPath)) {
return null;
}
const entryPointInfo: EntryPoint = {
name,
package: packagePath,
path: entryPointPath,
typings: path.resolve(entryPointPath, typings),
};
if (esm2015) {
entryPointInfo.esm2015 = path.resolve(entryPointPath, esm2015);
}
if (fesm2015) {
entryPointInfo.fesm2015 = path.resolve(entryPointPath, fesm2015);
}
if (fesm5) {
entryPointInfo.fesm5 = path.resolve(entryPointPath, fesm5);
}
if (esm5) {
entryPointInfo.esm5 = path.resolve(entryPointPath, esm5);
}
if (main) {
entryPointInfo.umd = path.resolve(entryPointPath, main);
}
return entryPointInfo;
}

View File

@ -19,38 +19,74 @@ describe('ngcc main()', () => {
afterEach(restoreRealFileSystem);
it('should run ngcc without errors for fesm2015', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['fesm2015']})).not.toThrow();
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm2015']}))
.not.toThrow();
});
it('should run ngcc without errors for fesm5', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['fesm5']})).not.toThrow();
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm5']}))
.not.toThrow();
});
it('should run ngcc without errors for esm2015', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['esm2015']})).not.toThrow();
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm2015']}))
.not.toThrow();
});
it('should run ngcc without errors for esm5', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', formats: ['esm5']})).not.toThrow();
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm5']}))
.not.toThrow();
});
it('should only compile the given package entry-point (and its dependencies)', () => {
mainNgcc({
baseSourcePath: '/node_modules',
formats: ['esm2015'],
targetEntryPointPath: '@angular/common'
});
mainNgcc({baseSourcePath: '/node_modules', targetEntryPointPath: '@angular/common/http'});
expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({
esm2015: '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'
});
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({
esm2015: '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'
});
expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toBeUndefined();
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toBeUndefined();
});
it('should only build the format properties specified for each entry-point', () => {
mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['main', 'esm5', 'module']});
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',
});
});
});

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {readFileSync, writeFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {checkMarkerFile, writeMarkerFile} from '../../src/packages/build_marker';
import {checkMarker, writeMarker} from '../../src/packages/build_marker';
import {EntryPoint} from '../../src/packages/entry_point';
function createMockFileSystem() {
@ -94,58 +94,55 @@ function restoreRealFileSystem() {
}
function createEntryPoint(path: string): EntryPoint {
return {name: 'some-package', path, package: '', typings: ''};
return {
name: 'some-package',
path,
package: path,
typings: '',
packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8'))
};
}
describe('Marker files', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
describe('writeMarkerFile', () => {
it('should write a file containing the version placeholder', () => {
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(false);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false);
describe('writeMarker', () => {
it('should write a property in the package.json containing the version placeholder', () => {
let pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__).toBeUndefined();
expect(pkg.__modified_by_ngcc__).toBeUndefined();
writeMarkerFile(createEntryPoint('/node_modules/@angular/common'), 'fesm2015');
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(true);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false);
expect(
readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
writeMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015');
pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__modified_by_ngcc__.esm5).toBeUndefined();
writeMarkerFile(createEntryPoint('/node_modules/@angular/common'), 'esm5');
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__'))
.toBe(true);
expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(true);
expect(
readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
expect(readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__', 'utf8'))
.toEqual('0.0.0-PLACEHOLDER');
writeMarker(createEntryPoint('/node_modules/@angular/common'), 'esm5');
pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__modified_by_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
});
});
describe('checkMarkerFile', () => {
it('should return false if the marker file does not exist', () => {
expect(checkMarkerFile(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
describe('checkMarker', () => {
it('should return false if the marker property does not exist', () => {
expect(checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
.toBe(false);
});
it('should return true if the marker file exists and contains the correct version', () => {
writeFileSync(
'/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', '0.0.0-PLACEHOLDER',
'utf8');
expect(checkMarkerFile(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
.toBe(true);
it('should return true if the marker property exists and contains the correct version', () => {
const pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
pkg.__modified_by_ngcc__ = {fesm2015: '0.0.0-PLACEHOLDER'};
writeFileSync('/node_modules/@angular/common/package.json', JSON.stringify(pkg), 'utf8');
expect(checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015')).toBe(true);
});
it('should throw if the marker file exists but contains the wrong version', () => {
writeFileSync(
'/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'WRONG_VERSION',
'utf8');
expect(() => checkMarkerFile(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
it('should throw if the marker property exists but contains the wrong version', () => {
const pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
pkg.__modified_by_ngcc__ = {fesm2015: 'WRONG_VERSION'};
writeFileSync('/node_modules/@angular/common/package.json', JSON.stringify(pkg), 'utf8');
expect(() => checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');

View File

@ -25,7 +25,7 @@ describe('findEntryPoints()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should find sub-entry-points within a package', () => {
it('should find sub-entry-points within a package', () => {
const {entryPoints} = finder.findEntryPoints('/sub_entry_points');
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {getEntryPointInfo} from '../../src/packages/entry_point';
describe('getEntryPointInfo()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
@ -27,6 +27,7 @@ describe('getEntryPointInfo()', () => {
fesm5: `/some_package/valid_entry_point/fesm2015/valid_entry_point.js`,
esm5: `/some_package/valid_entry_point/esm2015/valid_entry_point.js`,
umd: `/some_package/valid_entry_point/bundles/valid_entry_point.umd.js`,
packageJson: loadPackageJson('/some_package/valid_entry_point'),
});
});
@ -63,6 +64,7 @@ describe('getEntryPointInfo()', () => {
fesm5: `/some_package/types_rather_than_typings/fesm2015/types_rather_than_typings.js`,
esm5: `/some_package/types_rather_than_typings/esm2015/types_rather_than_typings.js`,
umd: `/some_package/types_rather_than_typings/bundles/types_rather_than_typings.umd.js`,
packageJson: loadPackageJson('/some_package/types_rather_than_typings'),
});
});
@ -76,6 +78,7 @@ describe('getEntryPointInfo()', () => {
fesm2015: `/some_package/material_style/esm2015/material_style.js`,
fesm5: `/some_package/material_style/esm5/material_style.es5.js`,
umd: `/some_package/material_style/bundles/material_style.umd.js`,
packageJson: loadPackageJson('/some_package/material_style'),
});
});
@ -154,3 +157,7 @@ function createPackageJson(
}
return JSON.stringify(packageJson);
}
export function loadPackageJson(packagePath: string) {
return JSON.parse(readFileSync(packagePath + '/package.json', 'utf8'));
}