fix(ivy): move the generation of ɵNonEmptyModule to shim construction (#27483)
ngfactory files have a ɵNonEmptyModule constant included if there are no other exported factory symbols. Previously this extra export was added dynamically in a TS transformer. However, synthetically constructed exports don't get properly downleveled during JS emit, and this generated constant caused issues with downstream tests. Instead, this commit configures the shim to always have this export to begin with, and to filter it out if it's not required. Testing strategy: covered by existing ngtsc_spec tests which verify the presence of the ɵNonEmptyModule symbol. PR Close #27483
This commit is contained in:
parent
d553ec2f36
commit
276bdd1f3e
|
@ -64,6 +64,11 @@ export class FactoryGenerator implements ShimGenerator {
|
||||||
...varLines,
|
...varLines,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add an extra export to ensure this module has at least one. It'll be removed later in the
|
||||||
|
// factory transformer if it ends up not being needed.
|
||||||
|
sourceText += '\nexport const ɵNonEmptyModule = true;';
|
||||||
|
|
||||||
const genFile = ts.createSourceFile(
|
const genFile = ts.createSourceFile(
|
||||||
genFilePath, sourceText, original.languageVersion, true, ts.ScriptKind.TS);
|
genFilePath, sourceText, original.languageVersion, true, ts.ScriptKind.TS);
|
||||||
if (original.moduleName !== undefined) {
|
if (original.moduleName !== undefined) {
|
||||||
|
@ -114,38 +119,67 @@ function transformFactorySourceFile(
|
||||||
|
|
||||||
const clone = ts.getMutableClone(file);
|
const clone = ts.getMutableClone(file);
|
||||||
|
|
||||||
const transformedStatements = file.statements.map(stmt => {
|
// Not every exported factory statement is valid. They were generated before the program was
|
||||||
|
// analyzed, and before ngtsc knew which symbols were actually NgModules. factoryMap contains
|
||||||
|
// that knowledge now, so this transform filters the statement list and removes exported factories
|
||||||
|
// that aren't actually factories.
|
||||||
|
//
|
||||||
|
// This could leave the generated factory file empty. To prevent this (it causes issues with
|
||||||
|
// closure compiler) a 'ɵNonEmptyModule' export was added when the factory shim was created.
|
||||||
|
// Preserve that export if needed, and remove it otherwise.
|
||||||
|
//
|
||||||
|
// Additionally, an import to @angular/core is generated, but the current compilation unit could
|
||||||
|
// actually be @angular/core, in which case such an import is invalid and should be replaced with
|
||||||
|
// the proper path to access Ivy symbols in core.
|
||||||
|
|
||||||
|
// The filtered set of statements.
|
||||||
|
const transformedStatements: ts.Statement[] = [];
|
||||||
|
|
||||||
|
// The statement identified as the ɵNonEmptyModule export.
|
||||||
|
let nonEmptyExport: ts.Statement|null = null;
|
||||||
|
|
||||||
|
// Consider all the statements.
|
||||||
|
for (const stmt of file.statements) {
|
||||||
|
// Look for imports to @angular/core.
|
||||||
if (coreImportsFrom !== null && ts.isImportDeclaration(stmt) &&
|
if (coreImportsFrom !== null && ts.isImportDeclaration(stmt) &&
|
||||||
ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === '@angular/core') {
|
ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === '@angular/core') {
|
||||||
|
// Update the import path to point to the correct file (coreImportsFrom).
|
||||||
const path = relativePathBetween(sourceFilePath, coreImportsFrom.fileName);
|
const path = relativePathBetween(sourceFilePath, coreImportsFrom.fileName);
|
||||||
if (path !== null) {
|
if (path !== null) {
|
||||||
return ts.updateImportDeclaration(
|
transformedStatements.push(ts.updateImportDeclaration(
|
||||||
stmt, stmt.decorators, stmt.modifiers, stmt.importClause, ts.createStringLiteral(path));
|
stmt, stmt.decorators, stmt.modifiers, stmt.importClause,
|
||||||
} else {
|
ts.createStringLiteral(path)));
|
||||||
return ts.createNotEmittedStatement(stmt);
|
|
||||||
}
|
}
|
||||||
} else if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
} else if (ts.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
||||||
const decl = stmt.declarationList.declarations[0];
|
const decl = stmt.declarationList.declarations[0];
|
||||||
|
|
||||||
|
// If this is the ɵNonEmptyModule export, then save it for later.
|
||||||
if (ts.isIdentifier(decl.name)) {
|
if (ts.isIdentifier(decl.name)) {
|
||||||
const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
if (decl.name.text === 'ɵNonEmptyModule') {
|
||||||
if (match === null || !moduleSymbolNames.has(match[1])) {
|
nonEmptyExport = stmt;
|
||||||
// Remove the given factory as it wasn't actually for an NgModule.
|
continue;
|
||||||
return ts.createNotEmittedStatement(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, check if this export is a factory for a known NgModule, and retain it if so.
|
||||||
|
const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
||||||
|
if (match !== null && moduleSymbolNames.has(match[1])) {
|
||||||
|
transformedStatements.push(stmt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Leave the statement alone, as it can't be understood.
|
||||||
|
transformedStatements.push(stmt);
|
||||||
}
|
}
|
||||||
return stmt;
|
|
||||||
} else {
|
} else {
|
||||||
return stmt;
|
// Include non-variable statements (imports, etc).
|
||||||
|
transformedStatements.push(stmt);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
if (!transformedStatements.some(ts.isVariableStatement)) {
|
|
||||||
|
// Check whether the empty module export is still needed.
|
||||||
|
if (!transformedStatements.some(ts.isVariableStatement) && nonEmptyExport !== null) {
|
||||||
// If the resulting file has no factories, include an empty export to
|
// If the resulting file has no factories, include an empty export to
|
||||||
// satisfy closure compiler.
|
// satisfy closure compiler.
|
||||||
transformedStatements.push(ts.createVariableStatement(
|
transformedStatements.push(nonEmptyExport);
|
||||||
[ts.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
||||||
ts.createVariableDeclarationList(
|
|
||||||
[ts.createVariableDeclaration('ɵNonEmptyModule', undefined, ts.createTrue())],
|
|
||||||
ts.NodeFlags.Const)));
|
|
||||||
}
|
}
|
||||||
clone.statements = ts.createNodeArray(transformedStatements);
|
clone.statements = ts.createNodeArray(transformedStatements);
|
||||||
return clone;
|
return clone;
|
||||||
|
|
Loading…
Reference in New Issue