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:

committed by
Matias Niemelä

parent
c4733c15c0
commit
e030375d9a
@ -8,6 +8,7 @@
|
||||
import {dirname, relative} from 'canonical-path';
|
||||
import * as ts from 'typescript';
|
||||
import MagicString from 'magic-string';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
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 {
|
||||
for (const statement of sf.statements) {
|
||||
if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {FileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {CompileResult} from '../../../src/ngtsc/transform';
|
||||
import {translateType, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {DecorationAnalyses} from '../analysis/types';
|
||||
@ -33,6 +34,7 @@ class DtsRenderInfo {
|
||||
classInfo: DtsClassInfo[] = [];
|
||||
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
||||
privateExports: ExportInfo[] = [];
|
||||
reexports: Reexport[] = [];
|
||||
}
|
||||
|
||||
|
||||
@ -94,6 +96,13 @@ export class DtsRenderer {
|
||||
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
||||
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(
|
||||
@ -103,8 +112,6 @@ export class DtsRenderer {
|
||||
this.dtsFormatter.addImports(
|
||||
outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
||||
|
||||
|
||||
|
||||
return renderSourceAndMap(dtsFile, input, outputText);
|
||||
}
|
||||
|
||||
@ -123,6 +130,15 @@ export class DtsRenderer {
|
||||
const dtsFile = dtsDeclaration.getSourceFile();
|
||||
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile) ! : new DtsRenderInfo();
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
@ -16,6 +16,7 @@ import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyze
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {RenderingFormatter, RedundantDecoratorMap} from './rendering_formatter';
|
||||
import {stripExtension} from './utils';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -83,6 +83,11 @@ export class Renderer {
|
||||
compiledFile.compiledClasses.forEach(clazz => {
|
||||
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||
this.srcFormatter.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
|
||||
if (!isEntryPoint && clazz.reexports.length > 0) {
|
||||
this.srcFormatter.addDirectExports(
|
||||
outputText, clazz.reexports, importManager, compiledFile.sourceFile);
|
||||
}
|
||||
});
|
||||
|
||||
this.srcFormatter.addConstants(
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import MagicString from 'magic-string';
|
||||
import * as ts from 'typescript';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
import {Import, ImportManager} from '../../../src/ngtsc/translator';
|
||||
import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {CompiledClass} from '../analysis/types';
|
||||
@ -31,6 +32,9 @@ export interface RenderingFormatter {
|
||||
addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
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;
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: RedundantDecoratorMap): void;
|
||||
rewriteSwitchableDeclarations(
|
||||
|
@ -13,6 +13,7 @@ import {ExportInfo} from '../analysis/private_declarations_analyzer';
|
||||
import {UmdReflectionHost} from '../host/umd_host';
|
||||
import {Esm5RenderingFormatter} from './esm5_rendering_formatter';
|
||||
import {stripExtension} from './utils';
|
||||
import {Reexport} from '../../../src/ngtsc/imports';
|
||||
|
||||
type CommonJsConditional = 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.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user