跳到主要内容

MicroApp 快速上手

demo架构

主应用 - react

1. 项目准备

1. 创建项目

  1. 创建主应用项目:npx create-react-app react18
  2. 创建子应用项目-react:npx create-react-app react18
  3. 创建子应用项目-vue3:vue create vue3

2. 安装相关依赖

  1. 【主应用】安装微前端框架: npm i @micro-zoe/micro-app --save
  2. 【主应用】安装路由依赖:npm i react-router-dom
  3. 【子应用-react】安装路由依赖:npm i react-router-dom
  4. 【子应用-react】安装跨域解决支持依赖:npm install react-app-rewired customize-cra --save-dev
  5. 【子应用-vue3】安装路由依赖:npm install vue-router

2. 搭建相关路由系统

  1. 【主应用】路由系统

    // router/index.js
    import { lazy, Suspense } from 'react'
    import { createBrowserRouter } from 'react-router-dom'
    import Home from '../pages/home'

    const React18 = lazy(() =>
    import(/* webpackChunkName: "react18" */ '../pages/React18')
    )
    const Vue3 = lazy(() => import(/* webpackChunkName: "vue3" */ '../pages/Vue3'))

    const router = createBrowserRouter([
    {
    path: '/',
    element: <Home />,
    errorElement: <div>404</div>,
    children: [
    {
    path: 'react18',
    element: (
    <Suspense fallback={<div>loading...</div>}>
    <React18 />
    </Suspense>
    ),
    },
    {
    path: 'vue3',
    element: (
    <Suspense fallback={<div>loading...</div>}>
    <Vue3 />
    </Suspense>
    ),
    },
    ],
    },
    ])

    export default router
  2. 【主应用】路由系统相关组件

    // react18
    const React18 = () => {
    return (
    <div>
    <micro-app
    name="react18"
    url="http://localhost:3311/"
    baseroute="/react18"
    />
    </div>
    )
    }

    export default React18

    // vue3
    const Vue3 = () => {
    return (
    <div>
    <micro-app name="vue3" url="http://localhost:3312/" baseroute="/vue3" />
    </div>
    )
    }

    export default Vue3
  3. 【子应用-react】路由系统

    // router/index.js
    import { createBrowserRouter } from 'react-router-dom'
    import { lazy, Suspense } from 'react'
    import Home from '../pages/home'

    const About = lazy(() =>
    import(/* webpackChunkName: "about" */ '../pages/about')
    )
    const Detail = lazy(() =>
    import(/* webpackChunkName: "detail" */ '../pages/detail')
    )

    const router = createBrowserRouter(
    [
    {
    path: '/',
    element: <Home />,
    // 这里放置自己的路由即可
    children: [
    {
    path: '/about',
    element: (
    <Suspense fallback={<div>loading</div>}>
    <About />
    </Suspense>
    ),
    },
    {
    path: 'detail/:id',
    element: (
    <Suspense fallback={<div>loading</div>}>
    <Detail />
    </Suspense>
    ),
    },
    ],
    },
    ],
    // 注意此处为微前端侵入性修改
    {
    basename: window.__MICRO_APP_BASE_ROUTE__ || '/',
    }
    )

    export default router
  4. 【子应用-vue3】路由系统

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

    const router = createRouter({
    // 注意此处为微前端侵入性修改
    history: createWebHistory(
    window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL
    ),
    routes: [
    {
    path: '/detail',
    name: 'detail',
    component: () => import('../pages/Detail.vue'),
    },
    {
    path: '/about',
    name: 'about',
    component: () => import('../pages/AboutView.vue'),
    },
    ],
    })

    export default router

3. 引入微前端框架

  1. 【主应用】入口文件引入框架

    // index.js
    import microApp from '@micro-zoe/micro-app'

    microApp.start()

4. 子应用跨域问题处理

  1. 【子应用-react】修改package.json

    "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
    },
  2. 【子应用-react】根目录下添加 config-overrides.js

    const { overrideDevServer } = require('customize-cra')
    module.exports = {
    devServer: overrideDevServer((config) => {
    return {
    ...config,
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    }
    }),
    }
  3. 【子应用-vue】根目录vue.config.js中添加配置

    const { defineConfig } = require('@vue/cli-service')

    module.exports = defineConfig({
    // ...
    devServer: {
    port: 3312,
    headers: {
    'Access-Control-Allow-Origin': '*',
    },
    },
    })

5. 子应用设置publicPath

  1. 【子应用-react】src目录下创建名称为public-path.js的文件,并添加如下内容

    // __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
    if (window.__MICRO_APP_ENVIRONMENT__) {
    // eslint-disable-next-line
    __webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
    }
  2. 【子应用-react】入口文件的最顶部引入public-path.js

    // entry
    import './public-path'
  3. 【子应用-vue】src目录下创建名称为public-path.js的文件,并添加 同上述react子应用中相同内容

  4. 【子应用-vue】入口文件的最顶部引入public-path.js

