Implementation of micro front end with webpack 5 module Federation

Implementation of micro front end with webpack 5 module Federation

Micro front end: split the huge single front-end system into multiple independent small systems, and finally integrate it into a system architecture idea, divide and rule, and make the system
It is easier to maintain and expand. The implementation of micro front end is a process of splitting first and then merging.

The front-end application here refers to the single page application with front and back ends separated. It is meaningful to talk about the micro front-end on this basis.

Both micro front end and micro service are designed to solve the problems of large projects and large teams: large projects realize module decoupling and large teams realize personnel decoupling. These two problems are software engineering
>The main problems studied in this course.

Conway's Law: software structure reflects personnel structure, and personnel structure determines software structure.

Introduction to Ruan Yifeng software engineering

The theoretical basis of microservice Architecture - Conway's law

Conway's law

Why do I need a micro front end

With the increase of business, Jushi monomer system becomes more and more bloated. Multiple teams develop it together, with high communication success and difficulties in compilation, deployment, testing and maintenance. The micro front end can solve these problems.

  1. Application autonomy: each application is independent of each other, smaller in scale, easier to expand, test, build, maintain, debug, upgrade and rely on;
  2. Team autonomy: after the application is independent, the team will also be independent, reducing the number of people developing at the same time and interacting with each other in a boulder application, so as to improve the development efficiency;
  3. Technology independent: each application can choose different frameworks for development and try to maintain unity. Otherwise, the interaction between applications may encounter difficulties and is not conducive to component reuse, such as failure to reuse
    Shared component level code;
  4. Try new technologies: after application splitting, it is easy to try new technologies in the system.
  5. Incremental reconfiguration of old system.

Disadvantages:

  1. It is difficult to unify code specifications (many personnel and projects), which is 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 and interaction are easy to be inconsistent and overcome

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

Implementation recommendations:

  1. Consistent working methods: team members need to agree on working methods in advance, especially the interaction agreement between host applications and remote applications;
  2. Combining business: before using the micro front-end architecture, think about the business division and the value of the micro front-end to the team;
  3. Follow a consistent code style to facilitate later maintenance;
  4. Don't overuse: use only when you want to achieve the goal of splitting personnel or technology, or when it is necessary to split personnel or technology.

Introduction to micro front end

The earliest discussion article on micro front end

How to integrate - aggregate

There are three main integration modes:

Integration modeSpecific descriptionadvantageshortcomingother
Build time integrationPackage the micro application into the main application, and the micro application is usually published in npm packageImplementation is easy and well understoodOne of the dependent applications needs to be updated and the other needs to be updated and then deployed. In fact, it cannot be deployed independentlyIn fact, this method does not achieve the goal of micro front-end
Runtime buildAfter the main application is loaded in the browser, it can obtain the micro application codeIndependently deployed, the main application can decide which micro application version to use, which is flexible and has good performanceThe setting is complex and difficult to understandAt present, it is a common way to achieve the goal of micro front-end
Server IntegrationThe main application requests the micro application from the server, and the server decides whether to give the codeHeavily dependent on background codeThe implementation is complex and generally not used

What issues need to be considered during integration?

  1. Avoid style conflicts
  2. Simple communication between applications
  3. Navigation between different applications
  4. Ability to integrate specific versions
  5. Version control does not interfere with each other
  6. It can facilitate dependency sharing

Common micro front end implementations

After the emergence of micro front-end architecture, there are some frameworks and solutions in the industry. Common ones are:

Frame:

MicroApp ,single-spa,qiankuan

Solution without trouser rack:

web components are not recommended and cannot provide modular functions. The larger the project, the easier it is to get out of control. The essence of web component s is to encapsulate custom html, which will soon come to the jQuery era.

iframe

webpack5 module federation, a new feature of webpack5, can share code across applications.

How to split micro applications?

When splitting, it is hoped that the interaction between different applications will be minimized and decoupled to the greatest extent, so as to facilitate the communication between different applications. Otherwise, it will be difficult to test applications and determine problems
Bit.

According to the business division, the same business is split into the same micro application.

According to the permission division, the functions used by different users are divided together according to the user's permission.

According to the division of background micro services, some teams use micro service architecture in the background, which can be considered to be divided according to micro services.

Module Federation -- module Federation

Module Federation is a feature introduced by webpack 5, which can easily share code between two projects built using webpack, and even combine different applications into one application.

Module Federation official website

Configuration of module Federation

Module federation can share code across applications. Take two applications as examples to illustrate their configuration.

A dashboard vue3 application that wants to provide code to other applications:

// const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin ') / / export module Federation
const { ModuleFederationPlugin } = require('webpack').container // It's OK
// webpack plug-in configuration
{
plugins: [
    new ModuleFederationPlugin({
      name: 'dashboard', // The module name shared by exposeRemoteName will be used in applications consuming this module and cannot be used-
      filename: 'remoteEntry.js', // The name of the file loaded remotely can be seen in the browser request panel. The default name is remoteentry js
      exposes: {
        './DashboardApp': './src/bootstrap', // The shared modules exposed from this application can share multiple module key s/ At the beginning, value points to a local file
      },
      shared: packageJson.dependencies, // Dependencies you want to share
    }),
  ],
}

