Introduction
1. What is the Bridge Solution?
It is a micro-frontend solution that uses a clever method to achieve micro-frontend architecture. By calling a higher-order function, it enables interoperability between different technology stacks.
2. What does "seamless" mean?
Micro-applications can be integrated with native technology stack applications without any differences, requiring no additional information.
3. Why is it needed, and is it reinventing the wheel?
During the project implementation process, we researched and tried different solutions. Some had compatibility issues, while others required heavy project modifications. CSS style isolation was also a headache. Thus, the bridge solution was born.
There are many micro-frontend solutions available, so why use the bridge solution? Let's summarize the main methods currently in use:
1. Module Federation based on Webpack 5
- Advantages: Allows sharing of third-party dependencies, reducing unnecessary code inclusion; micro-applications can be dynamically updated without repackaging and publishing the entire application; decentralized, allowing direct communication between micro-applications.
- Disadvantages: Highly dependent on Webpack 5, making it difficult to integrate into projects not using Webpack 5.
2. Based on iframe
- Advantages: Simple to implement, requires minimal modification to existing applications; provides native browser isolation, with JS, CSS, and DOM completely isolated and unaffected by each other; can communicate via postMessage API.
- Disadvantages: Cannot maintain route state, losing routes after refresh, and limited browser forward/backward functionality; difficult to share context between applications, making interaction challenging; pop-ups can only be displayed within the iframe; full resource loading results in poor performance; not SEO-friendly.
3. Based on Web Components + Sandbox
- Advantages: Natural isolation of CSS and JavaScript, avoiding style conflicts and script pollution; native browser support, not dependent on specific frameworks or libraries; multiple sub-applications can coexist, supporting parallel development and independent deployment.
- Disadvantages: Browser compatibility issues, with some browsers not fully supporting it; high development cost, potentially requiring rewriting of existing applications; additional design needed for component communication.
4. single-spa + Sandbox
- Advantages: Technology stack agnostic, allowing integration of applications with any technology stack; low integration cost with HTML entry method; provides features like style isolation, JS sandbox, and resource preloading.
- Disadvantages: Incomplete style isolation, limitations with packaging tools, component communication issues, etc.
How is the bridge solution different from other micro-frontend solutions?
- 1. Simpler and more natural to use, with no additional knowledge required, creating bridge components through higher-order functions and using them directly.
- 2. No dependency on iframe or shadow DOM, resulting in better compatibility.
- 3. More natural component communication (via native props).
- 4. Allows componentized import of sub-applications/sub-components.
- 5. Better handling of style isolation issues (css-module or scoped, depending on the project's packaging tool configuration).
How to prove the above conclusions? To avoid the suspicion of self-promotion, let's go straight to the code.
Code Demonstration
Using React 18 as the main application, if you want to integrate Vue2
1. First step: Create a bridge application
// Assume the child app path is .children-app/accesstor/Button
import Vue from 'vue'
import { createVueBridge } from 'micro-frontend-bridge/for-react'
import Button from './Button.vue'
// Assume the child app is a Vue2 project
// Create a bridge accessor for Vue2
const accesstor = createVueBridge(Vue)
// The accessor is a higher-order function used to link the Vue button
export default accesstor(Button)
Second step: Output the bridge component
Package the button into a lib and import it into the main application through the packaging tool.
Third step: Use the bridge component in the main application
// Assume the main application has a main.jsx file and a bridge folder containing the bridge component
// The main application is a React 18 project
import React from 'react'
import Button from 'bridge/Button'
// Use the Vue2 button in React 18, it is recommended to use uppercase component names to distinguish bridge components
const BUTTON = Button(React)
const App = () => {
return (
<div>
<BUTTON color="grey" />
</div>
)
}
Yes, at this point React 18 and Vue2 are seamlessly connected. How to achieve component communication?
Component communication is also very natural, directly based on props. The color attribute in the code will be passed to the Vue2 button, and parent-child component communication can be achieved through callback functions, with no difference from native communication modes. (You can directly practice this through the online address at the end).
To summarize the usage process:
- Create a bridge component
- Output the component
- Use the bridge component
In fact, the second step can be omitted depending on the project's architecture. You can place the bridge component in the main application and add packaging support for the sub-application in the main application. If your main application uses Webpack, you can add Vue packaging support for the sub-application, skipping the step of packaging the lib.
Adjust the bridge component directory to the main application
// Assume the bridge component application path is bridge/Button
import Vue from './children-app/node_modules/vue'
import { createVueBridge } from 'micro-frontend-bridge/for-react'
// Adjust the import path
import Button from '../children-app/Button.vue'
// Assume the child app is a Vue2 project
// Create a bridge accessor for Vue2
const accesstor = createVueBridge(Vue)
// The accessor is a higher-order function used to link the Vue button
export default accesstor(Button)
Adjust Webpack packaging support
{
test: /\.vue$/,
include: [path.resolve(__dirname, 'children-app')],
use: {
loader: './children-app/node_modules/vue-loader/lib',
options: {
compiler: Vue2TemplateCompiler
}
}
},
Use the component
// Assume the main application has a main.jsx file and a bridge folder containing the bridge component
// The main application is a React 18 project
import React from 'react'
import Button from 'bridge/Button'
// Use the Vue2 button in React 18, it is recommended to use uppercase component names to distinguish bridge components
const BUTTON = Button(React)
const App = () => {
return (
<div>
<BUTTON color="grey" />
</div>
)
}
(Specific demonstration can be seen at the end of the online link)
How to optimize this micro-frontend application?
Generally speaking, integrating micro-frontend projects may lead to excessive package size due to the presence of different technology stacks. Since the bridge solution itself is based on higher-order functions, using dynamic import and suspense can achieve on-demand loading to optimize the application.
Using Vue2 and Vue3 as the main applications, if you want to integrate React
import React from 'react'
import ReactDOM from 'react-dom'
import { h } from 'vue'
import { createReactBridge } from '@micro-frontend-bridge/for-vue'
import { App } from './reactApp.tsx'
// Create Vue3 accessor
const v3reactAccessor = createReactBridge(React, ReactDOM)
// Create Vue2 accessor
const v2reactAccessor = createReactBridge(React, ReactDOM)
// Bridge the React app to Vue3
const V3APP = v3reactAccessor(h)(App)
// Bridge the React app to Vue2
const V2APP = v2reactAccessor()(App)
Top comments (0)