feat(ivy): ngcc - turn on CommonJS support (#30200)

PR Close #30200
This commit is contained in:
Pete Bacon Darwin 2019-04-29 18:51:52 +01:00 committed by Jason Aden
parent c7a9987067
commit e20b92ba37
6 changed files with 51 additions and 24 deletions

View File

@ -8,6 +8,7 @@
import {DepGraph} from 'dependency-graph'; import {DepGraph} from 'dependency-graph';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point'; import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
import {DependencyHost} from './dependency_host'; import {DependencyHost} from './dependency_host';
@ -65,7 +66,8 @@ export interface SortedEntryPointsInfo extends DependencyDiagnostics { entryPoin
*/ */
export class DependencyResolver { export class DependencyResolver {
constructor( constructor(
private logger: Logger, private hosts: Partial<Record<EntryPointFormat, DependencyHost>>) {} private fs: FileSystem, private logger: Logger,
private hosts: Partial<Record<EntryPointFormat, DependencyHost>>) {}
/** /**
* Sort the array of entry points so that the dependant entry points always come later than * Sort the array of entry points so that the dependant entry points always come later than
* their dependencies in the array. * their dependencies in the array.
@ -115,7 +117,7 @@ export class DependencyResolver {
// Now add the dependencies between them // Now add the dependencies between them
angularEntryPoints.forEach(entryPoint => { angularEntryPoints.forEach(entryPoint => {
const formatInfo = getEntryPointFormatInfo(entryPoint); const formatInfo = this.getEntryPointFormatInfo(entryPoint);
const host = this.hosts[formatInfo.format]; const host = this.hosts[formatInfo.format];
if (!host) { if (!host) {
throw new Error( throw new Error(
@ -164,22 +166,22 @@ export class DependencyResolver {
}); });
} }
} }
}
function getEntryPointFormatInfo(entryPoint: EntryPoint): private getEntryPointFormatInfo(entryPoint: EntryPoint):
{format: EntryPointFormat, path: AbsoluteFsPath} { {format: EntryPointFormat, path: AbsoluteFsPath} {
const properties = Object.keys(entryPoint.packageJson); const properties = Object.keys(entryPoint.packageJson);
for (let i = 0; i < properties.length; i++) { for (let i = 0; i < properties.length; i++) {
const property = properties[i] as EntryPointJsonProperty; const property = properties[i] as EntryPointJsonProperty;
const format = getEntryPointFormat(property); const format = getEntryPointFormat(this.fs, entryPoint, property);
if (format === 'esm2015' || format === 'esm5' || format === 'umd') { if (format === 'esm2015' || format === 'esm5' || format === 'umd' || format === 'commonjs') {
const formatPath = entryPoint.packageJson[property] !; const formatPath = entryPoint.packageJson[property] !;
return {format, path: AbsoluteFsPath.resolve(entryPoint.path, formatPath)}; return {format, path: AbsoluteFsPath.resolve(entryPoint.path, formatPath)};
}
} }
throw new Error(
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
} }
throw new Error(
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
} }
interface DependencyGraph extends DependencyDiagnostics { interface DependencyGraph extends DependencyDiagnostics {

View File

@ -7,6 +7,7 @@
*/ */
import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../src/ngtsc/path';
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
import {DependencyResolver} from './dependencies/dependency_resolver'; import {DependencyResolver} from './dependencies/dependency_resolver';
import {EsmDependencyHost} from './dependencies/esm_dependency_host'; import {EsmDependencyHost} from './dependencies/esm_dependency_host';
import {ModuleResolver} from './dependencies/module_resolver'; import {ModuleResolver} from './dependencies/module_resolver';
@ -64,7 +65,7 @@ export interface NgccOptions {
pathMappings?: PathMappings; pathMappings?: PathMappings;
} }
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd']; const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd', 'commonjs'];
/** /**
* This is the main entry-point into ngcc (aNGular Compatibility Compiler). * This is the main entry-point into ngcc (aNGular Compatibility Compiler).
@ -83,8 +84,13 @@ export function mainNgcc(
const moduleResolver = new ModuleResolver(fs, pathMappings); const moduleResolver = new ModuleResolver(fs, pathMappings);
const esmDependencyHost = new EsmDependencyHost(fs, moduleResolver); const esmDependencyHost = new EsmDependencyHost(fs, moduleResolver);
const umdDependencyHost = new UmdDependencyHost(fs, moduleResolver); const umdDependencyHost = new UmdDependencyHost(fs, moduleResolver);
const resolver = new DependencyResolver( const commonJsDependencyHost = new CommonJsDependencyHost(fs, moduleResolver);
logger, {esm5: esmDependencyHost, esm2015: esmDependencyHost, umd: umdDependencyHost}); const resolver = new DependencyResolver(fs, logger, {
esm5: esmDependencyHost,
esm2015: esmDependencyHost,
umd: umdDependencyHost,
commonjs: commonJsDependencyHost
});
const finder = new EntryPointFinder(fs, logger, resolver); const finder = new EntryPointFinder(fs, logger, resolver);
const fileWriter = getFileWriter(fs, createNewEntryPointFormats); const fileWriter = getFileWriter(fs, createNewEntryPointFormats);
@ -127,7 +133,7 @@ export function mainNgcc(
for (let i = 0; i < propertiesToConsider.length; i++) { for (let i = 0; i < propertiesToConsider.length; i++) {
const property = propertiesToConsider[i] as EntryPointJsonProperty; const property = propertiesToConsider[i] as EntryPointJsonProperty;
const formatPath = entryPointPackageJson[property]; const formatPath = entryPointPackageJson[property];
const format = getEntryPointFormat(property); const format = getEntryPointFormat(fs, entryPoint, property);
// No format then this property is not supposed to be compiled. // No format then this property is not supposed to be compiled.
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue; if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;

View File

@ -5,8 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system'; import {FileSystem} from '../file_system/file_system';
import {parseStatementForUmdModule} from '../host/umd_host';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
/** /**
@ -107,7 +109,8 @@ export function getEntryPointInfo(
* @param property The property to convert to a format. * @param property The property to convert to a format.
* @returns An entry-point format or `undefined` if none match the given property. * @returns An entry-point format or `undefined` if none match the given property.
*/ */
export function getEntryPointFormat(property: string): EntryPointFormat|undefined { export function getEntryPointFormat(
fs: FileSystem, entryPoint: EntryPoint, property: string): EntryPointFormat|undefined {
switch (property) { switch (property) {
case 'fesm2015': case 'fesm2015':
return 'esm2015'; return 'esm2015';
@ -120,7 +123,8 @@ export function getEntryPointFormat(property: string): EntryPointFormat|undefine
case 'esm5': case 'esm5':
return 'esm5'; return 'esm5';
case 'main': case 'main':
return 'umd'; const pathToMain = AbsoluteFsPath.join(entryPoint.path, entryPoint.packageJson['main'] !);
return isUmdModule(fs, pathToMain) ? 'umd' : 'commonjs';
case 'module': case 'module':
return 'esm5'; return 'esm5';
default: default:
@ -143,3 +147,10 @@ function loadEntryPointPackage(
return null; return null;
} }
} }
function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean {
const sourceFile =
ts.createSourceFile(sourceFilePath, fs.readFile(sourceFilePath), ts.ScriptTarget.ES5);
return sourceFile.statements.length > 0 &&
parseStatementForUmdModule(sourceFile.statements[0]) !== null;
}

View File

@ -13,11 +13,13 @@ import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
import {FileSystem} from '../file_system/file_system'; import {FileSystem} from '../file_system/file_system';
import {CommonJsReflectionHost} from '../host/commonjs_host';
import {Esm2015ReflectionHost} from '../host/esm2015_host'; import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host'; import {Esm5ReflectionHost} from '../host/esm5_host';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {UmdReflectionHost} from '../host/umd_host'; import {UmdReflectionHost} from '../host/umd_host';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {CommonJsRenderingFormatter} from '../rendering/commonjs_rendering_formatter';
import {DtsRenderer} from '../rendering/dts_renderer'; import {DtsRenderer} from '../rendering/dts_renderer';
import {Esm5RenderingFormatter} from '../rendering/esm5_rendering_formatter'; import {Esm5RenderingFormatter} from '../rendering/esm5_rendering_formatter';
import {EsmRenderingFormatter} from '../rendering/esm_rendering_formatter'; import {EsmRenderingFormatter} from '../rendering/esm_rendering_formatter';
@ -97,6 +99,9 @@ export class Transformer {
case 'umd': case 'umd':
return new UmdReflectionHost( return new UmdReflectionHost(
this.logger, isCore, bundle.src.program, bundle.src.host, bundle.dts); this.logger, isCore, bundle.src.program, bundle.src.host, bundle.dts);
case 'commonjs':
return new CommonJsReflectionHost(
this.logger, isCore, bundle.src.program, bundle.src.host, bundle.dts);
default: default:
throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`); throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`);
} }
@ -114,6 +119,8 @@ export class Transformer {
throw new Error('UmdRenderer requires a UmdReflectionHost'); throw new Error('UmdRenderer requires a UmdReflectionHost');
} }
return new UmdRenderingFormatter(host, isCore); return new UmdRenderingFormatter(host, isCore);
case 'commonjs':
return new CommonJsRenderingFormatter(host, isCore);
default: default:
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
} }