About name setting

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 an error may be reported.

container react app consumption dashboard

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
{
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',// For modules that want to be shared, although the container is not consumed by other applications, it is a good practice to declare the name
      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,// Modules you want to share
    }),
  ],
}

How to use dashboard in container?

Create a new dashboardapp in the container JSX component to introduce dashboard:

Dashboard is the field of remotes in the container. Dashboard app is the exposure of dashboard in exposes, and key is a hang exported from dashboard
Load function. You can mount the application anywhere in the container

// exposeRemoteName/expose
import { mount } from 'dashboard/DashboardApp' // NOTE: Please NOTE the correspondence between the writing method 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 the dashboard to the div through mount. These parameters and return values are the way to realize data sharing, which will be described in detail 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 Subapplication jump', nextPathname)
          history.push(nextPathname)
        }
      },
      sharedData: { isSignedIn, user },
    })
    console.log('container dashboard navigate')
    history.listen(onParentNavigate)
  }, [])

  return <div ref={ref} />
}

Then export the component and place it in the / Dashboard path to navigate to the 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 local storage
                    window.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>
  )
}

What are the exposed modules of dashboard?

Bootstrap in dashboard 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 }

Key points:

① Provide a mount function and export the function, so that the application consuming the module can mount it arbitrarily. In the independent deployment and development environment, call the function to mount the application to the page
Come on.

② Reasonably design the parameters and return values of mount to realize data sharing and routing switching between applications.

③ In the webpack entry file index JS introduces bootstrap js:

import('./bootstrap.js')

In the entry file index JS directly uses bootstrap JS content, what will happen?

When running the micro application alone, an error is reported:

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

history.listen(onNavigate) doesn't work. I don't know why.

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

What is the execution order of the introduced remote modules?

How to avoid conflict between container and dashboard routing and navigation?

In order to realize independent deployment and switch pages, each microenterprise should have its own navigation, and the container also has its own navigation. How to solve the navigation after the container loads the dashboard
Aviation conflict?

Why are there navigation conflicts?

A path can only correspond to one page: at the same time, the browser has only one path, which corresponds to one page or component. When switching to this path, the page or component will be rendered
Components.

Vue router provides Web routing and memory routing. When using web routing, switch the path of the application, the browser address bar will change, the browser address will change, and the rendering in the application will change
The of components will also change; When using memory navigation, the address bar is decoupled from the rendering of components in the application.

Why is there memory routing? For non browser environments.

vue and react have these two routes.

When the micro application is deployed separately, it uses web routing. When it is integrated into the container, it uses memory routing. The web routing is taken over by the container. When the browser address bar changes, it will be notified
Appeal to the integrated micro application, and then the micro application jumps to the corresponding page.

Routing configuration of 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 routing configuration function. web routing is used by default
export function setupRouter(app, { history = createWebHistory() } = {}) {
  const router = createRouter({
    history,
    routes,
  })
  app.use(router)
}

How to use setupRouter in mount?

Key codes:

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

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 })
  }
}

Memory routing is used during integration. At the same time, it is also necessary to detect whether the browser path changes and switch the route when it changes. Otherwise, the dashboard will not change during container navigation.

On app Jump process in 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()

      // Jump only when the NOTE route changes and a jump function is provided
      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 the home page by default
          nextPath = currentPath.replace(basePath, '') ?? '/'
        }
        // If the NOTE is a memoryHistory, jump to the corresponding component when mounting to solve the problem that the page cannot be maintained when the browser is refreshed
        isMemoryHistory && router.push(nextPath)
      })

      return {}
    },
  }
</script>

App receives the current browser path, the basic path (the path in the container of the dashboard), the specific method of memory routing and jump. These parameters are from
container passed in.

function mount(el, { isMemoryHistory, basePath, currentPath, onNavigate, sharedData = {} }) {
  //NOTE basePath, currentPath, isMemoryHistory, onNavigate, which is the container passed to app Vue data
  // onNavigate is a jump function, and the dashboard route change is to tell the container where to jump through this function
  const app = createApp(App, { basePath, currentPath, isMemoryHistory, onNavigate, sharedData })
  const history = isMemoryHistory ? createMemoryHistory(basePath) : createWebHistory()
  setupRouter(app, { history })
  app.mount(el)
}

How are these parameters passed to the App?

Pass through mount in container application

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 Subapplication jump', nextPathname)
          history.push(nextPathname)
        }
      },
    })
    console.log('container dashboard navigate')
  }, [])

  return <div ref={ref} />
}

The above method realizes the route jump in the dashboard and notifies the container to switch the path. When the container switches the path, it needs to notify the dashboard to jump the path. How
Find this?

