Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: substitute aliased modules #39

Merged
merged 1 commit into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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();
});
});
11 changes: 5 additions & 6 deletions src/compileTypes/helpers/includeTypesFromNodeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/compileTypes/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './getTSConfigCompilerOptions';
export * from './includeTypesFromNodeModules';
export * from './reportCompileDiagnostic';
export * from './substituteAliasedModules';
31 changes: 31 additions & 0 deletions src/compileTypes/helpers/substituteAliasedModules.ts
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 6 additions & 2 deletions src/compileTypes/rewritePathsWithExposedFederatedModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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$/, '');

Expand All @@ -55,6 +58,7 @@ export function rewritePathsWithExposedFederatedModules(
].join('\n');
});

typingsUpdated = substituteAliasedModules(federationConfig.name, typingsUpdated);
typingsUpdated = includeTypesFromNodeModules(federationConfig, typingsUpdated);

mkdirp.sync(path.dirname(outFile));
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading