Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support proxy bridge export #2875

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
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
7 changes: 7 additions & 0 deletions .changeset/great-feet-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@module-federation/bridge-react': patch
'@module-federation/bridge-vue3': patch
'@module-federation/runtime': patch
---

feat: support module isolated reported
118 changes: 64 additions & 54 deletions packages/bridge/bridge-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ type ProviderFnParams<T> = {
id?: HTMLElement | string,
) => RootType | Promise<RootType>;
};
type Provider<T> = {
render(info: any): Promise<void>;
destroy(info: { dom: HTMLElement }): Promise<void>;
rawComponent: React.ComponentType<T>;
__BRIDGE_FN__: (_args: T) => void;
};

export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
let provider: Provider<T>;
return () => {
const rootMap = new Map<any, RootType>();
const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -36,63 +43,66 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
);
};

return {
async render(info: RenderFnParams & any) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const {
moduleName,
dom,
basename,
memoryRoute,
fallback,
...propsInfo
} = info;
const rootComponentWithErrorBoundary = (
// set ErrorBoundary for RawComponent rendering error, usually caused by user app rendering error
<ErrorBoundary FallbackComponent={fallback}>
<RawComponent
appInfo={{
moduleName,
basename,
memoryRoute,
}}
propsInfo={propsInfo}
/>
</ErrorBoundary>
);
if (!provider) {
provider = {
async render(info: RenderFnParams & any) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const {
moduleName,
dom,
basename,
memoryRoute,
fallback,
...propsInfo
} = info;
const rootComponentWithErrorBoundary = (
// set ErrorBoundary for RawComponent rendering error, usually caused by user app rendering error
<ErrorBoundary FallbackComponent={fallback}>
<RawComponent
appInfo={{
moduleName,
basename,
memoryRoute,
}}
propsInfo={propsInfo}
/>
</ErrorBoundary>
);

if (atLeastReact18(React)) {
if (bridgeInfo?.render) {
// in case bridgeInfo?.render is an async function, resolve this to promise
Promise.resolve(
bridgeInfo?.render(rootComponentWithErrorBoundary, dom),
).then((root: RootType) => rootMap.set(info.dom, root));
if (atLeastReact18(React)) {
if (bridgeInfo?.render) {
// in case bridgeInfo?.render is an async function, resolve this to promise
Promise.resolve(
bridgeInfo?.render(rootComponentWithErrorBoundary, dom),
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
).then((root: RootType) => rootMap.set(info.dom, root));
} else {
const root: RootType = ReactDOMClient.createRoot(info.dom);
root.render(rootComponentWithErrorBoundary);
rootMap.set(info.dom, root);
}
} else {
const root: RootType = ReactDOMClient.createRoot(info.dom);
root.render(rootComponentWithErrorBoundary);
rootMap.set(info.dom, root);
// react 17 render
const renderFn = bridgeInfo?.render || ReactDOM.render;
renderFn?.(rootComponentWithErrorBoundary, info.dom);
}
} else {
// react 17 render
const renderFn = bridgeInfo?.render || ReactDOM.render;
renderFn?.(rootComponentWithErrorBoundary, info.dom);
}
},
async destroy(info: { dom: HTMLElement }) {
LoggerInstance.log(`createBridgeComponent destroy Info`, {
dom: info.dom,
});
if (atLeastReact18(React)) {
const root = rootMap.get(info.dom);
(root as ReactDOMClient.Root)?.unmount();
rootMap.delete(info.dom);
} else {
ReactDOM.unmountComponentAtNode(info.dom);
}
},
rawComponent: bridgeInfo.rootComponent,
__BRIDGE_FN__: (_args: T) => {},
};
},
async destroy(info: { dom: HTMLElement }) {
LoggerInstance.log(`createBridgeComponent destroy Info`, {
dom: info.dom,
});
if (atLeastReact18(React)) {
const root = rootMap.get(info.dom);
(root as ReactDOMClient.Root)?.unmount();
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
rootMap.delete(info.dom);
} else {
ReactDOM.unmountComponentAtNode(info.dom);
}
},
rawComponent: bridgeInfo.rootComponent,
__BRIDGE_FN__: (_args: T) => {},
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
};
}
return provider;
};
}

Expand Down
83 changes: 46 additions & 37 deletions packages/bridge/vue3-bridge/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,59 @@ import { RenderFnParams } from '@module-federation/bridge-shared';
import { LoggerInstance } from './utils';

declare const __APP_VERSION__: string;
type Provider = {
__APP_VERSION__: string;
render(info: RenderFnParams): void;
destroy(info: { dom: HTMLElement }): void;
};

