fix(ivy): ngcc - resolve `main` property paths correctly (#31509)
There are two places in the ngcc processing where it needs to load the content of a file given by a general path: * when determining the format of an entry-point. To do this ngcc uses the value of the relevant property in package.json. But in the case of `main` it must parse the contents of the entry-point file to decide whether the format is UMD or CommonJS. * when parsing the source files for dependencies to determine the order in which compilation must occur. The relative imports in each file are parsed and followed recursively, looking for external imports. Previously, we naively assumed that the path would match the file name exactly. But actually we must consider the standard module resolution conventions. E.g. the extension (.js) may be missing, or the path may refer to a directory containing an index.js file. This commit fixes both places. This commit now requires the `DependencyHost` instances to check the existence of more files than before (at worst all the different possible post-fixes). This should not create a significant performance reduction for ngcc. Since the results of the checks will be cached, and similar work is done inside the TS compiler, so what we lose in doing it here, is saved later in the processing. The main performance loss would be where there are lots of files that need to be parsed for dependencies that do not end up being processed by TS. But compared to the main ngcc processing this dependency parsing is a small proportion of the work done and so should not impact much on the overall performance of ngcc. // FW-1444 PR Close #31509
This commit is contained in:
parent
63e458dd3a
commit
fac20bd8d1
|
@ -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<AbsoluteFsPath>();
|
||||
const missing = new Set<AbsoluteFsPath|PathSegment>();
|
||||
const deepImports = new Set<AbsoluteFsPath>();
|
||||
const alreadySeen = new Set<AbsoluteFsPath>();
|
||||
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<AbsoluteFsPath>, missing: Set<string>,
|
||||
deepImports: Set<AbsoluteFsPath>, alreadySeen: Set<AbsoluteFsPath>): 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); }
|
||||
}
|
||||
|
|
|
@ -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<AbsoluteFsPath|PathSegment>;
|
||||
deepImports: Set<AbsoluteFsPath>;
|
||||
}
|
||||
|
||||
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<AbsoluteFsPath>();
|
||||
const missing = new Set<AbsoluteFsPath|PathSegment>();
|
||||
const deepImports = new Set<AbsoluteFsPath>();
|
||||
const alreadySeen = new Set<AbsoluteFsPath>();
|
||||
|
||||
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<AbsoluteFsPath>, missing: Set<string>,
|
||||
deepImports: Set<AbsoluteFsPath>, alreadySeen: Set<AbsoluteFsPath>): void;
|
||||
}
|
||||
|
|
|
@ -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<AbsoluteFsPath>();
|
||||
const missing = new Set<AbsoluteFsPath|PathSegment>();
|
||||
const deepImports = new Set<AbsoluteFsPath>();
|
||||
const alreadySeen = new Set<AbsoluteFsPath>();
|
||||
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<AbsoluteFsPath>, missing: Set<string>,
|
||||
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): 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,17 +72,6 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +84,19 @@ export class EsmDependencyHost implements DependencyHost {
|
|||
* @returns false if there are definitely no import or re-export statements
|
||||
* in this file, true otherwise.
|
||||
*/
|
||||
hasImportOrReexportStatements(source: string): boolean {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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<AbsoluteFsPath>();
|
||||
const missing = new Set<AbsoluteFsPath|PathSegment>();
|
||||
const deepImports = new Set<AbsoluteFsPath>();
|
||||
const alreadySeen = new Set<AbsoluteFsPath>();
|
||||
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<AbsoluteFsPath>, missing: Set<string>,
|
||||
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): 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); }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue