Skip to content

Commit 1a9c6e7

Browse files
authored
feat: support config multiple shared (#2313)
1 parent 7994276 commit 1a9c6e7

File tree

23 files changed

+597
-946
lines changed

23 files changed

+597
-946
lines changed

.changeset/violet-rice-admire.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@module-federation/webpack-bundler-runtime': patch
3+
'@module-federation/enhanced': patch
4+
'@module-federation/runtime': patch
5+
'@module-federation/sdk': patch
6+
---
7+
8+
feat: support config multiple versions shared

apps/website-new/docs/en/guide/basic/runtime.mdx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,25 @@ type InitOptions = {
7171
// The list of dependencies that the current consumer needs to share
7272
// When using the build plugin, users can configure the dependencies to be shared in the build plugin, and the build plugin will inject the shared dependencies into the runtime sharing configuration
7373
// Shared must be manually passed in the version instance when passed at runtime because it cannot be directly passed at runtime.
74-
shared?: ShareInfos;
74+
shared?: {
75+
[pkgName: string]: ShareArgs | ShareArgs[];
76+
};
7577
};
7678

79+
type ShareArgs =
80+
| (SharedBaseArgs & { get: SharedGetter })
81+
| (SharedBaseArgs & { lib: () => Module });
82+
83+
type SharedBaseArgs = {
84+
version: string;
85+
shareConfig?: SharedConfig;
86+
scope?: string | Array<string>;
87+
deps?: Array<string>;
88+
strategy?: 'version-first' | 'loaded-first';
89+
};
90+
91+
type SharedGetter = (() => () => Module) | (() => Promise<() => Module>);
92+
7793
type RemoteInfo = (RemotesWithEntry | RemotesWithVersion) & {
7894
alias?: string;
7995
};
@@ -139,7 +155,7 @@ loadRemote('app2/util').then((m) => m.add(1, 2, 3));
139155

140156
### loadShare
141157

142-
- Type: `loadShare(pkgName: string)`
158+
- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial<Shared>;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})`
143159
- Obtains the `share` dependency. When a "shared" dependency matching the current consumer exists in the global environment, the existing and eligible dependency will be reused first. Otherwise, it loads its own dependency and stores it in the global cache.
144160
- This `API` is usually not called directly by users but is used by the build plugin to convert its own dependencies.
145161

@@ -180,6 +196,50 @@ loadShare('react').then((reactFactory) => {
180196
});
181197
```
182198

199+
If has set multiple version shared, `loadShare` will return the loaded and has max version shared. The behavior can be controlled by set `extraOptions.resolver`:
200+
201+
```js
202+
import { init, loadRemote, loadShare } from '@module-federation/runtime';
203+
204+
init({
205+
name: '@demo/main-app',
206+
remotes: [],
207+
shared: {
208+
react: [
209+
{
210+
version: '17.0.0',
211+
scope: 'default',
212+
get: async ()=>() => ({ version: '17.0.0)' }),
213+
shareConfig: {
214+
singleton: true,
215+
requiredVersion: '^17.0.0',
216+
},
217+
},
218+
{
219+
version: '18.0.0',
220+
scope: 'default',
221+
// pass lib means the shared has loaded
222+
lib: () => ({ version: '18.0.0)' }),
223+
shareConfig: {
224+
singleton: true,
225+
requiredVersion: '^18.0.0',
226+
},
227+
},
228+
],
229+
},
230+
});
231+
232+
loadShare('react', {
233+
resolver: (sharedOptions) => {
234+
return (
235+
sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0]
236+
);
237+
},
238+
}).then((reactFactory) => {
239+
console.log(reactFactory()); // { version: '17.0.0' }
240+
});
241+
```
242+
183243
### preloadRemote
184244
185245
:::warning

apps/website-new/docs/en/plugin/dev/index.mdx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,21 @@ const customSharedPlugin: () => FederationRuntimePlugin = function () {
380380
return args;
381381
}
382382

