Skip to content

Commit d30b864

Browse files
authored
fix(runtime): hooks loadEntry default behavior (#3030)
1 parent fac6ecf commit d30b864

File tree

4 files changed

+370
-23
lines changed

4 files changed

+370
-23
lines changed

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,136 @@ const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
546546
};
547547
```
548548

549+
### loadEntry
550+
The `loadEntry` function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation.
551+
552+
`asyncHook`
553+
554+
- **Type**
555+
556+
```typescript
557+
function createScript(args: LoadEntryOptions): HTMLScriptElement | {script?: HTMLScriptElement, timeout?: number } | void;
558+
559+
type LoadEntryOptions = {
560+
createScriptHook: SyncHook,
561+
remoteEntryExports?: RemoteEntryExports,
562+
remoteInfo: RemoteInfo
563+
};
564+
interface RemoteInfo {
565+
name: string;
566+
version?: string;
567+
buildVersion?: string;
568+
entry: string;
569+
type: RemoteEntryType;
570+
entryGlobalName: string;
571+
shareScope: string;
572+
}
573+
export type RemoteEntryExports = {
574+
get: (id: string) => () => Promise<Module>;
575+
init: (
576+
shareScope: ShareScopeMap[string],
577+
initScope?: InitScope,
578+
remoteEntryInitOPtions?: RemoteEntryInitOptions,
579+
) => void | Promise<void>;
580+
};
581+
```
582+
583+
- Example Loading JSON Data
584+
585+
```typescript
586+
// load-json-data-plugin.ts
587+
import { init } from '@module-federation/enhanced/runtime';
588+
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
589+
590+
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
591+
return {
592+
name: 'load-json-data-plugin',
593+
loadEntry({ remoteInfo }) {
594+
if (remoteInfo.jsonA === "jsonA") {
595+
return {
596+
init(shareScope, initScope, remoteEntryInitOPtions) {},
597+
async get(path) {
598+
const json = await fetch(remoteInfo.entry + ".json").then(res => res.json())
599+
return () => ({
600+
path,
601+
json
602+
})
603+
}
604+
}
605+
}
606+
},
607+
};
608+
};
609+
```
610+
```ts
611+
// module-federation-config
612+
{
613+
remotes: {
614+
jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package"
615+
}
616+
}
617+
```
618+
```ts
619+
// src/bootstrap.js
620+
import jsonA from "jsonA"
621+
jsonA // {...json data}
622+
```
623+
624+
- Exmaple Delegate Modules
625+
626+
```typescript
627+
// delegate-modules-plugin.ts
628+
import { init } from '@module-federation/enhanced/runtime';
629+
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
630+
631+
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
632+
return {
633+
name: 'delegate-modules-plugin',
634+
loadEntry({ remoteInfo }) {
635+
if (remoteInfo.name === "delegateModulesA") {
636+
return {
637+
init(shareScope, initScope, remoteEntryInitOPtions) {},
638+
async get(path) {
639+
path = path.replace("./", "")
640+
const {[path]: factory} = await import("./delegateModulesA.js")
641+
const result = await factory()
642+
return () => result
643+
}
644+
}
645+
}
646+
},
647+
};
648+
};
649+
```
650+
```ts
651+
// ./src/delegateModulesA.js
652+
export async function test1() {
653+
return new Promise(resolve => {
654+
setTimeout(() => {
655+
resolve("test1 value")
656+
}, 3000)
657+
})
658+
}
659+
export async function test2() {
660+
return new Promise(resolve => {
661+
setTimeout(() => {
662+
resolve("test2 value")
663+
}, 3000)
664+
})
665+
}
666+
```
667+
```ts
668+
// module-federation-config
669+
{
670+
remotes: {
671+
delegateModulesA: "delegateModulesA@https://delegateModulesA.js"
672+
}
673+
}
674+
```
675+
```ts
676+
// src/bootstrap.js
677+
import test1 from "delegateModulesA/test1"
678+
import test2 from "delegateModulesA/test2"
679+
test1 // "test1 value"
680+
test2 // "test2 value"
681+
```

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

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,138 @@ const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
543543
};
544544
};
545545
```
546+
547+
### loadEntry
548+
可以完全自定义remote, 可以扩展新的remote类型。
549+
下面两个简单的例子分别实现了加载json数据和模块代理
550+
551+
`asyncHook`
552+
553+
- 类型
554+
555+
```typescript
556+
function createScript(args: LoadEntryOptions): HTMLScriptElement | {script?: HTMLScriptElement, timeout?: number } | void;
557+
558+
type LoadEntryOptions = {
559+
createScriptHook: SyncHook,
560+
remoteEntryExports?: RemoteEntryExports,
561+
remoteInfo: RemoteInfo
562+
};
563+
interface RemoteInfo {
564+
name: string;
565+
version?: string;
566+
buildVersion?: string;
567+
entry: string;
568+
type: RemoteEntryType;
569+
entryGlobalName: string;
570+
shareScope: string;
571+
}
572+
export type RemoteEntryExports = {
573+
get: (id: string) => () => Promise<Module>;
574+
init: (
575+
shareScope: ShareScopeMap[string],
576+
initScope?: InitScope,
577+
remoteEntryInitOPtions?: RemoteEntryInitOptions,
578+
) => void | Promise<void>;
579+
};
580+
```
581+
582+
- 示例(加载json数据)
583+
584+
```typescript
585+
// load-json-data-plugin.ts
586+
import { init } from '@module-federation/enhanced/runtime';
587+
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
588+
589+
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
590+
return {
591+
name: 'load-json-data-plugin',
592+
loadEntry({ remoteInfo }) {
593+
if (remoteInfo.jsonA === "jsonA") {
594+
return {
595+
init(shareScope, initScope, remoteEntryInitOPtions) {},
596+
async get(path) {
597+
const json = await fetch(remoteInfo.entry + ".json").then(res => res.json())
598+
return () => ({
599+
path,
600+
json
601+
})
602+
}
603+
}
604+
}
605+
},
606+
};
607+
};
608+
```
609+
```ts
610+
// module-federation-config
611+
{
612+
remotes: {
613+
jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package"
614+
}
615+
}
616+
```
617+
```ts
618+
// src/bootstrap.js
619+
import jsonA from "jsonA"
620+
jsonA // {...json data}
621+
```
622+
623+
- 示例(模块代理)
624+
625+
```typescript
626+
// delegate-modules-plugin.ts
627+
import { init } from '@module-federation/enhanced/runtime';
628+
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
629+
630+
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
631+
return {
632+
name: 'delegate-modules-plugin',
633+
loadEntry({ remoteInfo }) {
634+
if (remoteInfo.name === "delegateModulesA") {
635+
return {
636+
init(shareScope, initScope, remoteEntryInitOPtions) {},
637+
async get(path) {
638+
path = path.replace("./", "")
639+
const {[path]: factory} = await import("./delegateModulesA.js")
640+
const result = await factory()
641+
return () => result
642+
}
643+
}
644+
}
645+
},
646+
};
647+
};
648+
```
649+
```ts
650+
// ./src/delegateModulesA.js
651+
export async function test1() {
652+
return new Promise(resolve => {
653+
setTimeout(() => {
654+
resolve("test1 value")
655+
}, 3000)
656+
})
657+
}
658+
export async function test2() {
659+
return new Promise(resolve => {
660+
setTimeout(() => {
661+
resolve("test2 value")
662+
}, 3000)
663+
})
664+
}
665+
```
666+
```ts
667+
// module-federation-config
668+
{
669+
remotes: {
670+
delegateModulesA: "delegateModulesA@https://delegateModulesA.js"
671+
}
672+
}
673+
```
674+
```ts
675+
// src/bootstrap.js
676+
import test1 from "delegateModulesA/test1"
677+
import test2 from "delegateModulesA/test2"
678+
test1 // "test1 value"
679+
test2 // "test2 value"
680+
```

