Skip to content

Commit 35c3695

Browse files
authored
perf: use jiti to load mf config (#4219)
1 parent 94d8868 commit 35c3695

File tree

12 files changed

+386
-23
lines changed

12 files changed

+386
-23
lines changed

.changeset/sour-swans-tell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@module-federation/cli': patch
3+
---
4+
5+
perf(cli): use jiti to replace modernjs utils to load config

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"@rollup/plugin-replace": "6.0.1",
127127
"@rslib/core": "^0.10.4",
128128
"@rspack/core": "1.3.9",
129+
"@rstest/core": "^0.6.5",
129130
"@rspack/dev-server": "1.1.1",
130131
"@semantic-release/changelog": "^6.0.3",
131132
"@semantic-release/exec": "^6.0.3",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`readConfig integration tests > readConfig with real jiti > should handle config with complex TypeScript features 1`] = `
4+
{
5+
"exposes": {
6+
"./Button": "./src/components/Button.tsx",
7+
"./Footer": "./src/components/Footer.tsx",
8+
"./Header": "./src/components/Header.tsx",
9+
},
10+
"filename": "remoteEntry.js",
11+
"name": "complex-mf-app",
12+
"remotes": {
13+
"remote-app-1": "remote-app-1@http://localhost:3001/remoteEntry.js",
14+
"remote-app-2": "remote-app-2@http://localhost:3002/remoteEntry.js",
15+
},
16+
"shared": {
17+
"lodash": {
18+
"singleton": false,
19+
"strictVersion": true,
20+
},
21+
"react": {
22+
"requiredVersion": "^18.0.0",
23+
"singleton": true,
24+
},
25+
"react-dom": {
26+
"requiredVersion": "^18.0.0",
27+
"singleton": true,
28+
},
29+
},
30+
"sharedScope": "default",
31+
}
32+
`;
33+
34+
exports[`readConfig integration tests > readConfig with real jiti > should handle config with complex TypeScript features 2`] = `
35+
{
36+
"exposes": {
37+
"./Button": "./src/components/Button.tsx",
38+
"./Header": "./src/components/Header.tsx",
39+
},
40+
"filename": "remoteEntry.js",
41+
"name": "test-mf-app",
42+
"remotes": {
43+
"remote-app": "remoteApp@http://localhost:3001/remoteEntry.js",
44+
},
45+
"shared": {
46+
"react": {
47+
"requiredVersion": "^18.0.0",
48+
"singleton": true,
49+
},
50+
"react-dom": {
51+
"requiredVersion": "^18.0.0",
52+
"singleton": true,
53+
},
54+
},
55+
}
56+
`;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Test TypeScript features that jiti should handle
2+
interface SharedConfig {
3+
singleton?: boolean;
4+
requiredVersion?: string;
5+
strictVersion?: boolean;
6+
}
7+
8+
interface RemoteConfig {
9+
url: string;
10+
format?: 'esm' | 'var';
11+
}
12+
13+
enum ExposedModules {
14+
Button = './Button',
15+
Header = './Header',
16+
Footer = './Footer',
17+
}
18+
19+
const sharedDependencies: Record<string, SharedConfig> = {
20+
react: { singleton: true, requiredVersion: '^18.0.0' },
21+
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
22+
lodash: { singleton: false, strictVersion: true },
23+
};
24+
25+
const remoteApps: Record<string, RemoteConfig> = {
26+
'remote-app-1': {
27+
url: 'http://localhost:3001/remoteEntry.js',
28+
format: 'esm',
29+
},
30+
'remote-app-2': {
31+
url: 'http://localhost:3002/remoteEntry.js',
32+
format: 'var',
33+
},
34+
};
35+
36+
export default {
37+
name: 'complex-mf-app',
38+
filename: 'remoteEntry.js',
39+
exposes: {
40+
[ExposedModules.Button]: './src/components/Button.tsx',
41+
[ExposedModules.Header]: './src/components/Header.tsx',
42+
[ExposedModules.Footer]: './src/components/Footer.tsx',
43+
},
44+
shared: sharedDependencies,
45+
remotes: Object.entries(remoteApps).reduce(
46+
(acc, [name, config]) => {
47+
acc[name] = `${name}@${config.url}`;
48+
return acc;
49+
},
50+
{} as Record<string, string>,
51+
),
52+
// Test generic types
53+
sharedScope: createSharedScope<'default'>(),
54+
};
55+
56+
// Test generic function
57+
function createSharedScope<T extends string>(): T {
58+
return 'default' as T;
59+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default {
2+
name: 'test-mf-app',
3+
filename: 'remoteEntry.js',
4+
exposes: {
5+
'./Button': './src/components/Button.tsx',
6+
'./Header': './src/components/Header.tsx',
7+
},
8+
shared: {
9+
react: { singleton: true, requiredVersion: '^18.0.0' },
10+
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
11+
},
12+
remotes: {
13+
'remote-app': 'remoteApp@http://localhost:3001/remoteEntry.js',
14+
},
15+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, it, expect } from '@rstest/core';
2+
import path from 'path';
3+
import { readConfig } from '../src/utils/readConfig';
4+
5+
describe('readConfig integration tests', () => {
6+
describe('readConfig with real jiti', () => {
7+
it('should handle config with complex TypeScript features', async () => {
8+
const resultComplex = await readConfig(
9+
path.join(__dirname, 'fixtures', 'complex-config.ts'),
10+
);
11+
expect(resultComplex).toMatchSnapshot();
12+
13+
const result = await readConfig(
14+
path.join(__dirname, 'fixtures', 'test-config.ts'),
15+
);
16+
expect(result).toMatchSnapshot();
17+
});
18+
});
19+
});

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"@module-federation/dts-plugin": "workspace:*",
2828
"commander": "11.1.0",
2929
"chalk": "3.0.0",
30-
"@modern-js/node-bundle-require": "2.68.2"
30+
"jiti": "2.4.2"
3131
},
3232
"devDependencies": {
3333
"@types/node": "~16.11.7"

packages/cli/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"parallel": false,
3737
"commands": [
3838
{
39-
"command": "echo 'waiting for adding test case...'",
39+
"command": "rstest run -c packages/cli/rstest.config.ts",
4040
"forwardAllArgs": false
4141
}
4242
]

packages/cli/rstest.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from '@rstest/core';
2+
import path from 'path';
3+
4+
export default defineConfig({
5+
testEnvironment: 'node',
6+
include: [path.resolve(__dirname, '__tests__/**.test.ts')],
7+
globals: true,
8+
testTimeout: 10000,
9+
});
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import path from 'path';
2-
import { bundle } from '@modern-js/node-bundle-require';
32
import type { moduleFederationPlugin } from '@module-federation/sdk';
4-
import { pathToFileURL } from 'url';
53

4+
const { createJiti } = require('jiti');
65
const DEFAULT_CONFIG_PATH = 'module-federation.config.ts';
76

87
export const getConfigPath = (userConfigPath?: string) => {
@@ -15,8 +14,18 @@ export const getConfigPath = (userConfigPath?: string) => {
1514

1615
export async function readConfig(userConfigPath?: string) {
1716
const configPath = getConfigPath(userConfigPath);
18-
const preBundlePath = await bundle(configPath);
19-
const mfConfig = (await import(pathToFileURL(preBundlePath).href)).default
20-
.default as unknown as moduleFederationPlugin.ModuleFederationPluginOptions;
21-
return mfConfig;
17+
const jit = createJiti(__filename, {
18+
interopDefault: true,
19+
esmResolve: true,
20+
});
21+
const configModule = await jit(configPath);
22+
const resolvedConfig = (
23+
configModule &&
24+
typeof configModule === 'object' &&
25+
'default' in configModule
26+
? (configModule as { default: unknown }).default
27+
: configModule
28+
) as moduleFederationPlugin.ModuleFederationPluginOptions;
29+
30+
return resolvedConfig;
2231
}

0 commit comments

Comments
 (0)