fix(ngcc): correctly detect dependencies in CommonJS (#34528)
Previously, `CommonJsDependencyHost.collectDependencies()` would only find dependencies via imports of the form `var foo = require('...');` or `var foo = require('...'), bar = require('...');` However, CommonJS files can have imports in many different forms. By failing to recognize other forms of imports, the associated dependencies were missed, which in turn resulted in entry-points being compiled out-of-order and failing due to that. While we cannot easily capture all different types of imports, this commit enhances `CommonJsDependencyHost` to recognize the following common forms of imports: - Imports in property assignments. E.g.: `exports.foo = require('...');` or `module.exports = {foo: require('...')};` - Imports for side-effects only. E.g.: `require('...');` - Star re-exports (with both emitted and imported heleprs). E.g.: `__export(require('...'));` or `tslib_1.__exportStar(require('...'), exports);` PR Close #34528
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {isRequireCall} from '../host/commonjs_umd_utils';
|
||||
import {RequireCall, isReexportStatement, isRequireCall} from '../host/commonjs_umd_utils';
|
||||
import {DependencyHostBase} from './dependency_host';
|
||||
import {ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
|
||||
|
||||
@ -40,33 +40,72 @@ export class CommonJsDependencyHost extends DependencyHostBase {
|
||||
// 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 requireCalls: RequireCall[] = [];
|
||||
|
||||
for (const statement of sf.statements) {
|
||||
const declarations =
|
||||
ts.isVariableStatement(statement) ? statement.declarationList.declarations : [];
|
||||
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);
|
||||
if (resolvedModule) {
|
||||
if (resolvedModule instanceof ResolvedRelativeModule) {
|
||||
const internalDependency = resolvedModule.modulePath;
|
||||
if (!alreadySeen.has(internalDependency)) {
|
||||
alreadySeen.add(internalDependency);
|
||||
this.recursivelyCollectDependencies(
|
||||
internalDependency, dependencies, missing, deepImports, alreadySeen);
|
||||
}
|
||||
} else {
|
||||
if (resolvedModule instanceof ResolvedDeepImport) {
|
||||
deepImports.add(resolvedModule.importPath);
|
||||
} else {
|
||||
dependencies.add(resolvedModule.entryPointPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
missing.add(importPath);
|
||||
for (const stmt of sf.statements) {
|
||||
if (ts.isVariableStatement(stmt)) {
|
||||
// Regular import(s):
|
||||
// `var foo = require('...')` or `var foo = require('...'), bar = require('...')`
|
||||
const declarations = stmt.declarationList.declarations;
|
||||
for (const declaration of declarations) {
|
||||
if ((declaration.initializer !== undefined) && isRequireCall(declaration.initializer)) {
|
||||
requireCalls.push(declaration.initializer);
|
||||
}
|
||||
}
|
||||
} else if (ts.isExpressionStatement(stmt)) {
|
||||
if (isRequireCall(stmt.expression)) {
|
||||
// Import for the side-effects only:
|
||||
// `require('...')`
|
||||
requireCalls.push(stmt.expression);
|
||||
} else if (isReexportStatement(stmt)) {
|
||||
// Re-export in one of the following formats:
|
||||
// - `__export(require('...'))`
|
||||
// - `__export(<identifier>)`
|
||||
// - `tslib_1.__exportStar(require('...'), exports)`
|
||||
// - `tslib_1.__exportStar(<identifier>, exports)`
|
||||
const firstExportArg = stmt.expression.arguments[0];
|
||||
|
||||
if (isRequireCall(firstExportArg)) {
|
||||
// Re-export with `require()` call:
|
||||
// `__export(require('...'))` or `tslib_1.__exportStar(require('...'), exports)`
|
||||
requireCalls.push(firstExportArg);
|
||||
}
|
||||
} else if (
|
||||
ts.isBinaryExpression(stmt.expression) &&
|
||||
(stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken)) {
|
||||
if (isRequireCall(stmt.expression.right)) {
|
||||
// Import with assignment. E.g.:
|
||||
// `exports.foo = require('...')`
|
||||
requireCalls.push(stmt.expression.right);
|
||||
} else if (ts.isObjectLiteralExpression(stmt.expression.right)) {
|
||||
// Import in object literal. E.g.:
|
||||
// `module.exports = {foo: require('...')}`
|
||||
stmt.expression.right.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop) && isRequireCall(prop.initializer)) {
|
||||
requireCalls.push(prop.initializer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const importPaths = new Set(requireCalls.map(call => call.arguments[0].text));
|
||||
for (const importPath of importPaths) {
|
||||
const resolvedModule = this.moduleResolver.resolveModuleImport(importPath, file);
|
||||
if (resolvedModule === null) {
|
||||
missing.add(importPath);
|
||||
} else if (resolvedModule instanceof ResolvedRelativeModule) {
|
||||
const internalDependency = resolvedModule.modulePath;
|
||||
if (!alreadySeen.has(internalDependency)) {
|
||||
alreadySeen.add(internalDependency);
|
||||
this.recursivelyCollectDependencies(
|
||||
internalDependency, dependencies, missing, deepImports, alreadySeen);
|
||||
}
|
||||
} else if (resolvedModule instanceof ResolvedDeepImport) {
|
||||
deepImports.add(resolvedModule.importPath);
|
||||
} else {
|
||||
dependencies.add(resolvedModule.entryPointPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user