perf(ivy): ngcc - only find dependencies when targeting a single entry-point (#30525)

Previously, ngcc had to walk the entire `node_modules` tree looking for
entry-points, even if it only needed to process a single target entry-point
and its dependencies.

This added up to a few seconds to each execution of ngcc, which is noticeable
when being run via the CLI integration.

Now, if an entry-point target is provided, only that target and its entry-points
are considered rather than the whole folder tree.

PR Close #30525
This commit is contained in:
Pete Bacon Darwin 2019-05-16 08:53:19 +01:00 committed by Jason Aden
parent 7f2330a968
commit a581773887
12 changed files with 693 additions and 334 deletions

View File

@ -10,7 +10,7 @@ import {DepGraph} from 'dependency-graph';
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system'; import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/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, DependencyInfo} from './dependency_host';
/** /**
* Holds information about entry points that are removed because * Holds information about entry points that are removed because
@ -98,6 +98,16 @@ export class DependencyResolver {
}; };
} }
getEntryPointDependencies(entryPoint: EntryPoint): DependencyInfo {
const formatInfo = this.getEntryPointFormatInfo(entryPoint);
const host = this.hosts[formatInfo.format];
if (!host) {
throw new Error(
`Could not find a suitable format for computing dependencies of entry-point: '${entryPoint.path}'.`);
}
return host.findDependencies(formatInfo.path);
}
/** /**
* Computes a dependency graph of the given entry-points. * Computes a dependency graph of the given entry-points.
* *
@ -116,13 +126,7 @@ export class DependencyResolver {
// Now add the dependencies between them // Now add the dependencies between them
angularEntryPoints.forEach(entryPoint => { angularEntryPoints.forEach(entryPoint => {
const formatInfo = this.getEntryPointFormatInfo(entryPoint); const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
const host = this.hosts[formatInfo.format];
if (!host) {
throw new Error(
`Could not find a suitable format for computing dependencies of entry-point: '${entryPoint.path}'.`);
}
const {dependencies, missing, deepImports} = host.findDependencies(formatInfo.path);
if (missing.size > 0) { if (missing.size > 0) {
// This entry point has dependencies that are missing // This entry point has dependencies that are missing

View File

@ -8,61 +8,31 @@
import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file_system'; import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file_system';
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {NgccConfiguration} from '../packages/configuration';
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
import {PathMappings} from '../utils'; import {PathMappings} from '../utils';
import {NgccConfiguration} from './configuration'; import {EntryPointFinder} from './interface';
import {EntryPoint, getEntryPointInfo} from './entry_point'; import {getBasePaths} from './utils';
export class EntryPointFinder { /**
* An EntryPointFinder that searches for all entry-points that can be found given a `basePath` and
* `pathMappings`.
*/
export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
private basePaths = getBasePaths(this.sourceDirectory, this.pathMappings);
constructor( constructor(
private fs: FileSystem, private config: NgccConfiguration, private logger: Logger, private fs: FileSystem, private config: NgccConfiguration, private logger: Logger,
private resolver: DependencyResolver) {} private resolver: DependencyResolver, private sourceDirectory: AbsoluteFsPath,
private pathMappings: PathMappings|undefined) {}
/** /**
* Search the given directory, and sub-directories, for Angular package entry points. * Search the `sourceDirectory`, and sub-directories, using `pathMappings` as necessary, to find
* @param sourceDirectory An absolute path to the directory to search for entry points. * all package entry-points.
*/ */
findEntryPoints( findEntryPoints(): SortedEntryPointsInfo {
sourceDirectory: AbsoluteFsPath, targetEntryPointPath?: AbsoluteFsPath, const unsortedEntryPoints = this.basePaths.reduce<EntryPoint[]>(
pathMappings?: PathMappings): SortedEntryPointsInfo {
const basePaths = this.getBasePaths(sourceDirectory, pathMappings);
const unsortedEntryPoints = basePaths.reduce<EntryPoint[]>(
(entryPoints, basePath) => entryPoints.concat(this.walkDirectoryForEntryPoints(basePath)), (entryPoints, basePath) => entryPoints.concat(this.walkDirectoryForEntryPoints(basePath)),
[]); []);
const targetEntryPoint = targetEntryPointPath ? return this.resolver.sortEntryPointsByDependency(unsortedEntryPoints);
unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) :
undefined;
return this.resolver.sortEntryPointsByDependency(unsortedEntryPoints, targetEntryPoint);
}
/**
* Extract all the base-paths that we need to search for entry-points.
*
* This always contains the standard base-path (`sourceDirectory`).
* But it also parses the `paths` mappings object to guess additional base-paths.
*
* For example:
*
* ```
* getBasePaths('/node_modules', {baseUrl: '/dist', paths: {'*': ['lib/*', 'lib/generated/*']}})
* > ['/node_modules', '/dist/lib']
* ```
*
* Notice that `'/dist'` is not included as there is no `'*'` path,
* and `'/dist/lib/generated'` is not included as it is covered by `'/dist/lib'`.
*
* @param sourceDirectory The standard base-path (e.g. node_modules).
* @param pathMappings Path mapping configuration, from which to extract additional base-paths.
*/
private getBasePaths(sourceDirectory: AbsoluteFsPath, pathMappings?: PathMappings):
AbsoluteFsPath[] {
const basePaths = [sourceDirectory];
if (pathMappings) {
const baseUrl = resolve(pathMappings.baseUrl);
values(pathMappings.paths).forEach(paths => paths.forEach(path => {
basePaths.push(join(baseUrl, extractPathPrefix(path)));
}));
}
basePaths.sort(); // Get the paths in order with the shorter ones first.
return basePaths.filter(removeDeeperPaths);
} }
/** /**
@ -168,38 +138,6 @@ export class EntryPointFinder {
} }
} }
/**
* Extract everything in the `path` up to the first `*`.
* @param path The path to parse.
* @returns The extracted prefix.
*/
function extractPathPrefix(path: string) {
return path.split('*', 1)[0];
}
/**
* A filter function that removes paths that are already covered by higher paths.
*
* @param value The current path.
* @param index The index of the current path.
* @param array The array of paths (sorted alphabetically).
* @returns true if this path is not already covered by a previous path.
*/
function removeDeeperPaths(value: AbsoluteFsPath, index: number, array: AbsoluteFsPath[]) {
for (let i = 0; i < index; i++) {
if (value.startsWith(array[i])) return false;
}
return true;
}
/**
* Extract all the values (not keys) from an object.
* @param obj The object to process.
*/
function values<T>(obj: {[key: string]: T}): T[] {
return Object.keys(obj).map(key => obj[key]);
}
function stripJsExtension<T extends string>(filePath: T): T { function stripJsExtension<T extends string>(filePath: T): T {
return filePath.replace(/\.js$/, '') as T; return filePath.replace(/\.js$/, '') as T;
} }

