From 6bc13cf001059aa7e50b9a1f998c46814609d27e Mon Sep 17 00:00:00 2001 From: Dell-it <69255597+Dell-it@users.noreply.github.com> Date: Tue, 4 Mar 2025 04:58:43 +0200 Subject: [PATCH] fix(vue3-bridge): resolved remote component prop handling and root container attribute passing (#3562) Co-authored-by: CocooDanielle --- .changeset/tough-pets-learn.md | 12 ++++++++++ .../docs/en/practice/bridge/vue-bridge.mdx | 21 +++++++++++------ .../docs/zh/practice/bridge/vue-bridge.mdx | 17 ++++++++++---- packages/bridge/vue3-bridge/src/create.ts | 4 +++- packages/bridge/vue3-bridge/src/provider.ts | 12 +++++----- packages/bridge/vue3-bridge/src/remoteApp.tsx | 23 ++++++++++++++----- 6 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 .changeset/tough-pets-learn.md diff --git a/.changeset/tough-pets-learn.md b/.changeset/tough-pets-learn.md new file mode 100644 index 00000000000..65c68de665c --- /dev/null +++ b/.changeset/tough-pets-learn.md @@ -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 ``, 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. diff --git a/apps/website-new/docs/en/practice/bridge/vue-bridge.mdx b/apps/website-new/docs/en/practice/bridge/vue-bridge.mdx index ffac82fd21f..92a92f4c39b 100644 --- a/apps/website-new/docs/en/practice/bridge/vue-bridge.mdx +++ b/apps/website-new/docs/en/practice/bridge/vue-bridge.mdx @@ -20,11 +20,13 @@ import { PackageManagerTabs } from '@theme'; function createRemoteComponent( options: { // Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app') - loader: () => Promise, + loader: () => Promise; // Default is 'default', used to specify module export - export?: E, + export?: E; // Parameters that will be passed to defineAsyncComponent asyncComponentOptions?: Omit; + // Attributes that will be bound to the root container where the remote Vue application will be mounted + rootAttrs?: Record; } ): (props: { basename?: string; @@ -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 ], }); @@ -137,11 +139,13 @@ export default router; function createRemoteComponent( options: { // Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app') - loader: () => Promise, + loader: () => Promise; // Default is 'default', used to specify module export export?: E; // Parameters that will be passed to defineAsyncComponent asyncComponentOptions?: Omit; + // Attributes that will be bound to the root container where the remote Vue application will be mounted + rootAttrs?: Record; } ): (props: { basename?: string; @@ -164,6 +168,9 @@ const Remote1App = createRemoteComponent({ loader: () => loadRemote('remote1/exp * `asyncComponentOptions` * type: `Omit` * Purpose: Parameters that will be passed to defineAsyncComponent, except for the loader parameter + * `rootAttrs` + * type: `Record` + * Purpose: Attributes that will be bound to the root container where the remote Vue application will be mounted ```tsx // remote export const provider = createBridgeComponent({ @@ -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 ], }); diff --git a/apps/website-new/docs/zh/practice/bridge/vue-bridge.mdx b/apps/website-new/docs/zh/practice/bridge/vue-bridge.mdx index 0ca273ac46c..a6960a06c71 100644 --- a/apps/website-new/docs/zh/practice/bridge/vue-bridge.mdx +++ b/apps/website-new/docs/zh/practice/bridge/vue-bridge.mdx @@ -21,11 +21,13 @@ import { PackageManagerTabs } from '@theme'; function createRemoteComponent( options: { // Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app') - loader: () => Promise, + loader: () => Promise; // Default is 'default', used to specify module export - export?: E, + export?: E; // Parameters that will be passed to defineAsyncComponent asyncComponentOptions?: Omit; + // Attributes that will be bound to the root container where the remote Vue application will be mounted + rootAttrs?: Record; } ): (props: { basename?: string; @@ -141,11 +143,13 @@ export default router; function createRemoteComponent( options: { // Function to load remote application, e.g., loadRemote('remote1/export-app') or import('remote1/export-app') - loader: () => Promise, + loader: () => Promise; // Default is 'default', used to specify module export export?: E; // Parameters that will be passed to defineAsyncComponent asyncComponentOptions?: Omit; + // Attributes that will be bound to the root container where the remote Vue application will be mounted + rootAttrs?: Record; } ): (props: { basename?: string; @@ -168,6 +172,9 @@ const Remote1App = createRemoteComponent({ loader: () => loadRemote('remote1/exp * `asyncComponentOptions` * type: `Omit` * Purpose: Parameters that will be passed to defineAsyncComponent, except for the loader parameter + * `rootAttrs` + * type: `Record` + * Purpose: Attributes that will be bound to the root container where the remote Vue application will be mounted ```tsx // remote export const provider = createBridgeComponent({ @@ -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 ], }); diff --git a/packages/bridge/vue3-bridge/src/create.ts b/packages/bridge/vue3-bridge/src/create.ts index 20b2374fb0f..fa711b5ec97 100644 --- a/packages/bridge/vue3-bridge/src/create.ts +++ b/packages/bridge/vue3-bridge/src/create.ts @@ -9,6 +9,7 @@ export function createRemoteComponent(info: { loader: () => Promise; export?: string; asyncComponentOptions?: Omit; + rootAttrs?: Record; }) { return defineAsyncComponent({ __APP_VERSION__, @@ -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') { @@ -49,6 +50,7 @@ export function createRemoteComponent(info: { moduleName, providerInfo: exportFn, basename, + rootAttrs: info.rootAttrs, }); }, }; diff --git a/packages/bridge/vue3-bridge/src/provider.ts b/packages/bridge/vue3-bridge/src/provider.ts index 77239ca2dea..43e3e90a999 100644 --- a/packages/bridge/vue3-bridge/src/provider.ts +++ b/packages/bridge/vue3-bridge/src/provider.ts @@ -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); @@ -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 }) { diff --git a/packages/bridge/vue3-bridge/src/remoteApp.tsx b/packages/bridge/vue3-bridge/src/remoteApp.tsx index c6f7b8ca235..11d87ca2a8e 100644 --- a/packages/bridge/vue3-bridge/src/remoteApp.tsx +++ b/packages/bridge/vue3-bridge/src/remoteApp.tsx @@ -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'; @@ -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, @@ -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); @@ -92,6 +103,6 @@ export default defineComponent({ }); }); - return () =>
; + return () =>
; }, });