diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts index a4f5323755..d4338b5d7e 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts @@ -7,15 +7,11 @@ */ import {DepGraph} from 'dependency-graph'; - import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {Logger} from '../logging/logger'; -import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point'; - +import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point'; import {DependencyHost} from './dependency_host'; - - /** * Holds information about entry points that are removed because * they have dependencies that are missing (directly or transitively). @@ -68,7 +64,8 @@ export interface SortedEntryPointsInfo extends DependencyDiagnostics { entryPoin * A class that resolves dependencies between entry-points. */ export class DependencyResolver { - constructor(private logger: Logger, private host: DependencyHost) {} + constructor( + private logger: Logger, private hosts: Partial>) {} /** * Sort the array of entry points so that the dependant entry points always come later than * their dependencies in the array. @@ -118,8 +115,13 @@ export class DependencyResolver { // Now add the dependencies between them angularEntryPoints.forEach(entryPoint => { - const entryPointPath = getEntryPointPath(entryPoint); - const {dependencies, missing, deepImports} = this.host.findDependencies(entryPointPath); + const formatInfo = 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}'.`); + } + const {dependencies, missing, deepImports} = host.findDependencies(formatInfo.path); if (missing.size > 0) { // This entry point has dependencies that are missing @@ -164,7 +166,8 @@ export class DependencyResolver { } } -function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath { +function getEntryPointFormatInfo(entryPoint: EntryPoint): + {format: EntryPointFormat, path: AbsoluteFsPath} { const properties = Object.keys(entryPoint.packageJson); for (let i = 0; i < properties.length; i++) { const property = properties[i] as EntryPointJsonProperty; @@ -172,7 +175,7 @@ function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath { if (format === 'esm2015' || format === 'esm5' || format === 'umd') { const formatPath = entryPoint.packageJson[property] !; - return AbsoluteFsPath.resolve(entryPoint.path, formatPath); + return {format, path: AbsoluteFsPath.resolve(entryPoint.path, formatPath)}; } } throw new Error( diff --git a/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts new file mode 100644 index 0000000000..f8ad0ae9d6 --- /dev/null +++ b/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts @@ -0,0 +1,111 @@ +/** + * @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 * as ts from 'typescript'; + +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {FileSystem} from '../file_system/file_system'; +import {getImportsOfUmdModule, parseStatementForUmdModule} from '../host/umd_host'; + +import {DependencyHost, DependencyInfo} from './dependency_host'; +import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; + + + +/** + * Helper functions for computing dependencies. + */ +export class UmdDependencyHost implements DependencyHost { + constructor(private fs: FileSystem, private moduleResolver: ModuleResolver) {} + + /** + * Find all the dependencies for the entry-point at the given path. + * + * @param entryPointPath The absolute path to the JavaScript file that represents an entry-point. + * @returns Information about the dependencies of the entry-point, including those that were + * missing or deep imports into other entry-points. + */ + findDependencies(entryPointPath: AbsoluteFsPath): DependencyInfo { + const dependencies = new Set(); + const missing = new Set(); + const deepImports = new Set(); + const alreadySeen = new Set(); + this.recursivelyFindDependencies( + entryPointPath, dependencies, missing, deepImports, alreadySeen); + return {dependencies, missing, deepImports}; + } + + /** + * Compute the dependencies of the given file. + * + * @param file An absolute path to the file whose dependencies we want to get. + * @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 deepImports A set that will have the import paths that exist but cannot be mapped to + * entry-points, i.e. deep-imports. + * @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck + * in a + * circular dependency loop. + */ + private recursivelyFindDependencies( + file: AbsoluteFsPath, dependencies: Set, missing: Set, + deepImports: Set, alreadySeen: Set): void { + const fromContents = this.fs.readFile(file); + if (!this.hasRequireCalls(fromContents)) { + // Avoid parsing the source file as there are no require calls. + return; + } + + // Parse the source into a TypeScript AST and then walk it looking for imports and re-exports. + const sf = + ts.createSourceFile(file, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS); + if (sf.statements.length !== 1) { + return; + } + + const umdModule = parseStatementForUmdModule(sf.statements[0]); + const umdImports = umdModule && getImportsOfUmdModule(umdModule); + if (umdImports === null) { + return; + } + + umdImports.forEach(umdImport => { + const resolvedModule = this.moduleResolver.resolveModuleImport(umdImport.path, file); + if (resolvedModule) { + if (resolvedModule instanceof ResolvedRelativeModule) { + const internalDependency = resolvedModule.modulePath; + if (!alreadySeen.has(internalDependency)) { + alreadySeen.add(internalDependency); + this.recursivelyFindDependencies( + internalDependency, dependencies, missing, deepImports, alreadySeen); + } + } else { + if (resolvedModule instanceof ResolvedDeepImport) { + deepImports.add(resolvedModule.importPath); + } else { + dependencies.add(resolvedModule.entryPointPath); + } + } + } else { + missing.add(umdImport.path); + } + }); + } + + /** + * Check whether a source file needs to be parsed for imports. + * This is a performance short-circuit, which saves us from creating + * a TypeScript AST unnecessarily. + * + * @param source The content of the source file to check. + * + * @returns false if there are definitely no require calls + * in this file, true otherwise. + */ + hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); } +} diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index 85d4adbf25..bf98e04b1c 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -10,6 +10,7 @@ import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {DependencyResolver} from './dependencies/dependency_resolver'; import {EsmDependencyHost} from './dependencies/esm_dependency_host'; import {ModuleResolver} from './dependencies/module_resolver'; +import {UmdDependencyHost} from './dependencies/umd_dependency_host'; import {FileSystem} from './file_system/file_system'; import {NodeJSFileSystem} from './file_system/node_js_file_system'; import {ConsoleLogger, LogLevel} from './logging/console_logger'; @@ -80,8 +81,10 @@ export function mainNgcc( const fs = new NodeJSFileSystem(); const transformer = new Transformer(fs, logger); const moduleResolver = new ModuleResolver(fs, pathMappings); - const host = new EsmDependencyHost(fs, moduleResolver); - const resolver = new DependencyResolver(logger, host); + const esmDependencyHost = new EsmDependencyHost(fs, moduleResolver); + const umdDependencyHost = new UmdDependencyHost(fs, moduleResolver); + const resolver = new DependencyResolver( + logger, {esm5: esmDependencyHost, esm2015: esmDependencyHost, umd: umdDependencyHost}); const finder = new EntryPointFinder(fs, logger, resolver); const fileWriter = getFileWriter(fs, createNewEntryPointFormats); diff --git a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts index 288793fe6c..89538b7169 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts @@ -9,6 +9,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; +import {FileSystem} from '../../src/file_system/file_system'; import {EntryPoint} from '../../src/packages/entry_point'; import {MockFileSystem} from '../helpers/mock_file_system'; import {MockLogger} from '../helpers/mock_logger'; @@ -18,10 +19,13 @@ const _ = AbsoluteFsPath.from; describe('DependencyResolver', () => { let host: EsmDependencyHost; let resolver: DependencyResolver; + let fs: FileSystem; + let moduleResolver: ModuleResolver; beforeEach(() => { - const fs = new MockFileSystem(); - host = new EsmDependencyHost(fs, new ModuleResolver(fs)); - resolver = new DependencyResolver(new MockLogger(), host); + fs = new MockFileSystem(); + moduleResolver = new ModuleResolver(fs); + host = new EsmDependencyHost(fs, moduleResolver); + resolver = new DependencyResolver(new MockLogger(), {esm5: host, esm2015: host}); }); describe('sortEntryPointsByDependency()', () => { const first = { @@ -112,6 +116,13 @@ describe('DependencyResolver', () => { ])).toThrowError(`There is no appropriate source code format in '/first' entry-point.`); }); + it('should error if there is no appropriate DependencyHost for the given formats', () => { + resolver = new DependencyResolver(new MockLogger(), {esm2015: host}); + expect(() => resolver.sortEntryPointsByDependency([first])) + .toThrowError( + `Could not find a suitable format for computing dependencies of entry-point: '/first'.`); + }); + it('should capture any dependencies that were ignored', () => { spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies(dependencies)); const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]); @@ -138,6 +149,29 @@ describe('DependencyResolver', () => { expect(sorted.entryPoints).toEqual([fifth]); }); + it('should use the appropriate DependencyHost for each entry-point', () => { + const esm5Host = new EsmDependencyHost(fs, moduleResolver); + const esm2015Host = new EsmDependencyHost(fs, moduleResolver); + resolver = new DependencyResolver(new MockLogger(), {esm5: esm5Host, esm2015: esm2015Host}); + spyOn(esm5Host, 'findDependencies').and.callFake(createFakeComputeDependencies(dependencies)); + spyOn(esm2015Host, 'findDependencies') + .and.callFake(createFakeComputeDependencies(dependencies)); + const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]); + expect(result.entryPoints).toEqual([fifth, fourth, third, second, first]); + + expect(esm5Host.findDependencies).toHaveBeenCalledWith('/first/index.js'); + expect(esm5Host.findDependencies).not.toHaveBeenCalledWith('/second/sub/index.js'); + expect(esm5Host.findDependencies).toHaveBeenCalledWith('/third/index.js'); + expect(esm5Host.findDependencies).not.toHaveBeenCalledWith('/fourth/sub2/index.js'); + expect(esm5Host.findDependencies).toHaveBeenCalledWith('/fifth/index.js'); + + expect(esm2015Host.findDependencies).not.toHaveBeenCalledWith('/first/index.js'); + expect(esm2015Host.findDependencies).toHaveBeenCalledWith('/second/sub/index.js'); + expect(esm2015Host.findDependencies).not.toHaveBeenCalledWith('/third/index.js'); + expect(esm2015Host.findDependencies).toHaveBeenCalledWith('/fourth/sub2/index.js'); + expect(esm2015Host.findDependencies).not.toHaveBeenCalledWith('/fifth/index.js'); + }); + interface DepMap { [path: string]: {resolved: string[], missing: string[]}; } diff --git a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts index 0d10e82da0..69ec54d5a5 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts @@ -14,7 +14,7 @@ import {MockFileSystem} from '../helpers/mock_file_system'; const _ = AbsoluteFsPath.from; -describe('DependencyHost', () => { +describe('EsmDependencyHost', () => { let host: EsmDependencyHost; beforeEach(() => { const fs = createMockFileSystem(); diff --git a/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts new file mode 100644 index 0000000000..5423506e20 --- /dev/null +++ b/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts @@ -0,0 +1,192 @@ +/** + * @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 * as ts from 'typescript'; + +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {ModuleResolver} from '../../src/dependencies/module_resolver'; +import {UmdDependencyHost} from '../../src/dependencies/umd_dependency_host'; +import {MockFileSystem} from '../helpers/mock_file_system'; + +const _ = AbsoluteFsPath.from; + +describe('UmdDependencyHost', () => { + let host: UmdDependencyHost; + beforeEach(() => { + const fs = createMockFileSystem(); + host = new UmdDependencyHost(fs, new ModuleResolver(fs)); + }); + + describe('getDependencies()', () => { + it('should not generate a TS AST if the source does not contain any require calls', () => { + spyOn(ts, 'createSourceFile'); + host.findDependencies(_('/no/imports/or/re-exports/index.js')); + expect(ts.createSourceFile).not.toHaveBeenCalled(); + }); + + it('should resolve all the external imports of the source file', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/imports/index.js')); + expect(dependencies.size).toBe(2); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(0); + expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); + expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); + }); + + it('should resolve all the external re-exports of the source file', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/re-exports/index.js')); + expect(dependencies.size).toBe(2); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(0); + expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); + expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); + }); + + it('should capture missing external imports', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/imports-missing/index.js')); + + expect(dependencies.size).toBe(1); + expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); + expect(missing.size).toBe(1); + expect(missing.has('missing')).toBe(true); + expect(deepImports.size).toBe(0); + }); + + it('should not register deep imports as missing', () => { + // This scenario verifies the behavior of the dependency analysis when an external import + // is found that does not map to an entry-point but still exists on disk, i.e. a deep import. + // Such deep imports are captured for diagnostics purposes. + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/deep-import/index.js')); + + expect(dependencies.size).toBe(0); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(1); + expect(deepImports.has('/node_modules/lib_1/deep/import')).toBe(true); + }); + + it('should recurse into internal dependencies', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/internal/outer/index.js')); + + expect(dependencies.size).toBe(1); + expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(0); + }); + + it('should handle circular internal dependencies', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/internal/circular_a/index.js')); + expect(dependencies.size).toBe(2); + expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); + expect(dependencies.has(_('/node_modules/lib_1/sub_1'))).toBe(true); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(0); + }); + + it('should support `paths` alias mappings when resolving modules', () => { + const fs = createMockFileSystem(); + host = new UmdDependencyHost(fs, new ModuleResolver(fs, { + baseUrl: '/dist', + paths: { + '@app/*': ['*'], + '@lib/*/test': ['lib/*/test'], + } + })); + const {dependencies, missing, deepImports} = host.findDependencies(_('/path-alias/index.js')); + expect(dependencies.size).toBe(4); + expect(dependencies.has(_('/dist/components'))).toBe(true); + expect(dependencies.has(_('/dist/shared'))).toBe(true); + expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true); + expect(dependencies.has(_('/node_modules/lib_1'))).toBe(true); + expect(missing.size).toBe(0); + expect(deepImports.size).toBe(0); + }); + }); + + function createMockFileSystem() { + return new MockFileSystem({ + '/no/imports/or/re-exports/index.js': '// some text but no import-like statements', + '/no/imports/or/re-exports/package.json': '{"esm2015": "./index.js"}', + '/no/imports/or/re-exports/index.metadata.json': 'MOCK METADATA', + '/external/imports/index.js': umd('imports_index', ['lib_1', 'lib_1/sub_1']), + '/external/imports/package.json': '{"esm2015": "./index.js"}', + '/external/imports/index.metadata.json': 'MOCK METADATA', + '/external/re-exports/index.js': + umd('imports_index', ['lib_1', 'lib_1/sub_1'], ['lib_1.X', 'lib_1sub_1.Y']), + '/external/re-exports/package.json': '{"esm2015": "./index.js"}', + '/external/re-exports/index.metadata.json': 'MOCK METADATA', + '/external/imports-missing/index.js': umd('imports_missing', ['lib_1', 'missing']), + '/external/imports-missing/package.json': '{"esm2015": "./index.js"}', + '/external/imports-missing/index.metadata.json': 'MOCK METADATA', + '/external/deep-import/index.js': umd('deep_import', ['lib_1/deep/import']), + '/external/deep-import/package.json': '{"esm2015": "./index.js"}', + '/external/deep-import/index.metadata.json': 'MOCK METADATA', + '/internal/outer/index.js': umd('outer', ['../inner']), + '/internal/outer/package.json': '{"esm2015": "./index.js"}', + '/internal/outer/index.metadata.json': 'MOCK METADATA', + '/internal/inner/index.js': umd('inner', ['lib_1/sub_1'], ['X']), + '/internal/circular_a/index.js': umd('circular_a', ['../circular_b', 'lib_1/sub_1'], ['Y']), + '/internal/circular_b/index.js': umd('circular_b', ['../circular_a', 'lib_1'], ['X']), + '/internal/circular_a/package.json': '{"esm2015": "./index.js"}', + '/internal/circular_a/index.metadata.json': 'MOCK METADATA', + '/re-directed/index.js': umd('re_directed', ['lib_1/sub_2']), + '/re-directed/package.json': '{"esm2015": "./index.js"}', + '/re-directed/index.metadata.json': 'MOCK METADATA', + '/path-alias/index.js': + umd('path_alias', ['@app/components', '@app/shared', '@lib/shared/test', 'lib_1']), + '/path-alias/package.json': '{"esm2015": "./index.js"}', + '/path-alias/index.metadata.json': 'MOCK METADATA', + '/node_modules/lib_1/index.d.ts': 'export declare class X {}', + '/node_modules/lib_1/package.json': '{"esm2015": "./index.js", "typings": "./index.d.ts"}', + '/node_modules/lib_1/index.metadata.json': 'MOCK METADATA', + '/node_modules/lib_1/deep/import/index.js': 'export class DeepImport {}', + '/node_modules/lib_1/sub_1/index.d.ts': 'export declare class Y {}', + '/node_modules/lib_1/sub_1/package.json': + '{"esm2015": "./index.js", "typings": "./index.d.ts"}', + '/node_modules/lib_1/sub_1/index.metadata.json': 'MOCK METADATA', + '/node_modules/lib_1/sub_2.d.ts': `export * from './sub_2/sub_2';`, + '/node_modules/lib_1/sub_2/sub_2.d.ts': `export declare class Z {}';`, + '/node_modules/lib_1/sub_2/package.json': + '{"esm2015": "./sub_2.js", "typings": "./sub_2.d.ts"}', + '/node_modules/lib_1/sub_2/sub_2.metadata.json': 'MOCK METADATA', + '/dist/components/index.d.ts': `export declare class MyComponent {};`, + '/dist/components/package.json': '{"esm2015": "./index.js", "typings": "./index.d.ts"}', + '/dist/components/index.metadata.json': 'MOCK METADATA', + '/dist/shared/index.d.ts': `import {X} from 'lib_1';\nexport declare class Service {}`, + '/dist/shared/package.json': '{"esm2015": "./index.js", "typings": "./index.d.ts"}', + '/dist/shared/index.metadata.json': 'MOCK METADATA', + '/dist/lib/shared/test/index.d.ts': `export class TestHelper {}`, + '/dist/lib/shared/test/package.json': '{"esm2015": "./index.js", "typings": "./index.d.ts"}', + '/dist/lib/shared/test/index.metadata.json': 'MOCK METADATA', + }); + } +}); + +function umd(moduleName: string, importPaths: string[], exportNames: string[] = []) { + const commonJsRequires = importPaths.map(p => `,require('${p}')`).join(''); + const amdDeps = importPaths.map(p => `,'${p}'`).join(''); + const globalParams = + importPaths.map(p => `,global.${p.replace('@angular/', 'ng.').replace(/\//g, '')}`).join(''); + const params = + importPaths.map(p => `,${p.replace('@angular/', '').replace(/\.?\.?\//g, '')}`).join(''); + const exportStatements = + exportNames.map(e => ` exports.${e.replace(/.+\./, '')} = ${e};`).join('\n'); + return ` +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports${commonJsRequires}) : + typeof define === 'function' && define.amd ? define('${moduleName}', ['exports'${amdDeps}], factory) : + (factory(global.${moduleName}${globalParams})); +}(this, (function (exports${params}) { 'use strict'; +${exportStatements} +}))); + `; +} diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts index e93af39185..4669e206c3 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts @@ -22,8 +22,8 @@ describe('findEntryPoints()', () => { let finder: EntryPointFinder; beforeEach(() => { const fs = createMockFileSystem(); - resolver = - new DependencyResolver(new MockLogger(), new EsmDependencyHost(fs, new ModuleResolver(fs))); + resolver = new DependencyResolver( + new MockLogger(), {esm2015: new EsmDependencyHost(fs, new ModuleResolver(fs))}); spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => { return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []}; });