🏷提示:

到这里已经完成一个微前端的基本搭建工作,展示效果如下:

basicdemo

整个接入过程非常简单,侵入性的操作总结有:

  • 主应用
    • microApp.start()
    • 添加微应用的容器组件
    • 添加路由指向这个容器组件
  • 微应用
    • 修改 public-path
    • 添加跨域访问
    • 自动切换路由的 basename

6. 数据通信

父传子

  1. 【主应用】修改路由

    // router/index.js
    import { lazy, Suspense } from 'react'
    import { createBrowserRouter } from 'react-router-dom'
    import Home from '../pages/home'

    const React18 = lazy(() =>
    import(/* webpackChunkName: "react18" */ '../pages/React18')
    )
    const Vue3 = lazy(() => import(/* webpackChunkName: "vue3" */ '../pages/Vue3'))

    const router = createBrowserRouter([
    {
    path: '/*',
    element: <Home />,
    errorElement: <div>404</div>,
    children: [
    {
    path: 'react18/*',
    element: (
    <Suspense fallback={<div>loading</div>}>
    <React18 />
    </Suspense>
    ),
    },
    {
    path: 'vue3/*',
    element: (
    <Suspense fallback={<div>loading</div>}>
    <Vue3 />
    </Suspense>
    ),
    },
    ],
    },
    ])

    export default router
  2. 【主应用】页面组件添加以下内容

    // React18
    const location = useLocation()

    useEffect(() => {
    // 第一个参数为子应用名称
    microApp.setData('react18', {
    path: location.pathname.replace('/react18', ''),
    })
    }, [location.pathname])

    // 其他页面组件将相应第一个参数和路由进行修改即可
  3. 【子应用-react】在首页添加监听事件并重定向路由

    const navigate = useNavigate()

    useEffect(() => {
    function dataListener(data) {
    if (data.path) {
    navigate(data.path)
    }
    }
    if (window.__MICRO_APP_ENVIRONMENT__) {
    window.microApp.addDataListener(dataListener)
    }
    return () => {
    if (window.__MICRO_APP_ENVIRONMENT__) {
    // 解绑监听函数
    window.microApp.removeDataListener(dataListener)
    // 清空当前子应用的所有绑定函数(全局数据函数除外)
    window.microApp.clearDataListener()
    }
    }
    }, [])
  4. 【子应用-vue】在首页添加监听事件并重定向路由

    mounted() {
    const router = useRouter()
    function dataListener(data) {
    if (data.path) {
    router.push(data.path)
    }
    }
    if (window.__MICRO_APP_ENVIRONMENT__) {
    window.microApp.addDataListener(dataListener)
    }
    },
    unmounted() {
    if (window.__MICRO_APP_ENVIRONMENT__) {
    // 解绑监听函数
    window.microApp.removeDataListener()
    // 清空当前子应用的所有绑定函数(全局数据函数除外)
    window.microApp.clearDataListener()
    }
    },

子传父

  1. 【子应用-react】监听路由改变

    // home/index.js
    const location = useLocation()

    useEffect(() => {
    if (window.microApp) {
    window.microApp.dispatch({
    path: location.pathname,
    })
    }
    }, [location.pathname])
  2. 【主应用】获取数据并做出响应

    // home/index.js
    const [selectedKeys, setSelectedKeys] = useState([])

    useEffect(() => {
    function dataListener(data) {
    setSelectedKeys([`/react18${data.path}`])
    }

    microApp.addDataListener('react18', dataListener)

    return () => {
    // 解绑监听my-app子应用的函数
    microApp.removeDataListener('react18', dataListener)
    // 清空所有监听appName子应用的函数
    microApp.clearDataListener('react18')
    }
    }, [])

    子应用发送数据等操作同理,这里就不做演示了,可直接看源码~

完成效果如下:

数据通信

常见问题整理

使用数据通信后 在浏览器中使用前进后退需要操作2次

原始问题描述:

from: https://github.com/micro-zoe/micro-app/issues/849

主应用使用浏览器的前进后退功能时,需要点击2次才能正确加载子应用的页面

问题原因:

主应用在进行了路由跳转之后,子应用接收到父应用发送的data数据:路由改变,然后子应用进行路由重定向,当前windows路由本身已经跳转了一次,子应用接收到消息后,类似重新执行了一次 window.history.push() 导致当前路由存储了两次,所以在回退/前进的时候需要操作两次。

问题处理:

附言:

当前这种处理方式会增加资源消耗,毕竟会多加载子应用资源。否则主应用处留一处进入子应用入口,其他事情交给子应用自行处理即可。

相关指南