The WebPack 5 module federates the micro front end

Micro front end: the huge single front-end system is divided into a number of independent small systems, and finally integrated into a system architecture idea, divide and conquer, so that the system is easier to maintain and expand, the implementation of micro front end is a process of splitting, then merging.

When I say front-end applications, I mean single-page applications with separate front and back ends, so it makes sense to talk about a micro front end.

Both micro-front-end and micro-service are aimed at solving the problems of large projects and large teams: large projects realize module decoupling and large teams realize personnel decoupling, which are the main problems studied in software engineering.

Conway’s law: software structure reflects personnel structure, and personnel structure determines software structure.

Ruan Yifeng software engineering introduction

The theoretical basis of microservices architecture – Conway’s Law

Conway’s law

Why do we need a micro front end

With the increase of business, Monomer system of Monolith becomes more and more bloated. Multiple teams develop together, which leads to high success in communication and difficulties in compilation, deployment, testing and maintenance. The micro front end can solve these problems.

  1. Application autonomy: Independent applications are smaller in scale and easier to expand, test, build, maintain, troubleshoot, and upgrade dependencies.
  2. Team autonomy: After the application is independent, the team will also be independent, reducing the number of people developing in a monolith application at the same time, influencing each other and improving the development efficiency;
  3. Technology independent: Each application can choose different frameworks for development, and try to keep the same. Otherwise, the interaction between applications may encounter trouble, which is not conducive to component reuse, such as the failure to share component-level code;
  4. Try new technologies: After an application is split, it is easy to try new technologies in the system.
  5. Incremental refactoring of old systems.

Disadvantages:

  1. The unification of code specifications is difficult (many people, many projects) and easy to overcome
  2. Development may require running multiple projects at the same time, which is easy to overcome
  3. Integration testing is difficult
  4. UI, interaction, etc., tend to be inconsistent and easy to overcome

Compared to the advantages of micro front-end, the disadvantages are negligible.

Implementation Suggestions:

  1. Consistent working methods: Team members need to agree on a consistent working method, especially the interaction protocol between the host application and the remote application, which needs to be agreed in advance;
  2. Integrating the business: Think about the business segmentation and value that the micro front end brings to the team before using the micro front end architecture;
  3. Comply with the consistent code style, convenient later maintenance;
  4. Don’t overuse: Use only when you want to achieve your goal of splitting people or technology, or when it is necessary to do so.

Introduction to the micro front End

The first discussion article on the microfront end

How to integrate – aggregate

There are three main integration methods:

Integrated way A detailed description advantages disadvantages other
Build-time integration Microapplications are packaged into the main application and are usually distributed as NPM packages Implementation is easy and well understood Dependencies between applications where one is updated, the other is updated, and then deployed can’t actually be deployed independently In fact, this approach does not achieve the goals of the micro front end
Runtime build After the main application loads in the browser, the microapplication code is fetched Independent deployment, the main application can decide which microapplication version to use, flexible, good performance The setup is complicated and hard to understand Achieving the goal of a micro front end,The current more general way
Server-side integration The master application requests the microapplication to the server, and the server decides whether to give the code Heavy reliance on background code The implementation is complex and is generally not used

What issues need to be considered when integrating?

  1. Avoiding style conflicts

  2. Communication between applications is simple

  3. Navigation between applications is silky smooth

  4. Ability to integrate specific versions

  5. Version control does not interfere with each other

  6. Can facilitate the implementation of dependency sharing

Common micro front-end implementations

After the emergence of the micro-front-end architecture, some frameworks and solutions have emerged in the industry, including the following:

Framework:

MicroApp, Single-SPA, Qiankuan

The pantless rack solution:

Web Components are not recommended and cannot provide modularity. The bigger the project, the easier it is to get out of control. Web Components are essentially packaging custom HTML and will soon be the age of jQuery.

iframe

Webpack5 Module Federation, a new feature of Webpackage 5 that enables code sharing across applications.

How to split microapplications?

During splitting, the interaction between different applications should be minimized and decoupled to the maximum extent to facilitate communication between different applications. Otherwise, applications are difficult to test and problems to locate.

By service division, the same services are divided into one microapplication.

Users are divided by permissions based on their permissions and functions used by different users.

Division according to background microservices. For some teams, the background uses microservice architecture, which can be considered as division according to microservices.

Module Federation — Module Federation

Module federation, a feature introduced in WebPack 5, makes it easy to share code between two projects built using WebPack, or even combine different applications into one.

