Skip to content

Commit 9c5fc90

Browse files
authored
Merge branch 'main' into release-v0.1.12
2 parents b9f83d8 + 2e713f9 commit 9c5fc90

File tree

8 files changed

+149
-60
lines changed

8 files changed

+149
-60
lines changed

.changeset/bright-shirts-leave.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@module-federation/native-federation-typescript': minor
3+
'@module-federation/native-federation-tests': minor
4+
---
5+
6+
perf(native-federation): extract archive only if needed

packages/native-federation-tests/src/index.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ansiColors from 'ansi-colors';
2-
import { rm } from 'fs/promises';
32
import { resolve } from 'path';
43
import { mergeDeepRight, mergeRight } from 'rambda';
54
import { build } from 'tsup';
@@ -9,8 +8,11 @@ import { retrieveHostConfig } from './configurations/hostPlugin';
98
import { retrieveRemoteConfig } from './configurations/remotePlugin';
109
import { HostOptions } from './interfaces/HostOptions';
1110
import { RemoteOptions } from './interfaces/RemoteOptions';
12-
import { createTestsArchive, downloadTypesArchive } from './lib/archiveHandler';
13-
import { cleanMocksFolder } from './lib/mocksClean';
11+
import {
12+
createTestsArchive,
13+
deleteTestsFolder,
14+
downloadTypesArchive,
15+
} from './lib/archiveHandler';
1416

