refactor(ivy): ngcc - clean up the public API and export hasBeenProcessed helper (#29092)

Now the public API does not contain internal types, such as `AbsoluteFsPath` and
`EntryPointJsonProperty`. Instead we just accept strings and then guard them in
`mainNgcc` as appropriate.

A new public API function (`hasBeenProcessed`) has been exported to allow programmatic
checking of the build marker when the package.json contents are already known.

PR Close #29092
This commit is contained in:
Pete Bacon Darwin 2019-03-20 13:47:59 +00:00 committed by Matias Niemelä
parent 7ea0d1bd3a
commit 55ea8da6eb
5 changed files with 68 additions and 48 deletions

View File

@ -6,4 +6,5 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
export {mainNgcc} from './src/main'; export {NgccOptions, mainNgcc as process} from './src/main';
export {hasBeenProcessed} from './src/packages/build_marker';

View File

@ -9,10 +9,7 @@
import * as path from 'canonical-path'; import * as path from 'canonical-path';
import * as yargs from 'yargs'; import * as yargs from 'yargs';
import {AbsoluteFsPath} from '../src/ngtsc/path';
import {mainNgcc} from './src/main'; import {mainNgcc} from './src/main';
import {EntryPointJsonProperty} from './src/packages/entry_point';
// CLI entry point // CLI entry point
if (require.main === module) { if (require.main === module) {
@ -21,7 +18,8 @@ if (require.main === module) {
yargs yargs
.option('s', { .option('s', {
alias: 'source', alias: 'source',
describe: 'A path to the `node_modules` folder to compile.', describe:
'A path (relative to the working directory) of the `node_modules` folder to process.',
default: './node_modules' default: './node_modules'
}) })
.option('f', {alias: 'formats', hidden: true, array: true}) .option('f', {alias: 'formats', hidden: true, array: true})
@ -29,14 +27,15 @@ if (require.main === module) {
alias: 'properties', alias: 'properties',
array: true, array: true,
describe: describe:
'An array of names of properties in package.json (e.g. `module` or `es2015`)\n' + 'An array of names of properties in package.json to compile (e.g. `module` or `es2015`)\n' +
'These properties should hold a path to a bundle-format to compile.\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 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.' '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', { .option('t', {
alias: 'target', alias: 'target',
describe: 'A path to a single entry-point to compile (plus its dependencies).', describe:
'A relative path (from the `source` path) to a single entry-point to process (plus its dependencies).',
}) })
.option('first-only', { .option('first-only', {
describe: describe:
@ -51,13 +50,13 @@ if (require.main === module) {
'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.'); 'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.');
process.exit(1); process.exit(1);
} }
const baseSourcePath = AbsoluteFsPath.from(path.resolve(options['s'] || './node_modules')); const baseSourcePath = path.resolve(options['s'] || './node_modules');
const propertiesToConsider: EntryPointJsonProperty[] = options['p']; const propertiesToConsider: string[] = options['p'];
const targetEntryPointPath = const targetEntryPointPath = options['t'] ? options['t'] : undefined;
options['t'] ? AbsoluteFsPath.from(path.resolve(options['t'])) : undefined;
const compileAllFormats = !options['first-only']; const compileAllFormats = !options['first-only'];
try { try {
mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath, compileAllFormats}); mainNgcc(
{basePath: baseSourcePath, propertiesToConsider, targetEntryPointPath, compileAllFormats});
process.exitCode = 0; process.exitCode = 0;
} catch (e) { } catch (e) {
console.error(e.stack || e.message); console.error(e.stack || e.message);

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {resolve} from 'canonical-path';
import {AbsoluteFsPath, PathSegment} from '../../src/ngtsc/path';
import {checkMarker, writeMarker} from './packages/build_marker'; import {checkMarker, writeMarker} from './packages/build_marker';
import {DependencyHost} from './packages/dependency_host'; import {DependencyHost} from './packages/dependency_host';
@ -21,21 +23,23 @@ import {Transformer} from './packages/transformer';
* The options to configure the ngcc compiler. * The options to configure the ngcc compiler.
*/ */
export interface NgccOptions { export interface NgccOptions {
/** The path to the node_modules folder that contains the packages to compile. */ /** The absolute path to the `node_modules` folder that contains the packages to process. */
baseSourcePath: AbsoluteFsPath; basePath: string;
/** /**
* The path, relative to `baseSourcePath` of the primary package to be compiled. * The path, relative to `basePath` to the primary package to be processed.
* All its dependencies will need to be compiled too. *
* All its dependencies will need to be processed too.
*/ */
targetEntryPointPath?: AbsoluteFsPath; targetEntryPointPath?: string;
/** /**
* Which entry-point properties in the package.json to consider when compiling. * Which entry-point properties in the package.json to consider when processing an entry-point.
* Each of properties contain a path to particular bundle format for a given entry-point. * Each property should hold a path to the particular bundle format for the entry-point.
* Defaults to all the properties in the package.json.
*/ */
propertiesToConsider?: EntryPointJsonProperty[]; propertiesToConsider?: string[];
/** /**
* Whether to compile all specified formats or to stop compiling this entry-point at the first * Whether to process all formats specified by (`propertiesToConsider`) or to stop processing
* matching format. Defaults to `true`. * this entry-point at the first matching format. Defaults to `true`.
*/ */
compileAllFormats?: boolean; compileAllFormats?: boolean;
} }
@ -50,15 +54,19 @@ const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015'];
* *
* @param options The options telling ngcc what to compile and how. * @param options The options telling ngcc what to compile and how.
*/ */
export function mainNgcc({baseSourcePath, targetEntryPointPath, export function mainNgcc({basePath, targetEntryPointPath,
propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES,
compileAllFormats = true}: NgccOptions): void { compileAllFormats = true}: NgccOptions): void {
const transformer = new Transformer(baseSourcePath, baseSourcePath); const transformer = new Transformer(basePath, basePath);
const host = new DependencyHost(); const host = new DependencyHost();
const resolver = new DependencyResolver(host); const resolver = new DependencyResolver(host);
const finder = new EntryPointFinder(resolver); const finder = new EntryPointFinder(resolver);
const {entryPoints} = finder.findEntryPoints(baseSourcePath, targetEntryPointPath); const absoluteTargetEntryPointPath = targetEntryPointPath ?
AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) :
undefined;
const {entryPoints} =
finder.findEntryPoints(AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath);
entryPoints.forEach(entryPoint => { entryPoints.forEach(entryPoint => {
// Are we compiling the Angular core? // Are we compiling the Angular core?
@ -67,7 +75,7 @@ export function mainNgcc({baseSourcePath, targetEntryPointPath,
const compiledFormats = new Set<string>(); const compiledFormats = new Set<string>();
for (let i = 0; i < propertiesToConsider.length; i++) { for (let i = 0; i < propertiesToConsider.length; i++) {
const property = propertiesToConsider[i]; const property = propertiesToConsider[i] as EntryPointJsonProperty;
const formatPath = entryPoint.packageJson[property]; const formatPath = entryPoint.packageJson[property];
const format = getEntryPointFormat(property); const format = getEntryPointFormat(property);

View File

@ -14,17 +14,20 @@ import {EntryPoint, EntryPointJsonProperty} from './entry_point';
export const NGCC_VERSION = '0.0.0-PLACEHOLDER'; export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
/** /**
* Check whether there is a build marker for the given entry-point and format property. * Check whether ngcc has already processed a given entry-point format.
* @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 * The entry-point is defined by the package.json contents provided.
* marker. * The format is defined by the provided property name of the path to the bundle in the package.json
* @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 * @param packageJson The parsed contents of the package.json file for the entry-point.
* @param format The entry-point format property in the package.json to check.
* @returns true if the entry-point and format have already been processed with this ngcc version.
* @throws Error if the entry-point has already been processed with a different ngcc
* version. * version.
*/ */
export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty): boolean { export function hasBeenProcessed(packageJson: any, format: string): boolean {
const pkg = entryPoint.packageJson; const compiledVersion =
const compiledVersion = pkg.__modified_by_ngcc__ && pkg.__modified_by_ngcc__[format]; packageJson && packageJson.__modified_by_ngcc__ && packageJson.__modified_by_ngcc__[format];
if (compiledVersion && compiledVersion !== NGCC_VERSION) { if (compiledVersion && compiledVersion !== NGCC_VERSION) {
throw new Error( throw new Error(
'The ngcc compiler has changed since the last ngcc build.\n' + 'The ngcc compiler has changed since the last ngcc build.\n' +
@ -33,6 +36,21 @@ export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProper
return !!compiledVersion; return !!compiledVersion;
} }
/**
* Check whether there is a marker for the given entry-point and format property, indicating that
* the given bundle has already been processed.
* @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 processed with this ngcc version.
* @throws Error if the entry-point and format have already been processed with a different ngcc
* version.
*/
export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty): boolean {
const pkg = entryPoint.packageJson;
return hasBeenProcessed(pkg, format);
}
/** /**
* Write a build marker for the given entry-point and format property, to indicate that it has * Write a build marker for the given entry-point and format property, to indicate that it has
* been compiled by this version of ngcc. * been compiled by this version of ngcc.

View File

@ -13,30 +13,24 @@ const Module = require('module');
import {mainNgcc} from '../../src/main'; import {mainNgcc} from '../../src/main';
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
const NODE_MODULES = AbsoluteFsPath.from('/node_modules');
describe('ngcc main()', () => { describe('ngcc main()', () => {
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);
it('should run ngcc without errors for esm2015', () => { it('should run ngcc without errors for esm2015', () => {
expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm2015']})) expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']}))
.not.toThrow(); .not.toThrow();
}); });
it('should run ngcc without errors for esm5', () => { it('should run ngcc without errors for esm5', () => {
expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm5']})) expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm5']}))
.not.toThrow(); .not.toThrow();
}); });
describe('with targetEntryPointPath', () => { describe('with targetEntryPointPath', () => {
it('should only compile the given package entry-point (and its dependencies).', () => { it('should only compile the given package entry-point (and its dependencies).', () => {
mainNgcc({ mainNgcc({basePath: '/node_modules', targetEntryPointPath: '@angular/common/http'});
baseSourcePath: NODE_MODULES,
targetEntryPointPath: AbsoluteFsPath.from('/node_modules/@angular/common/http')
});
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER',
@ -74,7 +68,7 @@ describe('ngcc main()', () => {
it('should only compile the entry-point formats given in the `propertiesToConsider` list', it('should only compile the entry-point formats given in the `propertiesToConsider` list',
() => { () => {
mainNgcc({ mainNgcc({
baseSourcePath: NODE_MODULES, basePath: '/node_modules',
propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'] propertiesToConsider: ['main', 'esm5', 'module', 'fesm5']
}); });
@ -107,7 +101,7 @@ describe('ngcc main()', () => {
describe('with compileAllFormats set to false', () => { describe('with compileAllFormats set to false', () => {
it('should only compile the first matching format', () => { it('should only compile the first matching format', () => {
mainNgcc({ mainNgcc({
baseSourcePath: NODE_MODULES, basePath: '/node_modules',
propertiesToConsider: ['main', 'module', 'fesm5', 'esm5'], propertiesToConsider: ['main', 'module', 'fesm5', 'esm5'],
compileAllFormats: false compileAllFormats: false
}); });