diff --git a/packages/compiler-cli/ngcc/index.ts b/packages/compiler-cli/ngcc/index.ts index 619de61b3a..90d58bb7d1 100644 --- a/packages/compiler-cli/ngcc/index.ts +++ b/packages/compiler-cli/ngcc/index.ts @@ -7,14 +7,16 @@ */ import {CachedFileSystem, NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system'; -import {mainNgcc} from './src/main'; +import {AsyncNgccOptions, NgccOptions, SyncNgccOptions, mainNgcc} from './src/main'; export {ConsoleLogger, LogLevel} from './src/logging/console_logger'; export {Logger} from './src/logging/logger'; -export {NgccOptions} from './src/main'; +export {AsyncNgccOptions, NgccOptions, SyncNgccOptions} from './src/main'; export {PathMappings} from './src/utils'; -export function process(...args: Parameters) { +export function process(options: AsyncNgccOptions): Promise; +export function process(options: SyncNgccOptions): void; +export function process(options: NgccOptions): void|Promise { // Recreate the file system on each call to reset the cache setFileSystem(new CachedFileSystem(new NodeJSFileSystem())); - return mainNgcc(...args); + return mainNgcc(options); } diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts index 665200a6a5..8943c4531c 100644 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ b/packages/compiler-cli/ngcc/main-ngcc.ts @@ -64,17 +64,21 @@ if (require.main === module) { const targetEntryPointPath = options['t'] ? options['t'] : undefined; const compileAllFormats = !options['first-only']; const logLevel = options['l'] as keyof typeof LogLevel | undefined; - try { - mainNgcc({ - basePath: baseSourcePath, - propertiesToConsider, - targetEntryPointPath, - compileAllFormats, - logger: logLevel && new ConsoleLogger(LogLevel[logLevel]), - }); - process.exitCode = 0; - } catch (e) { - console.error(e.stack || e.message); - process.exitCode = 1; - } + + (async() => { + try { + await mainNgcc({ + basePath: baseSourcePath, + propertiesToConsider, + targetEntryPointPath, + compileAllFormats, + logger: logLevel && new ConsoleLogger(LogLevel[logLevel]), + async: true, + }); + process.exitCode = 0; + } catch (e) { + console.error(e.stack || e.message); + process.exitCode = 1; + } + })(); } diff --git a/packages/compiler-cli/ngcc/src/execution/api.ts b/packages/compiler-cli/ngcc/src/execution/api.ts index e1a9eb6731..c72156f770 100644 --- a/packages/compiler-cli/ngcc/src/execution/api.ts +++ b/packages/compiler-cli/ngcc/src/execution/api.ts @@ -33,7 +33,7 @@ export interface ExecutionOptions { export interface Executor { execute( analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn, - options: ExecutionOptions): void; + options: ExecutionOptions): void|Promise; } /** Represents metadata related to the processing of an entry-point. */ diff --git a/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts b/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts index 0d54b678cc..92d9031203 100644 --- a/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts +++ b/packages/compiler-cli/ngcc/src/execution/single_process_executor.ts @@ -44,3 +44,12 @@ export class SingleProcessExecutor implements Executor { checkForUnprocessedEntryPoints(processingMetadataPerEntryPoint, options.propertiesToConsider); } } + +/** + * An `Executor` that processes all tasks serially, but still completes asynchronously. + */ +export class AsyncSingleProcessExecutor extends SingleProcessExecutor { + async execute(...args: Parameters): Promise { + return super.execute(...args); + } +} diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index a8e00da5b7..9b29773e25 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -17,7 +17,7 @@ import {UmdDependencyHost} from './dependencies/umd_dependency_host'; import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder'; import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder'; import {AnalyzeEntryPointsFn, CreateCompileFn, EntryPointProcessingMetadata, Executor, Task, TaskProcessingOutcome} from './execution/api'; -import {SingleProcessExecutor} from './execution/single_process_executor'; +import {AsyncSingleProcessExecutor, SingleProcessExecutor} from './execution/single_process_executor'; import {ConsoleLogger, LogLevel} from './logging/console_logger'; import {Logger} from './logging/logger'; import {hasBeenProcessed, markAsProcessed} from './packages/build_marker'; @@ -32,11 +32,12 @@ import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer'; import {DirectPackageJsonUpdater, PackageJsonUpdater} from './writing/package_json_updater'; /** - * The options to configure the ngcc compiler. + * The options to configure the ngcc compiler for synchronous execution. */ -export interface NgccOptions { +export interface SyncNgccOptions { /** The absolute path to the `node_modules` folder that contains the packages to process. */ basePath: string; + /** * The path to the primary package to be processed. If not absolute then it must be relative to * `basePath`. @@ -44,36 +45,60 @@ export interface NgccOptions { * All its dependencies will need to be processed too. */ targetEntryPointPath?: string; + /** * Which entry-point properties in the package.json to consider when processing an 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?: string[]; + /** * Whether to process all formats specified by (`propertiesToConsider`) or to stop processing * this entry-point at the first matching format. Defaults to `true`. */ compileAllFormats?: boolean; + /** * Whether to create new entry-points bundles rather than overwriting the original files. */ createNewEntryPointFormats?: boolean; + /** * Provide a logger that will be called with log messages. */ logger?: Logger; + /** * Paths mapping configuration (`paths` and `baseUrl`), as found in `ts.CompilerOptions`. * These are used to resolve paths to locally built Angular libraries. */ pathMappings?: PathMappings; + /** * Provide a file-system service that will be used by ngcc for all file interactions. */ fileSystem?: FileSystem; + + /** + * Whether the compilation should run and return asynchronously. Allowing asynchronous execution + * may speed up the compilation by utilizing multiple CPU cores (if available). + * + * Default: `false` (i.e. run synchronously) + */ + async?: false; } +/** + * The options to configure the ngcc compiler for asynchronous execution. + */ +export type AsyncNgccOptions = Omit& {async: true}; + +/** + * The options to configure the ngcc compiler. + */ +export type NgccOptions = AsyncNgccOptions | SyncNgccOptions; + /** * This is the main entry-point into ngcc (aNGular Compatibility Compiler). * @@ -82,10 +107,13 @@ export interface NgccOptions { * * @param options The options telling ngcc what to compile and how. */ +export function mainNgcc(options: AsyncNgccOptions): Promise; +export function mainNgcc(options: SyncNgccOptions): void; export function mainNgcc( {basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, compileAllFormats = true, createNewEntryPointFormats = false, - logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void { + logger = new ConsoleLogger(LogLevel.info), pathMappings, async = false}: NgccOptions): void| + Promise { const fileSystem = getFileSystem(); const pkgJsonUpdater = new DirectPackageJsonUpdater(fileSystem); @@ -191,7 +219,7 @@ export function mainNgcc( }; // The executor for actually planning and getting the work done. - const executor = getExecutor(logger, pkgJsonUpdater); + const executor = getExecutor(async, logger, pkgJsonUpdater); const execOpts = {compileAllFormats, propertiesToConsider}; return executor.execute(analyzeEntryPoints, createCompileFn, execOpts); @@ -226,8 +254,12 @@ function getFileWriter( new InPlaceFileWriter(fs); } -function getExecutor(logger: Logger, pkgJsonUpdater: PackageJsonUpdater): Executor { - return new SingleProcessExecutor(logger, pkgJsonUpdater); +function getExecutor(async: boolean, logger: Logger, pkgJsonUpdater: PackageJsonUpdater): Executor { + if (async) { + return new AsyncSingleProcessExecutor(logger, pkgJsonUpdater); + } else { + return new SingleProcessExecutor(logger, pkgJsonUpdater); + } } function getEntryPoints( diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 31351e81c6..68970b6b0e 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -11,6 +11,7 @@ import {loadStandardTestFiles, loadTestFiles} from '../../../test/helpers'; import {mainNgcc} from '../../src/main'; import {markAsProcessed} from '../../src/packages/build_marker'; import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; +import {Transformer} from '../../src/packages/transformer'; import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater'; import {MockLogger} from '../helpers/mock_logger'; @@ -56,6 +57,53 @@ runInEachFileSystem(() => { }); }); + it('should throw, if an error happens during processing', () => { + spyOn(Transformer.prototype, 'transform').and.throwError('Test error.'); + + expect(() => mainNgcc({ + basePath: '/dist', + targetEntryPointPath: 'local-package', + propertiesToConsider: ['main', 'es2015'], + logger: new MockLogger(), + })) + .toThrowError(`Test error.`); + + expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); + expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toBeUndefined(); + }); + + describe('in async mode', () => { + it('should run ngcc without errors for fesm2015', async() => { + const promise = mainNgcc({ + basePath: '/node_modules', + propertiesToConsider: ['fesm2015'], + async: true, + }); + + expect(promise).toEqual(jasmine.any(Promise)); + await promise; + }); + + it('should reject, if an error happens during processing', async() => { + spyOn(Transformer.prototype, 'transform').and.throwError('Test error.'); + + const promise = mainNgcc({ + basePath: '/dist', + targetEntryPointPath: 'local-package', + propertiesToConsider: ['main', 'es2015'], + logger: new MockLogger(), + async: true, + }); + + await promise.then( + () => Promise.reject('Expected promise to be rejected.'), + err => expect(err).toEqual(new Error('Test error.'))); + + expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); + expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toBeUndefined(); + }); + }); + describe('with targetEntryPointPath', () => { it('should only compile the given package entry-point (and its dependencies).', () => { const STANDARD_MARKERS = {