View File

@ -0,0 +1,15 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
export interface EntryPointFinder {
/**
* Search for Angular package entry-points.
*/
findEntryPoints(): SortedEntryPointsInfo;
}

View File

@ -0,0 +1,123 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteFsPath, FileSystem, PathSegment, join, relative, relativeFrom} from '../../../src/ngtsc/file_system';
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
import {Logger} from '../logging/logger';
import {NgccConfiguration} from '../packages/configuration';
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
import {PathMappings} from '../utils';
import {EntryPointFinder} from './interface';
import {getBasePaths} from './utils';
/**
* An EntryPointFinder that starts from a target entry-point and only finds
* entry-points that are dependencies of the target.
*
* This is faster than searching the entire file-system for all the entry-points,
* and is used primarily by the CLI integration.
*/
export class TargetedEntryPointFinder implements EntryPointFinder {
private unprocessedPaths: AbsoluteFsPath[] = [];
private unsortedEntryPoints = new Map<AbsoluteFsPath, EntryPoint>();
private basePaths = getBasePaths(this.basePath, this.pathMappings);
constructor(
private fs: FileSystem, private config: NgccConfiguration, private logger: Logger,
private resolver: DependencyResolver, private basePath: AbsoluteFsPath,
private targetPath: AbsoluteFsPath, private pathMappings: PathMappings|undefined) {}
findEntryPoints(): SortedEntryPointsInfo {
this.unprocessedPaths = [this.targetPath];
while (this.unprocessedPaths.length > 0) {
this.processNextPath();
}
const targetEntryPoint = this.unsortedEntryPoints.get(this.targetPath);
return this.resolver.sortEntryPointsByDependency(
Array.from(this.unsortedEntryPoints.values()), targetEntryPoint);
}
private processNextPath(): void {
const path = this.unprocessedPaths.shift() !;
const entryPoint = this.getEntryPoint(path);
if (entryPoint !== null) {
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
const deps = this.resolver.getEntryPointDependencies(entryPoint);
deps.dependencies.forEach(dep => {
if (!this.unsortedEntryPoints.has(dep)) {
this.unprocessedPaths.push(dep);
}
});
}
}
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
const packagePath = this.computePackagePath(entryPointPath);
return getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
}
/**
* Search down to the `entryPointPath` from each `basePath` for the first `package.json` that we
* come to. This is the path to the entry-point's containing package. For example if `basePath` is
* `/a/b/c` and `entryPointPath` is `/a/b/c/d/e` and there exists `/a/b/c/d/package.json` and
* `/a/b/c/d/e/package.json`, then we will return `/a/b/c/d`.
*
* To account for nested `node_modules` we actually start the search at the last `node_modules` in
* the `entryPointPath` that is below the `basePath`. E.g. if `basePath` is `/a/b/c` and
* `entryPointPath` is `/a/b/c/d/node_modules/x/y/z`, we start the search at
* `/a/b/c/d/node_modules`.
*/
private computePackagePath(entryPointPath: AbsoluteFsPath): AbsoluteFsPath {
for (const basePath of this.basePaths) {
if (entryPointPath.startsWith(basePath)) {
let packagePath = basePath;
const segments = this.splitPath(relative(basePath, entryPointPath));
// Start the search at the last nested `node_modules` folder if the relative
// `entryPointPath` contains one or more.
let nodeModulesIndex = segments.lastIndexOf(relativeFrom('node_modules'));
while (nodeModulesIndex >= 0) {
packagePath = join(packagePath, segments.shift() !);
nodeModulesIndex--;
}
// Note that we skip the first `packagePath` and start looking from the first folder below
// it because that will be the `node_modules` folder.
for (const segment of segments) {
packagePath = join(packagePath, segment);
if (this.fs.exists(join(packagePath, 'package.json'))) {
return packagePath;
}
}
// If we got here then we couldn't find a `packagePath` for the current `basePath` but since
// `basePath`s are guaranteed not to be a sub-directory each other then no other `basePath`
// will match either.
break;
}
}
// If we get here then none of the `basePaths` matched the `entryPointPath`, which
// is somewhat unexpected and means that this entry-point lives completely outside
// any of the `basePaths`.
// All we can do is assume that his entry-point is a primary entry-point to a package.
return entryPointPath;
}
/**
* Split the given `path` into path segments using an FS independent algorithm.
* @param path The path to split.
*/
private splitPath(path: PathSegment) {
const segments = [];
while (path !== '.') {
segments.unshift(this.fs.basename(path));
path = this.fs.dirname(path);
}
return segments;
}
}