1517
export const NativeFederationTestsRemote = createUnplugin(
1618
(options: RemoteOptions) => {
@@ -37,9 +39,8 @@ export const NativeFederationTestsRemote = createUnplugin(
3739

3840
await createTestsArchive(remoteOptions, compiledFilesFolder);
3941

40-
if (remoteOptions.deleteTestsFolder) {
41-
await rm(compiledFilesFolder, { recursive: true, force: true });
42-
}
42+
await deleteTestsFolder(remoteOptions, compiledFilesFolder);
43+
4344
console.log(ansiColors.green('Federated mocks created correctly'));
4445
} catch (error) {
4546
console.error(
@@ -82,17 +83,10 @@ export const NativeFederationTestsRemote = createUnplugin(
8283
export const NativeFederationTestsHost = createUnplugin(
8384
(options: HostOptions) => {
8485
const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options);
86+
const typesDownloader = downloadTypesArchive(hostOptions);
8587
return {
8688
name: 'native-federation-tests/host',
8789
async writeBundle() {
88-
if (hostOptions.deleteTestsFolder) {
89-
await cleanMocksFolder(
90-
hostOptions,
91-
Object.keys(mapRemotesToDownload),
92-
);
93-
}
94-
95-
const typesDownloader = downloadTypesArchive(hostOptions);
9690
const downloadPromises =
9791
Object.entries(mapRemotesToDownload).map(typesDownloader);
9892

packages/native-federation-tests/src/lib/archiveHandler.test.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ describe('archiveHandler', () => {
4141
});
4242

4343
describe('downloadTypesArchive', () => {
44-
const archivePath = join(tmpDir, 'testsHostFolder');
44+
const destinationFolder = 'testsHostFolder';
45+
const archivePath = join(tmpDir, destinationFolder);
46+
const fileToDownload = 'https://foo.it';
4547
const hostOptions = {
4648
moduleFederationConfig: {},
4749
mocksFolder: archivePath,
@@ -66,10 +68,37 @@ describe('archiveHandler', () => {
6668
axios.get = vi.fn().mockResolvedValueOnce({ data: zip.toBuffer() });
6769

6870
await downloadTypesArchive(hostOptions)([
69-
'testsHostFolder',
70-
'https://foo.it',
71+
destinationFolder,
72+
fileToDownload,
7173
]);
7274
expect(existsSync(archivePath)).toBeTruthy();
7375
});
76+
77+
it('correctly extracts downloaded archive - skips same zip file', async () => {
78+
const zip = new AdmZip();
79+
await zip.addLocalFolderPromise(tmpDir, {});
80+
81+
axios.get = vi.fn().mockResolvedValue({ data: zip.toBuffer() });
82+
83+
const downloader = downloadTypesArchive(hostOptions);
84+
85+
await downloader([destinationFolder, fileToDownload]);
86+
await downloader([destinationFolder, fileToDownload]);
87+
88+
expect(existsSync(archivePath)).toBeTruthy();
89+
expect(axios.get).toHaveBeenCalledTimes(2);
90+
expect(axios.get.mock.calls[0]).toStrictEqual([
91+
fileToDownload,
92+
{
93+
responseType: 'arraybuffer',
94+
},
95+
]);
96+
expect(axios.get.mock.calls[1]).toStrictEqual([
97+
fileToDownload,
98+
{
99+
responseType: 'arraybuffer',
100+
},
101+
]);
102+
});
74103
});
75104
});

packages/native-federation-tests/src/lib/archiveHandler.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import AdmZip from 'adm-zip';
22
import ansiColors from 'ansi-colors';
33
import axios from 'axios';
4-
import { join } from 'path';
4+
import { createHash } from 'node:crypto';
5+
import { rm } from 'node:fs/promises';
6+
import { join } from 'node:path';
57

68
import { HostOptions } from '../interfaces/HostOptions';
79
import { RemoteOptions } from '../interfaces/RemoteOptions';
@@ -26,8 +28,24 @@ const downloadErrorLogger =
2628
};
2729
};
2830

31+
export const deleteTestsFolder = async (
32+
options: Required<HostOptions> | Required<RemoteOptions>,
33+
destinationPath: string,
34+
) => {
35+
if (options.deleteTestsFolder) {
36+
await rm(destinationPath, {
37+
recursive: true,
38+
force: true,
39+
}).catch((error) =>
40+
console.error(ansiColors.red(`Unable to remove tests folder, ${error}`)),
41+
);
42+
}
43+
};
44+
2945
export const downloadTypesArchive = (hostOptions: Required<HostOptions>) => {
3046
const retriesPerFile: Record<string, number> = {};
47+
const hashPerFile: Record<string, string> = {};
48+
3149
return async ([destinationFolder, fileToDownload]: string[]) => {
3250
retriesPerFile[fileToDownload] = 0;
3351
const destinationPath = join(hostOptions.mocksFolder, destinationFolder);
@@ -38,8 +56,19 @@ export const downloadTypesArchive = (hostOptions: Required<HostOptions>) => {
3856
.get(fileToDownload, { responseType: 'arraybuffer' })
3957
.catch(downloadErrorLogger(destinationFolder, fileToDownload));
4058

41-
const zip = new AdmZip(Buffer.from(response.data));
42-
zip.extractAllTo(destinationPath, true);
59+
const responseBuffer = Buffer.from(response.data);
60+
61+
const hash = createHash('sha256').update(responseBuffer).digest('hex');
62+
63+
if (hashPerFile[fileToDownload] !== hash) {
64+
await deleteTestsFolder(hostOptions, destinationPath);
65+
66+
const zip = new AdmZip(responseBuffer);
67+
zip.extractAllTo(destinationPath, true);
68+
69+
hashPerFile[fileToDownload] = hash;
70+
}
71+
4372
break;
4473
} catch (error: any) {
4574
console.error(

packages/native-federation-tests/src/lib/mocksClean.ts

-16
This file was deleted.

packages/native-federation-typescript/src/index.ts

+10-21
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import ansiColors from 'ansi-colors';
2-
import { rm } from 'fs/promises';
3-
import { resolve } from 'path';
2+
import { resolve } from 'node:path';
43
import { mergeDeepRight } from 'rambda';
54
import { UnpluginOptions, createUnplugin } from 'unplugin';
65

76
import { retrieveHostConfig } from './configurations/hostPlugin';
87
import { retrieveRemoteConfig } from './configurations/remotePlugin';
98
import { HostOptions } from './interfaces/HostOptions';
109
import { RemoteOptions } from './interfaces/RemoteOptions';
11-
import { createTypesArchive, downloadTypesArchive } from './lib/archiveHandler';
10+
import {
11+
createTypesArchive,
12+
deleteTypesFolder,
13+
downloadTypesArchive,
14+
} from './lib/archiveHandler';
1215
import {
1316
compileTs,
1417
retrieveMfTypesPath,
@@ -19,6 +22,7 @@ export const NativeFederationTypeScriptRemote = createUnplugin(
1922
(options: RemoteOptions) => {
2023
const { remoteOptions, tsConfig, mapComponentsToExpose } =
2124
retrieveRemoteConfig(options);
25+
const typesPath = retrieveMfTypesPath(tsConfig, remoteOptions);
2226
return {
2327
name: 'native-federation-typescript/remote',
2428
async writeBundle() {
@@ -27,12 +31,8 @@ export const NativeFederationTypeScriptRemote = createUnplugin(
2731

2832
await createTypesArchive(tsConfig, remoteOptions);
2933

30-
if (remoteOptions.deleteTypesFolder) {
31-
await rm(retrieveMfTypesPath(tsConfig, remoteOptions), {
32-
recursive: true,
33-
force: true,
34-
});
35-
}
34+
await deleteTypesFolder(remoteOptions, typesPath);
35+
3636
console.log(ansiColors.green('Federated types created correctly'));
3737
} catch (error) {
3838
console.error(
@@ -79,21 +79,10 @@ export const NativeFederationTypeScriptRemote = createUnplugin(
7979
export const NativeFederationTypeScriptHost = createUnplugin(
8080
(options: HostOptions) => {
8181
const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options);
82+
const typesDownloader = downloadTypesArchive(hostOptions);
8283
return {
8384
name: 'native-federation-typescript/host',
8485
async writeBundle() {
85-
if (hostOptions.deleteTypesFolder) {
86-
await rm(hostOptions.typesFolder, {
87-
recursive: true,
88-
force: true,
89-
}).catch((error) =>
90-
console.error(
91-
ansiColors.red(`Unable to remove types folder, ${error}`),
92-
),
93-
);
94-
}
95-
96-
const typesDownloader = downloadTypesArchive(hostOptions);
9786
const downloadPromises =
9887
Object.entries(mapRemotesToDownload).map(typesDownloader);
9988

packages/native-federation-typescript/src/lib/archiveHandler.test.ts

+29
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,35 @@ describe('archiveHandler', () => {
7575
});
7676
});
7777

78+
it('correctly extracts downloaded archive - skips same zip file', async () => {
79+
const archivePath = join(tmpDir, destinationFolder);
80+
81+
const zip = new AdmZip();
82+
await zip.addLocalFolderPromise(tmpDir, {});
83+
84+
axios.get = vi.fn().mockResolvedValue({ data: zip.toBuffer() });
85+
86+
const downloader = downloadTypesArchive(hostOptions);
87+
88+
await downloader([destinationFolder, fileToDownload]);
89+
await downloader([destinationFolder, fileToDownload]);
90+
91+
expect(existsSync(archivePath)).toBeTruthy();
92+
expect(axios.get).toHaveBeenCalledTimes(2);
93+
expect(axios.get.mock.calls[0]).toStrictEqual([
94+
fileToDownload,
95+
{
96+
responseType: 'arraybuffer',
97+
},
98+
]);
99+
expect(axios.get.mock.calls[1]).toStrictEqual([
100+
fileToDownload,
101+
{
102+
responseType: 'arraybuffer',
103+
},
104+
]);
105+
});
106+
78107
it('correctly handles exception', async () => {
79108
const message = 'Rejected value';
80109

packages/native-federation-typescript/src/lib/archiveHandler.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import AdmZip from 'adm-zip';
22
import ansiColors from 'ansi-colors';
33
import axios from 'axios';
4-
import { join } from 'path';
4+
import { createHash } from 'node:crypto';
5+
import { rm } from 'node:fs/promises';
6+
import { join } from 'node:path';
57
import typescript from 'typescript';
68

79
import { HostOptions } from '../interfaces/HostOptions';
@@ -36,8 +38,24 @@ const downloadErrorLogger =
3638
};
3739
};
3840

41+
export const deleteTypesFolder = async (
42+
options: Required<HostOptions> | Required<RemoteOptions>,
43+
destinationPath: string,
44+
) => {
45+
if (options.deleteTypesFolder) {
46+
await rm(destinationPath, {
47+
recursive: true,
48+
force: true,
49+
}).catch((error) =>
50+
console.error(ansiColors.red(`Unable to remove types folder, ${error}`)),
51+
);
52+
}
53+
};
54+
3955
export const downloadTypesArchive = (hostOptions: Required<HostOptions>) => {
4056
const retriesPerFile: Record<string, number> = {};
57+
const hashPerFile: Record<string, string> = {};
58+
4159
return async ([destinationFolder, fileToDownload]: string[]) => {
4260
retriesPerFile[fileToDownload] = 0;
4361
const destinationPath = join(hostOptions.typesFolder, destinationFolder);
@@ -48,8 +66,19 @@ export const downloadTypesArchive = (hostOptions: Required<HostOptions>) => {
4866
.get(fileToDownload, { responseType: 'arraybuffer' })
4967
.catch(downloadErrorLogger(destinationFolder, fileToDownload));
5068

51-
const zip = new AdmZip(Buffer.from(response.data));
52-
zip.extractAllTo(destinationPath, true);
69+
const responseBuffer = Buffer.from(response.data);
70+
71+
const hash = createHash('sha256').update(responseBuffer).digest('hex');
72+
73+
if (hashPerFile[fileToDownload] !== hash) {
74+
await deleteTypesFolder(hostOptions, destinationPath);
75+
76+
const zip = new AdmZip(responseBuffer);
77+
zip.extractAllTo(destinationPath, true);
78+
79+
hashPerFile[fileToDownload] = hash;
80+
}
81+
5382
break;
5483
} catch (error: any) {
5584
console.error(

0 commit comments

Comments
 (0)