View File

@ -25,7 +25,7 @@ describe('DependencyResolver', () => {
fs = new MockFileSystem(); fs = new MockFileSystem();
moduleResolver = new ModuleResolver(fs); moduleResolver = new ModuleResolver(fs);
host = new EsmDependencyHost(fs, moduleResolver); host = new EsmDependencyHost(fs, moduleResolver);
resolver = new DependencyResolver(new MockLogger(), {esm5: host, esm2015: host}); resolver = new DependencyResolver(fs, new MockLogger(), {esm5: host, esm2015: host});
}); });
describe('sortEntryPointsByDependency()', () => { describe('sortEntryPointsByDependency()', () => {
const first = { const first = {
@ -117,7 +117,7 @@ describe('DependencyResolver', () => {
}); });
it('should error if there is no appropriate DependencyHost for the given formats', () => { it('should error if there is no appropriate DependencyHost for the given formats', () => {
resolver = new DependencyResolver(new MockLogger(), {esm2015: host}); resolver = new DependencyResolver(fs, new MockLogger(), {esm2015: host});
expect(() => resolver.sortEntryPointsByDependency([first])) expect(() => resolver.sortEntryPointsByDependency([first]))
.toThrowError( .toThrowError(
`Could not find a suitable format for computing dependencies of entry-point: '${first.path}'.`); `Could not find a suitable format for computing dependencies of entry-point: '${first.path}'.`);
@ -152,7 +152,8 @@ describe('DependencyResolver', () => {
it('should use the appropriate DependencyHost for each entry-point', () => { it('should use the appropriate DependencyHost for each entry-point', () => {
const esm5Host = new EsmDependencyHost(fs, moduleResolver); const esm5Host = new EsmDependencyHost(fs, moduleResolver);
const esm2015Host = new EsmDependencyHost(fs, moduleResolver); const esm2015Host = new EsmDependencyHost(fs, moduleResolver);
resolver = new DependencyResolver(new MockLogger(), {esm5: esm5Host, esm2015: esm2015Host}); resolver =
new DependencyResolver(fs, new MockLogger(), {esm5: esm5Host, esm2015: esm2015Host});
spyOn(esm5Host, 'findDependencies').and.callFake(createFakeComputeDependencies(dependencies)); spyOn(esm5Host, 'findDependencies').and.callFake(createFakeComputeDependencies(dependencies));
spyOn(esm2015Host, 'findDependencies') spyOn(esm2015Host, 'findDependencies')
.and.callFake(createFakeComputeDependencies(dependencies)); .and.callFake(createFakeComputeDependencies(dependencies));

View File

@ -23,7 +23,7 @@ describe('findEntryPoints()', () => {
beforeEach(() => { beforeEach(() => {
const fs = createMockFileSystem(); const fs = createMockFileSystem();
resolver = new DependencyResolver( resolver = new DependencyResolver(
new MockLogger(), {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))}); fs, new MockLogger(), {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))});
spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => { spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => {
return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []}; return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []};
}); });