diff --git a/src/compileTypes/helpers/__tests__/includeTypesFromNodeModules.test.ts b/src/compileTypes/helpers/__tests__/includeTypesFromNodeModules.test.ts new file mode 100644 index 0000000..95c809d --- /dev/null +++ b/src/compileTypes/helpers/__tests__/includeTypesFromNodeModules.test.ts @@ -0,0 +1,58 @@ +import { FederationConfig } from '../../../models'; +import { setLogger } from '../../../helpers'; +import { includeTypesFromNodeModules } from '../includeTypesFromNodeModules'; + +// Assuming logger mock setup is similar to previous example +const mockLogger = { + log: jest.fn(), + warn: jest.fn(), +}; + +setLogger(mockLogger); + +describe('includeTypesFromNodeModules', () => { + test('correctly includes typings for exposed NPM packages', () => { + const federationConfig: FederationConfig = { + name: 'myApp', + exposes: { + ModuleA: './node_modules/libraryA', + ModuleB: './node_modules/libraryB', + }, + }; + const initialTypings = 'initial typings content'; + + const result = includeTypesFromNodeModules(federationConfig, initialTypings); + + const moduleADeclaration = [ + 'declare module "myApp/ModuleA" {', + ' export * from "libraryA"', + '}', + ].join('\n'); + const moduleBDeclaration = [ + 'declare module "myApp/ModuleB" {', + ' export * from "libraryB"', + '}', + ].join('\n'); + + expect(result).toBe([initialTypings, moduleADeclaration, moduleBDeclaration].join('\n')); + expect(mockLogger.log).toHaveBeenCalledWith('Including typings for npm packages:', [ + ['ModuleA', 'libraryA'], + ['ModuleB', 'libraryB'], + ]); + }); + + test('does not modify typings when there are no NPM package paths', () => { + const federationConfig: FederationConfig = { + name: 'myApp', + exposes: { + LocalModule: './src/LocalModule', + }, + }; + const initialTypings = 'initial typings content'; + + const result = includeTypesFromNodeModules(federationConfig, initialTypings); + + expect(result).toBe(initialTypings); + expect(mockLogger.log).not.toHaveBeenCalledWith(); + }); +}); diff --git a/src/compileTypes/helpers/__tests__/substituteAliasedModules.test.ts b/src/compileTypes/helpers/__tests__/substituteAliasedModules.test.ts new file mode 100644 index 0000000..e15f7ff --- /dev/null +++ b/src/compileTypes/helpers/__tests__/substituteAliasedModules.test.ts @@ -0,0 +1,41 @@ +import { substituteAliasedModules } from '../substituteAliasedModules'; +import { + getLogger, setLogger, +} from '../../../helpers'; +import { PREFIX_NOT_FOR_IMPORT } from '../../../constants'; + +const mockLogger = { + log: jest.fn(), +}; + +setLogger(mockLogger); + +describe('substituteAliasedModules', () => { + const federatedModuleName = 'myCommon'; + const logger = getLogger(); + + test('substitutes import path when #not-for-import version exists', () => { + const modulePath = 'libs/currency'; + const typings = ` + Some import("${modulePath}") more content + declare module "${PREFIX_NOT_FOR_IMPORT}/${federatedModuleName}/${modulePath}" + `; + + const subsituted = substituteAliasedModules(federatedModuleName, typings); + + expect(subsituted).toBe(` + Some import("${PREFIX_NOT_FOR_IMPORT}/${federatedModuleName}/${modulePath}") more content + declare module "${PREFIX_NOT_FOR_IMPORT}/${federatedModuleName}/${modulePath}" + `); + expect(logger.log).toHaveBeenCalledWith(`Substituting import path: ${modulePath}`); + }); + + test('does not modify typings when a #not-for-import version does not exist', () => { + const originalTypings = 'Some content import("another/module") more content'; + + const result = substituteAliasedModules(federatedModuleName, originalTypings); + + expect(result).toBe(originalTypings); + expect(logger.log).not.toHaveBeenCalled(); + }); +}); diff --git a/src/compileTypes/helpers/includeTypesFromNodeModules.ts b/src/compileTypes/helpers/includeTypesFromNodeModules.ts index abd50b9..d0e31ab 100644 --- a/src/compileTypes/helpers/includeTypesFromNodeModules.ts +++ b/src/compileTypes/helpers/includeTypesFromNodeModules.ts @@ -15,12 +15,11 @@ export function includeTypesFromNodeModules(federationConfig: FederationConfig, exposeTargetPath.replace('./node_modules/', ''), ]); - // language=TypeScript - const createNpmModule = (exposedModuleKey: string, packageName: string) => ` - declare module "${federationConfig.name}/${exposedModuleKey}" { - export * from "${packageName}" - } - `; + const createNpmModule = (exposedModuleKey: string, packageName: string) => [ + `declare module "${federationConfig.name}/${exposedModuleKey}" {`, + ` export * from "${packageName}"`, + '}', + ].join('\n'); if (exposedNpmPackages.length) { logger.log('Including typings for npm packages:', exposedNpmPackages); diff --git a/src/compileTypes/helpers/index.ts b/src/compileTypes/helpers/index.ts index b06d413..cf53ceb 100644 --- a/src/compileTypes/helpers/index.ts +++ b/src/compileTypes/helpers/index.ts @@ -1,3 +1,4 @@ export * from './getTSConfigCompilerOptions'; export * from './includeTypesFromNodeModules'; export * from './reportCompileDiagnostic'; +export * from './substituteAliasedModules'; diff --git a/src/compileTypes/helpers/substituteAliasedModules.ts b/src/compileTypes/helpers/substituteAliasedModules.ts new file mode 100644 index 0000000..88d0c0b --- /dev/null +++ b/src/compileTypes/helpers/substituteAliasedModules.ts @@ -0,0 +1,31 @@ +import { getLogger } from '../../helpers'; +import { PREFIX_NOT_FOR_IMPORT } from '../../constants'; + +export function substituteAliasedModules(federatedModuleName: string, typings: string): string { + const logger = getLogger(); + + // Collect all instances of `import("...")` + const regexImportPaths = /import\("([^"]*)"\)/g; + const uniqueImportPaths = new Set(); + + let match = regexImportPaths.exec(typings); + while (match) { + uniqueImportPaths.add(match[1]); + match = regexImportPaths.exec(typings); + } + + uniqueImportPaths.forEach(importPath => { + const notForImportPath = `${PREFIX_NOT_FOR_IMPORT}/${federatedModuleName}/${importPath}`; + + if (typings.includes(`declare module "${notForImportPath}"`)) { + logger.log(`Substituting import path: ${importPath}`); + + typings = typings.replace( + new RegExp(`import\\("${importPath}"\\)`, 'g'), + `import("${notForImportPath}")`, + ); + } + }); + + return typings; +} diff --git a/src/compileTypes/rewritePathsWithExposedFederatedModules.ts b/src/compileTypes/rewritePathsWithExposedFederatedModules.ts index 5975604..f11f20f 100644 --- a/src/compileTypes/rewritePathsWithExposedFederatedModules.ts +++ b/src/compileTypes/rewritePathsWithExposedFederatedModules.ts @@ -4,8 +4,11 @@ import fs from 'fs'; import mkdirp from 'mkdirp'; import { FederationConfig } from '../models'; +import { PREFIX_NOT_FOR_IMPORT } from '../constants'; -import { includeTypesFromNodeModules } from './helpers'; +import { + includeTypesFromNodeModules, substituteAliasedModules, +} from './helpers'; export function rewritePathsWithExposedFederatedModules( federationConfig: FederationConfig, @@ -38,7 +41,7 @@ export function rewritePathsWithExposedFederatedModules( let federatedModulePath = exposedModuleKey ? `${federationConfig.name}/${exposedModuleKey}` - : `#not-for-import/${federationConfig.name}/${importPath}`; + : `${PREFIX_NOT_FOR_IMPORT}/${federationConfig.name}/${importPath}`; federatedModulePath = federatedModulePath.replace(/\/index$/, ''); @@ -55,6 +58,7 @@ export function rewritePathsWithExposedFederatedModules( ].join('\n'); }); + typingsUpdated = substituteAliasedModules(federationConfig.name, typingsUpdated); typingsUpdated = includeTypesFromNodeModules(federationConfig, typingsUpdated); mkdirp.sync(path.dirname(outFile)); diff --git a/src/constants.ts b/src/constants.ts index e6105da..dada365 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,3 +8,5 @@ export const DEFAULT_DIR_GLOBAL_TYPES = 'src/@types'; export const DEFAULT_DIR_DOWNLOADED_TYPES = 'src/@types/remotes'; export const DEFAULT_DOWNLOAD_TYPES_INTERVAL_IN_SECONDS = 60; + +export const PREFIX_NOT_FOR_IMPORT = '#not-for-import';