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:
Alex Rickabaugh
2019-02-19 17:36:26 -08:00
committed by Ben Lesh
parent 15c065f9a0
commit c1392ce618
21 changed files with 608 additions and 60 deletions

View File

@ -16,7 +16,7 @@ import {ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry, ScopeDirective, extractDirectiveGuards} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck';
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
@ -326,7 +326,7 @@ export class ComponentDecoratorHandler implements
}
}
resolve(node: ts.ClassDeclaration, analysis: ComponentHandlerData): void {
resolve(node: ts.ClassDeclaration, analysis: ComponentHandlerData): ResolveResult {
const context = node.getSourceFile();
// Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope.
@ -361,6 +361,7 @@ export class ComponentDecoratorHandler implements
this.scopeRegistry.setComponentAsRequiringRemoteScoping(node);
}
}
return {};
}
compile(node: ts.ClassDeclaration, analysis: ComponentHandlerData, pool: ConstantPool):

View File

@ -15,7 +15,7 @@ import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {getSourceFile} from '../../util/src/typescript';
import {generateSetClassMetadataCall} from './metadata';
@ -175,6 +175,17 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
};
}
resolve(node: ts.Declaration, analysis: NgModuleAnalysis): ResolveResult {
const scope = this.scopeRegistry.getScopeOfModule(node);
if (scope === null || scope.reexports === null) {
return {};
} else {
return {
reexports: scope.reexports,
};
}
}
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
const ngModuleDef = compileNgModule(analysis.ngModuleDef);

View File

@ -48,8 +48,9 @@ describe('ComponentDecoratorHandler', () => {
const moduleResolver = new ModuleResolver(program, options, host);
const importGraph = new ImportGraph(moduleResolver);
const cycleAnalyzer = new CycleAnalyzer(importGraph);
const scopeRegistry =
new LocalModuleScopeRegistry(new MetadataDtsModuleScopeResolver(checker, reflectionHost));
const scopeRegistry = new LocalModuleScopeRegistry(
new MetadataDtsModuleScopeResolver(checker, reflectionHost, null), new ReferenceEmitter([]),
null);
const refEmitter = new ReferenceEmitter([]);
const handler = new ComponentDecoratorHandler(

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript';
import {ReferenceEmitter} from '../../imports';
import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
@ -39,8 +40,9 @@ describe('DirectiveDecoratorHandler', () => {
const checker = program.getTypeChecker();
const reflectionHost = new TestReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
const scopeRegistry =
new LocalModuleScopeRegistry(new MetadataDtsModuleScopeResolver(checker, reflectionHost));
const scopeRegistry = new LocalModuleScopeRegistry(
new MetadataDtsModuleScopeResolver(checker, reflectionHost, null), new ReferenceEmitter([]),
null);
const handler = new DirectiveDecoratorHandler(reflectionHost, evaluator, scopeRegistry, false);
const analyzeDirective = (dirName: string) => {