The Module Federation’s official website

Configuration of module federation

Module federation enables code sharing across applications. Two applications are used as examples to illustrate its configuration.

A Dashboard Vue3 application that wants to provide code to other applications:

/ / const ModuleFederationPlugin = the require (' webpack/lib/container/ModuleFederationPlugin ') / / export modules federal
const { ModuleFederationPlugin } = require('webpack').container // You can do that
// WebPack plugin configuration
{
plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard'.// exposeRemoteName Specifies the shared module name. It will be used in applications that consume this module
      filename: 'remoteEntry.js'.// The name of the file to be loaded remotely is seen in the browser request panel. The default name is remoteentry.js
      exposes: {
        './DashboardApp': './src/bootstrap'.// From the shared module exposed by this application, multiple modules can be shared./ and value points to a local file
      },
      shared: packageJson.dependencies, // Dependencies that you want to share})],}Copy the code

About the name Settings

The value of name will be exported as a global variable, do not use -, do not be the same as the id in the page, otherwise you may report an error.

Container React App consumption Dashboard

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
{
  plugins: [
    new ModuleFederationPlugin({
      name: 'container'.// It is a good practice to declare the name of the module that you want to share, even though the container is not consumed by other applications
      remotes: {
        // marketing: 'marketing@http://localhost:8081/remoteEntry.js',
        // auth: 'auth@http://localhost:8082/remoteEntry.js',
        dashboard: 'dashboard@http://localhost:8083/remoteEntry.js'.// Specify the remote module
        //NOTE remoteName:exposeRemoteName@fileOnRemoteServer
      },
      shared: packageJson.dependencies,// The module you want to share})],}Copy the code

How do I use Dashboard in Containers?

Create a new Dashboardapp. JSX component in the Container to introduce Dashboard:

Dashboard is the Remotes field in the Container, DashboardApp is the dashboard that is exposed in the object, and key is a mount function exported from the dashboard, The application can be mounted anywhere in the Container

// exposeRemoteName/expose
import { mount } from 'dashboard/DashboardApp' // NOTE the corresponding relationship between the syntax and the configuration
import React, { useRef, useEffect } from 'react'
import { useHistory } from 'react-router-dom'

export default ({ isSignedIn, user }) => {
  const ref = useRef(null)
  const history = useHistory()
  useEffect(() = > {
    // NOTE mounts dashboard to div via mount. These parameters and return values are how data is shared, more on this later
    const { onParentNavigate } = mount(ref.current, {
      isMemoryHistory: true.basePath: '/dashboard'.currentPath: history.location.pathname,
      onNavigate: nextPathname= > {
        const { pathname } = history.location
        if(pathname ! == nextPathname) {console.log('Vue sub-app Jump', nextPathname)
          history.push(nextPathname)
        }
      },
      sharedData: { isSignedIn, user },
    })
    console.log('container dashboard navigate')
    history.listen(onParentNavigate)
  }, [])

  return <div ref={ref} />
}
Copy the code

Then export this component and place it under/Dashboard to navigate to Dashboard:

import React, { lazy, Suspense, useState, useEffect } from 'react'
import { Router, Route, Switch, Redirect } from 'react-router-dom'
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles'
import { createBrowserHistory } from 'history'

import { Progress, Header } from './components'

const MarketingLazy = lazy(() = > import('./components/MarketingApp'))
const AuthLazy = lazy(() = > import('./components/AuthApp'))
const DashboardLazy = lazy(() = > import('./components/DashboardApp'))

const generateClassName = createGenerateClassName({
  productionPrefix: 'co',})const history = createBrowserHistory()

export default() = > {const [isSignedIn, setIsSignedIn] = useState(window.localStorage.getItem('isSignedIn') = = ='true')
  const [user, setUser] = useState(JSON.parse(window.localStorage.getItem('user')))

  useEffect(() = > {
    if (isSignedIn) {
      history.push('/dashboard')
    }
  }, [isSignedIn])

  return (
    <Router history={history}>
      <StylesProvider generateClassName={generateClassName}>
        <div>
          <Header
            onSignOut={()= > {
              window.localStorage.removeItem('isSignedIn')
              window.localStorage.removeItem('user')
              window.sessionStorage.removeItem('user')
              setIsSignedIn(false)
            }}
            isSignedIn={isSignedIn}
          />
          <Suspense fallback={<Progress />} ><Switch>
              <Route path='/auth'>
                <AuthLazy
                  onSignIn={user= >{setUser (user) / / use the local store window. The sessionStorage. SetItem (' user ', JSON.stringify(user)) window.localStorage.setItem('user', JSON.stringify(user)) window.localStorage.setItem('isSignedIn', JSON.stringify(true)) setIsSignedIn(true) }} /></Route>
              <Route path='/dashboard'>{{/ *! isSignedIn &&<Redirect to='/' />* /}}<DashboardLazy user={user} isSignedIn={isSignedIn} />
              </Route>
              <Route path='/' component={MarketingLazy} />
            </Switch>
          </Suspense>
        </div>
      </StylesProvider>
    </Router>)}Copy the code

What are the modules exposed by Dashboard?

In the dashboard bootstrap. Js

import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './route'
import { createWebHistory, createMemoryHistory } from 'vue-router'

function mount(el, { isMemoryHistory, basePath, currentPath, onNavigate, sharedData = {} }) {
  const app = createApp(App, { basePath, currentPath, isMemoryHistory, onNavigate, sharedData })
  const history = isMemoryHistory ? createMemoryHistory(basePath) : createWebHistory()

  setupRouter(app, { history })

  app.mount(el)

  return {
    onParentNavigate({ pathname: nextPathname }) {
      console.log('dashboard vue onParentNavigate', nextPathname)
      history.listen(currentPath= > {
        if(currentPath ! == nextPathname) { history.push(nextPathname) } }) }, } }// If we are in development and in isolation,
// call mount immediately
const devRoot = document.querySelector('#dashboard-dev-root')

if (devRoot) {
  mount(devRoot, { isMemoryHistory: false})}// We are running through container
// and we should export the mount function
export { mount }
Copy the code

Key points:

① Provide a mount function, export the function, so that the consumption of the module application can mount it arbitrarily, in the independent deployment and development environment, call the function to achieve the application mount to the page.

(2) Reasonably design mount parameters and return values to realize data sharing and route switching between applications.

Add bootstrap.js to webpack entry file index.js:

import('./bootstrap.js')
Copy the code

How about using the contents of bootstrap.js directly in the entry file index.js?

An error is reported when running the microapplication alone:

Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react
Copy the code

History. listen(onNavigate) is not working, no idea why.

Functions such as useRoute cannot be used in the mount function either.

What is the order of execution of the imported remote modules?

How do I avoid navigation conflicts between Containers and Dashboards?

In order to switch pages in independent deployment, microphones should have their own navigation, and Containers should also have their own navigation. How to resolve navigation conflicts after Containers load dashboards?

Why are there navigation conflicts?

A path corresponds to only one page: The browser has only one path at a time, and this path corresponds to a page or component. When switching to this path, the page or component is rendered.

Vue-router provides web routing and memory routing. When you switch the application path, the browser address bar changes, and the browser address changes, as do the rendered components in the application. When using in-memory navigation, the address bar is detached from the component rendering in the application.

Why memory routing? For non-browser environments.

Vue and React have these two routes.

When microapplications are deployed independently, web routes are used. When microapplications are integrated into Containers, memory routes are used. The Container takes over web routes.

Route configuration for Dashboard:

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/'.name: 'home'.component: () = > import('.. /views/Home.vue'),}, {path: '/upload'.name: 'upload'.component: () = > import('.. /views/Upload.vue'),},]// Export the route configuration function. Web routes are used by default
export function setupRouter(app, { history = createWebHistory() } = {}) {
  const router = createRouter({
    history,
    routes,
  })
  app.use(router)
}
Copy the code

How to use setupRouter in mount?

Key code:

// Pass an isMemoryHistory identifier indicating the route used
function mount(el, { isMemoryHistory }) {
  // el is the element to be mounted
  const history = isMemoryHistory ? createMemoryHistory(basePath) : createWebHistory()
  setupRouter(app, { history }) // app is the return value of createApp
}
Copy the code

When deployed and developed separately, use Web routing:

// If we are in development and in isolation,
// call mount immediately
if (process.env.NODE_ENV === 'development') {
  const devRoot = document.querySelector('#dashboard-dev-root')

  if (devRoot) {
    // NOTE uses web routing
    mount(devRoot, { isMemoryHistory: false}}})Copy the code

During the integration, in-memory routes are used. In addition, check whether the browser path changes and switch routes when the browser path changes. Otherwise, the Dashboard does not change during container navigation.

Implement the jump process in app.vue:

<script>
  import { onMounted, watch } from '@vue/runtime-core'
  import { useRouter, useRoute } from 'vue-router'
  export default {
    name: 'App'.props: {
      onNavigate: {
        type: Function,},basePath: {
        type: String.default: '/',},currentPath: {
        type: String.default: '/',},isMemoryHistory: {
        type: Boolean.default: false,}},setup(props) {
      const { basePath, currentPath, isMemoryHistory, onNavigate } = props
      const router = useRouter()
      const route = useRoute()

      // NOTE route changes and provides a jump function
      function onRouteChange(newPath) {
        onNavigate && onNavigate(basePath + newPath)
      }

      watch(() = > route.path, onRouteChange)

      onMounted(() = > {
        console.log('App vue mounted', basePath, currentPath)
        let nextPath = currentPath
        if (currentPath.startsWith(basePath)) {
          //NOTE goes to home page by default
          nextPath = currentPath.replace(basePath, ' ')??'/'
        }
        // NOTE If memoryHistory is memoryHistory, jump to the corresponding component when mounting, solve the problem that the page cannot be maintained after the browser refreshes
        isMemoryHistory && router.push(nextPath)
      })

      return{}}},</script>
Copy the code

The App receives the current browser path, the base path (dashboard’s path in the Container), whether or not to route in memory, and the specific method to jump to, all of which are passed in from the Container.

function mount(el, { isMemoryHistory, basePath, currentPath, onNavigate, sharedData = {} }) {
  //NOTE basePath, currentPath, isMemoryHistory, onNavigate is the data passed by the Container to app. vue
  // onNavigate is the jump function that tells the Container where to navigate to when a dashboard route changes
  const app = createApp(App, { basePath, currentPath, isMemoryHistory, onNavigate, sharedData })
  const history = isMemoryHistory ? createMemoryHistory(basePath) : createWebHistory()
  setupRouter(app, { history })
  app.mount(el)
}
Copy the code

How do these parameters get passed to the App?

In the Container application, pass it through mount

export default() = > {const ref = useRef(null)
  const history = useHistory()
  useEffect(() = > {
    mount(ref.current, {
      isMemoryHistory: true.basePath: '/dashboard'.currentPath: history.location.pathname,
      onNavigate: nextPathname= > {
        const { pathname } = history.location
        if(pathname ! == nextPathname) {console.log('Vue sub-app Jump', nextPathname)
          history.push(nextPathname)
        }
      },
    })
    console.log('container dashboard navigate')}, [])return <div ref={ref} />
}
Copy the code

The preceding methods enable dashboard route redirection and notify Container to switch paths. When A Container switches paths, you need to notify The Dashboard redirect path. How do I find this method?

Mount returns a function that handles the specific jump logic. Because mount is called inside the Container, it retrieves the return value and is called when the Container route changes.

function mount(el, { isMemoryHistory, basePath, currentPath, onNavigate, sharedData = {} }) {
  const history = isMemoryHistory ? createMemoryHistory(basePath) : createWebHistory()
  return {
    // PathName is the browser path passed by the Container
    onParentNavigate({ pathname: nextPathname }) {
      console.log('dashboard vue onParentNavigate', nextPathname)
      // history.listen Is a function provided by vue-router to listen for path changes. The parameter is a callback function to call the current application path
      history.listen(currentPath= > {
        if(currentPath ! == nextPathname) { history.push(nextPathname) } }) }, } }Copy the code

Call onParentNavigate in dashboardapp. JSX:

import { mount } from 'dashboard/DashboardApp'
import React, { useRef, useEffect } from 'react'
import { useHistory } from 'react-router-dom'

export default() = > {const ref = useRef(null)
  const history = useHistory()
  useEffect(() = > {
    const { onParentNavigate } = mount(ref.current, {})
    console.log('container dashboard navigate')
    history.listen(onParentNavigate) // The path of the container changes, then call onParentNavigate
    // history.listen is a function provided by react-router-dom, which triggers the callback when the address bar changes, and the history object when the callback attempts
  }, [])

  return <div ref={ref} />
}
Copy the code

How do you share data?

Once routing conflicts are resolved, how do applications share data?

Again, through mount.

Mount is called in the Container, defined in Dashboard, so the container’s data can be passed to dashboard when called.

Mount has a sharedData field that takes arguments from the Container and passes them to app.vue via the second argument to createApp.

function mount(el, { sharedData = {} }) {
  const app = createApp(App, { sharedData })
}
Copy the code

App.vue accepts sharedData via props:

<template>
  <div id="app">
    <div id="nav">
      <RouterLink to="/">Home</RouterLink>
      <RouterLink to="/upload">Upload Dropzone</RouterLink>
    </div>
    <! -- NOTE to route exit -->
    <RouterView :sharedData="sharedData" />
  </div>
</template>
<script>
  import { onMounted } from '@vue/runtime-core'
  export default {
    name: 'App'.props: {
      sharedData: {
        type: Object.default: () = >({})}},setup(props) {
      const { sharedData } = props

      onMounted(() = > {
        console.log('App vue mounted', sharedData)
      })

      return{}}},</script>
Copy the code

In particular, the RouterView can pass the data and then receive it in the corresponding routing component.

Call mount on the dashboardapp. JSX of the Container

// isSignedIn and user are props for the component
export default ({ isSignedIn, user }) => {
  const ref = useRef(null)
  useEffect(() = > {
    const { onParentNavigate } = mount(ref.current, {
      sharedData: { isSignedIn, user },
    })
  }, [])

  return <div ref={ref} />
}
Copy the code

Passing data in the routing configuration of a Container:

<DashboardLazy user={user} isSignedIn={isSignedIn} />
Copy the code

How do YOU implement style isolation?

Common style scoping solutions

  1. The custom CSS

CSS in JS: will recompile the selector. Different projects using the same CSS in JS library can lead to style conflicts in a production environment.

Cause: In the production environment, the class name generated by CSS in JS is short. As a result, different front-end applications have the same class name and different rules, leading to conflicts.

Solution: Use a different CSS-in-JS library, or query its local directory, and configure a user-defined prefix.

For example, @material-ui createGenerateClassName can be used to customize the class name prefix.

import React from 'react'
import { Switch, Route, Router } from 'react-router-dom'
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles'

import { Landing, Pricing } from './components'

const generateClassName = createGenerateClassName({
  productionPrefix: 'ma',})export default ({ history }) => {
  return (
    <div>
      <StylesProvider generateClassName={generateClassName}>
        <Router history={history}>
          <Switch>
            <Route exact path='/pricing' component={Pricing} />
            <Route path='/' component={Landing} />
          </Switch>
        </Router>
      </StylesProvider>
    </div>)}Copy the code

② Vue’s built-in Scoped style: add custom attributes to tags

Others: Adding a Namespace to the CSS: Setting a special selector

  1. CSS library

CSS style library, self-construction, more trouble.

  1. Different versions of the same style library result in different style rules

① The same class name and different rules lead to inconsistent styles;

② Same rules, different class names lead to style failure.

Workaround: Different style libraries do not share.

In contrast, Vue Scoped and class name prefixes are the most convenient solution.

Are there any other plans?

At this point, routing navigation, data sharing and style conflict problems have been solved, and the problems encountered in integration have been basically solved. The implementation of micro front-end architecture is a process of first dividing and then combining, and how to divide is discussed below.

React microapplication development, how to integrate into container?

How does the React application integrate into Container?

Install mount from react micro. Install mount from React micro. Install mount from React micro.

There is now a Marketing React application that you want to integrate into containers.

Entry file dashboard.js

import React from 'react'
import ReactDOM from 'react-dom'
import { createMemoryHistory, createBrowserHistory } from 'history'
import App from './App'

// Mount function to start up the app
function mount(el, { onChildNavigate, defaultHistory, currentPathParent }) {
  const history =
    defaultHistory ||
    createMemoryHistory({
      initialEntries: [currentPathParent],
    })
  const { pathname: currentPathChild } = history.location
  // When the NOTE browser is refreshed, the application will be remounted. Keep the path as the current path
  if(currentPathParent && currentPathChild && currentPathParent ! == currentPathChild) {console.log('child history.push'. currentPathParent) history.push(currentPathParent) } onChildNavigate && history.listen(onChildNavigate) ReactDOM.render(<App history={history} />, el)

  return {
    onParentNavigate({ pathname: nextPathname }) {
      const{ pathname } = history.location nextPathname && pathname ! == nextPathname && history.push(nextPathname) }, } }// If we are in development and in isolation,
// call mount immediately
if (process.env.NODE_ENV === 'development') {
  const el = document.getElementById('_marketing-dev-root')
  const history = createBrowserHistory()
  el && mount(el, { defaultHistory: history })
}

// We are running through container
// and we should export the mount function
export { mount }
Copy the code

Bootstrap.js is introduced in index.js

import('./bootstrap')
Copy the code

Webpack. Dev. Js configuration

const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const commonConfig = require('./webpack.common')
const packageJson = require('.. /package.json')

const devConfig = {
  mode: 'development'.output: {
    publicPath: 'http://localhost:8081/'.clean: true.// Clear the dist directory before build
  },
  devServer: {
    port: 8081.historyApiFallback: true,},plugins: [
    new ModuleFederationPlugin({
      name: 'marketing'.filename: 'remoteEntry.js'.exposes: {
        './MarketingApp': './src/bootstrap',},shared: packageJson.dependencies,
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',})],}module.exports = merge(commonConfig, devConfig)
Copy the code

Introduce marketing in the Container’s marketingapp.jsx

import { mount } from 'marketing/MarketingApp'
import React, { useRef, useEffect } from 'react'
import { useHistory } from 'react-router-dom'

export default() = > {const ref = useRef(null)
  const history = useHistory()

  useEffect(() = > {
    const { onParentNavigate } = mount(ref.current, {
      currentPathParent: history.location.pathname,
      onChildNavigate: ({ pathname: nextPathname }) = > {
        console.log('marketing react: ', nextPathname)
        const{ pathname } = history.location nextPathname && pathname ! == nextPathname && history.push(nextPathname) }, }) history.listen(onParentNavigate) }, [])return <div ref={ref} />
}
Copy the code

Configure MarketingApp in the Container route

import React, { lazy, Suspense, useState, useEffect } from 'react'
import { Router, Route, Switch, Redirect } from 'react-router-dom'
import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles'
import { createBrowserHistory } from 'history'

import { Progress, Header } from './components'

const MarketingLazy = lazy(() = > import('./components/MarketingApp'))

const generateClassName = createGenerateClassName({
  productionPrefix: 'co',})const history = createBrowserHistory()

export default() = > {const [isSignedIn, setIsSignedIn] = useState(window.localStorage.getItem('isSignedIn') = = ='true')
  const [user, setUser] = useState(JSON.parse(window.localStorage.getItem('user')))

  useEffect(() = > {
    if (isSignedIn) {
      history.push('/dashboard')
    }
  }, [isSignedIn])

  return (
    <Router history={history}>
      <StylesProvider generateClassName={generateClassName}>
        <div>
          <Header
            onSignOut={()= > {
              window.localStorage.removeItem('isSignedIn')
              window.localStorage.removeItem('user')
              window.sessionStorage.removeItem('user')
              setIsSignedIn(false)
            }}
            isSignedIn={isSignedIn}
          />
          <Suspense fallback={<Progress />} ><Switch>
              <Route path='/' component={MarketingLazy} />
            </Switch>
          </Suspense>
        </div>
      </StylesProvider>
    </Router>)}Copy the code

The marketing application uses the routing library version “react-router-dom”: “^5.2.0”, which is having difficulty using the latest version.

Use the latest library integration successful friends can tell me about it.

Why use functions for data sharing

Function call, function call characteristics:

  1. inDefined in theTo receiveExternal data (by parameter)In theCall the placeAccess to theThe return value.parameterandThe return valueAssociate where you define and where you call.
  2. With few dependencies and a properly designed function, they can be effectively reduced – at best, they rely only on parameters and are easily extensible.
  3. Functions are JS code that can be called in any framework.

Why not export components for integration in Containers?

Components are difficult to implement across frameworks.

Why not use a state management library for data sharing?

The state management library requires the same framework and is difficult to achieve technology independence.

Even if you use the same framework to build a micro front end, avoid using state management libraries to share data between different applications, which increases coupling.

Results show and source code

Using GH-Pages to deploy these applications, the module federation in production environment needs to be modified, see the warehouse source code.

container --- react
auth --- react
dashboard --- vue3
marketing --- react
Copy the code

Micro – front – end – MFD source code

Integrated applications

Independently deployed applications

Marketing – the react micro application

Vue3 micro – dashboard application

The react – auth micro application

Recommend the tutorial

The React microfrontend tends with React A Complete Developer’s Guide, and p43 — P66 explains CICD, which can be skipped.

reference

Webpack 5 and Module Federation – A Microfrontend Revolution

Webpack 5 Module Federation: A game-changer in JavaScript architecture

The WebPack 5 module federates micro-front-end troubleshooting

Can WebPack fight back against the ESM development model?