fix(ngcc): handle CommonJS re-exports by reference (#34254)

In TS we can re-export imports using statements of the form:

```
export * from 'some-import';
```

This can be downleveled in CommonJS to either:

```
__export(require('some-import'));
```

or

```
var someImport = require('some-import');
__export(someImport);
```

Previously we only supported the first downleveled version.
This commit adds support for the second version.

PR Close #34254
This commit is contained in:
Pete Bacon Darwin 2019-12-18 14:03:04 +00:00 committed by Kara Erickson
parent 0b837e2f0d
commit 47666f548c
2 changed files with 37 additions and 23 deletions

View File

@ -137,24 +137,35 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
private extractCommonJsReexports(statement: ReexportStatement, containingFile: ts.SourceFile): private extractCommonJsReexports(statement: ReexportStatement, containingFile: ts.SourceFile):
CommonJsExportDeclaration[] { CommonJsExportDeclaration[] {
const reexports: CommonJsExportDeclaration[] = []; const reexportArg = statement.expression.arguments[0];
const requireCall = statement.expression.arguments[0];
const requireCall = isRequireCall(reexportArg) ?
reexportArg :
ts.isIdentifier(reexportArg) ? this.findRequireCallReference(reexportArg) : null;
if (requireCall === null) {
return [];
}
const importPath = requireCall.arguments[0].text; const importPath = requireCall.arguments[0].text;
const importedFile = this.resolveModuleName(importPath, containingFile); const importedFile = this.resolveModuleName(importPath, containingFile);
if (importedFile !== undefined) { if (importedFile === undefined) {
return [];
}
const viaModule = stripExtension(importedFile.fileName); const viaModule = stripExtension(importedFile.fileName);
const importedExports = this.getExportsOfModule(importedFile); const importedExports = this.getExportsOfModule(importedFile);
if (importedExports !== null) { if (importedExports === null) {
return [];
}
const reexports: CommonJsExportDeclaration[] = [];
importedExports.forEach((decl, name) => { importedExports.forEach((decl, name) => {
if (decl.node !== null) { if (decl.node !== null) {
reexports.push({name, declaration: {node: decl.node, viaModule}}); reexports.push({name, declaration: {node: decl.node, viaModule}});
} else { } else {
reexports.push( reexports.push({name, declaration: {node: null, expression: decl.expression, viaModule}});
{name, declaration: {node: null, expression: decl.expression, viaModule}});
} }
}); });
}
}
return reexports; return reexports;
} }
@ -162,11 +173,14 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
// Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`? // Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`?
// If so capture the symbol of the namespace, e.g. `core`. // If so capture the symbol of the namespace, e.g. `core`.
const nsIdentifier = findNamespaceOfIdentifier(id); const nsIdentifier = findNamespaceOfIdentifier(id);
const nsSymbol = nsIdentifier && this.checker.getSymbolAtLocation(nsIdentifier) || null; return nsIdentifier && this.findRequireCallReference(nsIdentifier);
const nsDeclaration = nsSymbol && nsSymbol.valueDeclaration; }
private findRequireCallReference(id: ts.Identifier): RequireCall|null {
const symbol = id && this.checker.getSymbolAtLocation(id) || null;
const declaration = symbol && symbol.valueDeclaration;
const initializer = const initializer =
nsDeclaration && ts.isVariableDeclaration(nsDeclaration) && nsDeclaration.initializer || declaration && ts.isVariableDeclaration(declaration) && declaration.initializer || null;
null;
return initializer && isRequireCall(initializer) ? initializer : null; return initializer && isRequireCall(initializer) ? initializer : null;
} }
@ -237,13 +251,12 @@ function findNamespaceOfIdentifier(id: ts.Identifier): ts.Identifier|null {
null; null;
} }
type ReexportStatement = ts.ExpressionStatement & {expression: {arguments: [RequireCall]}}; type ReexportStatement = ts.ExpressionStatement & {expression: ts.CallExpression};
function isReexportStatement(statement: ts.Statement): statement is ReexportStatement { function isReexportStatement(statement: ts.Statement): statement is ReexportStatement {
return ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && return ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) &&
ts.isIdentifier(statement.expression.expression) && ts.isIdentifier(statement.expression.expression) &&
statement.expression.expression.text === '__export' && statement.expression.expression.text === '__export' &&
statement.expression.arguments.length === 1 && statement.expression.arguments.length === 1;
isRequireCall(statement.expression.arguments[0]);
} }
function stripExtension(fileName: string): string { function stripExtension(fileName: string): string {

View File

@ -557,7 +557,8 @@ exports.xtra2 = xtra2;
function __export(m) { function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
} }
__export(require("./b_module")); var b_module = require("./b_module");
__export(b_module);
__export(require("./xtra_module")); __export(require("./xtra_module"));
` `
}, },