fix(ivy): in ngcc, handle inline exports in commonjs code (#32129)
One of the compiler's tasks is to enumerate the exports of a given ES module. This can happen for example to resolve `foo.bar` where `foo` is a namespace import: ```typescript import * as foo from './foo'; @NgModule({ directives: [foo.DIRECTIVES], }) ``` In this case, the compiler must enumerate the exports of `foo.ts` in order to evaluate the expression `foo.DIRECTIVES`. When this operation occurs under ngcc, it must deal with the different module formats and types of exports that occur. In commonjs code, a problem arises when certain exports are downleveled. ```typescript export const DIRECTIVES = [ FooDir, BarDir, ]; ``` can be downleveled to: ```javascript exports.DIRECTIVES = [ FooDir, BarDir, ``` Previously, ngtsc and ngcc expected that any export would have an associated `ts.Declaration` node. `export class`, `export function`, etc. all retain `ts.Declaration`s even when downleveled. But the `export const` construct above does not. Therefore, ngcc would not detect `DIRECTIVES` as an export of `foo.ts`, and the evaluation of `foo.DIRECTIVES` would therefore fail. To solve this problem, the core concept of an exported `Declaration` according to the `ReflectionHost` API is split into a `ConcreteDeclaration` which has a `ts.Declaration`, and an `InlineDeclaration` which instead has a `ts.Expression`. Differentiating between these allows ngcc to return an `InlineDeclaration` for `DIRECTIVES` and correctly keep track of this export. PR Close #32129
This commit is contained in:

committed by
Andrew Kushnir

parent
69ce1c2d41
commit
02bab8cf90
@ -92,9 +92,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
for (const statement of this.getModuleStatements(sourceFile)) {
|
||||
if (isCommonJsExportStatement(statement)) {
|
||||
const exportDeclaration = this.extractCommonJsExportDeclaration(statement);
|
||||
if (exportDeclaration !== null) {
|
||||
moduleMap.set(exportDeclaration.name, exportDeclaration.declaration);
|
||||
}
|
||||
moduleMap.set(exportDeclaration.name, exportDeclaration.declaration);
|
||||
} else if (isReexportStatement(statement)) {
|
||||
const reexports = this.extractCommonJsReexports(statement, sourceFile);
|
||||
for (const reexport of reexports) {
|
||||
@ -106,14 +104,22 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
}
|
||||
|
||||
private extractCommonJsExportDeclaration(statement: CommonJsExportStatement):
|
||||
CommonJsExportDeclaration|null {
|
||||
CommonJsExportDeclaration {
|
||||
const exportExpression = statement.expression.right;
|
||||
const declaration = this.getDeclarationOfExpression(exportExpression);
|
||||
if (declaration === null) {
|
||||
return null;
|
||||
}
|
||||
const name = statement.expression.left.name.text;
|
||||
return {name, declaration};
|
||||
if (declaration !== null) {
|
||||
return {name, declaration};
|
||||
} else {
|
||||
return {
|
||||
name,
|
||||
declaration: {
|
||||
node: null,
|
||||
expression: exportExpression,
|
||||
viaModule: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private extractCommonJsReexports(statement: ReexportStatement, containingFile: ts.SourceFile):
|
||||
@ -126,8 +132,14 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
const viaModule = stripExtension(importedFile.fileName);
|
||||
const importedExports = this.getExportsOfModule(importedFile);
|
||||
if (importedExports !== null) {
|
||||
importedExports.forEach(
|
||||
(decl, name) => reexports.push({name, declaration: {node: decl.node, viaModule}}));
|
||||
importedExports.forEach((decl, name) => {
|
||||
if (decl.node !== null) {
|
||||
reexports.push({name, declaration: {node: decl.node, viaModule}});
|
||||
} else {
|
||||
reexports.push(
|
||||
{name, declaration: {node: null, expression: decl.expression, viaModule}});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return reexports;
|
||||
|
Reference in New Issue
Block a user