View File

@ -0,0 +1,65 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteFsPath, join, resolve} from '../../../src/ngtsc/file_system';
import {PathMappings} from '../utils';
/**
* Extract all the base-paths that we need to search for entry-points.
*
* This always contains the standard base-path (`sourceDirectory`).
* But it also parses the `paths` mappings object to guess additional base-paths.
*
* For example:
*
* ```
* getBasePaths('/node_modules', {baseUrl: '/dist', paths: {'*': ['lib/*', 'lib/generated/*']}})
* > ['/node_modules', '/dist/lib']
* ```
*
* Notice that `'/dist'` is not included as there is no `'*'` path,
* and `'/dist/lib/generated'` is not included as it is covered by `'/dist/lib'`.
*
* @param sourceDirectory The standard base-path (e.g. node_modules).
* @param pathMappings Path mapping configuration, from which to extract additional base-paths.
*/
export function getBasePaths(
sourceDirectory: AbsoluteFsPath, pathMappings: PathMappings | undefined): AbsoluteFsPath[] {
const basePaths = [sourceDirectory];
if (pathMappings) {
const baseUrl = resolve(pathMappings.baseUrl);
Object.values(pathMappings.paths).forEach(paths => paths.forEach(path => {
basePaths.push(join(baseUrl, extractPathPrefix(path)));
}));
}
basePaths.sort(); // Get the paths in order with the shorter ones first.
return basePaths.filter(removeDeeperPaths);
}
/**
* Extract everything in the `path` up to the first `*`.
* @param path The path to parse.
* @returns The extracted prefix.
*/
function extractPathPrefix(path: string) {
return path.split('*', 1)[0];
}
/**
* A filter function that removes paths that are already covered by higher paths.
*
* @param value The current path.
* @param index The index of the current path.
* @param array The array of paths (sorted alphabetically).
* @returns true if this path is not already covered by a previous path.
*/
function removeDeeperPaths(value: AbsoluteFsPath, index: number, array: AbsoluteFsPath[]) {
for (let i = 0; i < index; i++) {
if (value.startsWith(array[i])) return false;
}
return true;
}

View File

