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:
Alex Rickabaugh 2019-10-14 13:04:42 -07:00 committed by Matias Niemelä
parent c4733c15c0
commit e030375d9a
17 changed files with 287 additions and 11 deletions

View File

@ -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);
}
} }

View File

@ -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[];

View File

@ -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;
} }
/** /**

View File

@ -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;

View File

@ -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)) {

View File

@ -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);
} }
}); });

View File

@ -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.
*/ */

View File

@ -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(

View File

@ -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(

View File

@ -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.
*/ */

View File

@ -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)`)

View File

@ -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;

View File

@ -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')));

View File

@ -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,

View File

@ -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,
}); });
}); });

View File

@ -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');
} }

View File

@ -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');
} }