packages/runtime/__tests__/hooks.spec.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,91 @@ describe('hooks', () => {
266266
assert(res);
267267
expect(res()).toBe('hello app2');
268268
});
269+
270+
it('loaderEntry hooks', async () => {
271+
const data = {
272+
id: '@loader-hooks/app2',
273+
name: '@loader-hooks/app2',
274+
metaData: {
275+
name: '@loader-hooks/app2',
276+
publicPath: 'http://localhost:1111/',
277+
type: 'app',
278+
buildInfo: {
279+
buildVersion: 'custom',
280+
},
281+
remoteEntry: {
282+
name: 'federation-remote-entry.js',
283+
path: 'resources/hooks/app2/',
284+
},
285+
types: {
286+
name: 'index.d.ts',
287+
path: './',
288+
},
289+
globalName: '@loader-hooks/app2',
290+
},
291+
remotes: [],
292+
shared: [],
293+
exposes: [],
294+
};
295+
296+
const responseBody = new Response(JSON.stringify(data), {
297+
status: 200,
298+
statusText: 'OK',
299+
headers: { 'Content-Type': 'application/json' },
300+
});
301+
302+
const fetchPlugin: () => FederationRuntimePlugin = function () {
303+
return {
304+
name: 'fetch-plugin',
305+
fetch(url, options) {
306+
if (
307+
url === 'http://mockxxx.com/loader-fetch-hooks-mf-manifest.json'
308+
) {
309+
return Promise.resolve(responseBody);
310+
}
311+
},
312+
};
313+
};
314+
const loadEntryPlugin = function (): FederationRuntimePlugin {
315+
return {
316+
name: 'load-entry-plugin',
317+
loadEntry({ remoteInfo }) {
318+
if (remoteInfo.name === '@loader-hooks/app3') {
319+
return {
320+
init() {},
321+
get(path) {
322+
return () => path;
323+
},
324+
};
325+
}
326+
},
327+
} as any;
328+
};
329+
330+
const INSTANCE = new FederationHost({
331+
name: '@loader-hooks/fetch',
332+
remotes: [
333+
{
334+
name: '@loader-hooks/app2',
335+
entry: 'http://mockxxx.com/loader-fetch-hooks-mf-manifest.json',
336+
},
337+
{
338+
name: '@loader-hooks/app3',
339+
entry: 'http://mockxxx.com/loader-fetch-hooks-mf-manifest.json',
340+
},
341+
],
342+
plugins: [fetchPlugin(), loadEntryPlugin()],
343+
});
344+
345+
const res = await INSTANCE.loadRemote<() => string>(
346+
'@loader-hooks/app2/say',
347+
);
348+
assert(res);
349+
expect(res()).toBe('hello app2');
350+
const loadEntryTestRes = await INSTANCE.loadRemote<() => string>(
351+
'@loader-hooks/app3/testtest',
352+
);
353+
assert(loadEntryTestRes);
354+
expect(loadEntryTestRes).toBe('./testtest');
355+
});
269356
});

0 commit comments

Comments
 (0)