diff --git a/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts index e4269c2c6e..2fc8338b50 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/commonjs_dependency_host.ts @@ -6,34 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; -import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; import {isRequireCall} from '../host/commonjs_host'; -import {DependencyHost, DependencyInfo} from './dependency_host'; -import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; +import {resolveFileWithPostfixes} from '../utils'; +import {DependencyHostBase} from './dependency_host'; +import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; /** * Helper functions for computing dependencies. */ -export class CommonJsDependencyHost 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}; - } - +export class CommonJsDependencyHost extends DependencyHostBase { /** * Compute the dependencies of the given file. * @@ -44,21 +26,25 @@ export class CommonJsDependencyHost implements DependencyHost { * @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. + * in a circular dependency loop. */ - private recursivelyFindDependencies( + protected recursivelyFindDependencies( file: AbsoluteFsPath, dependencies: Set, missing: Set, deepImports: Set, alreadySeen: Set): void { - const fromContents = this.fs.readFile(file); + const resolvedFile = resolveFileWithPostfixes(this.fs, file, ['', '.js', '/index.js']); + if (resolvedFile === null) { + return; + } + const fromContents = this.fs.readFile(resolvedFile); + if (!this.hasRequireCalls(fromContents)) { - // Avoid parsing the source file as there are no require calls. + // Avoid parsing the source file as there are no imports. 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); + const sf = ts.createSourceFile( + resolvedFile, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS); for (const statement of sf.statements) { const declarations = @@ -66,7 +52,7 @@ export class CommonJsDependencyHost implements DependencyHost { for (const declaration of declarations) { if (declaration.initializer && isRequireCall(declaration.initializer)) { const importPath = declaration.initializer.arguments[0].text; - const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file); + const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, resolvedFile); if (resolvedModule) { if (resolvedModule instanceof ResolvedRelativeModule) { const internalDependency = resolvedModule.modulePath; @@ -100,5 +86,5 @@ export class CommonJsDependencyHost implements DependencyHost { * @returns false if there are definitely no require calls * in this file, true otherwise. */ - hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); } + private hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); } } diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts index c63a5ce2f2..b5b2a618d5 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/dependency_host.ts @@ -5,8 +5,8 @@ * 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, PathSegment} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; +import {ModuleResolver} from './module_resolver'; export interface DependencyHost { findDependencies(entryPointPath: AbsoluteFsPath): DependencyInfo; @@ -17,3 +17,41 @@ export interface DependencyInfo { missing: Set; deepImports: Set; } + +export abstract class DependencyHostBase implements DependencyHost { + constructor(protected fs: FileSystem, protected 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. + */ + protected abstract recursivelyFindDependencies( + file: AbsoluteFsPath, dependencies: Set, missing: Set, + deepImports: Set, alreadySeen: Set): void; +} diff --git a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts index 75c463662d..e164328282 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts @@ -6,33 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; -import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; -import {DependencyHost, DependencyInfo} from './dependency_host'; -import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; +import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; +import {resolveFileWithPostfixes} from '../utils'; +import {DependencyHostBase} from './dependency_host'; +import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; /** * Helper functions for computing dependencies. */ -export class EsmDependencyHost 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}; - } - +export class EsmDependencyHost extends DependencyHostBase { /** * Compute the dependencies of the given file. * @@ -43,28 +25,33 @@ export class EsmDependencyHost implements DependencyHost { * @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. + * in a circular dependency loop. */ - private recursivelyFindDependencies( + protected recursivelyFindDependencies( file: AbsoluteFsPath, dependencies: Set, missing: Set, deepImports: Set, alreadySeen: Set): void { - const fromContents = this.fs.readFile(file); - if (!this.hasImportOrReexportStatements(fromContents)) { + const resolvedFile = resolveFileWithPostfixes(this.fs, file, ['', '.js', '/index.js']); + if (resolvedFile === null) { + return; + } + const fromContents = this.fs.readFile(resolvedFile); + + if (!hasImportOrReexportStatements(fromContents)) { + // Avoid parsing the source file as there are no imports. 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); + const sf = ts.createSourceFile( + resolvedFile, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS); sf.statements // filter out statements that are not imports or reexports - .filter(this.isStringImportOrReexport) + .filter(isStringImportOrReexport) // Grab the id of the module that is being imported .map(stmt => stmt.moduleSpecifier.text) // Resolve this module id into an absolute path .forEach(importPath => { - const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file); + const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, resolvedFile); if (resolvedModule) { if (resolvedModule instanceof ResolvedRelativeModule) { const internalDependency = resolvedModule.modulePath; @@ -85,30 +72,31 @@ export class EsmDependencyHost implements DependencyHost { } }); } - - /** - * Check whether the given statement is an import with a string literal module specifier. - * @param stmt the statement node to check. - * @returns true if the statement is an import with a string literal module specifier. - */ - isStringImportOrReexport(stmt: ts.Statement): stmt is ts.ImportDeclaration& - {moduleSpecifier: ts.StringLiteral} { - return ts.isImportDeclaration(stmt) || - ts.isExportDeclaration(stmt) && !!stmt.moduleSpecifier && - ts.isStringLiteral(stmt.moduleSpecifier); - } - - /** - * 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 import or re-export statements - * in this file, true otherwise. - */ - hasImportOrReexportStatements(source: string): boolean { - return /(import|export)\s.+from/.test(source); - } +} + +/** + * 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 import or re-export statements + * in this file, true otherwise. + */ +export function hasImportOrReexportStatements(source: string): boolean { + return /(import|export)\s.+from/.test(source); +} + + +/** + * Check whether the given statement is an import with a string literal module specifier. + * @param stmt the statement node to check. + * @returns true if the statement is an import with a string literal module specifier. + */ +export function isStringImportOrReexport(stmt: ts.Statement): stmt is ts.ImportDeclaration& + {moduleSpecifier: ts.StringLiteral} { + return ts.isImportDeclaration(stmt) || + ts.isExportDeclaration(stmt) && !!stmt.moduleSpecifier && + ts.isStringLiteral(stmt.moduleSpecifier); } diff --git a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts index 549690a092..1208744b98 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts @@ -5,11 +5,8 @@ * 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, dirname, isRoot, join, resolve} from '../../../src/ngtsc/file_system'; -import {PathMappings, isRelativePath} from '../utils'; - - +import {PathMappings, isRelativePath, resolveFileWithPostfixes} from '../utils'; /** * This is a very cut-down implementation of the TypeScript module resolution strategy. @@ -72,8 +69,8 @@ export class ModuleResolver { * If neither of these files exist then the method returns `null`. */ private resolveAsRelativePath(moduleName: string, fromPath: AbsoluteFsPath): ResolvedModule|null { - const resolvedPath = - this.resolvePath(resolve(dirname(fromPath), moduleName), this.relativeExtensions); + const resolvedPath = resolveFileWithPostfixes( + this.fs, resolve(dirname(fromPath), moduleName), this.relativeExtensions); return resolvedPath && new ResolvedRelativeModule(resolvedPath); } @@ -133,20 +130,6 @@ export class ModuleResolver { return null; } - /** - * Attempt to resolve a `path` to a file by appending the provided `postFixes` - * to the `path` and checking if the file exists on disk. - * @returns An absolute path to the first matching existing file, or `null` if none exist. - */ - private resolvePath(path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null { - for (const postFix of postFixes) { - const testPath = absoluteFrom(path + postFix); - if (this.fs.exists(testPath)) { - return testPath; - } - } - return null; - } /** * Can we consider the given path as an entry-point to a package? diff --git a/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts index 7828e3db88..64ca73d25a 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts @@ -6,34 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; -import {AbsoluteFsPath, FileSystem, PathSegment} from '../../../src/ngtsc/file_system'; +import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; import {getImportsOfUmdModule, parseStatementForUmdModule} from '../host/umd_host'; -import {DependencyHost, DependencyInfo} from './dependency_host'; -import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; +import {resolveFileWithPostfixes} from '../utils'; +import {DependencyHostBase} from './dependency_host'; +import {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}; - } - +export class UmdDependencyHost extends DependencyHostBase { /** * Compute the dependencies of the given file. * @@ -44,21 +26,26 @@ export class UmdDependencyHost implements DependencyHost { * @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. + * in a circular dependency loop. */ - private recursivelyFindDependencies( + protected recursivelyFindDependencies( file: AbsoluteFsPath, dependencies: Set, missing: Set, deepImports: Set, alreadySeen: Set): void { - const fromContents = this.fs.readFile(file); + const resolvedFile = resolveFileWithPostfixes(this.fs, file, ['', '.js', '/index.js']); + if (resolvedFile === null) { + return; + } + const fromContents = this.fs.readFile(resolvedFile); + if (!this.hasRequireCalls(fromContents)) { - // Avoid parsing the source file as there are no require calls. + // Avoid parsing the source file as there are no imports. 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); + const sf = ts.createSourceFile( + resolvedFile, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS); + if (sf.statements.length !== 1) { return; } @@ -70,7 +57,7 @@ export class UmdDependencyHost implements DependencyHost { } umdImports.forEach(umdImport => { - const resolvedModule = this.moduleResolver.resolveModuleImport(umdImport.path, file); + const resolvedModule = this.moduleResolver.resolveModuleImport(umdImport.path, resolvedFile); if (resolvedModule) { if (resolvedModule instanceof ResolvedRelativeModule) { const internalDependency = resolvedModule.modulePath; @@ -102,5 +89,5 @@ export class UmdDependencyHost implements DependencyHost { * @returns false if there are definitely no require calls * in this file, true otherwise. */ - hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); } + private hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); } } diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index 2c043d210f..10b6785fc0 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file_system'; import {parseStatementForUmdModule} from '../host/umd_host'; import {Logger} from '../logging/logger'; +import {resolveFileWithPostfixes} from '../utils'; import {NgccConfiguration, NgccEntryPointConfig} from './configuration'; /** @@ -167,8 +168,12 @@ function loadEntryPointPackage( } function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean { + const resolvedPath = resolveFileWithPostfixes(fs, sourceFilePath, ['', '.js', '/index.js']); + if (resolvedPath === null) { + return false; + } const sourceFile = - ts.createSourceFile(sourceFilePath, fs.readFile(sourceFilePath), ts.ScriptTarget.ES5); + ts.createSourceFile(sourceFilePath, fs.readFile(resolvedPath), ts.ScriptTarget.ES5); return sourceFile.statements.length > 0 && parseStatementForUmdModule(sourceFile.statements[0]) !== null; } diff --git a/packages/compiler-cli/ngcc/src/utils.ts b/packages/compiler-cli/ngcc/src/utils.ts index 9cb91a2c09..c3eb08378b 100644 --- a/packages/compiler-cli/ngcc/src/utils.ts +++ b/packages/compiler-cli/ngcc/src/utils.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; +import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../src/ngtsc/file_system'; export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol { return function(symbol: ts.Symbol) { @@ -65,3 +66,19 @@ export type PathMappings = { export function isRelativePath(path: string): boolean { return /^\/|^\.\.?($|\/)/.test(path); } + +/** + * Attempt to resolve a `path` to a file by appending the provided `postFixes` + * to the `path` and checking if the file exists on disk. + * @returns An absolute path to the first matching existing file, or `null` if none exist. + */ +export function resolveFileWithPostfixes( + fs: FileSystem, path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null { + for (const postFix of postFixes) { + const testPath = absoluteFrom(path + postFix); + if (fs.exists(testPath) && fs.stat(testPath).isFile()) { + return testPath; + } + } + return null; +} diff --git a/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts index 6ca396c4cd..d577dd3680 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/commonjs_dependency_host_spec.ts @@ -203,6 +203,16 @@ runInEachFileSystem(() => { expect(missing.size).toBe(0); expect(deepImports.size).toBe(0); }); + + it('should handle entry-point paths with no extension', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/imports/index')); + 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); + }); }); }); 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 916d1411ad..610132fc24 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 @@ -10,7 +10,7 @@ import * as ts from 'typescript'; import {absoluteFrom, getFileSystem, relativeFrom} from '../../../src/ngtsc/file_system'; import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadTestFiles} from '../../../test/helpers'; -import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; +import {EsmDependencyHost, hasImportOrReexportStatements, isStringImportOrReexport} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; runInEachFileSystem(() => { @@ -116,6 +116,16 @@ runInEachFileSystem(() => { expect(missing.size).toBe(0); expect(deepImports.size).toBe(0); }); + + it('should handle entry-point paths with no extension', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/imports/index')); + 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); + }); }); function setupMockFileSystem(): void { @@ -210,24 +220,20 @@ runInEachFileSystem(() => { describe('isStringImportOrReexport', () => { it('should return true if the statement is an import', () => { - expect(host.isStringImportOrReexport(createStatement('import {X} from "some/x";'))) - .toBe(true); - expect(host.isStringImportOrReexport(createStatement('import * as X from "some/x";'))) + expect(isStringImportOrReexport(createStatement('import {X} from "some/x";'))).toBe(true); + expect(isStringImportOrReexport(createStatement('import * as X from "some/x";'))) .toBe(true); }); it('should return true if the statement is a re-export', () => { - expect(host.isStringImportOrReexport(createStatement('export {X} from "some/x";'))) - .toBe(true); - expect(host.isStringImportOrReexport(createStatement('export * from "some/x";'))) - .toBe(true); + expect(isStringImportOrReexport(createStatement('export {X} from "some/x";'))).toBe(true); + expect(isStringImportOrReexport(createStatement('export * from "some/x";'))).toBe(true); }); it('should return false if the statement is not an import or a re-export', () => { - expect(host.isStringImportOrReexport(createStatement('class X {}'))).toBe(false); - expect(host.isStringImportOrReexport(createStatement('export function foo() {}'))) - .toBe(false); - expect(host.isStringImportOrReexport(createStatement('export const X = 10;'))).toBe(false); + expect(isStringImportOrReexport(createStatement('class X {}'))).toBe(false); + expect(isStringImportOrReexport(createStatement('export function foo() {}'))).toBe(false); + expect(isStringImportOrReexport(createStatement('export const X = 10;'))).toBe(false); }); function createStatement(source: string) { @@ -239,28 +245,25 @@ runInEachFileSystem(() => { describe('hasImportOrReexportStatements', () => { it('should return true if there is an import statement', () => { - expect(host.hasImportOrReexportStatements('import {X} from "some/x";')).toBe(true); - expect(host.hasImportOrReexportStatements('import * as X from "some/x";')).toBe(true); - expect(host.hasImportOrReexportStatements( - 'blah blah\n\n import {X} from "some/x";\nblah blah')) + expect(hasImportOrReexportStatements('import {X} from "some/x";')).toBe(true); + expect(hasImportOrReexportStatements('import * as X from "some/x";')).toBe(true); + expect(hasImportOrReexportStatements('blah blah\n\n import {X} from "some/x";\nblah blah')) .toBe(true); - expect(host.hasImportOrReexportStatements('\t\timport {X} from "some/x";')).toBe(true); + expect(hasImportOrReexportStatements('\t\timport {X} from "some/x";')).toBe(true); }); it('should return true if there is a re-export statement', () => { - expect(host.hasImportOrReexportStatements('export {X} from "some/x";')).toBe(true); - expect(host.hasImportOrReexportStatements( - 'blah blah\n\n export {X} from "some/x";\nblah blah')) + expect(hasImportOrReexportStatements('export {X} from "some/x";')).toBe(true); + expect(hasImportOrReexportStatements('blah blah\n\n export {X} from "some/x";\nblah blah')) .toBe(true); - expect(host.hasImportOrReexportStatements('\t\texport {X} from "some/x";')).toBe(true); - expect(host.hasImportOrReexportStatements( + expect(hasImportOrReexportStatements('\t\texport {X} from "some/x";')).toBe(true); + expect(hasImportOrReexportStatements( 'blah blah\n\n export * from "@angular/core;\nblah blah')) .toBe(true); }); it('should return false if there is no import nor re-export statement', () => { - expect(host.hasImportOrReexportStatements('blah blah')).toBe(false); - expect(host.hasImportOrReexportStatements('export function moo() {}')).toBe(false); - expect( - host.hasImportOrReexportStatements('Some text that happens to include the word import')) + expect(hasImportOrReexportStatements('blah blah')).toBe(false); + expect(hasImportOrReexportStatements('export function moo() {}')).toBe(false); + expect(hasImportOrReexportStatements('Some text that happens to include the word import')) .toBe(false); }); }); 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 index 2dd04369e5..b20e659ff6 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/umd_dependency_host_spec.ts @@ -115,6 +115,16 @@ runInEachFileSystem(() => { expect(missing.size).toBe(0); expect(deepImports.size).toBe(0); }); + + it('should handle entry-point paths with no extension', () => { + const {dependencies, missing, deepImports} = + host.findDependencies(_('/external/imports/index')); + 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); + }); }); function setupMockFileSystem(): void { diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts index 984379a11b..968c3acc03 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts @@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem} from '../../../ import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadTestFiles} from '../../../test/helpers'; import {NgccConfiguration} from '../../src/packages/configuration'; -import {SUPPORTED_FORMAT_PROPERTIES, getEntryPointInfo} from '../../src/packages/entry_point'; +import {EntryPoint, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point'; import {MockLogger} from '../helpers/mock_logger'; runInEachFileSystem(() => { @@ -340,6 +340,92 @@ runInEachFileSystem(() => { }); }); + describe('getEntryPointFormat', () => { + let SOME_PACKAGE: AbsoluteFsPath; + let _: typeof absoluteFrom; + let fs: FileSystem; + let entryPoint: EntryPoint; + + beforeEach(() => { + _ = absoluteFrom; + SOME_PACKAGE = _('/project/node_modules/some_package'); + fs = getFileSystem(); + loadTestFiles([{ + name: _('/project/node_modules/some_package/valid_entry_point/package.json'), + contents: createPackageJson('valid_entry_point') + }]); + const config = new NgccConfiguration(fs, _('/project')); + entryPoint = getEntryPointInfo( + fs, config, new MockLogger(), SOME_PACKAGE, + _('/project/node_modules/some_package/valid_entry_point')) !; + }); + + it('should return `esm2015` format for `fesm2015` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'fesm2015')).toBe('esm2015'); }); + + it('should return `esm5` format for `fesm5` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'fesm5')).toBe('esm5'); }); + + it('should return `esm2015` format for `es2015` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'es2015')).toBe('esm2015'); }); + + it('should return `esm2015` format for `esm2015` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'esm2015')).toBe('esm2015'); }); + + it('should return `esm5` format for `esm5` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'esm5')).toBe('esm5'); }); + + it('should return `esm5` format for `module` property', + () => { expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm5'); }); + + it('should return `umd` for `main` if the file contains a UMD wrapper function', () => { + loadTestFiles([{ + name: _( + '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : + typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) : + (global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core)); + }(this, function (exports, core) { 'use strict'; })); + ` + }]); + expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd'); + }); + + it('should return `commonjs` for `main` if the file does not contain a UMD wrapper function', () => { + loadTestFiles([{ + name: _( + '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), + contents: ` + const core = require('@angular/core); + module.exports = {}; + ` + }]); + expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('commonjs'); + }); + + it('should resolve the format path with suitable postfixes', () => { + loadTestFiles([{ + name: _( + '/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : + typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) : + (global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core)); + }(this, function (exports, core) { 'use strict'; })); + ` + }]); + + entryPoint.packageJson.main = './bundles/valid_entry_point/index'; + expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd'); + + entryPoint.packageJson.main = './bundles/valid_entry_point'; + expect(getEntryPointFormat(fs, entryPoint, 'main')).toBe('umd'); + }); + }); + function createPackageJson( packageName: string, {excludes}: {excludes?: string[]} = {}, typingsProp: string = 'typings'): string { @@ -351,7 +437,7 @@ runInEachFileSystem(() => { es2015: `./es2015/${packageName}.js`, fesm5: `./fesm5/${packageName}.js`, esm5: `./esm5/${packageName}.js`, - main: `./bundles/${packageName}.umd.js`, + main: `./bundles/${packageName}/index.js`, module: './index.js', }; if (excludes) {