You can return a function through mount, which handles the specific jump logic. Since mount is called inside the container, you can obtain the return value in the container
This function is called when the 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, which can listen for path changes. The parameter is a callback function. The callback is the current application path
      history.listen(currentPath => {
        if (currentPath !== nextPathname) {
          history.push(nextPathname)
        }
      })
    },
  }
}

At dashboardapp onParentNavigate is invoked in 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) // container path changes, then call onParentNavigate
    // history.listen is a function provided by react router dom. It triggers the execution of the callback function when the address bar changes. The attempt of the callback function is the history object
  }, [])

  return <div ref={ref} />
}

How do I share data?

The problem of routing conflict is solved. How do applications share data?

Or through mount.

mount is invoked in container, defined in dashboard, so that container data can be transferred to dashboard at the time of invocation.

mount has a sharedData field, which accepts the parameters passed from container, and then passes them to app through the second parameter of createApp Vue.

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

App.vue receives sharedData through props:

<template>
  <div id="app">
    <div id="nav">
      <RouterLink to="/">Home</RouterLink>
      <RouterLink to="/upload">Upload Dropzone</RouterLink>
    </div>
    <!-- NOTE  Pass data to routing 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>

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

Dashboardapp. In container JSX calls mount

// props of components when isSignedIn and user
export default ({ isSignedIn, user }) => {
  const ref = useRef(null)
  useEffect(() => {
    const { onParentNavigate } = mount(ref.current, {
      sharedData: { isSignedIn, user },
    })
  }, [])

  return <div ref={ref} />
}

Transfer data in the routing configuration of the container:

<DashboardLazy user={user} isSignedIn={isSignedIn} />

How to implement style isolation?

Common style scope solutions

  1. Custom css

① css in js: the selector is recompiled. Different projects use the same css in js library, which may lead to style conflicts in the production environment.

Reason: in the production environment, the class name generated by css in js is short, which leads to the same class name and different rules between different micro front-end applications, resulting in conflicts.

Solution: use different CSS in JS libraries, or query their local, and configure custom prefixes.

For example, createGenerateClassName of @ material UI can customize the prefix of class name.

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>
  )
}

② vue built-in scoped style: add custom attributes to labels

Other: add namespace to css: set special selector

  1. css Library

css style library, self built, more troublesome.

  1. Different versions of the same style library lead to different style rules

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

② The same rules and different class names lead to style invalidation.

Solution: different style libraries are not shared.

In contrast, vue scoped and setting the prefix of class name are the most convenient solutions.

Are there any other plans?

So far, the problems of route navigation, data sharing and style conflict have been solved, and the problems encountered in integration have been basically solved. The implementation of micro front-end architecture is a * * divide first and then
The process of closing * *, let's talk about how to divide.

Micro applications are developed with react. How can they be integrated into the container?

The above and vue3 are integrated into the container. How can react applications be integrated into the container?

The idea is similar to the previous one. When deriving mount from react micro application, we should also deal with the problems of routing to the end, style conflict and so on.

Now there is a react application for marketing, which hopes to be integrated into the container.

The 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 refreshes, the application will be re mounted. At this time, keep the path consistent with 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 }

In index JS introduces bootstrap js

import('./bootstrap')

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)

In the marketingapp. Of the container Introducing JSX into marketing

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} />
}

Configure MarketingApp in container routing

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>
  )
}

The routing library version used by the marketing application is "react router DOM": "^ 5.2.0". It is difficult to use the latest version.

A friend who has successfully integrated with the latest library can tell me.

Why use functions for data sharing

Function call, features of function call:

  1. External data is received at the definition (through parameters), and the return value is obtained at the call. Parameters and return values connect the definition with the call.
  2. Less dependency and proper function design can effectively reduce dependency - at best, it only depends on parameters and is very easy to expand.
  3. The function is js code and can be invoked in any frame.

Why not export components for integration in container?

Components are difficult to use across frameworks.

Why not use the state management library for data sharing?

The state management library requires the same framework, so it is difficult to realize technology independence.

Even if the same framework is used to build the micro front end, it is necessary to avoid using the state management library to share data between different applications, which increases the coupling.

Result display and source code

To deploy these applications using GH pages, the module Federation of the production environment needs to be modified. See the warehouse source code for details.

container --- react
auth --- react
dashboard --- vue3
marketing --- react

Micro front end MFD source code

Integrated application

Independently deployed applications

Marketing react micro application

Vue3 dashboard micro application

React auth microapplication

Recommended tutorial

This article is based on video teaching
Cheng Micro frontends with React a complete developer's Guide Sorting, highly recommended, p43 --- p66 explain CICD, which can be skipped.

reference resources

Webpack 5 and Module Federation - A Microfrontend Revolution

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

Troubleshooting of micro front end implemented by webpack 5 module Federation

Does webpack still have the strength to fight back against the ESM development model?

Keywords: Vue.js Webpack webpack5

Added by hjunw on Mon, 24 Jan 2022 05:49:48 +0200