Skip to content

Commit

Permalink
Merge pull request #47 from cloudbeds/fix/logger-and-parsing-tsconfig
Browse files Browse the repository at this point in the history
Fix logging and parsing tsconfig
  • Loading branch information
steven-pribilinskiy authored Oct 14, 2024
2 parents 156d94e + e692801 commit 6d10114
Show file tree
Hide file tree
Showing 18 changed files with 1,078 additions and 252 deletions.
997 changes: 821 additions & 176 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
"url": "https://github.com/cloudbeds/webpack-module-federation-types-plugin.git"
},
"main": "dist/plugin.js",
"files": [
"dist"
],
"files": ["dist"],
"bin": {
"make-federated-types": "dist/bin/make-federated-types.js",
"download-federated-types": "dist/bin/download-federated-types.js"
Expand All @@ -37,7 +35,7 @@
"@types/minimist": "^1.2.5",
"@types/mkdirp": "^2.0.0",
"@types/node": "^22.7.5",
"@vitest/coverage-v8": "^2.1.2",
"@vitest/coverage-istanbul": "^2.1.3",
"simple-git-hooks": "^2.11.1",
"tsx": "^4.19.1",
"typescript": "^5.6.3",
Expand Down
97 changes: 97 additions & 0 deletions src/compileTypes/__tests__/workerLogger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { parentPort } from 'node:worker_threads';
import { describe, expect, test, vi } from 'vitest';

import { sendLog, workerLogger } from '../workerLogger';

vi.mock('node:worker_threads', () => ({
parentPort: {
postMessage: vi.fn(),
},
}));

describe('workerLogger', () => {
const mockPostMessage = vi.mocked(parentPort?.postMessage);

test('sendLog handles various data types correctly', () => {
const testData = [
{ a: 'b', c: { d: 'e' } },
null,
'c',
1,
['a', 'b'],
undefined,
0,
true,
false,
() => 'anonymous function',
/test/,
];

sendLog('info', testData);

expect(mockPostMessage).toHaveBeenCalledWith({
status: 'log',
level: 'info',
message: [
'{\n "a": "b",\n "c": {\n "d": "e"\n }\n}',
'null',
'c',
'1',
'[\n "a",\n "b"\n]',
'',
'0',
'true',
'false',
'() => "anonymous function"',
'/test/',
].join(' '),
});
});

test('workerLogger methods call sendLog with correct level and data', () => {
const logMethods = ['error', 'warn', 'info', 'log', 'group', 'groupCollapsed'] as const;
const testData = ['Test message', { key: 'value' }, 42, true];

logMethods.forEach(method => {
workerLogger[method](...testData);
expect(mockPostMessage).toHaveBeenCalledWith({
status: 'log',
level: method,
message: 'Test message {\n "key": "value"\n} 42 true',
});
});
});

test('workerLogger.groupEnd calls sendLog with empty array', () => {
workerLogger.groupEnd();
expect(mockPostMessage).toHaveBeenCalledWith({
status: 'log',
level: 'groupEnd',
message: '',
});
});

test('sendLog throws error on circular references', () => {
const circular: Dict = { a: 'circular' };
circular.self = circular;

expect(() => sendLog('info', [circular])).toThrow('Converting circular structure to JSON');

expect(mockPostMessage).not.toHaveBeenCalled();
});

test('sendLog handles deep nested objects and arrays', () => {
const deepNested = {
a: [1, { b: { c: [2, 3, { d: 4 }] } }],
e: { f: { g: { h: 5 } } },
};

sendLog('info', [deepNested]);

expect(mockPostMessage).toHaveBeenCalledWith({
status: 'log',
level: 'info',
message: JSON.stringify(deepNested, null, 2),
});
});
});
9 changes: 7 additions & 2 deletions src/compileTypes/compileTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ export function compileTypes(
...getAllFilePaths(`./${dirGlobalTypes}`).filter(filePath => filePath.endsWith('.d.ts')),
);
}
logger.log('[compileTypes]: Including a set of root files in compilation');
logger.log(JSON.stringify(exposedFileNames, null, 2));

logger.groupCollapsed(
'[compileTypes]: Including a set of exposed modules in compilation',
`(${exposedFileNames.length} npm packages and root paths)`,
);
logger.log(exposedFileNames);
logger.groupEnd();

const program = ts.createProgram(exposedFileNames, compilerOptions, host);
const { diagnostics, emitSkipped } = program.emit();
Expand Down
25 changes: 16 additions & 9 deletions src/compileTypes/compileTypesAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import type {
} from './compileTypesWorker';

let worker: Worker | null = null;
let workerIndex = 0;