@ -7,17 +7,18 @@
*/ */
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system'; import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host'; import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
import {DependencyResolver} from './dependencies/dependency_resolver'; import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} 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';
import {UmdDependencyHost} from './dependencies/umd_dependency_host'; 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 {ConsoleLogger, LogLevel} from './logging/console_logger'; import {ConsoleLogger, LogLevel} from './logging/console_logger';
import {Logger} from './logging/logger'; import {Logger} from './logging/logger';
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker'; import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
import {NgccConfiguration} from './packages/configuration'; import {NgccConfiguration} from './packages/configuration';
import {EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point'; import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
import {makeEntryPointBundle} from './packages/entry_point_bundle'; import {makeEntryPointBundle} from './packages/entry_point_bundle';
import {EntryPointFinder} from './packages/entry_point_finder';
import {Transformer} from './packages/transformer'; import {Transformer} from './packages/transformer';
import {PathMappings} from './utils'; import {PathMappings} from './utils';
import {FileWriter} from './writing/file_writer'; import {FileWriter} from './writing/file_writer';
@ -93,36 +94,12 @@ export function mainNgcc(
umd: umdDependencyHost, umd: umdDependencyHost,
commonjs: commonJsDependencyHost commonjs: commonJsDependencyHost
}); });
const config = new NgccConfiguration(fileSystem, dirname(absoluteFrom(basePath))); const absBasePath = absoluteFrom(basePath);
const finder = new EntryPointFinder(fileSystem, config, logger, resolver); const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats); const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
const entryPoints = getEntryPoints(
const absoluteTargetEntryPointPath = fileSystem, config, logger, resolver, absBasePath, targetEntryPointPath, pathMappings,
targetEntryPointPath ? resolve(basePath, targetEntryPointPath) : undefined; propertiesToConsider, compileAllFormats);
if (absoluteTargetEntryPointPath &&
hasProcessedTargetEntryPoint(
fileSystem, absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) {
logger.debug('The target entry-point has already been processed');
return;
}
const {entryPoints, invalidEntryPoints} =
finder.findEntryPoints(absoluteFrom(basePath), absoluteTargetEntryPointPath, pathMappings);
invalidEntryPoints.forEach(invalidEntryPoint => {
logger.debug(
`Invalid entry-point ${invalidEntryPoint.entryPoint.path}.`,
`It is missing required dependencies:\n` +
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
});
if (absoluteTargetEntryPointPath && entryPoints.length === 0) {
markNonAngularPackageAsProcessed(
fileSystem, absoluteTargetEntryPointPath, propertiesToConsider);
return;
}
for (const entryPoint of entryPoints) { for (const entryPoint of entryPoints) {
// Are we compiling the Angular core? // Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core'; const isCore = entryPoint.name === '@angular/core';
@ -188,6 +165,47 @@ function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): Fil
return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs) : new InPlaceFileWriter(fs); return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs) : new InPlaceFileWriter(fs);
} }
function getEntryPoints(
fs: FileSystem, config: NgccConfiguration, logger: Logger, resolver: DependencyResolver,
basePath: AbsoluteFsPath, targetEntryPointPath: string | undefined,
pathMappings: PathMappings | undefined, propertiesToConsider: string[],
compileAllFormats: boolean): EntryPoint[] {
const {entryPoints, invalidEntryPoints} = (targetEntryPointPath !== undefined) ?
getTargetedEntryPoints(
fs, config, logger, resolver, basePath, targetEntryPointPath, propertiesToConsider,
compileAllFormats, pathMappings) :
getAllEntryPoints(fs, config, logger, resolver, basePath, pathMappings);
logInvalidEntryPoints(logger, invalidEntryPoints);
return entryPoints;
}
function getTargetedEntryPoints(
fs: FileSystem, config: NgccConfiguration, logger: Logger, resolver: DependencyResolver,
basePath: AbsoluteFsPath, targetEntryPointPath: string, propertiesToConsider: string[],
compileAllFormats: boolean, pathMappings: PathMappings | undefined): SortedEntryPointsInfo {
const absoluteTargetEntryPointPath = resolve(basePath, targetEntryPointPath);
if (hasProcessedTargetEntryPoint(
fs, absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) {
logger.debug('The target entry-point has already been processed');
return {entryPoints: [], invalidEntryPoints: [], ignoredDependencies: []};
}
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
const entryPointInfo = finder.findEntryPoints();
if (entryPointInfo.entryPoints.length === 0) {
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
}
return entryPointInfo;
}
function getAllEntryPoints(
fs: FileSystem, config: NgccConfiguration, logger: Logger, resolver: DependencyResolver,
basePath: AbsoluteFsPath, pathMappings: PathMappings | undefined): SortedEntryPointsInfo {
const finder =
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, pathMappings);
return finder.findEntryPoints();
}
function hasProcessedTargetEntryPoint( function hasProcessedTargetEntryPoint(
fs: FileSystem, targetPath: AbsoluteFsPath, propertiesToConsider: string[], fs: FileSystem, targetPath: AbsoluteFsPath, propertiesToConsider: string[],
compileAllFormats: boolean) { compileAllFormats: boolean) {
@ -233,3 +251,12 @@ function markNonAngularPackageAsProcessed(
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty); markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
}); });
} }
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
invalidEntryPoints.forEach(invalidEntryPoint => {
logger.debug(
`Invalid entry-point ${invalidEntryPoint.entryPoint.path}.`,
`It is missing required dependencies:\n` +
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
});
}

View File

@ -0,0 +1,196 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem, relative} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {DirectoryWalkerEntryPointFinder} from '../../src/entry_point_finder/directory_walker_entry_point_finder';
import {NgccConfiguration} from '../../src/packages/configuration';
import {EntryPoint} from '../../src/packages/entry_point';
import {PathMappings} from '../../src/utils';
import {MockLogger} from '../helpers/mock_logger';
runInEachFileSystem(() => {
describe('DirectoryWalkerEntryPointFinder', () => {
let fs: FileSystem;
let resolver: DependencyResolver;
let logger: MockLogger;
let config: NgccConfiguration;
let _Abs: typeof absoluteFrom;
beforeEach(() => {
fs = getFileSystem();
_Abs = absoluteFrom;
logger = new MockLogger();
resolver = new DependencyResolver(
fs, logger, {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))});
config = new NgccConfiguration(fs, _Abs('/'));
});
describe('findEntryPoints()', () => {
it('should find sub-entry-points within a package', () => {
const basePath = _Abs('/sub_entry_points/node_modules');
loadTestFiles([
...createPackage(basePath, 'common'),
...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']),
...createPackage(
fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']),
...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']),
]);
const finder =
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['common', 'common'],
['common', 'common/http'],
['common', 'common/testing'],
['common', 'common/http/testing'],
]);
});
it('should find packages inside a namespace', () => {
const basePath = _Abs('/namespaced/node_modules');
loadTestFiles([
...createPackage(fs.resolve(basePath, '@angular'), 'common'),
...createPackage(fs.resolve(basePath, '@angular/common'), 'http', ['@angular/common']),
...createPackage(
fs.resolve(basePath, '@angular/common/http'), 'testing',
['@angular/common/http', '@angular/common/testing']),
...createPackage(fs.resolve(basePath, '@angular/common'), 'testing', ['@angular/common']),
]);
const finder =
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['@angular/common', '@angular/common'],
['@angular/common', '@angular/common/http'],
['@angular/common', '@angular/common/testing'],
['@angular/common', '@angular/common/http/testing'],
]);
});
it('should return an empty array if there are no packages', () => {
fs.ensureDir(_Abs('/no_packages/node_modules/should_not_be_found'));
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/no_packages/node_modules'), undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should return an empty array if there are no valid entry-points', () => {
loadTestFiles([
{
name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'),
contents: '{}'
},
]);
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should ignore folders starting with .', () => {
loadTestFiles([
...createPackage(_Abs('/dotted_folders/node_modules/'), '.common'),
]);
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/dotted_folders/node_modules'), undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should ignore folders that are symlinked', () => {
fs.ensureDir(_Abs('/symlinked_folders/node_modules'));
fs.symlink(
_Abs('/external/node_modules/common'), _Abs('/symlinked_folders/node_modules/common'));
loadTestFiles(createPackage(_Abs('/external/node_modules'), 'common'));
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/symlinked_folders/node_modules'), undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should handle nested node_modules folders', () => {
loadTestFiles([
...createPackage(_Abs('/nested_node_modules/node_modules'), 'outer', ['inner']),
...createPackage(_Abs('/nested_node_modules/node_modules/outer/node_modules'), 'inner'),
]);
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), undefined);
const {entryPoints} = finder.findEntryPoints();
// Note that the `inner` entry-point is not part of the `outer` package
expect(dumpEntryPointPaths(_Abs('/nested_node_modules/node_modules'), entryPoints))
.toEqual([
['outer/node_modules/inner', 'outer/node_modules/inner'],
['outer', 'outer'],
]);
});
it('should handle dependencies via pathMappings', () => {
const basePath = _Abs('/path_mapped/node_modules');
const pathMappings: PathMappings = {
baseUrl: '/path_mapped/dist',
paths: {
'@x/*': ['*'],
'@y/*/test': ['lib/*/test'],
}
};
loadTestFiles([
...createPackage(
_Abs('/path_mapped/node_modules'), 'test', ['pkg1', '@x/pkg2', '@y/pkg3/test']),
...createPackage(_Abs('/path_mapped/node_modules'), 'pkg1'),
...createPackage(_Abs('/path_mapped/dist'), 'pkg2', ['pkg4']),
...createPackage(_Abs('/path_mapped/dist/pkg2/node_modules'), 'pkg4'),
...createPackage(_Abs('/path_mapped/dist/lib/pkg3'), 'test'),
]);
resolver = new DependencyResolver(
fs, logger, {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings))});
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, basePath, pathMappings);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['pkg1', 'pkg1'],
['../dist/pkg2/node_modules/pkg4', '../dist/pkg2/node_modules/pkg4'],
['../dist/pkg2', '../dist/pkg2'],
['../dist/lib/pkg3/test', '../dist/lib/pkg3/test'],
['test', 'test'],
]);
});
function createPackage(
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
return [
{
name: _Abs(`${basePath}/${packageName}/package.json`),
contents: JSON.stringify({
typings: `./${packageName}.d.ts`,
fesm2015: `./fesm2015/${packageName}.js`,
})
},
{
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
contents: 'metadata info'
},
{
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'),
},
];
}
function dumpEntryPointPaths(
basePath: AbsoluteFsPath, entryPoints: EntryPoint[]): [string, string][] {
return entryPoints.map(x => [relative(basePath, x.package), relative(basePath, x.path)]);
}
});
});
});