export function createBridgeComponent(bridgeInfo: any) {
let provider: Provider;
const rootMap = new Map();
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
return () => {
return {
__APP_VERSION__,
render(info: RenderFnParams) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const app = Vue.createApp(bridgeInfo.rootComponent);
rootMap.set(info.dom, app);
const appOptions = bridgeInfo.appOptions({
basename: info.basename,
memoryRoute: info.memoryRoute,
});
if (!provider) {
provider = {
__APP_VERSION__,
render(info: RenderFnParams) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const app = Vue.createApp(bridgeInfo.rootComponent);
rootMap.set(info.dom, app);
const appOptions = bridgeInfo.appOptions({
basename: info.basename,
memoryRoute: info.memoryRoute,
});

const history = info.memoryRoute
? VueRouter.createMemoryHistory(info.basename)
: VueRouter.createWebHistory(info.basename);
const router = VueRouter.createRouter({
...appOptions.router.options,
history,
routes: appOptions.router.getRoutes(),
});
const history = info.memoryRoute
? VueRouter.createMemoryHistory(info.basename)
: VueRouter.createWebHistory(info.basename);
const router = VueRouter.createRouter({
...appOptions.router.options,
history,
routes: appOptions.router.getRoutes(),
});

LoggerInstance.log(`createBridgeComponent render router info>>>`, {
name: info.moduleName,
router,
});
// memory route Initializes the route
if (info.memoryRoute) {
router.push(info.memoryRoute.entryPath).then(() => {
LoggerInstance.log(`createBridgeComponent render router info>>>`, {
name: info.moduleName,
router,
});
// memory route Initializes the route
if (info.memoryRoute) {
router.push(info.memoryRoute.entryPath).then(() => {
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
app.use(router);
app.mount(info.dom);
});
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
} else {
app.use(router);
app.mount(info.dom);
});
} else {
app.use(router);
app.mount(info.dom);
}
},
destroy(info: { dom: HTMLElement }) {
LoggerInstance.log(`createBridgeComponent destroy Info`, info);
const root = rootMap.get(info?.dom);
root?.unmount();
},
};
}
},
destroy(info: { dom: HTMLElement }) {
LoggerInstance.log(`createBridgeComponent destroy Info`, info);
const root = rootMap.get(info?.dom);
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
root?.unmount();
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
},
};
}
return provider;
};
}
37 changes: 25 additions & 12 deletions packages/runtime/src/plugins/snapshot/SnapshotHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export class SnapshotHandler {
remoteSnapshot: ModuleInfo;
from: 'global' | 'manifest';
}>('loadRemoteSnapshot'),
afterLoadSnapshot: new AsyncWaterfallHook<{
options: Options;
moduleInfo: Remote;
remoteSnapshot: ModuleInfo;
}>('afterLoadSnapshot'),
});
loaderHook: FederationHost['loaderHook'];
manifestLoading: Record<string, Promise<ModuleInfo>> =
Expand Down Expand Up @@ -189,6 +194,8 @@ export class SnapshotHandler {
globalSnapshot,
});

let mSnapshot;
let gSnapshot;
// global snapshot includes manifest or module info includes manifest
if (globalRemoteSnapshot) {
if (isManifestProvider(globalRemoteSnapshot)) {
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -212,10 +219,8 @@ export class SnapshotHandler {
},
moduleSnapshot,
);
return {
remoteSnapshot: moduleSnapshot,
globalSnapshot: globalSnapshotRes,
};
mSnapshot = moduleSnapshot;
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
gSnapshot = globalSnapshotRes;
} else {
const { remoteSnapshot: remoteSnapshotRes } =
await this.hooks.lifecycle.loadRemoteSnapshot.emit({
Expand All @@ -224,10 +229,8 @@ export class SnapshotHandler {
remoteSnapshot: globalRemoteSnapshot,
from: 'global',
});
return {
remoteSnapshot: remoteSnapshotRes,
globalSnapshot: globalSnapshotRes,
};
mSnapshot = remoteSnapshotRes;
gSnapshot = globalSnapshotRes;
}
} else {
if (isRemoteInfoWithEntry(moduleInfo)) {
Expand All @@ -249,10 +252,9 @@ export class SnapshotHandler {
remoteSnapshot: moduleSnapshot,
from: 'global',
});
return {
remoteSnapshot: remoteSnapshotRes,
globalSnapshot: globalSnapshotRes,
};

mSnapshot = remoteSnapshotRes;
gSnapshot = globalSnapshotRes;
} else {
error(`
Cannot get remoteSnapshot with the name: '${
Expand All @@ -268,6 +270,17 @@ export class SnapshotHandler {
`);
}
}

await this.hooks.lifecycle.afterLoadSnapshot.emit({
options,
moduleInfo,
remoteSnapshot: mSnapshot,
});

return {
remoteSnapshot: mSnapshot,
globalSnapshot: gSnapshot,
};
nyqykk marked this conversation as resolved.
Show resolved Hide resolved
}

getGlobalRemoteInfo(moduleInfo: Remote): {
Expand Down
Loading