fix(ivy): prevent ngcc from referencing missing ɵsetClassMetadata (#27055)

When ngtsc compiles @angular/core, it rewrites core imports to the
r3_symbols.ts file that exposes all internal symbols under their
external name. When creating the FESM bundle, the r3_symbols.ts file
causes the external symbol names to be rewritten to their internal name.

Under ngcc compilations of FESM bundles, the indirection of
r3_symbols.ts is no longer in place such that the external names are
retained in the bundle. Previously, the external name `ɵdefineNgModule`
was explicitly declared internally to resolve this issue, but the
recently added `setClassMetadata` was not declared as such, causing
runtime errors.

Instead of relying on the r3_symbols.ts file to perform the rewrite of
the external modules to their internal variants, the translation is
moved into the `ImportManager` during the compilation itself. This
avoids the need for providing the external name manually.

PR Close #27055
This commit is contained in:
JoostK
2018-11-11 19:16:04 +01:00
committed by Miško Hevery
parent 8ce59a583b
commit c8c8648abf
10 changed files with 121 additions and 61 deletions

View File

@ -17,7 +17,7 @@ import * as ts from 'typescript';
import {ClassMemberKind, ReflectionHost} from '../../host';
const TS_DTS_JS_EXTENSION = /(\.d)?\.ts|\.js$/;
const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
/**
* Represents a value which cannot be determined statically.

View File

@ -38,16 +38,16 @@ const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
]);
const CORE_SUPPORTED_SYMBOLS = new Set<string>([
'defineInjectable',
'defineInjector',
'ɵdefineNgModule',
'inject',
'ɵsetClassMetadata',
'ɵInjectableDef',
'ɵInjectorDef',
'ɵNgModuleDefWithMeta',
'ɵNgModuleFactory',
const CORE_SUPPORTED_SYMBOLS = new Map<string, string>([
['defineInjectable', 'defineInjectable'],
['defineInjector', 'defineInjector'],
['ɵdefineNgModule', 'defineNgModule'],
['inject', 'inject'],
['ɵsetClassMetadata', 'setClassMetadata'],
['ɵInjectableDef', 'InjectableDef'],
['ɵInjectorDef', 'InjectorDef'],
['ɵNgModuleDefWithMeta', 'NgModuleDefWithMeta'],
['ɵNgModuleFactory', 'NgModuleFactory'],
]);
export class ImportManager {
@ -56,14 +56,28 @@ export class ImportManager {
constructor(protected isCore: boolean, private prefix = 'i') {}
generateNamedImport(moduleName: string, symbol: string): string|null {
generateNamedImport(moduleName: string, symbol: string):
{moduleImport: string | null, symbol: string} {
if (!this.moduleToIndex.has(moduleName)) {
this.moduleToIndex.set(moduleName, `${this.prefix}${this.nextIndex++}`);
}
if (this.isCore && moduleName === '@angular/core' && !CORE_SUPPORTED_SYMBOLS.has(symbol)) {
throw new Error(`Importing unexpected symbol ${symbol} while compiling core`);
return {
moduleImport: this.moduleToIndex.get(moduleName) !,
symbol: this.rewriteSymbol(moduleName, symbol)
};
}
protected rewriteSymbol(moduleName: string, symbol: string): string {
if (this.isCore && moduleName === '@angular/core') {
if (!CORE_SUPPORTED_SYMBOLS.has(symbol)) {
throw new Error(`Importing unexpected symbol ${symbol} while compiling core`);
}
symbol = CORE_SUPPORTED_SYMBOLS.get(symbol) !;
}
return this.moduleToIndex.get(moduleName) !;
return symbol;
}
getAllImports(contextPath: string, rewriteCoreImportsTo: ts.SourceFile|null):
@ -216,12 +230,13 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
if (ast.value.moduleName === null || ast.value.name === null) {
throw new Error(`Import unknown module or symbol ${ast.value}`);
}
const importIdentifier = this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
if (importIdentifier === null) {
return ts.createIdentifier(ast.value.name);
const {moduleImport, symbol} =
this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
if (moduleImport === null) {
return ts.createIdentifier(symbol);
} else {
return ts.createPropertyAccess(
ts.createIdentifier(importIdentifier), ts.createIdentifier(ast.value.name));
ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
}
}
@ -382,8 +397,9 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
if (ast.value.moduleName === null || ast.value.name === null) {
throw new Error(`Import unknown module or symbol`);
}
const moduleSymbol = this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
const base = `${moduleSymbol}.${ast.value.name}`;
const {moduleImport, symbol} =
this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
const base = moduleImport ? `${moduleImport}.${symbol}` : symbol;
if (ast.typeParams !== null) {
const generics = ast.typeParams.map(type => type.visitType(this, context)).join(', ');
return `${base}<${generics}>`;

View File

@ -10,10 +10,10 @@
import * as path from 'path';
const TS_DTS_EXTENSION = /(\.d)?\.ts$/;
const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
export function relativePathBetween(from: string, to: string): string|null {
let relative = path.posix.relative(path.dirname(from), to).replace(TS_DTS_EXTENSION, '');
let relative = path.posix.relative(path.dirname(from), to).replace(TS_DTS_JS_EXTENSION, '');
if (relative === '') {
return null;
@ -25,4 +25,4 @@ export function relativePathBetween(from: string, to: string): string|null {
}
return relative;
}
}