View File

@ -0,0 +1,201 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem, relative} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {TargetedEntryPointFinder} from '../../src/entry_point_finder/targeted_entry_point_finder';
import {NgccConfiguration} from '../../src/packages/configuration';
import {EntryPoint} from '../../src/packages/entry_point';
import {PathMappings} from '../../src/utils';
import {MockLogger} from '../helpers/mock_logger';
runInEachFileSystem(() => {
describe('TargetedEntryPointFinder', () => {
let fs: FileSystem;
let resolver: DependencyResolver;
let logger: MockLogger;
let config: NgccConfiguration;
let _Abs: typeof absoluteFrom;
beforeEach(() => {
fs = getFileSystem();
_Abs = absoluteFrom;
logger = new MockLogger();
resolver = new DependencyResolver(
fs, logger, {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))});
config = new NgccConfiguration(fs, _Abs('/'));
});
describe('findEntryPoints()', () => {
it('should find a single entry-point with no dependencies', () => {
const basePath = _Abs('/sub_entry_points/node_modules');
const targetPath = _Abs('/sub_entry_points/node_modules/common');
loadTestFiles([
...createPackage(fs.resolve(basePath, ''), 'common'),
...createPackage(fs.resolve(basePath, 'common'), 'http', ['@angular/common']),
...createPackage(
fs.resolve(basePath, 'common/http'), 'testing',
['@angular/common/http', '@angular/common/testing']),
...createPackage(fs.resolve(basePath, 'common'), 'testing', ['@angular/common']),
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/sub_entry_points/node_modules'), targetPath,
undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['common', 'common'],
]);
});
it('should find dependencies of secondary entry-points within a package', () => {
const basePath = _Abs('/sub_entry_points/node_modules');
const targetPath = _Abs('/sub_entry_points/node_modules/common/http/testing');
loadTestFiles([
...createPackage(fs.resolve(basePath, ''), 'common'),
...createPackage(fs.resolve(basePath, 'common'), 'http', ['common']),
...createPackage(
fs.resolve(basePath, 'common/http'), 'testing', ['common/http', 'common/testing']),
...createPackage(fs.resolve(basePath, 'common'), 'testing', ['common']),
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/sub_entry_points/node_modules'), targetPath,
undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['common', 'common'],
['common', 'common/http'],
['common', 'common/testing'],
['common', 'common/http/testing'],
]);
});
it('should find dependencies inside a namespace', () => {
const basePath = _Abs('/namespaced/node_modules');
const targetPath = _Abs('/namespaced/node_modules/@angular/common/http');
loadTestFiles([
...createPackage(fs.resolve(basePath, '@angular'), 'common'),
...createPackage(fs.resolve(basePath, '@angular/common'), 'http', ['@angular/common']),
...createPackage(
fs.resolve(basePath, '@angular/common/http'), 'testing',
['@angular/common/http', '@angular/common/testing']),
...createPackage(fs.resolve(basePath, '@angular/common'), 'testing', ['@angular/common']),
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/namespaced/node_modules'), targetPath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['@angular/common', '@angular/common'],
['@angular/common', '@angular/common/http'],
]);
});
it('should return an empty array if the target path is not an entry-point', () => {
const targetPath = _Abs('/no_packages/node_modules/should_not_be_found');
fs.ensureDir(_Abs('/no_packages/node_modules/should_not_be_found'));
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/no_packages/node_modules'), targetPath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should return an empty array if the target path is not an Angular entry-point', () => {
const targetPath = _Abs('/no_valid_entry_points/node_modules/some_package');
loadTestFiles([
{
name: _Abs('/no_valid_entry_points/node_modules/some_package/package.json'),
contents: '{}'
},
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/no_valid_entry_points/node_modules'), targetPath,
undefined);
const {entryPoints} = finder.findEntryPoints();
expect(entryPoints).toEqual([]);
});
it('should handle nested node_modules folders', () => {
const targetPath = _Abs('/nested_node_modules/node_modules/outer');
loadTestFiles([
...createPackage(_Abs('/nested_node_modules/node_modules'), 'outer', ['inner']),
...createPackage(_Abs('/nested_node_modules/node_modules/outer/node_modules'), 'inner'),
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), targetPath,
undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(_Abs('/nested_node_modules/node_modules'), entryPoints))
.toEqual([
['outer/node_modules/inner', 'outer/node_modules/inner'],
['outer', 'outer'],
]);
});
it('should handle dependencies via pathMappings', () => {
const basePath = _Abs('/path_mapped/node_modules');
const targetPath = _Abs('/path_mapped/node_modules/test');
const pathMappings: PathMappings = {
baseUrl: '/path_mapped/dist',
paths: {
'@x/*': ['*'],
'@y/*/test': ['lib/*/test'],
}
};
loadTestFiles([
...createPackage(
_Abs('/path_mapped/node_modules'), 'test', ['pkg1', '@x/pkg2', '@y/pkg3/test']),
...createPackage(_Abs('/path_mapped/node_modules'), 'pkg1'),
...createPackage(_Abs('/path_mapped/dist'), 'pkg2', ['pkg4']),
...createPackage(_Abs('/path_mapped/dist/pkg2/node_modules'), 'pkg4'),
...createPackage(_Abs('/path_mapped/dist/lib/pkg3'), 'test'),
]);
resolver = new DependencyResolver(
fs, logger, {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs, pathMappings))});
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, targetPath, pathMappings);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([
['pkg1', 'pkg1'],
['../dist/pkg2/node_modules/pkg4', '../dist/pkg2/node_modules/pkg4'],
['../dist/pkg2', '../dist/pkg2'],
['../dist/lib/pkg3/test', '../dist/lib/pkg3/test'],
['test', 'test'],
]);
});
function createPackage(
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
return [
{
name: _Abs(`${basePath}/${packageName}/package.json`),
contents: JSON.stringify({
typings: `./${packageName}.d.ts`,
fesm2015: `./fesm2015/${packageName}.js`,
})
},
{
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
contents: 'metadata info'
},
{
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'),
},
];
}
function dumpEntryPointPaths(
basePath: AbsoluteFsPath, entryPoints: EntryPoint[]): [string, string][] {
return entryPoints.map(x => [relative(basePath, x.package), relative(basePath, x.path)]);
}
});
});
});

