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 lifecycyle hooks in module-deferation bridge #2992

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3118053
feat: add hook when snapshot is ready
nyqykk Aug 15, 2024
3082116
feat: support isolated report
nyqykk Aug 22, 2024
f716f18
chore: merge main
nyqykk Aug 22, 2024
edaffa2
Merge branch 'main' into feat/isolated-monitor
nyqykk Aug 26, 2024
62480b6
feat: support bridge lifecycle
nyqykk Aug 30, 2024
c868988
Merge branch 'feat/isolated-monitor' of github.com:module-federation/…
nyqykk Aug 30, 2024
cf83897
chore: modify bridge lifecycle
nyqykk Aug 30, 2024
aa05fc5
chore: change symbol name for module
nyqykk Sep 2, 2024
8aebc66
chore: sync main code
nyqykk Sep 3, 2024
e414ded
feat: add vue3 bridge lifecycle
nyqykk Sep 3, 2024
f575ec7
chore: export bridge plugin type
nyqykk Sep 4, 2024
fe9ad08
Merge branch 'main' into feat/isolated-monitor
nyqykk Sep 5, 2024
7257784
chore: sync branch
nyqykk Sep 5, 2024
74f057a
chore: merge main branch
danpeen Sep 14, 2024
14a30fa
fix: fix router ci failed issue
danpeen Sep 14, 2024
b47ea80
feat: update bridge render hook name
danpeen Sep 18, 2024
b02d9f0
chore: merge main branch
danpeen Sep 19, 2024
93627d9
chore: merge main branch
danpeen Sep 23, 2024
5e33da2
feat: update bridge hook
danpeen Sep 23, 2024
83e54ad
feat: update bridge lifecycle register logic in bridge-vue
danpeen Sep 23, 2024
2fac2ea
fix: bridge should use raw basename from application router itself
danpeen Sep 24, 2024
4e319bb
Merge branch 'main' into feat/bridge-lifecycle-hook
ScriptedAlchemy Sep 25, 2024
a891716
feat: add params to pass bridge hooks
danpeen Sep 30, 2024
5c7f0be
chore: merge main branch
danpeen Sep 30, 2024
a6c9c97
feat: add afterBridgeRender hook and afterBridgeDestroy hook
danpeen Oct 10, 2024
10bc78b
feat: receive extraProps from rederhook
danpeen Oct 15, 2024
19acf8b
feat: receive extraProps from rederhook update
danpeen Oct 15, 2024
25d850b
feat: add default className for bridge root component
danpeen Oct 18, 2024
8588609
chore: merge main branch
danpeen Oct 18, 2024
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
3 changes: 2 additions & 1 deletion packages/bridge/bridge-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0",
"react-router-dom": ">=4"
"react-router-dom": ">=4",
"@module-federation/runtime": "workspace:*"
},
"devDependencies": {
"@testing-library/react": "15.0.7",
Expand Down
4 changes: 2 additions & 2 deletions packages/bridge/bridge-react/src/create.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { forwardRef } from 'react';
import type { ProviderParams } from '@module-federation/bridge-shared';
import { LoggerInstance } from './utils';
import {
ErrorBoundary,
ErrorBoundaryPropsWithComponent,
} from 'react-error-boundary';
import { LoggerInstance } from './utils';
import RemoteApp from './remote';
import type { ProviderParams } from '@module-federation/bridge-shared';

export interface RenderFnParams extends ProviderParams {
dom?: any;
Expand Down
29 changes: 29 additions & 0 deletions packages/bridge/bridge-react/src/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getInstance } from '@module-federation/runtime';
import helper from '@module-federation/runtime/helpers';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if we should make this use named exports in the future, so that better tree shake or destructure is possible later. (for the helpers stuff)
import {global} from helper


function registerBridgeLifeCycle() {
const { registerPlugins, pluginHelper } = helper.global;
const host = getInstance();
const pluginSystem = new pluginHelper.PluginSystem({
beforeBridgeRender: new pluginHelper.SyncHook<
[Record<string, any>],
void
>(),
beforeBridgeDestroy: new pluginHelper.SyncHook<
[Record<string, any>],
void
>(),
});

if (host) {
registerPlugins<typeof pluginSystem.lifecycle, typeof pluginSystem>(
host?.options?.plugins,
[pluginSystem],
);
return pluginSystem;
}

return null;
}

export { registerBridgeLifeCycle };
57 changes: 51 additions & 6 deletions packages/bridge/bridge-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,37 @@ import { useLayoutEffect, useRef, useState } from 'react';
import * as React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMClient from 'react-dom/client';
import { RouterContext } from './context';
import type {
ProviderParams,
RenderFnParams,
} from '@module-federation/bridge-shared';
import { LoggerInstance, atLeastReact18 } from './utils';
import { ErrorBoundary } from 'react-error-boundary';
import { RouterContext } from './context';
import { LoggerInstance, atLeastReact18 } from './utils';

type RenderParams = RenderFnParams & any;
type DestroyParams = {
dom: HTMLElement;
};
type RootType = HTMLElement | ReactDOMClient.Root;

type BridgeHooks = {
beforeBridgeRender?: (params: RenderFnParams) => void;
afterBridgeRender?: (params: RenderFnParams) => void;
beforeBridgeDestroy?: (params: DestroyParams) => void;
afterBridgeDestroy?: (params: DestroyParams) => void;
};

type ProviderFnParams<T> = {
rootComponent: React.ComponentType<T>;
render?: (
App: React.ReactElement,
id?: HTMLElement | string,
) => RootType | Promise<RootType>;
hooks?: BridgeHooks;
};

export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
return () => {
return (params: { hooks?: BridgeHooks }) => {
const rootMap = new Map<any, RootType>();
const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => {
const { appInfo, propsInfo, ...restProps } = info;
Expand All @@ -37,7 +49,7 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
};

return {
async render(info: RenderFnParams & any) {
async render(info: RenderParams) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const {
moduleName,
Expand All @@ -61,6 +73,12 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
</ErrorBoundary>
);

const beforeBridgeRender =
(bridgeInfo?.hooks && bridgeInfo?.hooks.beforeBridgeRender) ||
params?.hooks?.beforeBridgeRender;

beforeBridgeRender && beforeBridgeRender(info);
// call render function
if (atLeastReact18(React)) {
if (bridgeInfo?.render) {
// in case bridgeInfo?.render is an async function, resolve this to promise
Expand All @@ -77,18 +95,45 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
const renderFn = bridgeInfo?.render || ReactDOM.render;
renderFn?.(rootComponentWithErrorBoundary, info.dom);
}

const afterBridgeRender =
(bridgeInfo?.hooks && bridgeInfo?.hooks.afterBridgeDestroy) ||
params?.hooks?.afterBridgeRender;
afterBridgeRender && afterBridgeRender(info);
},
async destroy(info: { dom: HTMLElement }) {

async destroy(info: DestroyParams) {
LoggerInstance.log(`createBridgeComponent destroy Info`, {
dom: info.dom,
});

// call beforeBridgeDestroy hook
if (
bridgeInfo?.hooks &&
bridgeInfo?.hooks.beforeBridgeDestroy &&
typeof bridgeInfo?.hooks.beforeBridgeDestroy === 'function'
) {
bridgeInfo.hooks.beforeBridgeDestroy(info);
}

const beforeBridgeDestroy =
(bridgeInfo?.hooks && bridgeInfo?.hooks.beforeBridgeDestroy) ||
params?.hooks?.beforeBridgeDestroy;
beforeBridgeDestroy && beforeBridgeDestroy(info);

// call destroy function
if (atLeastReact18(React)) {
const root = rootMap.get(info.dom);
(root as ReactDOMClient.Root)?.unmount();
rootMap.delete(info.dom);
} else {
ReactDOM.unmountComponentAtNode(info.dom);
}

const afterBridgeDestroy =
(bridgeInfo?.hooks && bridgeInfo?.hooks.afterBridgeDestroy) ||
params?.hooks?.afterBridgeDestroy;
afterBridgeDestroy && afterBridgeDestroy(info);
},
rawComponent: bridgeInfo.rootComponent,
__BRIDGE_FN__: (_args: T) => {},
Expand Down
21 changes: 20 additions & 1 deletion packages/bridge/bridge-react/src/remote/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import React, {
} from 'react';
import * as ReactRouterDOM from 'react-router-dom';
import type { ProviderParams } from '@module-federation/bridge-shared';
import { LoggerInstance, pathJoin } from '../utils';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
import { ErrorBoundaryPropsWithComponent } from 'react-error-boundary';
import { registerBridgeLifeCycle } from '../lifecycle';
import { LoggerInstance, pathJoin } from '../utils';

declare const __APP_VERSION__: string;
export interface RenderFnParams extends ProviderParams {
Expand Down Expand Up @@ -39,6 +40,7 @@ const RemoteAppWrapper = forwardRef(function (
props: RemoteAppParams & RenderFnParams,
ref,
) {
const bridgeHook = registerBridgeLifeCycle();
const RemoteApp = () => {
LoggerInstance.log(`RemoteAppWrapper RemoteApp props >>>`, { props });
const {
Expand Down Expand Up @@ -78,6 +80,13 @@ const RemoteAppWrapper = forwardRef(function (
`createRemoteComponent LazyComponent render >>>`,
renderProps,
);

if (bridgeHook && bridgeHook?.lifecycle?.beforeBridgeRender) {
bridgeHook?.lifecycle?.beforeBridgeRender.emit({
...renderProps,
});
}

providerReturn.render(renderProps);
});

Expand All @@ -89,6 +98,16 @@ const RemoteAppWrapper = forwardRef(function (
`createRemoteComponent LazyComponent destroy >>>`,
{ moduleName, basename, dom: renderDom.current },
);
if (bridgeHook && bridgeHook?.lifecycle?.beforeBridgeDestroy) {
bridgeHook?.lifecycle?.beforeBridgeDestroy.emit({
moduleName,
dom: renderDom.current,
basename,
memoryRoute,
fallback,
...resProps,
});
}
providerInfoRef.current?.destroy({
dom: renderDom.current,
});
Expand Down
1 change: 0 additions & 1 deletion packages/bridge/bridge-react/src/router-v5.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useContext } from 'react';
// The upper alias react-router-dom$ into this file avoids the loop
// @ts-ignore
import * as ReactRouterDom from 'react-router-dom/index.js';

import { RouterContext } from './context';
import { LoggerInstance } from './utils';

Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/bridge-react/src/router-v6.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function WraperRouterProvider(
return <RouterProvider router={MemeoryRouterInstance} />;
} else {
const BrowserRouterInstance = createBrowserRouter(routers, {
basename: routerContextProps.basename,
basename: routerContextProps.basename || router?.basename,
future: router.future,
window: router.window,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/bridge-react/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function WrapperRouterProvider(
return <RouterProvider router={MemeoryRouterInstance} />;
} else {
const BrowserRouterInstance = createBrowserRouter(routers, {
basename: routerContextProps.basename,
basename: routerContextProps.basename || router?.basename,
future: router.future,
window: router.window,
});
Expand Down
3 changes: 2 additions & 1 deletion packages/bridge/vue3-bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
},
"peerDependencies": {
"vue": "=3",
"vue-router": "=3"
"vue-router": "=3",
"@module-federation/runtime": "workspace:*"
},
"dependencies": {
"@module-federation/bridge-shared": "workspace:*"
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/vue3-bridge/src/create.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineAsyncComponent, h } from 'vue';
import { useRoute } from 'vue-router';
import RemoteApp from './remoteApp.jsx';
import { LoggerInstance } from './utils.js';
import { useRoute } from 'vue-router';

declare const __APP_VERSION__: string;

Expand Down
29 changes: 29 additions & 0 deletions packages/bridge/vue3-bridge/src/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getInstance } from '@module-federation/runtime';
import helper from '@module-federation/runtime/helpers';

function registerBridgeLifeCycle() {
const { registerPlugins, pluginHelper } = helper.global;
const host = getInstance();
const pluginSystem = new pluginHelper.PluginSystem({
beforeBridgeRender: new pluginHelper.SyncHook<
[Record<string, any>],
void
>(),
beforeBridgeDestroy: new pluginHelper.SyncHook<
[Record<string, any>],
void
>(),
});

if (host) {
registerPlugins<typeof pluginSystem.lifecycle, typeof pluginSystem>(
host?.options?.plugins,
[pluginSystem],
);
return pluginSystem;
}

return null;
}

export { registerBridgeLifeCycle };
2 changes: 2 additions & 0 deletions packages/bridge/vue3-bridge/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function createBridgeComponent(bridgeInfo: any) {
LoggerInstance.log(`createBridgeComponent render Info`, info);
const app = Vue.createApp(bridgeInfo.rootComponent);
rootMap.set(info.dom, app);
bridgeInfo?.renderLifecycle?.(info);
const appOptions = bridgeInfo.appOptions({
basename: info.basename,
memoryRoute: info.memoryRoute,
Expand Down Expand Up @@ -46,6 +47,7 @@ export function createBridgeComponent(bridgeInfo: any) {
destroy(info: { dom: HTMLElement }) {
LoggerInstance.log(`createBridgeComponent destroy Info`, info);
const root = rootMap.get(info?.dom);
bridgeInfo?.destroyLifecycle?.(info);
root?.unmount();
},
};
Expand Down
19 changes: 18 additions & 1 deletion packages/bridge/vue3-bridge/src/remoteApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ref, onMounted, onBeforeUnmount, watch, defineComponent } from 'vue';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
import { useRoute } from 'vue-router';
import { registerBridgeLifeCycle } from './lifecycle';
import { LoggerInstance } from './utils';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';

export default defineComponent({
name: 'RemoteApp',
Expand All @@ -16,6 +17,7 @@ export default defineComponent({
const providerInfoRef = ref(null);
const pathname = ref('');
const route = useRoute();
const bridgeHook = registerBridgeLifeCycle();

const renderComponent = () => {
const providerReturn = props.providerInfo?.();
Expand All @@ -30,6 +32,12 @@ export default defineComponent({
`createRemoteComponent LazyComponent render >>>`,
renderProps,
);

if (bridgeHook && bridgeHook?.lifecycle?.beforeBridgeRender) {
bridgeHook?.lifecycle?.beforeBridgeRender.emit({
...renderProps,
});
}
providerReturn.render(renderProps);
};

Expand Down Expand Up @@ -61,6 +69,15 @@ export default defineComponent({
...props,
});
watchStopHandle();
if (bridgeHook && bridgeHook?.lifecycle?.beforeBridgeRender) {
bridgeHook?.lifecycle?.beforeBridgeDestroy.emit({
name: props.moduleName,
dom: rootRef.value,
basename: props.basename,
memoryRoute: props.memoryRoute,
});
}

(providerInfoRef.value as any)?.destroy({ dom: rootRef.value });
});

Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
Global,
} from './global';
import { getRegisteredShare, getGlobalShareScope } from './utils/share';
import * as pluginHelper from './utils/hooks';
import { registerPlugins } from './utils';

interface IShareUtils {
getRegisteredShare: typeof getRegisteredShare;
Expand Down Expand Up @@ -48,6 +50,8 @@ interface IGlobalUtils {
getGlobalHostPlugins: typeof getGlobalHostPlugins;
getPreloaded: typeof getPreloaded;
setPreloaded: typeof setPreloaded;
registerPlugins: typeof registerPlugins;
pluginHelper: typeof pluginHelper;
}

const GlobalUtils: IGlobalUtils = {
Expand All @@ -69,6 +73,8 @@ const GlobalUtils: IGlobalUtils = {
getGlobalHostPlugins,
getPreloaded,
setPreloaded,
registerPlugins,
pluginHelper,
};

export default {
Expand Down
Loading
Loading