383+
// set lib
383384
args.resolver = function () {
384-
shareScopeMap[scope][pkgName][version] = window.React; // Manually replace the local share scope with the desired module
385+
shareScopeMap[scope][pkgName][version] = {
386+
lib: ()=>window.React,
387+
loaded:true,
388+
loading: Promise.resolve(()=>window.React)
389+
}; // Manually replace the local share scope with the desired module
390+
return shareScopeMap[scope][pkgName][version];
391+
};
392+
393+
// set get
394+
args.resolver = function () {
395+
shareScopeMap[scope][pkgName][version] = {
396+
get: async ()=>()=>window.React,
397+
}; // Manually replace the local share scope with the desired module
385398
return shareScopeMap[scope][pkgName][version];
386399
};
387400
return args;
@@ -496,3 +509,5 @@ const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
496509
};
497510
};
498511
```
512+
import { resolve } from "path"
513+

apps/website-new/docs/zh/guide/basic/runtime.mdx

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,25 @@ type InitOptions {
7171
// 当前消费者需要共享的依赖项列表
7272
// 当使用构建插件时,用户可以在构建插件中配置需要共享的依赖项,而构建插件会将需要共享的依赖项注入到运行时共享配置中
7373
// Shared 在运行时传入时必须在版本实例引用中手动传入,因为它不能在运行时直接传入。
74-
shared?: ShareInfos;
74+
shared?: {
75+
[pkgName: string]: ShareArgs | ShareArgs[];
76+
};
77+
};
78+
79+
type ShareArgs =
80+
| (SharedBaseArgs & { get: SharedGetter })
81+
| (SharedBaseArgs & { lib: () => Module });
82+
83+
type SharedBaseArgs = {
84+
version: string;
85+
shareConfig?: SharedConfig;
86+
scope?: string | Array<string>;
87+
deps?: Array<string>;
88+
strategy?: 'version-first' | 'loaded-first';
7589
};
7690

91+
type SharedGetter = (() => () => Module) | (() => Promise<() => Module>);
92+
7793
type RemoteInfo = (RemotesWithEntry | RemotesWithVersion) & {
7894
alias?: string;
7995
};
@@ -139,7 +155,7 @@ loadRemote('app2/util').then((m) => m.add(1, 2, 3));
139155

140156
### loadShare
141157

142-
- Type: `loadShare(pkgName: string)`
158+
- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial<Shared>;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})`
143159
- 获取 `share` 依赖项。当全局环境中存在与当前消费者匹配的“共享”依赖时,现有的和符合共享条件的依赖将首先被复用。否则,加载它自己的依赖项并将它们存储在全局缓存中。
144160
- 这个 `API` 通常不是由用户直接调用,而是由构建插件使用来转换它们自己的依赖项。
145161

