feat(ivy): produce and consume ES2015 re-exports for NgModule re-exports (#28852)
In certain configurations (such as the g3 repository) which have lots of small compilation units as well as strict dependency checking on generated code, ngtsc's default strategy of directly importing directives/pipes into components will not work. To handle these cases, an additional mode is introduced, and is enabled when using the FileToModuleHost provided by such compilation environments. In this mode, when ngtsc encounters an NgModule which re-exports another from a different file, it will re-export all the directives it contains at the ES2015 level. The exports will have a predictable name based on the FileToModuleHost. For example, if the host says that a directive Foo is from the 'root/external/foo' module, ngtsc will add: ``` export {Foo as ɵng$root$external$foo$$Foo} from 'root/external/foo'; ``` Consumers of the re-exported directive will then import it via this path instead of directly from root/external/foo, preserving strict dependency semantics. PR Close #28852
This commit is contained in:

committed by
Ben Lesh

parent
15c065f9a0
commit
c1392ce618
36
packages/compiler-cli/src/ngtsc/transform/src/alias.ts
Normal file
36
packages/compiler-cli/src/ngtsc/transform/src/alias.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export function aliasTransformFactory(exportStatements: Map<string, Map<string, [string, string]>>):
|
||||
ts.TransformerFactory<ts.Bundle|ts.SourceFile> {
|
||||
return (context: ts.TransformationContext) => {
|
||||
return (file: ts.SourceFile | ts.Bundle) => {
|
||||
if (ts.isBundle(file) || !exportStatements.has(file.fileName)) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const statements = [...file.statements];
|
||||
exportStatements.get(file.fileName) !.forEach(([moduleName, symbolName], aliasName) => {
|
||||
const stmt = ts.createExportDeclaration(
|
||||
/* decorators */ undefined,
|
||||
/* modifiers */ undefined,
|
||||
/* exportClause */ ts.createNamedExports([ts.createExportSpecifier(
|
||||
/* propertyName */ symbolName,
|
||||
/* name */ aliasName)]),
|
||||
/* moduleSpecifier */ ts.createStringLiteral(moduleName));
|
||||
statements.push(stmt);
|
||||
});
|
||||
|
||||
file = ts.getMutableClone(file);
|
||||
file.statements = ts.createNodeArray(statements);
|
||||
return file;
|
||||
};
|
||||
};
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
import {ConstantPool, Expression, Statement, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reexport} from '../../imports';
|
||||
import {Decorator} from '../../reflection';
|
||||
import {TypeCheckContext} from '../../typecheck';
|
||||
|
||||
@ -82,7 +83,7 @@ export interface DecoratorHandler<A, M> {
|
||||
* `DecoratorHandler` a chance to leverage information from the whole compilation unit to enhance
|
||||
* the `analysis` before the emit phase.
|
||||
*/
|
||||
resolve?(node: ts.Declaration, analysis: A): void;
|
||||
resolve?(node: ts.Declaration, analysis: A): ResolveResult;
|
||||
|
||||
typeCheck?(ctx: TypeCheckContext, node: ts.Declaration, metadata: A): void;
|
||||
|
||||
@ -121,3 +122,5 @@ export interface CompileResult {
|
||||
statements: Statement[];
|
||||
type: Type;
|
||||
}
|
||||
|
||||
export interface ResolveResult { reexports?: Reexport[]; }
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import {ConstantPool, ExternalExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
@ -58,6 +58,8 @@ export class IvyCompilation {
|
||||
* Tracks the `DtsFileTransformer`s for each TS file that needs .d.ts transformations.
|
||||
*/
|
||||
private dtsMap = new Map<string, DtsFileTransformer>();
|
||||
|
||||
private reexportMap = new Map<string, Map<string, [string, string]>>();
|
||||
private _diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
|
||||
@ -76,6 +78,8 @@ export class IvyCompilation {
|
||||
private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
|
||||
|
||||
|
||||
get exportStatements(): Map<string, Map<string, [string, string]>> { return this.reexportMap; }
|
||||
|
||||
analyzeSync(sf: ts.SourceFile): void { return this.analyze(sf, false); }
|
||||
|
||||
analyzeAsync(sf: ts.SourceFile): Promise<void>|undefined { return this.analyze(sf, true); }
|
||||
@ -243,7 +247,17 @@ export class IvyCompilation {
|
||||
for (const match of ivyClass.matchedHandlers) {
|
||||
if (match.handler.resolve !== undefined && match.analyzed !== null &&
|
||||
match.analyzed.analysis !== undefined) {
|
||||
match.handler.resolve(node, match.analyzed.analysis);
|
||||
const res = match.handler.resolve(node, match.analyzed.analysis);
|
||||
if (res.reexports !== undefined) {
|
||||
const fileName = node.getSourceFile().fileName;
|
||||
if (!this.reexportMap.has(fileName)) {
|
||||
this.reexportMap.set(fileName, new Map<string, [string, string]>());
|
||||
}
|
||||
const fileReexports = this.reexportMap.get(fileName) !;
|
||||
for (const reexport of res.reexports) {
|
||||
fileReexports.set(reexport.asAlias, [reexport.fromModule, reexport.symbolName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user