export function compileTypesAsync(
params: CompileTypesWorkerMessage,
loggerHint = '',
): Promise<void> {
const logger = getLogger();
workerIndex++;

return new Promise((resolve, reject) => {
if (worker) {
logger.log('Terminating existing worker process');
worker.terminate();
logger.log(`Terminating existing worker process #${workerIndex}`);
worker.postMessage({ type: 'exit' });
}

const workerPath = path.join(__dirname, 'compileTypesWorker.js');
Expand All @@ -27,17 +29,23 @@ export function compileTypesAsync(
worker.on('message', (result: CompileTypesWorkerResultMessage) => {
switch (result.status) {
case 'log':
logger[result.level]('[Worker]:', result.message);
logger[result.level](`[Worker #${workerIndex}]:`, result.message);
return;
case 'success':
resolve();
break;
case 'failure':
logger.warn('[Worker]: Failed to compile types for exposed modules.', loggerHint);
logger.warn(
`[Worker #${workerIndex}]: Failed to compile types for exposed modules.`,
loggerHint,
);
reject(new Error('Failed to compile types for exposed modules.'));
break;
case 'error':
logger.warn('[Worker]: Error compiling types for exposed modules.', loggerHint);
logger.warn(
`[Worker #${workerIndex}]: Error compiling types for exposed modules.`,
loggerHint,
);
reject(result.error);
break;
}
Expand All @@ -46,19 +54,18 @@ export function compileTypesAsync(
});

worker.on('error', error => {
logger.warn('[Worker]: Unexpected error.', loggerHint);
logger.warn(`[Worker #${workerIndex}]: Unexpected error.`, loggerHint);
logger.log(error);
reject(error);
worker?.terminate();
worker = null;
});

worker.on('exit', code => {
if (code === null || code === 0 || code === 1) {
logger.log(`[Worker]: Process exited with code ${code}`);
if (code === null || code === 0) {
resolve();
} else {
reject(new Error(`[Worker]: Process exited with code ${code}`));
reject(new Error(`[Worker #${workerIndex}]: Process exited with code ${code}`));
}
worker = null;
});
Expand Down
44 changes: 25 additions & 19 deletions src/compileTypes/compileTypesWorker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { parentPort } from 'node:worker_threads';

import type { Compilation } from 'webpack';
import type { FederationConfig } from '../models';
import type { FederationConfig, LogLevel } from '../models';
import { type CompileTypesParams, compileTypes } from './compileTypes';
import { rewritePathsWithExposedFederatedModules } from './rewritePathsWithExposedFederatedModules';
import { sendLog, workerLogger } from './workerLogger';

export type LogLevel = keyof Pick<
Compilation['logger'],
'log' | 'info' | 'warn' | 'error' | 'debug'
>;
import { workerLogger } from './workerLogger';

type CompileTypesWorkerResultMessageError = {
status: 'error';
Expand All @@ -20,36 +14,48 @@ export type CompileTypesWorkerMessage = CompileTypesParams & {
federationConfig: FederationConfig;
};

export type ExitMessage = {
type: 'exit';
};

export type CompileTypesWorkerResultMessage =
| { status: 'success' }
| { status: 'failure' }
| CompileTypesWorkerResultMessageError
| { status: 'log'; level: LogLevel; message: string };

parentPort?.on('message', ({ federationConfig, ...params }: CompileTypesWorkerMessage) => {
parentPort?.on('message', (message: CompileTypesWorkerMessage | ExitMessage) => {
if ((message as ExitMessage).type === 'exit') {
workerLogger.log('Exiting by request');
process.exit(0);
}

const { federationConfig, ...params } = message as CompileTypesWorkerMessage;

try {
let startTime = performance.now();
const startTime = performance.now();
const { isSuccess, typeDefinitions } = compileTypes(params, workerLogger);

if (isSuccess) {
let endTime = performance.now();
let timeTakenInSeconds = (endTime - startTime) / 1000;
sendLog('log', `Types compilation completed in ${timeTakenInSeconds.toFixed(2)} seconds`);
const timeTakenInSeconds = ((performance.now() - startTime) / 1000).toFixed(2);
workerLogger.log(`Types compilation completed in ${timeTakenInSeconds} seconds`);

sendLog(
'log',
workerLogger.log(
`Replacing paths with names of exposed federate modules in typings file: ${params.outFile}`,
);
startTime = performance.now();
const rewriteStartTime = performance.now();
rewritePathsWithExposedFederatedModules(
federationConfig,
params.outFile,
typeDefinitions,
workerLogger,
);
endTime = performance.now();
timeTakenInSeconds = (endTime - startTime) / 1000;
sendLog('log', `Typings file rewritten in ${timeTakenInSeconds.toFixed(2)} seconds`);
const rewriteTimeTakenInSeconds = ((performance.now() - rewriteStartTime) / 1000).toFixed(2);
workerLogger.log(`Typings file rewritten in ${rewriteTimeTakenInSeconds} seconds`);

workerLogger.info(
`Types compilation and modification completed in ${timeTakenInSeconds} + ${rewriteTimeTakenInSeconds} seconds`,
);

parentPort?.postMessage({ status: 'success' } satisfies CompileTypesWorkerResultMessage);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { includeTypesFromNodeModules } from '../includeTypesFromNodeModules';
const mockLogger = {
log: vi.fn(),
warn: vi.fn(),
groupCollapsed: vi.fn(),
groupEnd: vi.fn(),
};

setLogger(mockLogger);
Expand Down Expand Up @@ -36,17 +38,14 @@ describe('includeTypesFromNodeModules', () => {
].join('\n');

expect(result).toBe([initialTypings, moduleADeclaration, moduleBDeclaration].join('\n'));
expect(mockLogger.log).toHaveBeenCalledWith('Including typings for npm packages:');
expect(mockLogger.log).toHaveBeenCalledWith(
JSON.stringify(
[
['ModuleA', 'libraryA'],
['ModuleB', 'libraryB'],
],
null,
2,
),
expect(mockLogger.groupCollapsed).toHaveBeenCalledWith(
'Including typings for npm packages',
'(2 packages)',
);
expect(mockLogger.log).toHaveBeenCalledWith([
['ModuleA', 'libraryA'],
['ModuleB', 'libraryB'],
]);
});

test('does not modify typings when there are no NPM package paths', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { substituteAliasedModules } from '../substituteAliasedModules';

const mockLogger = {
log: vi.fn(),
groupCollapsed: vi.fn(),
groupEnd: vi.fn(),
};

setLogger(mockLogger);
Expand Down Expand Up @@ -36,7 +38,6 @@ describe('substituteAliasedModules', () => {
const result = substituteAliasedModules(federatedModuleName, originalTypings);

expect(result).toBe(originalTypings);
expect(logger.log).toHaveBeenCalledWith('Unique import paths in myCommon:');
expect(logger.log).toHaveBeenCalledWith(JSON.stringify(['another/module'], null, 2));
expect(logger.log).toHaveBeenCalledWith('Found 1 import path in myCommon: another/module');
});
});
24 changes: 17 additions & 7 deletions src/compileTypes/helpers/getTSConfigCompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ export function getTSConfigCompilerOptions(
process.exit(1);
}

const tsconfigJsonFile = ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
tsconfigJsonFile,
ts.sys,
path.dirname(tsconfigPath),
);
if (ts.version.match(/^[5-9]\.([4-9]|[1-9]\d)/)) {
const tsconfigJsonFile = ts.readJsonConfigFile(tsconfigPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
tsconfigJsonFile,
ts.sys,
path.dirname(tsconfigPath),
);

return parsedConfig.options;
logger.groupCollapsed(
`Parsed tsconfig compiler options for TypeScript >= 5.4 (current version: ${ts.version})`,
);
logger.log(parsedConfig.options);
logger.groupEnd();

return parsedConfig.options;
}

return require(tsconfigPath).compilerOptions;
}
19 changes: 15 additions & 4 deletions src/compileTypes/helpers/includeTypesFromNodeModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,28 @@ export function includeTypesFromNodeModules(
].join('\n');

if (exposedNpmPackages.length) {
logger.log('Including typings for npm packages:');
logger.log(JSON.stringify(exposedNpmPackages, null, 2));
if (exposedNpmPackages.length === 1) {
logger.log('Including typings for npm package', exposedNpmPackages[0]);
} else {
logger.groupCollapsed(
'Including typings for npm packages',
`(${exposedNpmPackages.length} packages)`,
);
logger.log(exposedNpmPackages);
logger.groupEnd();
}
}

try {
exposedNpmPackages.forEach(([exposedModuleKey, packageName]) => {
typingsWithNpmPackages += `\n${createNpmModule(exposedModuleKey, packageName)}`;
});
} catch (err) {
logger.warn(`Typings was not included for npm package: ${(err as Dict)?.url}`);
logger.log(JSON.stringify(err, null, 2));
const url = (err as Dict)?.url;
if (url) {
logger.warn(`Typings were not included for npm package: ${url}`);
}
logger.log(err);
}

return typingsWithNpmPackages;
Expand Down
Loading

0 comments on commit 6d10114

Please sign in to comment.