Skip to content

Commit

Permalink
fix(vue3-bridge): resolved remote component prop handling and root co…
Browse files Browse the repository at this point in the history
…ntainer attribute passing (#3562)

Co-authored-by: CocooDanielle <[email protected]>
  • Loading branch information
Dell-it and danpeen authored Mar 4, 2025
1 parent 5b391b5 commit 6bc13cf
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 25 deletions.
12 changes: 12 additions & 0 deletions .changeset/tough-pets-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@module-federation/bridge-vue3': patch
---

Fixed several issues:

1. Resolved inconsistencies in naming between `name` and `moduleName` to align with the type defined in `packages/bridge/bridge-shared/src/type.ts`. This also fixed an issue where `name` was being passed to the remote component, and if it was `<router-view>`, it caused rendering issues.

2. Issue: When passing props from a Vue 3 host application to a Vue 3 remote application created with `createRemoteComponent`, the props were being applied as attributes on the root container instead of being passed to the remote component.
Fix: Set `inheritAttrs: false` in `remoteApp.tsx` and explicitly pass all attributes to the remote component using `useAttrs()`.

3. Added a `rootAttrs` parameter to `createRemoteComponent` to allow passing attributes to the root container where the remote application is mounted. This enables setting classes, identifiers, and other attributes for the container element.
21 changes: 14 additions & 7 deletions apps/website-new/docs/en/practice/bridge/vue-bridge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { PackageManagerTabs } from '@theme';
function createRemoteComponent<T, E extends keyof T>(
options: {
// Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app')
loader: () => Promise<T>,
loader: () => Promise<T>;
// Default is 'default', used to specify module export
export?: E,
export?: E;
// Parameters that will be passed to defineAsyncComponent
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
// Attributes that will be bound to the root container where the remote Vue application will be mounted
rootAttrs?: Record<string, unknown>;
}
): (props: {
basename?: string;
Expand Down Expand Up @@ -115,14 +117,14 @@ export default defineConfig({
// ./src/router.ts
import * as bridge from '@module-federation/bridge-vue3';

const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app') });
const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app'), rootAttrs: {class: 'root-element-class'} });

const router = createRouter({
history: createWebHistory(),
routes: [
// Define your routes here
{ path: '/', component: Home },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2 },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2, props: { foo: 'bar' } },
// Other routes
],
});
Expand All @@ -137,11 +139,13 @@ export default router;
function createRemoteComponent<T, E extends keyof T>(
options: {
// Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app')
loader: () => Promise<T>,
loader: () => Promise<T>;
// Default is 'default', used to specify module export
export?: E;
// Parameters that will be passed to defineAsyncComponent
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
// Attributes that will be bound to the root container where the remote Vue application will be mounted
rootAttrs?: Record<string, unknown>;
}
): (props: {
basename?: string;
Expand All @@ -164,6 +168,9 @@ const Remote1App = createRemoteComponent({ loader: () => loadRemote('remote1/exp
* `asyncComponentOptions`
* type: `Omit<AsyncComponentOptions, 'loader'>`
* Purpose: Parameters that will be passed to defineAsyncComponent, except for the loader parameter
* `rootAttrs`
* type: `Record<string, unknown>`
* Purpose: Attributes that will be bound to the root container where the remote Vue application will be mounted
```tsx
// remote
export const provider = createBridgeComponent({
Expand All @@ -184,14 +191,14 @@ const Remote1App = createRemoteComponent({
```tsx
import * as bridge from '@module-federation/bridge-vue3';

const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app') });
const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app'), rootAttrs: {class: 'root-element-class'} });

const router = createRouter({
history: createWebHistory(),
routes: [
// Define your routes here
{ path: '/', component: Home },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2 },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2, props: { foo: 'bar' } },
// Other routes
],
});
Expand Down
17 changes: 12 additions & 5 deletions apps/website-new/docs/zh/practice/bridge/vue-bridge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { PackageManagerTabs } from '@theme';
function createRemoteComponent<T, E extends keyof T>(
options: {
// Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app')
loader: () => Promise<T>,
loader: () => Promise<T>;
// Default is 'default', used to specify module export
export?: E,
export?: E;
// Parameters that will be passed to defineAsyncComponent
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
// Attributes that will be bound to the root container where the remote Vue application will be mounted
rootAttrs?: Record<string, unknown>;
}
): (props: {
basename?: string;
Expand Down Expand Up @@ -141,11 +143,13 @@ export default router;
function createRemoteComponent<T, E extends keyof T>(
options: {
// Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app')
loader: () => Promise<T>,
loader: () => Promise<T>;
// Default is 'default', used to specify module export
export?: E;
// Parameters that will be passed to defineAsyncComponent
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
// Attributes that will be bound to the root container where the remote Vue application will be mounted
rootAttrs?: Record<string, unknown>;
}
): (props: {
basename?: string;
Expand All @@ -168,6 +172,9 @@ const Remote1App = createRemoteComponent({ loader: () => loadRemote('remote1/exp
* `asyncComponentOptions`
* type: `Omit<AsyncComponentOptions, 'loader'>`
* Purpose: Parameters that will be passed to defineAsyncComponent, except for the loader parameter
* `rootAttrs`
* type: `Record<string, unknown>`
* Purpose: Attributes that will be bound to the root container where the remote Vue application will be mounted
```tsx
// remote
export const provider = createBridgeComponent({
Expand All @@ -188,14 +195,14 @@ const Remote1App = createRemoteComponent({
```tsx
import * as bridge from '@module-federation/bridge-vue3';

const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app') });
const Remote2 = bridge.createRemoteComponent({ loader: () => loadRemote('remote1/export-app'), rootAttrs: {class: 'root-element-class'} });

const router = createRouter({
history: createWebHistory(),
routes: [
// Define your routes here
{ path: '/', component: Home },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2 },
{ path: '/remote1/:pathMatch(.*)*', component: Remote2, props: { foo: 'bar' } },
// Other routes
],
});
Expand Down
4 changes: 3 additions & 1 deletion packages/bridge/vue3-bridge/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function createRemoteComponent(info: {
loader: () => Promise<any>;
export?: string;
asyncComponentOptions?: Omit<AsyncComponentOptions, 'loader'>;
rootAttrs?: Record<string, unknown>;
}) {
return defineAsyncComponent({
__APP_VERSION__,
Expand Down Expand Up @@ -39,7 +40,7 @@ export function createRemoteComponent(info: {

LoggerInstance.debug(
`createRemoteComponent LazyComponent loadRemote info >>>`,
{ name: moduleName, module, exportName, basename, route },
{ moduleName, module, exportName, basename, route },
);

if (exportName in module && typeof exportFn === 'function') {
Expand All @@ -49,6 +50,7 @@ export function createRemoteComponent(info: {
moduleName,
providerInfo: exportFn,
basename,
rootAttrs: info.rootAttrs,
});
},
};
Expand Down
12 changes: 6 additions & 6 deletions packages/bridge/vue3-bridge/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
LoggerInstance.debug(`createBridgeComponent render Info`, info);
const { moduleName, dom, basename, memoryRoute, ...propsInfo } = info;
const app = Vue.createApp(bridgeInfo.rootComponent, propsInfo);
rootMap.set(info.dom, app);
rootMap.set(dom, app);

const beforeBridgeRenderRes =
await instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(info);
Expand Down Expand Up @@ -61,19 +61,19 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
routes: bridgeOptions.router.getRoutes(),
});

LoggerInstance.log(`createBridgeComponent render router info>>>`, {
name: info.moduleName,
LoggerInstance.debug(`createBridgeComponent render router info>>>`, {
moduleName,
router,
});
// memory route Initializes the route
if (info.memoryRoute) {
await router.push(info.memoryRoute.entryPath);
if (memoryRoute) {
await router.push(memoryRoute.entryPath);
}

app.use(router);
}

app.mount(info.dom);
app.mount(dom);
instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info);
},
destroy(info: { dom: HTMLElement }) {
Expand Down
23 changes: 17 additions & 6 deletions packages/bridge/vue3-bridge/src/remoteApp.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { ref, onMounted, onBeforeUnmount, watch, defineComponent } from 'vue';
import {
ref,
onMounted,
onBeforeUnmount,
watch,
defineComponent,
useAttrs,
} from 'vue';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
import { useRoute } from 'vue-router';
import { LoggerInstance } from './utils';
Expand All @@ -11,20 +18,24 @@ export default defineComponent({
basename: String,
memoryRoute: Object,
providerInfo: Function,
rootAttrs: Object,
},
inheritAttrs: false,
setup(props) {
const rootRef = ref(null);
const providerInfoRef = ref(null);
const pathname = ref('');
const route = useRoute();
const hostInstance = getInstance();
const componentAttrs = useAttrs();

const renderComponent = () => {
const renderComponent = async () => {
const providerReturn = props.providerInfo?.();
providerInfoRef.value = providerReturn;

let renderProps = {
name: props.moduleName,
...componentAttrs,
moduleName: props.moduleName,
dom: rootRef.value,
basename: props.basename,
memoryRoute: props.memoryRoute,
Expand All @@ -35,9 +46,9 @@ export default defineComponent({
);

const beforeBridgeRenderRes =
hostInstance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(
(await hostInstance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(
renderProps,
) || {};
)) || {};

renderProps = { ...renderProps, ...beforeBridgeRenderRes.extraProps };
providerReturn.render(renderProps);
Expand Down Expand Up @@ -92,6 +103,6 @@ export default defineComponent({
});
});

return () => <div ref={rootRef}></div>;
return () => <div {...(props.rootAttrs || {})} ref={rootRef}></div>;
},
});

0 comments on commit 6bc13cf

Please sign in to comment.