@@ -180,6 +196,50 @@ loadShare('react').then((reactFactory) => {
180196
});
181197
```
182198

199+
如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 `extraOptions.resolver` 来改变这个行为:
200+
201+
```js
202+
import { init, loadRemote, loadShare } from '@module-federation/runtime';
203+
204+
init({
205+
name: '@demo/main-app',
206+
remotes: [],
207+
shared: {
208+
react: [
209+
{
210+
version: '17.0.0',
211+
scope: 'default',
212+
get: async ()=>() => ({ version: '17.0.0)' }),
213+
shareConfig: {
214+
singleton: true,
215+
requiredVersion: '^17.0.0',
216+
},
217+
},
218+
{
219+
version: '18.0.0',
220+
scope: 'default',
221+
// pass lib means the shared has loaded
222+
lib: () => ({ version: '18.0.0)' }),
223+
shareConfig: {
224+
singleton: true,
225+
requiredVersion: '^18.0.0',
226+
},
227+
},
228+
],
229+
},
230+
});
231+
232+
loadShare('react', {
233+
resolver: (sharedOptions) => {
234+
return (
235+
sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0]
236+
);
237+
},
238+
}).then((reactFactory) => {
239+
console.log(reactFactory()); // { version: '17.0.0' }
240+
});
241+
```
242+
183243
### preloadRemote
184244
185245
:::warning

apps/website-new/docs/zh/plugin/dev/index.mdx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,21 @@ const customSharedPlugin: () => FederationRuntimePlugin = function () {
381381
return args;
382382
}
383383

384+
// set lib
384385
args.resolver = function () {
385-
shareScopeMap[scope][pkgName][version] = window.React; // replace local share scope manually with desired module
386+
shareScopeMap[scope][pkgName][version] = {
387+
lib: ()=>window.React,
388+
loaded:true,
389+
loading: Promise.resolve(()=>window.React)
390+
}; // Manually replace the local share scope with the desired module
391+
return shareScopeMap[scope][pkgName][version];
392+
};
393+
394+
// set get
395+
args.resolver = function () {
396+
shareScopeMap[scope][pkgName][version] = {
397+
get: async ()=>()=>window.React,
398+
}; // Manually replace the local share scope with the desired module
386399
return shareScopeMap[scope][pkgName][version];
387400
};
388401
return args;

packages/chrome-devtools/src/utils/chrome/fast-refresh.ts

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { FederationRuntimePlugin } from '@module-federation/runtime/types';
1+
import type {
2+
FederationRuntimePlugin,
3+
Shared,
4+
} from '@module-federation/runtime/types';
25
import { loadScript } from '@module-federation/sdk';
36

47
import { isObject, getUnpkgUrl } from '../index';
@@ -29,53 +32,54 @@ const fastRefreshPlugin = (): FederationRuntimePlugin => {
2932
orderResolve = resolve;
3033
});
3134
Object.keys(shareInfo).forEach(async (share) => {
32-
let get: () => any;
33-
if (share === 'react') {
34-
get = () =>
35-
loadScript(
36-
getUnpkgUrl(share, shareInfo[share].version) as string,
37-
{
38-
attrs: { defer: true, async: false },
39-
},
40-
).then(() => {
41-
orderResolve();
42-
});
43-
}
44-
if (share === 'react-dom') {
45-
get = () =>
46-
orderPromise.then(() =>
47-
loadScript(
48-
getUnpkgUrl(share, shareInfo[share].version) as string,
49-
{
50-
attrs: { defer: true, async: false },
51-
},
52-
),
53-
);
54-
}
55-
// @ts-expect-error
56-
if (enableFastRefresh && typeof get === 'function') {
35+
// @ts-expect-error legacy runtime shareInfo[share] is shared , and latest i shard[]
36+
const sharedArr: Shared[] = Array.isArray(shareInfo[share])
37+
? shareInfo[share]
38+
: [shareInfo[share]];
39+
40+
sharedArr.forEach((shared) => {
41+
let get: () => any;
5742
if (share === 'react') {
58-
shareInfo[share].get = async () => {
59-
if (!window.React) {
60-
await get();
61-
console.warn(
62-
'[Module Federation HMR]: You are using Module Federation Devtools to debug online host, it will cause your project load Dev mode React and ReactDOM. If not in this mode, please disable it in Module Federation Devtools',
63-
);
64-
}
65-
shareInfo[share].lib = () => window.React;
66-
return () => window.React;
67-
};
43+
get = () =>
44+
loadScript(getUnpkgUrl(share, shared.version) as string, {
45+
attrs: { defer: true, async: false },
46+
}).then(() => {
47+
orderResolve();
48+
});
6849
}
6950
if (share === 'react-dom') {
70-
shareInfo[share].get = async () => {
71-
if (!window.ReactDOM) {
72-
await get();
73-
}
74-
shareInfo[share].lib = () => window.ReactDOM;
75-
return () => window.ReactDOM;
76-
};
51+
get = () =>
52+
orderPromise.then(() =>
53+
loadScript(getUnpkgUrl(share, shared.version) as string, {
54+
attrs: { defer: true, async: false },
55+
}),
56+
);
57+
}
58+
// @ts-expect-error
59+
if (enableFastRefresh && typeof get === 'function') {
60+
if (share === 'react') {
61+
shared.get = async () => {
62+
if (!window.React) {
63+
await get();
64+
console.warn(
65+
'[Module Federation HMR]: You are using Module Federation Devtools to debug online host, it will cause your project load Dev mode React and ReactDOM. If not in this mode, please disable it in Module Federation Devtools',
66+
);
67+
}
68+
shared.lib = () => window.React;
69+
return () => window.React;
70+
};
71+
}
72+
if (share === 'react-dom') {
73+
shared.get = async () => {
74+
if (!window.ReactDOM) {
75+
await get();
76+
}
77+
shared.lib = () => window.ReactDOM;
78+
return () => window.ReactDOM;
79+
};
80+
}
7781
}
78-
}
82+
});
7983
});
8084

8185
return {

packages/enhanced/src/lib/sharing/ShareRuntimeModule.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ShareRuntimeModule extends RuntimeModule {
3939
throw new Error('ChunkGraph is undefined');
4040
}
4141
const initCodePerScope: Map<string, Map<number, Set<string>>> = new Map();
42-
let sharedInitOptionsStr = '';
42+
const sharedInitOptions: Record<string, any[]> = {};
4343

4444
for (const chunk of this.chunk?.getAllReferencedChunks() || []) {
4545
if (!chunk) {
@@ -77,17 +77,39 @@ class ShareRuntimeModule extends RuntimeModule {
7777
'share-init-option',
7878
);
7979
if (sharedOption) {
80-
sharedInitOptionsStr += `
81-
"${sharedOption.name}" : {
82-
version: ${sharedOption.version},
83-
get: ${sharedOption.getter},
84-
scope: ${JSON.stringify(sharedOption.shareScope)},
85-
shareConfig: ${JSON.stringify(sharedOption.shareConfig)}
86-
},
87-
`;
80+
sharedInitOptions[sharedOption.name] =
81+
sharedInitOptions[sharedOption.name] || [];
82+
const isSameVersion = sharedInitOptions[sharedOption.name].find(
83+
(s) => s.version === sharedOption.version,
84+
);
85+
if (!isSameVersion) {
86+
sharedInitOptions[sharedOption.name].push(sharedOption);
87+
}
8888
}
8989
}
9090
}
91+
92+
const sharedInitOptionsStr = Object.keys(sharedInitOptions).reduce(
93+
(sum, sharedName) => {
94+
const sharedOptions = sharedInitOptions[sharedName];
95+
let str = '';
96+
sharedOptions.forEach((sharedOption) => {
97+
str += `{${Template.indent([
98+
`version: ${sharedOption.version},`,
99+
`get: ${sharedOption.getter},`,
100+
`scope: ${JSON.stringify(sharedOption.shareScope)},`,
101+
`shareConfig: ${JSON.stringify(sharedOption.shareConfig)}`,
102+
])}},`;
103+
});
104+
str = `[${str}]`;
105+
106+
sum += `${Template.indent([`"${sharedName}": ${str},`])}`;
107+
108+
return sum;
109+
},
110+
'',
111+
);
112+
91113
const federationGlobal = getFederationGlobalScope(
92114
RuntimeGlobals || ({} as typeof RuntimeGlobals),
93115
);

0 commit comments

Comments
 (0)