refactor(ivy): ngcc - remove flat-format and use AbsoluteFsPath (#29092)

Now that we are using package.json properties to indicate which
entry-point format to compile, it turns out that we don't really
need to distinguish between flat and non-flat formats, unless we
are compiling `@angular/core`.

PR Close #29092
This commit is contained in:
Pete Bacon Darwin 2019-03-20 13:47:58 +00:00 committed by Matias Niemelä
parent cd449021c1
commit 7b55ba58b9
19 changed files with 276 additions and 283 deletions

View File

@ -17,7 +17,7 @@
"build": "yarn ~~build", "build": "yarn ~~build",
"prebuild-local": "yarn setup-local", "prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build", "build-local": "yarn ~~build",
"prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc", "prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc --properties module es2015",
"build-with-ivy": "node scripts/build-with-ivy", "build-with-ivy": "node scripts/build-with-ivy",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js cafa558cf", "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", "lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",

View File

@ -72,7 +72,7 @@ class ExampleBoilerPlate {
// the module typings if we specified an "es2015" format. This means that // 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 // we also need to build with "fesm2015" in order to get updated typings
// which are needed for compilation. // which are needed for compilation.
shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc`); shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc --properties module es2015`);
} }
exampleFolders.forEach(exampleFolder => { exampleFolders.forEach(exampleFolder => {

View File

@ -9,6 +9,8 @@
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'; import {EntryPointJsonProperty} from './src/packages/entry_point';
@ -19,7 +21,7 @@ if (require.main === module) {
yargs yargs
.option('s', { .option('s', {
alias: 'source', alias: 'source',
describe: 'A path to the root folder to compile.', describe: 'A path to the `node_modules` folder to compile.',
default: './node_modules' default: './node_modules'
}) })
.option('f', {alias: 'formats', hidden: true, array: true}) .option('f', {alias: 'formats', hidden: true, array: true})
@ -34,8 +36,7 @@ if (require.main === module) {
}) })
.option('t', { .option('t', {
alias: 'target', alias: 'target',
describe: 'A path to a root folder where the compiled files will be written.', describe: 'A path to a single entry-point to compile (plus its dependencies).',
defaultDescription: 'The `source` folder.'
}) })
.help() .help()
.parse(args); .parse(args);
@ -45,11 +46,12 @@ 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: string = path.resolve(options['s']); const baseSourcePath = AbsoluteFsPath.from(path.resolve(options['s'] || './node_modules'));
const propertiesToConsider: EntryPointJsonProperty[] = options['p']; const propertiesToConsider: EntryPointJsonProperty[] = options['p'];
const baseTargetPath: string = options['t']; const targetEntryPointPath =
options['t'] ? AbsoluteFsPath.from(path.resolve(options['t'])) : undefined;
try { try {
mainNgcc({baseSourcePath, propertiesToConsider, baseTargetPath}); mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath});
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,6 +6,7 @@
* 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 {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';
import {DependencyResolver} from './packages/dependency_resolver'; import {DependencyResolver} from './packages/dependency_resolver';
@ -19,14 +20,12 @@ import {Transformer} from './packages/transformer';
*/ */
export interface NgccOptions { export interface NgccOptions {
/** The path to the node_modules folder that contains the packages to compile. */ /** The path to the node_modules folder that contains the packages to compile. */
baseSourcePath: string; baseSourcePath: AbsoluteFsPath;
/** The path to the node_modules folder where modified files should be written. */
baseTargetPath?: string;
/** /**
* The path, relative to `baseSourcePath` of the primary package to be compiled. * The path, relative to `baseSourcePath` of the primary package to be compiled.
* All its dependencies will need to be compiled too. * All its dependencies will need to be compiled too.
*/ */
targetEntryPointPath?: string; targetEntryPointPath?: AbsoluteFsPath;
/** /**
* Which entry-point properties in the package.json to consider when compiling. * 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. * Each of properties contain a path to particular bundle format for a given entry-point.
@ -34,7 +33,7 @@ export interface NgccOptions {
propertiesToConsider?: EntryPointJsonProperty[]; propertiesToConsider?: EntryPointJsonProperty[];
} }
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'fesm5', 'fesm2015']; const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015'];
/** /**
* This is the main entry-point into ngcc (aNGular Compatibility Compiler). * This is the main entry-point into ngcc (aNGular Compatibility Compiler).
@ -44,9 +43,9 @@ const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'fesm5', 'fesm
* *
* @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, baseTargetPath = baseSourcePath, targetEntryPointPath, export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToConsider}: NgccOptions):
propertiesToConsider}: NgccOptions): void { void {
const transformer = new Transformer(baseSourcePath, baseTargetPath); const transformer = new Transformer(baseSourcePath, baseSourcePath);
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);
@ -57,51 +56,50 @@ export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targe
// Are we compiling the Angular core? // Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core'; const isCore = entryPoint.name === '@angular/core';
let dtsTransformFormat: EntryPointFormat|undefined;
const propertiesToCompile = const propertiesToCompile =
propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[]; propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[];
const compiledFormats = new Set<EntryPointFormat>(); const compiledFormats = new Set<string>();
for (let i = 0; i < propertiesToCompile.length; i++) { for (let i = 0; i < propertiesToCompile.length; i++) {
const property = propertiesToCompile[i]; const property = propertiesToCompile[i];
const format = getEntryPointFormat(entryPoint.packageJson, property); const formatPath = entryPoint.packageJson[property];
const format = getEntryPointFormat(property);
// No format then this property is not supposed to be compiled. // No format then this property is not supposed to be compiled.
if (!format || SUPPORTED_FORMATS.indexOf(format) === -1) continue; if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
// We don't want to compile a format more than once. if (checkMarker(entryPoint, property)) {
// This could happen if there are multiple properties that map to the same format... compiledFormats.add(formatPath);
// E.g. `fesm5` and `module` both can point to the flat ESM5 format. console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
if (!compiledFormats.has(format)) { continue;
compiledFormats.add(format); }
// Use the first format found for typings transformation. if (!compiledFormats.has(formatPath)) {
dtsTransformFormat = dtsTransformFormat || format; const bundle = makeEntryPointBundle(
entryPoint.path, formatPath, entryPoint.typings, isCore, format,
compiledFormats.size === 0);
if (checkMarker(entryPoint, property)) { if (bundle) {
const bundle = console.warn(`Compiling ${entryPoint.name} : ${property} as ${format}`);
makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat); transformer.transform(entryPoint, isCore, bundle);
if (bundle) { compiledFormats.add(formatPath);
transformer.transform(entryPoint, isCore, bundle); } else {
} else { console.warn(
console.warn( `Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
`Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`); }
} } else {
} else { console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`); }
}
// Either this format was just compiled or its underlying format was compiled because of a
// previous property.
if (compiledFormats.has(formatPath)) {
writeMarker(entryPoint, property);
} }
// Write the built-with-ngcc marker.
writeMarker(entryPoint, property);
} }
if (!dtsTransformFormat) { if (compiledFormats.size === 0) {
throw new Error( throw new Error(
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`); `Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`);
} }
}); });
} }
export {NGCC_VERSION} from './packages/build_marker';

View File

@ -10,6 +10,8 @@ import * as path from 'canonical-path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
/** /**
* Helper functions for computing dependencies. * Helper functions for computing dependencies.
*/ */
@ -17,7 +19,8 @@ export class DependencyHost {
/** /**
* Get a list of the resolved paths to all the dependencies of this entry point. * Get a list of the resolved paths to all the dependencies of this entry point.
* @param from An absolute path to the file whose dependencies we want to get. * @param from An absolute path to the file whose dependencies we want to get.
* @param resolved A set that will have the absolute paths of resolved entry points added to it. * @param dependencies A set that will have the absolute paths of resolved entry points added to
* it.
* @param missing A set that will have the dependencies that could not be found added to it. * @param missing A set that will have the dependencies that could not be found added to it.
* @param deepImports A set that will have the import paths that exist but cannot be mapped to * @param deepImports A set that will have the import paths that exist but cannot be mapped to
* entry-points, i.e. deep-imports. * entry-points, i.e. deep-imports.
@ -25,11 +28,16 @@ export class DependencyHost {
* circular dependency loop. * circular dependency loop.
*/ */
computeDependencies( computeDependencies(
from: string, resolved: Set<string>, missing: Set<string>, deepImports: Set<string>, from: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath> = new Set(),
internal: Set<string> = new Set()): void { missing: Set<PathSegment> = new Set(), deepImports: Set<PathSegment> = new Set(),
internal: Set<AbsoluteFsPath> = new Set()): {
dependencies: Set<AbsoluteFsPath>,
missing: Set<PathSegment>,
deepImports: Set<PathSegment>
} {
const fromContents = fs.readFileSync(from, 'utf8'); const fromContents = fs.readFileSync(from, 'utf8');
if (!this.hasImportOrReexportStatements(fromContents)) { if (!this.hasImportOrReexportStatements(fromContents)) {
return; return {dependencies, missing, deepImports};
} }
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports. // Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
@ -41,7 +49,7 @@ export class DependencyHost {
// Grab the id of the module that is being imported // Grab the id of the module that is being imported
.map(stmt => stmt.moduleSpecifier.text) .map(stmt => stmt.moduleSpecifier.text)
// Resolve this module id into an absolute path // Resolve this module id into an absolute path
.forEach(importPath => { .forEach((importPath: PathSegment) => {
if (importPath.startsWith('.')) { if (importPath.startsWith('.')) {
// This is an internal import so follow it // This is an internal import so follow it
const internalDependency = this.resolveInternal(from, importPath); const internalDependency = this.resolveInternal(from, importPath);
@ -49,12 +57,12 @@ export class DependencyHost {
if (!internal.has(internalDependency)) { if (!internal.has(internalDependency)) {
internal.add(internalDependency); internal.add(internalDependency);
this.computeDependencies( this.computeDependencies(
internalDependency, resolved, missing, deepImports, internal); internalDependency, dependencies, missing, deepImports, internal);
} }
} else { } else {
const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath); const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath);
if (resolvedEntryPoint !== null) { if (resolvedEntryPoint !== null) {
resolved.add(resolvedEntryPoint); dependencies.add(resolvedEntryPoint);
} else { } else {
// If the import could not be resolved as entry point, it either does not exist // If the import could not be resolved as entry point, it either does not exist
// at all or is a deep import. // at all or is a deep import.
@ -67,6 +75,7 @@ export class DependencyHost {
} }
} }
}); });
return {dependencies, missing, deepImports};
} }
/** /**
@ -75,11 +84,11 @@ export class DependencyHost {
* @param to the module specifier of the internal dependency to resolve * @param to the module specifier of the internal dependency to resolve
* @returns the resolved path to the import. * @returns the resolved path to the import.
*/ */
resolveInternal(from: string, to: string): string { resolveInternal(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath {
const fromDirectory = path.dirname(from); const fromDirectory = path.dirname(from);
// `fromDirectory` is absolute so we don't need to worry about telling `require.resolve` // `fromDirectory` is absolute so we don't need to worry about telling `require.resolve`
// about it - unlike `tryResolve` below. // about it by adding it to a `paths` parameter - unlike `tryResolve` below.
return require.resolve(path.resolve(fromDirectory, to)); return AbsoluteFsPath.from(require.resolve(path.resolve(fromDirectory, to)));
} }
/** /**
@ -99,9 +108,9 @@ export class DependencyHost {
* @returns the resolved path to the entry point directory of the import or null * @returns the resolved path to the entry point directory of the import or null
* if it cannot be resolved. * if it cannot be resolved.
*/ */
tryResolveEntryPoint(from: string, to: string): string|null { tryResolveEntryPoint(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null {
const entryPoint = this.tryResolve(from, `${to}/package.json`); const entryPoint = this.tryResolve(from, `${to}/package.json` as PathSegment);
return entryPoint && path.dirname(entryPoint); return entryPoint && AbsoluteFsPath.from(path.dirname(entryPoint));
} }
/** /**
@ -112,9 +121,9 @@ export class DependencyHost {
* @returns an absolute path to the entry-point of the dependency or null if it could not be * @returns an absolute path to the entry-point of the dependency or null if it could not be
* resolved. * resolved.
*/ */
tryResolve(from: string, to: string): string|null { tryResolve(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null {
try { try {
return require.resolve(to, {paths: [from]}); return AbsoluteFsPath.from(require.resolve(to, {paths: [from]}));
} catch (e) { } catch (e) {
return null; return null;
} }

View File

@ -6,9 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {resolve} from 'canonical-path';
import {DepGraph} from 'dependency-graph'; import {DepGraph} from 'dependency-graph';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyHost} from './dependency_host'; import {DependencyHost} from './dependency_host';
import {EntryPoint} from './entry_point'; import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat} from './entry_point';
/** /**
@ -105,11 +108,8 @@ export class DependencyResolver {
// Now add the dependencies between them // Now add the dependencies between them
entryPoints.forEach(entryPoint => { entryPoints.forEach(entryPoint => {
const dependencies = new Set<string>();
const missing = new Set<string>();
const deepImports = new Set<string>();
const entryPointPath = getEntryPointPath(entryPoint); const entryPointPath = getEntryPointPath(entryPoint);
this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports); const {dependencies, missing, deepImports} = this.host.computeDependencies(entryPointPath);
if (missing.size > 0) { if (missing.size > 0) {
// This entry point has dependencies that are missing // This entry point has dependencies that are missing
@ -152,12 +152,16 @@ export class DependencyResolver {
} }
} }
function getEntryPointPath(entryPoint: EntryPoint): string { function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath {
const entryPointPath = const properties = Object.keys(entryPoint.packageJson);
entryPoint.fesm2015 || entryPoint.fesm5 || entryPoint.esm2015 || entryPoint.esm5; for (let i = 0; i < properties.length; i++) {
if (!entryPointPath) { const property = properties[i] as EntryPointJsonProperty;
throw new Error( const format = getEntryPointFormat(property);
`There is no format with import statements in '${entryPoint.path}' entry-point.`);
if (format === 'esm2015' || format === 'esm5') {
const formatPath = entryPoint.packageJson[property] !;
return AbsoluteFsPath.from(resolve(entryPoint.path, formatPath));
}
} }
return entryPointPath; throw new Error(`There is no format with import statements in '${entryPoint.path}' entry-point.`);
} }

View File

@ -8,40 +8,30 @@
import * as path from 'canonical-path'; import * as path from 'canonical-path';
import * as fs from 'fs'; import * as fs from 'fs';
import {isDefined} from '../utils';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
/**
* An object containing paths to the entry-points for each format.
*/
export interface EntryPointPaths {
esm5?: string;
fesm5?: string;
esm2015?: string;
fesm2015?: string;
umd?: string;
}
/** /**
* The possible values for the format of an entry-point. * The possible values for the format of an entry-point.
*/ */
export type EntryPointFormat = keyof(EntryPointPaths); export type EntryPointFormat = 'esm5' | 'esm2015' | 'umd';
/** /**
* An object containing information about an entry-point, including paths * An object containing information about an entry-point, including paths
* to each of the possible entry-point formats. * to each of the possible entry-point formats.
*/ */
export interface EntryPoint extends EntryPointPaths { export interface EntryPoint {
/** The name of the package (e.g. `@angular/core`). */ /** The name of the package (e.g. `@angular/core`). */
name: string; name: string;
/** The parsed package.json file for this entry-point. */ /** The parsed package.json file for this entry-point. */
packageJson: EntryPointPackageJson; packageJson: EntryPointPackageJson;
/** The path to the package that contains this entry-point. */ /** The path to the package that contains this entry-point. */
package: string; package: AbsoluteFsPath;
/** The path to this entry point. */ /** The path to this entry point. */
path: string; path: AbsoluteFsPath;
/** The path to a typings (.d.ts) file for this entry-point. */ /** The path to a typings (.d.ts) file for this entry-point. */
typings: string; typings: AbsoluteFsPath;
} }
interface PackageJsonFormatProperties { interface PackageJsonFormatProperties {
@ -73,7 +63,8 @@ export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
* @param entryPointPath the absolute path to the potential entry-point. * @param entryPointPath the absolute path to the potential entry-point.
* @returns An entry-point if it is valid, `null` otherwise. * @returns An entry-point if it is valid, `null` otherwise.
*/ */
export function getEntryPointInfo(packagePath: string, entryPointPath: string): EntryPoint|null { export function getEntryPointInfo(
packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPoint|null {
const packageJsonPath = path.resolve(entryPointPath, 'package.json'); const packageJsonPath = path.resolve(entryPointPath, 'package.json');
if (!fs.existsSync(packageJsonPath)) { if (!fs.existsSync(packageJsonPath)) {
return null; return null;
@ -98,48 +89,31 @@ export function getEntryPointInfo(packagePath: string, entryPointPath: string):
return null; 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 = { const entryPointInfo: EntryPoint = {
name: entryPointPackageJson.name, name: entryPointPackageJson.name,
packageJson: entryPointPackageJson, packageJson: entryPointPackageJson,
package: packagePath, package: packagePath,
path: entryPointPath, path: entryPointPath,
typings: path.resolve(entryPointPath, typings) typings: AbsoluteFsPath.from(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; return entryPointInfo;
} }
/** /**
* Convert a package.json property into an entry-point format. * 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. * @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( export function getEntryPointFormat(property: string): EntryPointFormat|undefined {
entryPointProperties: EntryPointPackageJson, property: string): EntryPointFormat|undefined {
switch (property) { switch (property) {
case 'fesm2015': case 'fesm2015':
return 'fesm2015'; return 'esm2015';
case 'fesm5': case 'fesm5':
return 'fesm5'; return 'esm5';
case 'es2015': case 'es2015':
return !entryPointProperties.fesm2015 ? 'fesm2015' : 'esm2015'; return 'esm2015';
case 'esm2015': case 'esm2015':
return 'esm2015'; return 'esm2015';
case 'esm5': case 'esm5':
@ -147,7 +121,7 @@ export function getEntryPointFormat(
case 'main': case 'main':
return 'umd'; return 'umd';
case 'module': case 'module':
return !entryPointProperties.fesm5 ? 'fesm5' : 'esm5'; return 'esm5';
default: default:
return undefined; return undefined;
} }

View File

@ -5,12 +5,12 @@
* 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 {resolve} from 'canonical-path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {BundleProgram, makeBundleProgram} from './bundle_program'; import {BundleProgram, makeBundleProgram} from './bundle_program';
import {EntryPoint, EntryPointFormat} from './entry_point'; import {EntryPointFormat} from './entry_point';
@ -20,7 +20,7 @@ import {EntryPoint, EntryPointFormat} from './entry_point';
*/ */
export interface EntryPointBundle { export interface EntryPointBundle {
format: EntryPointFormat; format: EntryPointFormat;
isFlat: boolean; isFlatCore: boolean;
rootDirs: AbsoluteFsPath[]; rootDirs: AbsoluteFsPath[];
src: BundleProgram; src: BundleProgram;
dts: BundleProgram|null; dts: BundleProgram|null;
@ -28,34 +28,33 @@ export interface EntryPointBundle {
/** /**
* Get an object that describes a formatted bundle for an entry-point. * Get an object that describes a formatted bundle for an entry-point.
* @param entryPoint The entry-point that contains the bundle. * @param entryPointPath The path to the entry-point that contains the bundle.
* @param format The format of the bundle. * @param formatPath The path to the source files for this bundle.
* @param transformDts True if processing this bundle should also process its `.d.ts` files. * @param typingsPath The path to the typings files if we should transform them with this bundle.
* @param isCore This entry point is the Angular core package.
* @param format The underlying format of the bundle.
* @param transformDts Whether to transform the typings along with this bundle.
*/ */
export function makeEntryPointBundle( export function makeEntryPointBundle(
entryPoint: EntryPoint, isCore: boolean, format: EntryPointFormat, entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean,
transformDts: boolean): EntryPointBundle|null { format: EntryPointFormat, transformDts: boolean): EntryPointBundle|null {
// Bail out if the entry-point does not have this format.
const path = entryPoint[format];
if (!path) {
return null;
}
// Create the TS program and necessary helpers. // Create the TS program and necessary helpers.
const options: ts.CompilerOptions = { const options: ts.CompilerOptions = {
allowJs: true, allowJs: true,
maxNodeModuleJsDepth: Infinity, maxNodeModuleJsDepth: Infinity,
rootDir: entryPoint.path, rootDir: entryPointPath,
}; };
const host = ts.createCompilerHost(options); const host = ts.createCompilerHost(options);
const rootDirs = [AbsoluteFsPath.from(entryPoint.path)]; const rootDirs = [AbsoluteFsPath.from(entryPointPath)];
// Create the bundle programs, as necessary. // Create the bundle programs, as necessary.
const src = makeBundleProgram(isCore, path, 'r3_symbols.js', options, host); const src = makeBundleProgram(
isCore, resolve(entryPointPath, formatPath), 'r3_symbols.js', options, host);
const dts = transformDts ? const dts = transformDts ?
makeBundleProgram(isCore, entryPoint.typings, 'r3_symbols.d.ts', options, host) : makeBundleProgram(
isCore, resolve(entryPointPath, typingsPath), 'r3_symbols.d.ts', options, host) :
null; null;
const isFlat = src.r3SymbolsFile === null; const isFlatCore = isCore && src.r3SymbolsFile === null;
return {format, rootDirs, isFlat, src, dts}; return {format, rootDirs, isFlatCore, src, dts};
} }

View File

@ -8,6 +8,7 @@
import * as path from 'canonical-path'; import * as path from 'canonical-path';
import * as fs from 'fs'; import * as fs from 'fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver, SortedEntryPointsInfo} from './dependency_resolver'; import {DependencyResolver, SortedEntryPointsInfo} from './dependency_resolver';
import {EntryPoint, getEntryPointInfo} from './entry_point'; import {EntryPoint, getEntryPointInfo} from './entry_point';
@ -18,10 +19,9 @@ export class EntryPointFinder {
* Search the given directory, and sub-directories, for Angular package entry points. * Search the given directory, and sub-directories, for Angular package entry points.
* @param sourceDirectory An absolute path to the directory to search for entry points. * @param sourceDirectory An absolute path to the directory to search for entry points.
*/ */
findEntryPoints(sourceDirectory: string, targetEntryPointPath?: string): SortedEntryPointsInfo { findEntryPoints(sourceDirectory: AbsoluteFsPath, targetEntryPointPath?: AbsoluteFsPath):
SortedEntryPointsInfo {
const unsortedEntryPoints = walkDirectoryForEntryPoints(sourceDirectory); const unsortedEntryPoints = walkDirectoryForEntryPoints(sourceDirectory);
targetEntryPointPath =
targetEntryPointPath && path.resolve(sourceDirectory, targetEntryPointPath);
const targetEntryPoint = targetEntryPointPath ? const targetEntryPoint = targetEntryPointPath ?
unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) : unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) :
undefined; undefined;
@ -34,7 +34,7 @@ export class EntryPointFinder {
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`. * The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
* @param sourceDirectory An absolute path to the root directory where searching begins. * @param sourceDirectory An absolute path to the root directory where searching begins.
*/ */
function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] { function walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
const entryPoints: EntryPoint[] = []; const entryPoints: EntryPoint[] = [];
fs.readdirSync(sourceDirectory) fs.readdirSync(sourceDirectory)
// Not interested in hidden files // Not interested in hidden files
@ -49,14 +49,15 @@ function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] {
.forEach(p => { .forEach(p => {
// Either the directory is a potential package or a namespace containing packages (e.g // Either the directory is a potential package or a namespace containing packages (e.g
// `@angular`). // `@angular`).
const packagePath = path.join(sourceDirectory, p); const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p));
if (p.startsWith('@')) { if (p.startsWith('@')) {
entryPoints.push(...walkDirectoryForEntryPoints(packagePath)); entryPoints.push(...walkDirectoryForEntryPoints(packagePath));
} else { } else {
entryPoints.push(...getEntryPointsForPackage(packagePath)); entryPoints.push(...getEntryPointsForPackage(packagePath));
// Also check for any nested node_modules in this package // Also check for any nested node_modules in this package
const nestedNodeModulesPath = path.resolve(packagePath, 'node_modules'); const nestedNodeModulesPath =
AbsoluteFsPath.from(path.resolve(packagePath, 'node_modules'));
if (fs.existsSync(nestedNodeModulesPath)) { if (fs.existsSync(nestedNodeModulesPath)) {
entryPoints.push(...walkDirectoryForEntryPoints(nestedNodeModulesPath)); entryPoints.push(...walkDirectoryForEntryPoints(nestedNodeModulesPath));
} }
@ -70,7 +71,7 @@ function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] {
* @param packagePath The absolute path to an npm package that may contain entry points * @param packagePath The absolute path to an npm package that may contain entry points
* @returns An array of entry points that were discovered. * @returns An array of entry points that were discovered.
*/ */
function getEntryPointsForPackage(packagePath: string): EntryPoint[] { function getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
const entryPoints: EntryPoint[] = []; const entryPoints: EntryPoint[] = [];
// Try to get an entry point from the top level package directory // Try to get an entry point from the top level package directory
@ -96,7 +97,7 @@ function getEntryPointsForPackage(packagePath: string): EntryPoint[] {
* @param dir the directory to recursively walk. * @param dir the directory to recursively walk.
* @param fn the function to apply to each directory. * @param fn the function to apply to each directory.
*/ */
function walkDirectory(dir: string, fn: (dir: string) => void) { function walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) {
return fs return fs
.readdirSync(dir) .readdirSync(dir)
// Not interested in hidden files // Not interested in hidden files
@ -108,9 +109,9 @@ function walkDirectory(dir: string, fn: (dir: string) => void) {
const stat = fs.lstatSync(path.resolve(dir, p)); const stat = fs.lstatSync(path.resolve(dir, p));
return stat.isDirectory() && !stat.isSymbolicLink(); return stat.isDirectory() && !stat.isSymbolicLink();
}) })
.forEach(subdir => { .forEach(subDir => {
subdir = path.resolve(dir, subdir); const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir));
fn(subdir); fn(resolvedSubDir);
walkDirectory(subdir, fn); walkDirectory(resolvedSubDir, fn);
}); });
} }

View File

@ -56,8 +56,6 @@ export class Transformer {
* @param bundle the bundle to transform. * @param bundle the bundle to transform.
*/ */
transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void { transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void {
console.warn(`Compiling ${entryPoint.name} - ${bundle.format}`);
const reflectionHost = this.getHost(isCore, bundle); const reflectionHost = this.getHost(isCore, bundle);
// Parse and analyze the files. // Parse and analyze the files.
@ -78,10 +76,8 @@ export class Transformer {
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
switch (bundle.format) { switch (bundle.format) {
case 'esm2015': case 'esm2015':
case 'fesm2015':
return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
case 'esm5': case 'esm5':
case 'fesm5':
return new Esm5ReflectionHost(isCore, typeChecker, bundle.dts); return new Esm5ReflectionHost(isCore, typeChecker, 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.`);
@ -91,10 +87,8 @@ export class Transformer {
getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer { getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer {
switch (bundle.format) { switch (bundle.format) {
case 'esm2015': case 'esm2015':
case 'fesm2015':
return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath); return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath);
case 'esm5': case 'esm5':
case 'fesm5':
return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath); return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath);
default: default:
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);

View File

@ -137,7 +137,8 @@ export abstract class Renderer {
if (compiledFile) { if (compiledFile) {
const importManager = new ImportManager( const importManager = new ImportManager(
this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlat), IMPORT_PREFIX); this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlatCore),
IMPORT_PREFIX);
// TODO: remove constructor param metadata and property decorators (we need info from the // TODO: remove constructor param metadata and property decorators (we need info from the
// handlers to do this) // handlers to do this)

View File

@ -45,8 +45,10 @@ ts_library(
), ),
deps = [ deps = [
"//packages/compiler-cli/ngcc", "//packages/compiler-cli/ngcc",
"//packages/compiler-cli/src/ngtsc/path",
"//packages/compiler-cli/test:test_utils", "//packages/compiler-cli/test:test_utils",
"@npm//@types/mock-fs", "@npm//@types/mock-fs",
"@npm//rxjs",
], ],
) )
@ -60,7 +62,6 @@ jasmine_node_test(
], ],
deps = [ deps = [
":integration_lib", ":integration_lib",
"//packages/common",
"//tools/testing:node_no_angular", "//tools/testing:node_no_angular",
"@npm//canonical-path", "@npm//canonical-path",
"@npm//convert-source-map", "@npm//convert-source-map",

View File

@ -15,8 +15,6 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript'; export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
/** /**
* *
* @param format The format of the bundle. * @param format The format of the bundle.
@ -28,8 +26,8 @@ export function makeTestEntryPointBundle(
dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle { dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle {
const src = makeTestBundleProgram(files); const src = makeTestBundleProgram(files);
const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null; const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null;
const isFlat = src.r3SymbolsFile === null; const isFlatCore = src.r3SymbolsFile === null;
return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlat}; return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlatCore};
} }
/** /**

View File

@ -13,33 +13,29 @@ 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 fesm2015', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm2015']}))
.not.toThrow();
});
it('should run ngcc without errors for fesm5', () => {
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm5']}))
.not.toThrow();
});
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({baseSourcePath: 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({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm5']}))
.not.toThrow(); .not.toThrow();
}); });
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({baseSourcePath: '/node_modules', targetEntryPointPath: '@angular/common/http'}); mainNgcc({
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',
@ -69,7 +65,7 @@ describe('ngcc main()', () => {
}); });
it('should only build the format properties specified for each entry-point', () => { it('should only build the format properties specified for each entry-point', () => {
mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['main', 'esm5', 'module']}); mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['main', 'esm5', 'module']});
expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER', esm5: '0.0.0-PLACEHOLDER',
@ -95,6 +91,7 @@ function createMockFileSystem() {
mockFs({ mockFs({
'/node_modules/@angular': loadAngularPackages(), '/node_modules/@angular': loadAngularPackages(),
'/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')), '/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')),
'/node_modules/tslib': loadDirectory(resolveNpmTreeArtifact('tslib', 'tslib.js')),
}); });
spyOn(Module, '_resolveFilename').and.callFake(mockResolve); spyOn(Module, '_resolveFilename').and.callFake(mockResolve);
} }
@ -163,6 +160,6 @@ function mockResolve(request: string): string|null {
} }
} }
function loadPackage(packageName: string) { function loadPackage(packageName: string): any {
return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8')); return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8'));
} }

View File

@ -9,6 +9,7 @@
import {readFileSync, writeFileSync} from 'fs'; import {readFileSync, writeFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {checkMarker, writeMarker} from '../../src/packages/build_marker'; import {checkMarker, writeMarker} from '../../src/packages/build_marker';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
@ -94,11 +95,12 @@ function restoreRealFileSystem() {
} }
function createEntryPoint(path: string): EntryPoint { function createEntryPoint(path: string): EntryPoint {
const absolutePath = AbsoluteFsPath.from(path);
return { return {
name: 'some-package', name: 'some-package',
path, path: absolutePath,
package: path, package: absolutePath,
typings: '', typings: AbsoluteFsPath.from('/typings'),
packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8')) packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8'))
}; };
} }

View File

@ -9,6 +9,8 @@
import * as path from 'canonical-path'; import * as path from 'canonical-path';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
import {DependencyHost} from '../../src/packages/dependency_host'; import {DependencyHost} from '../../src/packages/dependency_host';
const Module = require('module'); const Module = require('module');
@ -16,6 +18,8 @@ interface DepMap {
[path: string]: {resolved: string[], missing: string[]}; [path: string]: {resolved: string[], missing: string[]};
} }
const _ = AbsoluteFsPath.from;
describe('DependencyHost', () => { describe('DependencyHost', () => {
let host: DependencyHost; let host: DependencyHost;
beforeEach(() => host = new DependencyHost()); beforeEach(() => host = new DependencyHost());
@ -27,7 +31,8 @@ describe('DependencyHost', () => {
it('should not generate a TS AST if the source does not contain any imports or re-exports', it('should not generate a TS AST if the source does not contain any imports or re-exports',
() => { () => {
spyOn(ts, 'createSourceFile'); spyOn(ts, 'createSourceFile');
host.computeDependencies('/no/imports/or/re-exports.js', new Set(), new Set(), new Set()); host.computeDependencies(
_('/no/imports/or/re-exports.js'), new Set(), new Set(), new Set());
expect(ts.createSourceFile).not.toHaveBeenCalled(); expect(ts.createSourceFile).not.toHaveBeenCalled();
}); });
@ -37,7 +42,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/external/imports.js', resolved, missing, deepImports); host.computeDependencies(_('/external/imports.js'), resolved, missing, deepImports);
expect(resolved.size).toBe(2); expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
@ -49,7 +54,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/external/re-exports.js', resolved, missing, deepImports); host.computeDependencies(_('/external/re-exports.js'), resolved, missing, deepImports);
expect(resolved.size).toBe(2); expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
@ -64,7 +69,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/external/imports-missing.js', resolved, missing, deepImports); host.computeDependencies(_('/external/imports-missing.js'), resolved, missing, deepImports);
expect(resolved.size).toBe(1); expect(resolved.size).toBe(1);
expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(missing.size).toBe(1); expect(missing.size).toBe(1);
@ -84,7 +89,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/external/deep-import.js', resolved, missing, deepImports); host.computeDependencies(_('/external/deep-import.js'), resolved, missing, deepImports);
expect(resolved.size).toBe(0); expect(resolved.size).toBe(0);
expect(missing.size).toBe(0); expect(missing.size).toBe(0);
expect(deepImports.size).toBe(1); expect(deepImports.size).toBe(1);
@ -101,7 +106,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/internal/outer.js', resolved, missing, deepImports); host.computeDependencies(_('/internal/outer.js'), resolved, missing, deepImports);
expect(getDependenciesSpy) expect(getDependenciesSpy)
.toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports); .toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports);
expect(getDependenciesSpy) expect(getDependenciesSpy)
@ -121,7 +126,7 @@ describe('DependencyHost', () => {
const resolved = new Set(); const resolved = new Set();
const missing = new Set(); const missing = new Set();
const deepImports = new Set(); const deepImports = new Set();
host.computeDependencies('/internal/circular-a.js', resolved, missing, deepImports); host.computeDependencies(_('/internal/circular-a.js'), resolved, missing, deepImports);
expect(resolved.size).toBe(2); expect(resolved.size).toBe(2);
expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
expect(resolved.has('RESOLVED/path/to/y')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
@ -144,14 +149,15 @@ describe('DependencyHost', () => {
describe('resolveInternal', () => { describe('resolveInternal', () => {
it('should resolve the dependency via `Module._resolveFilename`', () => { it('should resolve the dependency via `Module._resolveFilename`', () => {
spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
const result = host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE'); const result = host.resolveInternal(
expect(result).toEqual('RESOLVED_PATH'); _('/SOURCE/PATH/FILE'), PathSegment.fromFsPath('../TARGET/PATH/FILE'));
expect(result).toEqual('/RESOLVED_PATH');
}); });
it('should first resolve the `to` on top of the `from` directory', () => { it('should first resolve the `to` on top of the `from` directory', () => {
const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE'); host.resolveInternal(_('/SOURCE/PATH/FILE'), PathSegment.fromFsPath('../TARGET/PATH/FILE'));
expect(resolveSpy) expect(resolveSpy)
.toHaveBeenCalledWith('/SOURCE/TARGET/PATH/FILE', jasmine.any(Object), false, undefined); .toHaveBeenCalledWith('/SOURCE/TARGET/PATH/FILE', jasmine.any(Object), false, undefined);
}); });
@ -159,37 +165,39 @@ describe('DependencyHost', () => {
describe('tryResolveExternal', () => { describe('tryResolveExternal', () => {
it('should call `tryResolve`, appending `package.json` to the target path', () => { it('should call `tryResolve`, appending `package.json` to the target path', () => {
const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED'); const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED');
host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH'); host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'));
expect(tryResolveSpy).toHaveBeenCalledWith('SOURCE_PATH', 'TARGET_PATH/package.json'); expect(tryResolveSpy).toHaveBeenCalledWith('/SOURCE_PATH', 'TARGET_PATH/package.json');
}); });
it('should return the directory containing the result from `tryResolve', () => { it('should return the directory containing the result from `tryResolve', () => {
spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED'); spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED');
expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual('PATH/TO'); expect(host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')))
.toEqual(_('/PATH/TO'));
}); });
it('should return null if `tryResolve` returns null', () => { it('should return null if `tryResolve` returns null', () => {
spyOn(host, 'tryResolve').and.returnValue(null); spyOn(host, 'tryResolve').and.returnValue(null);
expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual(null); expect(host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')))
.toEqual(null);
}); });
}); });
describe('tryResolve()', () => { describe('tryResolve()', () => {
it('should resolve the dependency via `Module._resolveFilename`, passing the `from` path to the `paths` option', it('should resolve the dependency via `Module._resolveFilename`, passing the `from` path to the `paths` option',
() => { () => {
const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
const result = host.tryResolve('SOURCE_PATH', 'TARGET_PATH'); const result = host.tryResolve(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'));
expect(resolveSpy).toHaveBeenCalledWith('TARGET_PATH', jasmine.any(Object), false, { expect(resolveSpy).toHaveBeenCalledWith('TARGET_PATH', jasmine.any(Object), false, {
paths: ['SOURCE_PATH'] paths: ['/SOURCE_PATH']
}); });
expect(result).toEqual('RESOLVED_PATH'); expect(result).toEqual(_('/RESOLVED_PATH'));
}); });
it('should return null if `Module._resolveFilename` throws an error', () => { it('should return null if `Module._resolveFilename` throws an error', () => {
const resolveSpy = const resolveSpy =
spyOn(Module, '_resolveFilename').and.throwError(`Cannot find module 'TARGET_PATH'`); spyOn(Module, '_resolveFilename').and.throwError(`Cannot find module 'TARGET_PATH'`);
const result = host.tryResolve('SOURCE_PATH', 'TARGET_PATH'); const result = host.tryResolve(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'));
expect(result).toBe(null); expect(result).toBe(null);
}); });
}); });

View File

@ -6,10 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {resolve} from 'canonical-path';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyHost} from '../../src/packages/dependency_host'; import {DependencyHost} from '../../src/packages/dependency_host';
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver'; import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
const _ = AbsoluteFsPath.from;
describe('DependencyResolver', () => { describe('DependencyResolver', () => {
let host: DependencyHost; let host: DependencyHost;
let resolver: DependencyResolver; let resolver: DependencyResolver;
@ -18,18 +23,18 @@ describe('DependencyResolver', () => {
resolver = new DependencyResolver(host); resolver = new DependencyResolver(host);
}); });
describe('sortEntryPointsByDependency()', () => { describe('sortEntryPointsByDependency()', () => {
const first = { path: 'first', fesm2015: 'first/index.ts' } as EntryPoint; const first = { path: _('/first'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
const second = { path: 'second', esm2015: 'second/index.ts' } as EntryPoint; const second = { path: _('/second'), packageJson: {esm2015: 'sub/index.ts'} } as EntryPoint;
const third = { path: 'third', fesm2015: 'third/index.ts' } as EntryPoint; const third = { path: _('/third'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
const fourth = { path: 'fourth', esm2015: 'fourth/index.ts' } as EntryPoint; const fourth = { path: _('/fourth'), packageJson: {esm2015: 'sub2/index.ts'} } as EntryPoint;
const fifth = { path: 'fifth', fesm2015: 'fifth/index.ts' } as EntryPoint; const fifth = { path: _('/fifth'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
const dependencies = { const dependencies = {
'first/index.ts': {resolved: ['second', 'third', 'ignored-1'], missing: []}, [_('/first/index.ts')]: {resolved: [second.path, third.path, '/ignored-1'], missing: []},
'second/index.ts': {resolved: ['third', 'fifth'], missing: []}, [_('/second/sub/index.ts')]: {resolved: [third.path, fifth.path], missing: []},
'third/index.ts': {resolved: ['fourth', 'ignored-2'], missing: []}, [_('/third/index.ts')]: {resolved: [fourth.path, '/ignored-2'], missing: []},
'fourth/index.ts': {resolved: ['fifth'], missing: []}, [_('/fourth/sub2/index.ts')]: {resolved: [fifth.path], missing: []},
'fifth/index.ts': {resolved: [], missing: []}, [_('/fifth/index.ts')]: {resolved: [], missing: []},
}; };
it('should order the entry points by their dependency on each other', () => { it('should order the entry points by their dependency on each other', () => {
@ -40,58 +45,58 @@ describe('DependencyResolver', () => {
it('should remove entry-points that have missing direct dependencies', () => { it('should remove entry-points that have missing direct dependencies', () => {
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
'first/index.ts': {resolved: [], missing: ['missing']}, [_('/first/index.ts')]: {resolved: [], missing: ['/missing']},
'second/index.ts': {resolved: [], missing: []}, [_('/second/sub/index.ts')]: {resolved: [], missing: []},
})); }));
const result = resolver.sortEntryPointsByDependency([first, second]); const result = resolver.sortEntryPointsByDependency([first, second]);
expect(result.entryPoints).toEqual([second]); expect(result.entryPoints).toEqual([second]);
expect(result.invalidEntryPoints).toEqual([ expect(result.invalidEntryPoints).toEqual([
{entryPoint: first, missingDependencies: ['missing']}, {entryPoint: first, missingDependencies: ['/missing']},
]); ]);
}); });
it('should remove entry points that depended upon an invalid entry-point', () => { it('should remove entry points that depended upon an invalid entry-point', () => {
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
'first/index.ts': {resolved: ['second'], missing: []}, [_('/first/index.ts')]: {resolved: [second.path], missing: []},
'second/index.ts': {resolved: [], missing: ['missing']}, [_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']},
'third/index.ts': {resolved: [], missing: []}, [_('/third/index.ts')]: {resolved: [], missing: []},
})); }));
// Note that we will process `first` before `second`, which has the missing dependency. // Note that we will process `first` before `second`, which has the missing dependency.
const result = resolver.sortEntryPointsByDependency([first, second, third]); const result = resolver.sortEntryPointsByDependency([first, second, third]);
expect(result.entryPoints).toEqual([third]); expect(result.entryPoints).toEqual([third]);
expect(result.invalidEntryPoints).toEqual([ expect(result.invalidEntryPoints).toEqual([
{entryPoint: second, missingDependencies: ['missing']}, {entryPoint: second, missingDependencies: ['/missing']},
{entryPoint: first, missingDependencies: ['missing']}, {entryPoint: first, missingDependencies: ['/missing']},
]); ]);
}); });
it('should remove entry points that will depend upon an invalid entry-point', () => { it('should remove entry points that will depend upon an invalid entry-point', () => {
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
'first/index.ts': {resolved: ['second'], missing: []}, [_('/first/index.ts')]: {resolved: [second.path], missing: []},
'second/index.ts': {resolved: [], missing: ['missing']}, [_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']},
'third/index.ts': {resolved: [], missing: []}, [_('/third/index.ts')]: {resolved: [], missing: []},
})); }));
// Note that we will process `first` after `second`, which has the missing dependency. // Note that we will process `first` after `second`, which has the missing dependency.
const result = resolver.sortEntryPointsByDependency([second, first, third]); const result = resolver.sortEntryPointsByDependency([second, first, third]);
expect(result.entryPoints).toEqual([third]); expect(result.entryPoints).toEqual([third]);
expect(result.invalidEntryPoints).toEqual([ expect(result.invalidEntryPoints).toEqual([
{entryPoint: second, missingDependencies: ['missing']}, {entryPoint: second, missingDependencies: ['/missing']},
{entryPoint: first, missingDependencies: ['second']}, {entryPoint: first, missingDependencies: [second.path]},
]); ]);
}); });
it('should error if the entry point does not have either the fesm2015 nor esm2015 formats', it('should error if the entry point does not have either the esm5 nor esm2015 formats', () => {
() => { expect(() => resolver.sortEntryPointsByDependency([
expect(() => resolver.sortEntryPointsByDependency([{ path: 'first' } as EntryPoint])) { path: '/first', packageJson: {} } as EntryPoint
.toThrowError(`There is no format with import statements in 'first' entry-point.`); ])).toThrowError(`There is no format with import statements in '/first' entry-point.`);
}); });
it('should capture any dependencies that were ignored', () => { it('should capture any dependencies that were ignored', () => {
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies)); spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]); const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]);
expect(result.ignoredDependencies).toEqual([ expect(result.ignoredDependencies).toEqual([
{entryPoint: first, dependencyPath: 'ignored-1'}, {entryPoint: first, dependencyPath: '/ignored-1'},
{entryPoint: third, dependencyPath: 'ignored-2'}, {entryPoint: third, dependencyPath: '/ignored-2'},
]); ]);
}); });
@ -116,10 +121,14 @@ describe('DependencyResolver', () => {
[path: string]: {resolved: string[], missing: string[]}; [path: string]: {resolved: string[], missing: string[]};
} }
function createFakeComputeDependencies(dependencies: DepMap) { function createFakeComputeDependencies(deps: DepMap) {
return (entryPoint: string, resolved: Set<string>, missing: Set<string>) => { return (entryPoint: string) => {
dependencies[entryPoint].resolved.forEach(dep => resolved.add(dep)); const dependencies = new Set();
dependencies[entryPoint].missing.forEach(dep => missing.add(dep)); const missing = new Set();
const deepImports = new Set();
deps[entryPoint].resolved.forEach(dep => dependencies.add(dep));
deps[entryPoint].missing.forEach(dep => missing.add(dep));
return {dependencies, missing, deepImports};
}; };
} }
}); });

View File

@ -7,11 +7,15 @@
*/ */
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyHost} from '../../src/packages/dependency_host'; import {DependencyHost} from '../../src/packages/dependency_host';
import {DependencyResolver} from '../../src/packages/dependency_resolver'; import {DependencyResolver} from '../../src/packages/dependency_resolver';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointFinder} from '../../src/packages/entry_point_finder'; import {EntryPointFinder} from '../../src/packages/entry_point_finder';
const _ = AbsoluteFsPath.from;
describe('findEntryPoints()', () => { describe('findEntryPoints()', () => {
let resolver: DependencyResolver; let resolver: DependencyResolver;
let finder: EntryPointFinder; let finder: EntryPointFinder;
@ -26,56 +30,56 @@ describe('findEntryPoints()', () => {
afterEach(restoreRealFileSystem); 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 {entryPoints} = finder.findEntryPoints(_('/sub_entry_points'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]); const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([ expect(entryPointPaths).toEqual([
['/sub_entry_points/common', '/sub_entry_points/common'], [_('/sub_entry_points/common'), _('/sub_entry_points/common')],
['/sub_entry_points/common', '/sub_entry_points/common/http'], [_('/sub_entry_points/common'), _('/sub_entry_points/common/http')],
['/sub_entry_points/common', '/sub_entry_points/common/http/testing'], [_('/sub_entry_points/common'), _('/sub_entry_points/common/http/testing')],
['/sub_entry_points/common', '/sub_entry_points/common/testing'], [_('/sub_entry_points/common'), _('/sub_entry_points/common/testing')],
]); ]);
}); });
it('should find packages inside a namespace', () => { it('should find packages inside a namespace', () => {
const {entryPoints} = finder.findEntryPoints('/namespaced'); const {entryPoints} = finder.findEntryPoints(_('/namespaced'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]); const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([ expect(entryPointPaths).toEqual([
['/namespaced/@angular/common', '/namespaced/@angular/common'], [_('/namespaced/@angular/common'), _('/namespaced/@angular/common')],
['/namespaced/@angular/common', '/namespaced/@angular/common/http'], [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http')],
['/namespaced/@angular/common', '/namespaced/@angular/common/http/testing'], [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http/testing')],
['/namespaced/@angular/common', '/namespaced/@angular/common/testing'], [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/testing')],
]); ]);
}); });
it('should return an empty array if there are no packages', () => { it('should return an empty array if there are no packages', () => {
const {entryPoints} = finder.findEntryPoints('/no_packages'); const {entryPoints} = finder.findEntryPoints(_('/no_packages'));
expect(entryPoints).toEqual([]); expect(entryPoints).toEqual([]);
}); });
it('should return an empty array if there are no valid entry-points', () => { it('should return an empty array if there are no valid entry-points', () => {
const {entryPoints} = finder.findEntryPoints('/no_valid_entry_points'); const {entryPoints} = finder.findEntryPoints(_('/no_valid_entry_points'));
expect(entryPoints).toEqual([]); expect(entryPoints).toEqual([]);
}); });
it('should ignore folders starting with .', () => { it('should ignore folders starting with .', () => {
const {entryPoints} = finder.findEntryPoints('/dotted_folders'); const {entryPoints} = finder.findEntryPoints(_('/dotted_folders'));
expect(entryPoints).toEqual([]); expect(entryPoints).toEqual([]);
}); });
it('should ignore folders that are symlinked', () => { it('should ignore folders that are symlinked', () => {
const {entryPoints} = finder.findEntryPoints('/symlinked_folders'); const {entryPoints} = finder.findEntryPoints(_('/symlinked_folders'));
expect(entryPoints).toEqual([]); expect(entryPoints).toEqual([]);
}); });
it('should handle nested node_modules folders', () => { it('should handle nested node_modules folders', () => {
const {entryPoints} = finder.findEntryPoints('/nested_node_modules'); const {entryPoints} = finder.findEntryPoints(_('/nested_node_modules'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]); const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([ expect(entryPointPaths).toEqual([
['/nested_node_modules/outer', '/nested_node_modules/outer'], [_('/nested_node_modules/outer'), _('/nested_node_modules/outer')],
// Note that the inner entry point does not get included as part of the outer package // Note that the inner entry point does not get included as part of the outer package
[ [
'/nested_node_modules/outer/node_modules/inner', _('/nested_node_modules/outer/node_modules/inner'),
'/nested_node_modules/outer/node_modules/inner' _('/nested_node_modules/outer/node_modules/inner'),
], ],
]); ]);
}); });

View File

@ -6,84 +6,76 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path';
import {readFileSync} from 'fs'; import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {getEntryPointInfo} from '../../src/packages/entry_point'; import {getEntryPointInfo} from '../../src/packages/entry_point';
describe('getEntryPointInfo()', () => { describe('getEntryPointInfo()', () => {
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);
const _ = AbsoluteFsPath.from;
const SOME_PACKAGE = _('/some_package');
it('should return an object containing absolute paths to the formats of the specified entry-point', it('should return an object containing absolute paths to the formats of the specified entry-point',
() => { () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/valid_entry_point'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/valid_entry_point'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some-package/valid_entry_point', name: 'some-package/valid_entry_point',
package: '/some_package', package: SOME_PACKAGE,
path: '/some_package/valid_entry_point', path: _('/some_package/valid_entry_point'),
typings: `/some_package/valid_entry_point/valid_entry_point.d.ts`, typings: _(`/some_package/valid_entry_point/valid_entry_point.d.ts`),
fesm2015: `/some_package/valid_entry_point/fesm2015/valid_entry_point.js`,
esm2015: `/some_package/valid_entry_point/esm2015/valid_entry_point.js`,
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'), packageJson: loadPackageJson('/some_package/valid_entry_point'),
}); });
}); });
it('should return null if there is no package.json at the entry-point path', () => { it('should return null if there is no package.json at the entry-point path', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_package_json'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_package_json'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return null if there is no typings or types field in the package.json', () => { it('should return null if there is no typings or types field in the package.json', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_typings'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_typings'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return null if there is no esm2015 nor fesm2015 field in the package.json', () => { it('should return null if there is no esm2015 nor fesm2015 field in the package.json', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_esm2015'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_esm2015'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return null if there is no metadata.json file next to the typing file', () => { it('should return null if there is no metadata.json file next to the typing file', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_metadata.json'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_metadata.json'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should work if the typings field is named `types', () => { it('should work if the typings field is named `types', () => {
const entryPoint = const entryPoint =
getEntryPointInfo('/some_package', '/some_package/types_rather_than_typings'); getEntryPointInfo(SOME_PACKAGE, _('/some_package/types_rather_than_typings'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some-package/types_rather_than_typings', name: 'some-package/types_rather_than_typings',
package: '/some_package', package: SOME_PACKAGE,
path: '/some_package/types_rather_than_typings', path: _('/some_package/types_rather_than_typings'),
typings: `/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`, typings: _(`/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`),
fesm2015: `/some_package/types_rather_than_typings/fesm2015/types_rather_than_typings.js`,
esm2015: `/some_package/types_rather_than_typings/esm2015/types_rather_than_typings.js`,
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'), packageJson: loadPackageJson('/some_package/types_rather_than_typings'),
}); });
}); });
it('should work with Angular Material style package.json', () => { it('should work with Angular Material style package.json', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/material_style'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/material_style'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some_package/material_style', name: 'some_package/material_style',
package: '/some_package', package: SOME_PACKAGE,
path: '/some_package/material_style', path: _('/some_package/material_style'),
typings: `/some_package/material_style/material_style.d.ts`, typings: _(`/some_package/material_style/material_style.d.ts`),
fesm2015: `/some_package/material_style/esm2015/material_style.js`, packageJson: JSON.parse(readFileSync('/some_package/material_style/package.json', 'utf8')),
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'),
}); });
}); });
it('should return null if the package.json is not valid JSON', () => { it('should return null if the package.json is not valid JSON', () => {
const entryPoint = getEntryPointInfo('/some_package', '/some_package/unexpected_symbols'); const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/unexpected_symbols'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
}); });