View File

@ -1,212 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* 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
*/
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NgccConfiguration} from '../../src/packages/configuration';
import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointFinder} from '../../src/packages/entry_point_finder';
import {MockLogger} from '../helpers/mock_logger';
runInEachFileSystem(() => {
describe('findEntryPoints()', () => {
let resolver: DependencyResolver;
let finder: EntryPointFinder;
let _: typeof absoluteFrom;
beforeEach(() => {
const fs = getFileSystem();
_ = absoluteFrom;
setupMockFileSystem();
resolver = new DependencyResolver(
fs, new MockLogger(), {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))});
spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => {
return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []};
});
finder =
new EntryPointFinder(fs, new NgccConfiguration(fs, _('/')), new MockLogger(), resolver);
});
it('should find sub-entry-points within a package', () => {
const {entryPoints} = finder.findEntryPoints(_('/sub_entry_points'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([
[_('/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/testing')],
[_('/sub_entry_points/common'), _('/sub_entry_points/common/testing')],
]);
});
it('should find packages inside a namespace', () => {
const {entryPoints} = finder.findEntryPoints(_('/namespaced'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common')],
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http')],
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http/testing')],
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/testing')],
]);
});
it('should find entry-points via `pathMappings', () => {
const {entryPoints} = finder.findEntryPoints(
_('/pathMappings/node_modules'), undefined,
{baseUrl: _('/pathMappings'), paths: {'my-lib': ['dist/my-lib']}});
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([
[_('/pathMappings/dist/my-lib'), _('/pathMappings/dist/my-lib')],
[_('/pathMappings/dist/my-lib'), _('/pathMappings/dist/my-lib/sub-lib')],
[
_('/pathMappings/node_modules/@angular/common'),
_('/pathMappings/node_modules/@angular/common')
],
]);
});
it('should return an empty array if there are no packages', () => {
const {entryPoints} = finder.findEntryPoints(_('/no_packages'));
expect(entryPoints).toEqual([]);
});
it('should return an empty array if there are no valid entry-points', () => {
const {entryPoints} = finder.findEntryPoints(_('/no_valid_entry_points'));
expect(entryPoints).toEqual([]);
});
it('should ignore folders starting with .', () => {
const {entryPoints} = finder.findEntryPoints(_('/dotted_folders'));
expect(entryPoints).toEqual([]);
});
it('should ignore folders that are symlinked', () => {
const {entryPoints} = finder.findEntryPoints(_('/symlinked_folders'));
expect(entryPoints).toEqual([]);
});
it('should handle nested node_modules folders', () => {
const {entryPoints} = finder.findEntryPoints(_('/nested_node_modules'));
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
expect(entryPointPaths).toEqual([
[_('/nested_node_modules/outer'), _('/nested_node_modules/outer')],
// 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'),
],
]);
});
function setupMockFileSystem(): void {
loadTestFiles([
{name: _('/sub_entry_points/common/package.json'), contents: createPackageJson('common')},
{name: _('/sub_entry_points/common/common.metadata.json'), contents: 'metadata info'},
{
name: _('/sub_entry_points/common/http/package.json'),
contents: createPackageJson('http')
},
{name: _('/sub_entry_points/common/http/http.metadata.json'), contents: 'metadata info'},
{
name: _('/sub_entry_points/common/http/testing/package.json'),
contents: createPackageJson('testing')
},
{
name: _('/sub_entry_points/common/http/testing/testing.metadata.json'),
contents: 'metadata info'
},
{
name: _('/sub_entry_points/common/testing/package.json'),
contents: createPackageJson('testing')
},
{
name: _('/sub_entry_points/common/testing/testing.metadata.json'),
contents: 'metadata info'
},
{name: _('/pathMappings/dist/my-lib/package.json'), contents: createPackageJson('my-lib')},
{name: _('/pathMappings/dist/my-lib/my-lib.metadata.json'), contents: 'metadata info'},
{
name: _('/pathMappings/dist/my-lib/sub-lib/package.json'),
contents: createPackageJson('sub-lib')
},
{
name: _('/pathMappings/dist/my-lib/sub-lib/sub-lib.metadata.json'),
contents: 'metadata info'
},
{
name: _('/pathMappings/node_modules/@angular/common/package.json'),
contents: createPackageJson('common')
},
{
name: _('/pathMappings/node_modules/@angular/common/common.metadata.json'),
contents: 'metadata info'
},
{
name: _('/namespaced/@angular/common/package.json'),
contents: createPackageJson('common')
},
{name: _('/namespaced/@angular/common/common.metadata.json'), contents: 'metadata info'},
{
name: _('/namespaced/@angular/common/http/package.json'),
contents: createPackageJson('http')
},
{name: _('/namespaced/@angular/common/http/http.metadata.json'), contents: 'metadata info'},
{
name: _('/namespaced/@angular/common/http/testing/package.json'),
contents: createPackageJson('testing')
},
{
name: _('/namespaced/@angular/common/http/testing/testing.metadata.json'),
contents: 'metadata info'
},
{
name: _('/namespaced/@angular/common/testing/package.json'),
contents: createPackageJson('testing')
},
{
name: _('/namespaced/@angular/common/testing/testing.metadata.json'),
contents: 'metadata info'
},
{name: _('/no_valid_entry_points/some_package/package.json'), contents: '{}'},
{name: _('/dotted_folders/.common/package.json'), contents: createPackageJson('common')},
{name: _('/dotted_folders/.common/common.metadata.json'), contents: 'metadata info'},
{name: _('/nested_node_modules/outer/package.json'), contents: createPackageJson('outer')},
{name: _('/nested_node_modules/outer/outer.metadata.json'), contents: 'metadata info'},
{
name: _('/nested_node_modules/outer/node_modules/inner/package.json'),
contents: createPackageJson('inner')
},
{
name: _('/nested_node_modules/outer/node_modules/inner/inner.metadata.json'),
contents: 'metadata info'
},
]);
const fs = getFileSystem();
fs.ensureDir(_('/no_packages/should_not_be_found'));
fs.ensureDir(_('/symlinked_folders'));
fs.symlink(_('/sub_entry_points/common'), _('/symlinked_folders/common'));
}
});
function createPackageJson(packageName: string): string {
const packageJson: any = {
typings: `./${packageName}.d.ts`,
fesm2015: `./fesm2015/${packageName}.js`,
esm2015: `./esm2015/${packageName}.js`,
fesm5: `./fesm2015/${packageName}.js`,
esm5: `./esm2015/${packageName}.js`,
main: `./bundles/${packageName}.umd.js`,
};
return JSON.stringify(packageJson);
}
});

View File

@ -6,7 +6,8 @@
"stripInternal": false, "stripInternal": false,
"target": "es2015", "target": "es2015",
"lib": [ "lib": [
"es2015" "es2015",
"es2017.object",
], ],
"baseUrl": ".", "baseUrl": ".",
"rootDir": ".", "rootDir": ".",

View File

@ -3,7 +3,8 @@
"compilerOptions": { "compilerOptions": {
"target": "es2015", "target": "es2015",
"lib": [ "lib": [
"es2015" "es2015",
"es2017.object",
], ],
"strict": true, "strict": true,
"types": [ "types": [

View File

@ -19,7 +19,7 @@
}, },
"rootDir": ".", "rootDir": ".",
"inlineSourceMap": true, "inlineSourceMap": true,
"lib": ["es5", "dom", "es2015.promise", "es2015.collection", "es2015.iterable", "es2015.core"], "lib": ["es5", "dom", "es2015.promise", "es2015.collection", "es2015.iterable", "es2015.core", "es2017.object"],
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"skipLibCheck": true, "skipLibCheck": true,
"target": "es5", "target": "es5",