Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-boats-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/runtime': patch
---

feat: add registerRemotes api
73 changes: 70 additions & 3 deletions packages/runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,15 @@ init({
remotes: [
{
name: '@demo/sub1',
entry: 'http://localhost:2001/vmok-manifest.json',
entry: 'http://localhost:2001/mf-manifest.json',
},
{
name: '@demo/sub2',
entry: 'http://localhost:2001/vmok-manifest.json',
entry: 'http://localhost:2001/mf-manifest.json',
},
{
name: '@demo/sub3',
entry: 'http://localhost:2001/vmok-manifest.json',
entry: 'http://localhost:2001/mf-manifest.json',
},
],
});
Expand Down Expand Up @@ -258,6 +258,73 @@ preloadRemote([
]);
```

### registerRemotes

- Type: `registerRemotes(remotes: Remote[], options?: { force?: boolean }): void`
- Used to register remotes after init .

- Type

```typescript
function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {}

type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon;

interface RemoteInfoCommon {
alias?: string;
shareScope?: string;
type?: RemoteEntryType;
entryGlobalName?: string;
}

interface RemoteWithEntry {
name: string;
entry: string;
}

interface RemoteWithVersion {
name: string;
version: string;
}
```

- Details
**info**: Please be careful when setting `force:true` !

If set `force: true`, it will merge remote(include loaded remote), and remove loaded remote cache , as well as console.warn to tell this action may have risks.

* Example

```ts
import { init, registerRemotes } from '@module-federation/runtime';

init({
name: '@demo/register-new-remotes',
remotes: [
{
name: '@demo/sub1',
entry: 'http://localhost:2001/mf-manifest.json',
}
],
});

// add new remote @demo/sub2
registerRemotes([
{
name: '@demo/sub2',
entry: 'http://localhost:2002/mf-manifest.json',
}
]);

// override previous remote @demo/sub1
registerRemotes([
{
name: '@demo/sub1',
entry: 'http://localhost:2003/mf-manifest.json',
}
]);
```

## hooks

Lifecycle hooks for FederationHost interaction.
Expand Down
108 changes: 108 additions & 0 deletions packages/runtime/__tests__/register-remotes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { assert, describe, test, it } from 'vitest';
import { FederationHost } from '../src/index';

describe('FederationHost', () => {
it('register new remotes', async () => {
const FM = new FederationHost({
name: '@federation/instance',
version: '1.0.1',
remotes: [
{
name: '@register-remotes/app1',
entry:
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry.js',
},
],
});

const app1Module = await FM.loadRemote<Promise<() => string>>(
'@register-remotes/app1/say',
);
assert(app1Module);
const app1Res = await app1Module();
expect(app1Res).toBe('hello app1 entry1');

// register new remotes
FM.registerRemotes([
{
name: '@register-remotes/app2',
entry:
'http://localhost:1111/resources/register-remotes/app2/federation-remote-entry.js',
},
]);
const app2Module = await FM.loadRemote<Promise<() => string>>(
'@register-remotes/app2/say',
);
assert(app2Module);
const res = await app2Module();
expect(res).toBe('hello app2');
});

it('will not merge loaded remote by default', async () => {
const FM = new FederationHost({
name: '@federation/instance',
version: '1.0.1',
remotes: [
{
name: '@register-remotes/app1',
entry:
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry.js',
},
],
});
FM.registerRemotes([
{
name: '@register-remotes/app1',
// entry is different from the registered remote
entry:
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry2.js',
},
]);

const app1Module = await FM.loadRemote<Promise<() => string>>(
'@register-remotes/app1/say',
);
assert(app1Module);
const app1Res = await app1Module();
expect(app1Res).toBe('hello app1 entry1');
});

it('merge loaded remote by setting "force:true"', async () => {
const FM = new FederationHost({
name: '@federation/instance',
version: '1.0.1',
remotes: [
{
name: '@register-remotes/app1',
entry:
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry.js',
},
],
});
const app1Module = await FM.loadRemote<Promise<() => string>>(
'@register-remotes/app1/say',
);
assert(app1Module);
const app1Res = await app1Module();
expect(app1Res).toBe('hello app1 entry1');

FM.registerRemotes(
[
{
name: '@register-remotes/app1',
// entry is different from the registered remote
entry:
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry2.js',
},
],
{ force: true },
);
const newApp1Module = await FM.loadRemote<Promise<() => string>>(
'@register-remotes/app1/say',
);
assert(newApp1Module);
const newApp1Res = await newApp1Module();
// value is different from the registered remote
expect(newApp1Res).toBe('hello app1 entry2');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
globalThis[`@register-remotes/app1`] = {
get(scope) {
const moduleMap = {
'./say'() {
return () => 'hello app1 entry1';
},
};
return moduleMap[scope];
},
init() {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
globalThis[`@register-remotes/app1`] = {
get(scope) {
const moduleMap = {
'./say'() {
return () => 'hello app1 entry2';
},
};
return moduleMap[scope];
},
init() {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
globalThis[`@register-remotes/app2`] = {
get(scope) {
const moduleMap = {
'./say'() {
return () => 'hello app2';
},
};
return moduleMap[scope];
},
init() {},
};
6 changes: 6 additions & 0 deletions packages/runtime/src/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ initializeSharing(shareScopeName?: string): boolean | Promise<boolean>
```
Initializes sharing sequences for shared scopes.

### `registerRemotes`
```typescript
registerRemotes(remotes: Remote[], options?: { force?: boolean }): void
```
Register remotes after init.

## Hooks
`FederationHost` offers various lifecycle hooks for interacting at different stages of the module federation process. These hooks include:

Expand Down
Loading