feat(ngcc): enable private NgModule re-exports in ngcc on request (#33177)
This commit adapts the private NgModule re-export system (using aliasing) to ngcc. Not all ngcc compilations are compatible with these re-exports, as they assume a 1:1 correspondence between .js and .d.ts files. The primary concern here is supporting them for commonjs-only packages. PR Close #33177
This commit is contained in:
parent
c4733c15c0
commit
e030375d9a
|
@ -11,9 +11,10 @@ import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHa
|
||||||
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
|
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
|
||||||
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||||
import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
|
import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
|
||||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
|
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
|
||||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||||
|
import {ClassDeclaration} from '../../../src/ngtsc/reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
||||||
import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
|
import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
|
||||||
import {NgccClassSymbol, NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccClassSymbol, NgccReflectionHost} from '../host/ngcc_host';
|
||||||
|
@ -48,6 +49,11 @@ export class DecorationAnalyzer {
|
||||||
private rootDirs = this.bundle.rootDirs;
|
private rootDirs = this.bundle.rootDirs;
|
||||||
private packagePath = this.bundle.entryPoint.package;
|
private packagePath = this.bundle.entryPoint.package;
|
||||||
private isCore = this.bundle.isCore;
|
private isCore = this.bundle.isCore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of NgModule declarations to the re-exports for that NgModule.
|
||||||
|
*/
|
||||||
|
private reexportMap = new Map<ts.Declaration, Map<string, [string, string]>>();
|
||||||
resourceManager = new NgccResourceLoader(this.fs);
|
resourceManager = new NgccResourceLoader(this.fs);
|
||||||
metaRegistry = new LocalMetadataRegistry();
|
metaRegistry = new LocalMetadataRegistry();
|
||||||
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
|
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
|
||||||
|
@ -61,10 +67,12 @@ export class DecorationAnalyzer {
|
||||||
// based on whether a bestGuessOwningModule is present in the Reference.
|
// based on whether a bestGuessOwningModule is present in the Reference.
|
||||||
new LogicalProjectStrategy(this.reflectionHost, new LogicalFileSystem(this.rootDirs)),
|
new LogicalProjectStrategy(this.reflectionHost, new LogicalFileSystem(this.rootDirs)),
|
||||||
]);
|
]);
|
||||||
|
aliasingHost = this.bundle.entryPoint.generateDeepReexports?
|
||||||
|
new PrivateExportAliasingHost(this.reflectionHost): null;
|
||||||
dtsModuleScopeResolver =
|
dtsModuleScopeResolver =
|
||||||
new MetadataDtsModuleScopeResolver(this.dtsMetaReader, /* aliasGenerator */ null);
|
new MetadataDtsModuleScopeResolver(this.dtsMetaReader, this.aliasingHost);
|
||||||
scopeRegistry = new LocalModuleScopeRegistry(
|
scopeRegistry = new LocalModuleScopeRegistry(
|
||||||
this.metaRegistry, this.dtsModuleScopeResolver, this.refEmitter, /* aliasGenerator */ null);
|
this.metaRegistry, this.dtsModuleScopeResolver, this.refEmitter, this.aliasingHost);
|
||||||
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
|
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
|
||||||
evaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker);
|
evaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker);
|
||||||
moduleResolver = new ModuleResolver(this.program, this.options, this.host);
|
moduleResolver = new ModuleResolver(this.program, this.options, this.host);
|
||||||
|
@ -161,7 +169,9 @@ export class DecorationAnalyzer {
|
||||||
const constantPool = new ConstantPool();
|
const constantPool = new ConstantPool();
|
||||||
const compiledClasses: CompiledClass[] = analyzedFile.analyzedClasses.map(analyzedClass => {
|
const compiledClasses: CompiledClass[] = analyzedFile.analyzedClasses.map(analyzedClass => {
|
||||||
const compilation = this.compileClass(analyzedClass, constantPool);
|
const compilation = this.compileClass(analyzedClass, constantPool);
|
||||||
return {...analyzedClass, compilation};
|
const declaration = analyzedClass.declaration;
|
||||||
|
const reexports: Reexport[] = this.getReexportsForClass(declaration);
|
||||||
|
return {...analyzedClass, compilation, reexports};
|
||||||
});
|
});
|
||||||
return {constantPool, sourceFile: analyzedFile.sourceFile, compiledClasses};
|
return {constantPool, sourceFile: analyzedFile.sourceFile, compiledClasses};
|
||||||
}
|
}
|
||||||
|
@ -187,9 +197,30 @@ export class DecorationAnalyzer {
|
||||||
analyzedFile.analyzedClasses.forEach(({declaration, matches}) => {
|
analyzedFile.analyzedClasses.forEach(({declaration, matches}) => {
|
||||||
matches.forEach(({handler, analysis}) => {
|
matches.forEach(({handler, analysis}) => {
|
||||||
if ((handler.resolve !== undefined) && analysis) {
|
if ((handler.resolve !== undefined) && analysis) {
|
||||||
handler.resolve(declaration, analysis);
|
const res = handler.resolve(declaration, analysis);
|
||||||
|
if (res.reexports !== undefined) {
|
||||||
|
this.addReexports(res.reexports, declaration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getReexportsForClass(declaration: ClassDeclaration<ts.Declaration>) {
|
||||||
|
const reexports: Reexport[] = [];
|
||||||
|
if (this.reexportMap.has(declaration)) {
|
||||||
|
this.reexportMap.get(declaration) !.forEach(([fromModule, symbolName], asAlias) => {
|
||||||
|
reexports.push({asAlias, fromModule, symbolName});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return reexports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addReexports(reexports: Reexport[], declaration: ClassDeclaration<ts.Declaration>) {
|
||||||
|
const map = new Map<string, [string, string]>();
|
||||||
|
for (const reexport of reexports) {
|
||||||
|
map.set(reexport.asAlias, [reexport.fromModule, reexport.symbolName]);
|
||||||
|
}
|
||||||
|
this.reexportMap.set(declaration, map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {ConstantPool} from '@angular/compiler';
|
import {ConstantPool} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection';
|
||||||
import {CompileResult, DecoratorHandler} from '../../../src/ngtsc/transform';
|
import {CompileResult, DecoratorHandler} from '../../../src/ngtsc/transform';
|
||||||
|
|
||||||
|
@ -23,7 +24,14 @@ export interface AnalyzedClass {
|
||||||
matches: {handler: DecoratorHandler<any, any>; analysis: any;}[];
|
matches: {handler: DecoratorHandler<any, any>; analysis: any;}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompiledClass extends AnalyzedClass { compilation: CompileResult[]; }
|
export interface CompiledClass extends AnalyzedClass {
|
||||||
|
compilation: CompileResult[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any re-exports which should be added next to this class, both in .js and (if possible) .d.ts.
|
||||||
|
*/
|
||||||
|
reexports: Reexport[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CompiledFile {
|
export interface CompiledFile {
|
||||||
compiledClasses: CompiledClass[];
|
compiledClasses: CompiledClass[];
|
||||||
|
|
|
@ -50,6 +50,13 @@ export interface NgccEntryPointConfig {
|
||||||
* even in the face of such missing dependencies.
|
* even in the face of such missing dependencies.
|
||||||
*/
|
*/
|
||||||
ignoreMissingDependencies?: boolean;
|
ignoreMissingDependencies?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabling this option for an entrypoint tells ngcc that deep imports might be used for the files
|
||||||
|
* it contains, and that it should generate private re-exports alongside the NgModule of all the
|
||||||
|
* directives/pipes it makes available in support of those imports.
|
||||||
|
*/
|
||||||
|
generateDeepReexports?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,8 @@ export interface EntryPoint extends JsonObject {
|
||||||
compiledByAngular: boolean;
|
compiledByAngular: boolean;
|
||||||
/** Should ngcc ignore missing dependencies and process this entrypoint anyway? */
|
/** Should ngcc ignore missing dependencies and process this entrypoint anyway? */
|
||||||
ignoreMissingDependencies: boolean;
|
ignoreMissingDependencies: boolean;
|
||||||
|
/** Should ngcc generate deep re-exports for this entrypoint? */
|
||||||
|
generateDeepReexports: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type JsonPrimitive = string | number | boolean | null;
|
export type JsonPrimitive = string | number | boolean | null;
|
||||||
|
@ -124,6 +126,8 @@ export function getEntryPointInfo(
|
||||||
typings: resolve(entryPointPath, typings), compiledByAngular,
|
typings: resolve(entryPointPath, typings), compiledByAngular,
|
||||||
ignoreMissingDependencies:
|
ignoreMissingDependencies:
|
||||||
entryPointConfig !== undefined ? !!entryPointConfig.ignoreMissingDependencies : false,
|
entryPointConfig !== undefined ? !!entryPointConfig.ignoreMissingDependencies : false,
|
||||||
|
generateDeepReexports:
|
||||||
|
entryPointConfig !== undefined ? !!entryPointConfig.generateDeepReexports : false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return entryPointInfo;
|
return entryPointInfo;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import {dirname, relative} from 'canonical-path';
|
import {dirname, relative} from 'canonical-path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
import {isRequireCall} from '../host/commonjs_host';
|
import {isRequireCall} from '../host/commonjs_host';
|
||||||
|
@ -53,6 +54,17 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDirectExports(
|
||||||
|
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||||
|
file: ts.SourceFile): void {
|
||||||
|
for (const e of exports) {
|
||||||
|
const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName);
|
||||||
|
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
|
||||||
|
const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`;
|
||||||
|
output.append(exportStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected findEndOfImports(sf: ts.SourceFile): number {
|
protected findEndOfImports(sf: ts.SourceFile): number {
|
||||||
for (const statement of sf.statements) {
|
for (const statement of sf.statements) {
|
||||||
if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) {
|
if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {FileSystem} from '../../../src/ngtsc/file_system';
|
import {FileSystem} from '../../../src/ngtsc/file_system';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {CompileResult} from '../../../src/ngtsc/transform';
|
import {CompileResult} from '../../../src/ngtsc/transform';
|
||||||
import {translateType, ImportManager} from '../../../src/ngtsc/translator';
|
import {translateType, ImportManager} from '../../../src/ngtsc/translator';
|
||||||
import {DecorationAnalyses} from '../analysis/types';
|
import {DecorationAnalyses} from '../analysis/types';
|
||||||
|
@ -33,6 +34,7 @@ class DtsRenderInfo {
|
||||||
classInfo: DtsClassInfo[] = [];
|
classInfo: DtsClassInfo[] = [];
|
||||||
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
||||||
privateExports: ExportInfo[] = [];
|
privateExports: ExportInfo[] = [];
|
||||||
|
reexports: Reexport[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,6 +96,13 @@ export class DtsRenderer {
|
||||||
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
||||||
outputText.appendRight(endOfClass - 1, newStatement);
|
outputText.appendRight(endOfClass - 1, newStatement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (renderInfo.reexports.length > 0) {
|
||||||
|
for (const e of renderInfo.reexports) {
|
||||||
|
const newStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`;
|
||||||
|
outputText.appendRight(endOfClass, newStatement);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dtsFormatter.addModuleWithProvidersParams(
|
this.dtsFormatter.addModuleWithProvidersParams(
|
||||||
|
@ -103,8 +112,6 @@ export class DtsRenderer {
|
||||||
this.dtsFormatter.addImports(
|
this.dtsFormatter.addImports(
|
||||||
outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return renderSourceAndMap(dtsFile, input, outputText);
|
return renderSourceAndMap(dtsFile, input, outputText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +130,15 @@ export class DtsRenderer {
|
||||||
const dtsFile = dtsDeclaration.getSourceFile();
|
const dtsFile = dtsDeclaration.getSourceFile();
|
||||||
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile) ! : new DtsRenderInfo();
|
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile) ! : new DtsRenderInfo();
|
||||||
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||||
|
// Only add re-exports if the .d.ts tree is overlayed with the .js tree, as re-exports in
|
||||||
|
// ngcc are only used to support deep imports into e.g. commonjs code. For a deep import
|
||||||
|
// to work, the typing file and JS file must be in parallel trees. This logic will detect
|
||||||
|
// the simplest version of this case, which is sufficient to handle most commonjs
|
||||||
|
// libraries.
|
||||||
|
if (compiledClass.declaration.getSourceFile().fileName ===
|
||||||
|
dtsFile.fileName.replace(/\.d\.ts$/, '.js')) {
|
||||||
|
renderInfo.reexports.push(...compiledClass.reexports);
|
||||||
|
}
|
||||||
dtsMap.set(dtsFile, renderInfo);
|
dtsMap.set(dtsFile, renderInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyze
|
||||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
||||||
import {stripExtension} from './utils';
|
import {stripExtension} from './utils';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A RenderingFormatter that works with ECMAScript Module import and export statements.
|
* A RenderingFormatter that works with ECMAScript Module import and export statements.
|
||||||
|
@ -57,6 +58,22 @@ export class EsmRenderingFormatter implements RenderingFormatter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add plain exports to the end of the file.
|
||||||
|
*
|
||||||
|
* Unlike `addExports`, direct exports go directly in a .js and .d.ts file and don't get added to
|
||||||
|
* an entrypoint.
|
||||||
|
*/
|
||||||
|
addDirectExports(
|
||||||
|
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||||
|
file: ts.SourceFile): void {
|
||||||
|
for (const e of exports) {
|
||||||
|
const exportStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`;
|
||||||
|
output.append(exportStatement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the constants directly after the imports.
|
* Add the constants directly after the imports.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -83,6 +83,11 @@ export class Renderer {
|
||||||
compiledFile.compiledClasses.forEach(clazz => {
|
compiledFile.compiledClasses.forEach(clazz => {
|
||||||
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||||
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
||||||
|
|
||||||
|
if (!isEntryPoint && clazz.reexports.length > 0) {
|
||||||
|
this.srcFormatter.addDirectExports(
|
||||||
|
outputText, clazz.reexports, importManager, compiledFile.sourceFile);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.srcFormatter.addConstants(
|
this.srcFormatter.addConstants(
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
import {CompiledClass} from '../analysis/types';
|
import {CompiledClass} from '../analysis/types';
|
||||||
|
@ -31,6 +32,9 @@ export interface RenderingFormatter {
|
||||||
addExports(
|
addExports(
|
||||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||||
importManager: ImportManager, file: ts.SourceFile): void;
|
importManager: ImportManager, file: ts.SourceFile): void;
|
||||||
|
addDirectExports(
|
||||||
|
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||||
|
file: ts.SourceFile): void;
|
||||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
||||||
rewriteSwitchableDeclarations(
|
rewriteSwitchableDeclarations(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||||
import {UmdReflectionHost} from '../host/umd_host';
|
import {UmdReflectionHost} from '../host/umd_host';
|
||||||
import {Esm5RenderingFormatter} from './esm5_rendering_formatter';
|
import {Esm5RenderingFormatter} from './esm5_rendering_formatter';
|
||||||
import {stripExtension} from './utils';
|
import {stripExtension} from './utils';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
|
|
||||||
type CommonJsConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
type CommonJsConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||||
type AmdConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
type AmdConditional = ts.ConditionalExpression & {whenTrue: ts.CallExpression};
|
||||||
|
@ -71,6 +72,26 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDirectExports(
|
||||||
|
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||||
|
file: ts.SourceFile): void {
|
||||||
|
const umdModule = this.umdHost.getUmdModule(file);
|
||||||
|
if (!umdModule) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const factoryFunction = umdModule.factoryFn;
|
||||||
|
const lastStatement =
|
||||||
|
factoryFunction.body.statements[factoryFunction.body.statements.length - 1];
|
||||||
|
const insertionPoint =
|
||||||
|
lastStatement ? lastStatement.getEnd() : factoryFunction.body.getEnd() - 1;
|
||||||
|
for (const e of exports) {
|
||||||
|
const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName);
|
||||||
|
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
|
||||||
|
const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`;
|
||||||
|
output.appendRight(insertionPoint, exportStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the constants to the top of the UMD factory function.
|
* Add the constants to the top of the UMD factory function.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -80,6 +80,7 @@ runInEachFileSystem(() => {
|
||||||
handler.resolve.and.callFake((decl: ts.Declaration, analysis: any) => {
|
handler.resolve.and.callFake((decl: ts.Declaration, analysis: any) => {
|
||||||
logs.push(`resolve: ${(decl as any).name.text}@${analysis.decoratorName}`);
|
logs.push(`resolve: ${(decl as any).name.text}@${analysis.decoratorName}`);
|
||||||
analysis.resolved = true;
|
analysis.resolved = true;
|
||||||
|
return {};
|
||||||
});
|
});
|
||||||
// The "test" compilation result is just the name of the decorator being compiled
|
// The "test" compilation result is just the name of the decorator being compiled
|
||||||
// (suffixed with `(compiled)`)
|
// (suffixed with `(compiled)`)
|
||||||
|
|
|
@ -6,15 +6,19 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||||
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||||
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
||||||
|
import {NgccEntryPointConfig} from '../../src/packages/configuration';
|
||||||
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
|
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
|
||||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||||
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
||||||
|
|
||||||
|
export type TestConfig = Pick<NgccEntryPointConfig, 'generateDeepReexports'>;
|
||||||
|
|
||||||
export function makeTestEntryPoint(
|
export function makeTestEntryPoint(
|
||||||
entryPointName: string, packageName: string = entryPointName): EntryPoint {
|
entryPointName: string, packageName: string = entryPointName, config?: TestConfig): EntryPoint {
|
||||||
return {
|
return {
|
||||||
name: entryPointName,
|
name: entryPointName,
|
||||||
packageJson: {name: entryPointName},
|
packageJson: {name: entryPointName},
|
||||||
|
@ -23,6 +27,7 @@ export function makeTestEntryPoint(
|
||||||
typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`),
|
typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: config !== undefined ? !!config.generateDeepReexports : false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +39,8 @@ export function makeTestEntryPoint(
|
||||||
*/
|
*/
|
||||||
export function makeTestEntryPointBundle(
|
export function makeTestEntryPointBundle(
|
||||||
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
||||||
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
|
dtsRootNames?: AbsoluteFsPath[], config?: TestConfig): EntryPointBundle {
|
||||||
const entryPoint = makeTestEntryPoint(packageName);
|
const entryPoint = makeTestEntryPoint(packageName, packageName, config);
|
||||||
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
||||||
const dts =
|
const dts =
|
||||||
dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], entryPoint.package, isCore) : null;
|
dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], entryPoint.package, isCore) : null;
|
||||||
|
|
|
@ -752,6 +752,132 @@ runInEachFileSystem(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('aliasing re-exports in commonjs', () => {
|
||||||
|
it('should add re-exports to commonjs files', () => {
|
||||||
|
loadTestFiles([
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/package.json'),
|
||||||
|
contents: `
|
||||||
|
{
|
||||||
|
"name": "test-package",
|
||||||
|
"main": "./index.js",
|
||||||
|
"typings": "./index.d.ts"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/index.js'),
|
||||||
|
contents: `
|
||||||
|
var __export = null;
|
||||||
|
__export(require("./module"));
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/index.d.ts'),
|
||||||
|
contents: `
|
||||||
|
export * from "./module";
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/index.metadata.json'),
|
||||||
|
contents: '{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/module.js'),
|
||||||
|
contents: `
|
||||||
|
var __decorate = null;
|
||||||
|
var core_1 = require("@angular/core");
|
||||||
|
var directive_1 = require("./directive");
|
||||||
|
var FooModule = /** @class */ (function () {
|
||||||
|
function FooModule() {
|
||||||
|
}
|
||||||
|
FooModule = __decorate([
|
||||||
|
core_1.NgModule({
|
||||||
|
declarations: [directive_1.Foo],
|
||||||
|
exports: [directive_1.Foo],
|
||||||
|
})
|
||||||
|
], FooModule);
|
||||||
|
return FooModule;
|
||||||
|
}());
|
||||||
|
exports.FooModule = FooModule;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/module.d.ts'),
|
||||||
|
contents: `
|
||||||
|
export declare class FooModule {}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/module.metadata.json'),
|
||||||
|
contents: '{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/directive.js'),
|
||||||
|
contents: `
|
||||||
|
var __decorate = null;
|
||||||
|
var core_1 = require("@angular/core");
|
||||||
|
var Foo = /** @class */ (function () {
|
||||||
|
function Foo() {
|
||||||
|
}
|
||||||
|
Foo = __decorate([
|
||||||
|
core_1.Directive({
|
||||||
|
selector: '[foo]',
|
||||||
|
})
|
||||||
|
], Foo);
|
||||||
|
return Foo;
|
||||||
|
}());
|
||||||
|
exports.Foo = Foo;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/directive.d.ts'),
|
||||||
|
contents: `
|
||||||
|
export declare class Foo {}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/node_modules/test-package/directive.metadata.json'),
|
||||||
|
contents: '{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/ngcc.config.js'),
|
||||||
|
contents: `
|
||||||
|
module.exports = {
|
||||||
|
packages: {
|
||||||
|
'test-package': {
|
||||||
|
entryPoints: {
|
||||||
|
'.': {
|
||||||
|
generateDeepReexports: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
mainNgcc({
|
||||||
|
basePath: '/node_modules',
|
||||||
|
targetEntryPointPath: 'test-package',
|
||||||
|
propertiesToConsider: ['main'],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
|
||||||
|
main: '0.0.0-PLACEHOLDER',
|
||||||
|
typings: '0.0.0-PLACEHOLDER',
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsContents = fs.readFile(_(`/node_modules/test-package/module.js`));
|
||||||
|
const dtsContents = fs.readFile(_(`/node_modules/test-package/module.d.ts`));
|
||||||
|
expect(jsContents).toContain(`var ɵngcc1 = require('./directive');`);
|
||||||
|
expect(jsContents).toContain('exports.ɵngExportɵFooModuleɵFoo = ɵngcc1.Foo;');
|
||||||
|
expect(dtsContents)
|
||||||
|
.toContain(`export {Foo as ɵngExportɵFooModuleɵFoo} from './directive';`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function loadPackage(
|
function loadPackage(
|
||||||
packageName: string, basePath: AbsoluteFsPath = _('/node_modules')): EntryPointPackageJson {
|
packageName: string, basePath: AbsoluteFsPath = _('/node_modules')): EntryPointPackageJson {
|
||||||
return JSON.parse(fs.readFile(fs.resolve(basePath, packageName, 'package.json')));
|
return JSON.parse(fs.readFile(fs.resolve(basePath, packageName, 'package.json')));
|
||||||
|
|
|
@ -144,6 +144,7 @@ runInEachFileSystem(() => {
|
||||||
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
};
|
};
|
||||||
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
|
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
|
||||||
|
|
||||||
|
@ -191,6 +192,7 @@ runInEachFileSystem(() => {
|
||||||
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
};
|
};
|
||||||
const esm5bundle = makeEntryPointBundle(
|
const esm5bundle = makeEntryPointBundle(
|
||||||
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||||
|
@ -213,6 +215,7 @@ runInEachFileSystem(() => {
|
||||||
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
typings: absoluteFrom('/node_modules/test/index.d.ts'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
};
|
};
|
||||||
const esm5bundle = makeEntryPointBundle(
|
const esm5bundle = makeEntryPointBundle(
|
||||||
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
|
||||||
|
|
|
@ -51,6 +51,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'),
|
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: overriddenPackageJson,
|
packageJson: overriddenPackageJson,
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -158,6 +160,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: {name: 'some_package/missing_package_json', ...override},
|
packageJson: {name: 'some_package/missing_package_json', ...override},
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,6 +218,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'),
|
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -239,6 +243,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
|
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
|
||||||
compiledByAngular: false,
|
compiledByAngular: false,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -266,6 +271,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
|
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -295,6 +301,7 @@ runInEachFileSystem(() => {
|
||||||
loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'),
|
loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -327,6 +334,7 @@ runInEachFileSystem(() => {
|
||||||
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/material_style'),
|
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/material_style'),
|
||||||
compiledByAngular: true,
|
compiledByAngular: true,
|
||||||
ignoreMissingDependencies: false,
|
ignoreMissingDependencies: false,
|
||||||
|
generateDeepReexports: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import MagicString from 'magic-string';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {loadTestFiles} from '../../../test/helpers';
|
import {loadTestFiles} from '../../../test/helpers';
|
||||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||||
|
@ -29,6 +30,9 @@ class TestRenderingFormatter implements RenderingFormatter {
|
||||||
addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) {
|
addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) {
|
||||||
output.prepend('\n// ADD EXPORTS\n');
|
output.prepend('\n// ADD EXPORTS\n');
|
||||||
}
|
}
|
||||||
|
addDirectExports(output: MagicString, exports: Reexport[]) {
|
||||||
|
output.prepend('\n// ADD DIRECT EXPORTS\n');
|
||||||
|
}
|
||||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||||
output.prepend('\n// ADD CONSTANTS\n');
|
output.prepend('\n// ADD CONSTANTS\n');
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as ts from 'typescript';
|
||||||
import {fromObject, generateMapFileComment, SourceMapConverter} from 'convert-source-map';
|
import {fromObject, generateMapFileComment, SourceMapConverter} from 'convert-source-map';
|
||||||
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||||
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||||
|
import {Reexport} from '../../../src/ngtsc/imports';
|
||||||
import {loadTestFiles} from '../../../test/helpers';
|
import {loadTestFiles} from '../../../test/helpers';
|
||||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||||
|
@ -31,6 +32,9 @@ class TestRenderingFormatter implements RenderingFormatter {
|
||||||
addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) {
|
addExports(output: MagicString, baseEntryPointPath: string, exports: ExportInfo[]) {
|
||||||
output.prepend('\n// ADD EXPORTS\n');
|
output.prepend('\n// ADD EXPORTS\n');
|
||||||
}
|
}
|
||||||
|
addDirectExports(output: MagicString, exports: Reexport[]): void {
|
||||||
|
output.prepend('\n// ADD DIRECT EXPORTS\n');
|
||||||
|
}
|
||||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||||
output.prepend('\n// ADD CONSTANTS\n');
|
output.prepend('\n// ADD CONSTANTS\n');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue