重塑前端开发:如何利用 micro-app 实现高效微前端架构

1,314 阅读10分钟

前言

上一篇文章中,我们深入探讨了阿里基于 single-spa 的微前端框架 qiankun 及其核心理念和应用。然而,微前端的世界不断发展,京东的 micro-app 作为另一种解决方案,正以其基于 web-component 的设计和强大功能赢得开发者的青睐。micro-app 不仅实现了高效的前端模块化开发,还具备灵活的集成和独立部署能力,大大提升了开发和维护效率。

在本文中,我们将重点介绍 micro-app 的核心概念、关键特性及实际应用场景,帮助你更好地理解和选择这一微前端框架。希望本文能为你的前端开发提供新的视角和有价值的参考。

关于 micro-app 的核心概念

single-spa 通过监听 URL 变化事件,在路由变化时匹配并渲染相应的子应用。这一思路目前是实现微前端的主流方式。然而,single-spa 要求子应用修改渲染逻辑并暴露三个方法:bootstrapmountunmount,分别对应初始化、渲染和卸载。这就意味着子应用需要对其入口文件进行修改。此外,使用 qiankun 由于其基于 single-spa 进行封装,也继承了这些特点,并需要对 webpack 配置进行一定的调整。

与此不同,micro-app 并未沿袭 single-spa 的思路,而是借鉴了 WebComponent 的理念。通过将 CustomElement 与自定义的 Shadow DOM 结合,micro-app 将微前端封装成一个类 WebComponent 组件,从而实现组件化渲染。得益于自定义 Shadow DOM 的隔离特性,micro-app 不需要像 single-spaqiankun 那样要求子应用修改渲染逻辑并暴露方法,也无需修改 webpack 配置。这使得 micro-app 成为接入微前端成本最低的方案。

micro-app 和 qiankun 的核心特性对比

特性micro-appqiankun
使用简单类 WebComponent 组件,一行代码嵌入,提供完整功能集。基于 single-spa,配置简单,提供 js 沙箱、样式隔离等功能。
零依赖无依赖,体积小,扩展性高。依赖 single-spa,功能丰富,但体积较大。
兼容性兼容所有框架,支持独立开发和部署。与 single-spa 深度绑定,需额外配置支持某些框架。
社区支持与文档文档详细,社区支持较弱。社区活跃,文档完善,应用案例多。
学习曲线简单易用,学习成本低。需要一定学习曲线,特别是对 single-spa 不熟悉的开发者。

安装与快速上手

react 基座应用

1、首先,使用 create-react-app 创建一个新的 React 应用。

npx create-react-app main-app

2、安装micro-app依赖

npm i @micro-zoe/micro-app --save

3、在入口处引入

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

microApp.start()

4、React Router 来搭建基座应用的路由系统

// app.js
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/home';
import About from './pages/about';
import styles from './App.css';
function App() {
    return (
        <BrowserRouter>
            <div style={{ textAlign: 'center', marginTop: '20%' }}>
                <header className={styles.header}>
                    <Link to="/">基座 Home</Link>
                    <Link to="/about">基座 About</Link>
                </header>

                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route path="/about" element={<About />} />
                </Routes>
            </div>
        </BrowserRouter >
    );
}

export default App;

效果如图:

4、使用  micro-app  渲染子 react 微应用 在主应用的组件文件home.js中配置 micro-app,以便嵌入子应用。

    import React from 'react';

    const Home = () => {
        return (
            <div>
                <h2>首页Home Page</h2>
                <micro-app
                    name="subapp1"
                    url="http://localhost:3001/"
                    baseroute="/"
                ></micro-app>
            </div>
        );
    };

    export default Home;

react 子应用

1、 创建子应用

假设已有一个子应用,子应用可以使用任何前端框架创建,但为了演示,假设我们也用 create-react-app 创建子应用。

    npx create-react-app subapp1
    cd subapp1

2、配置子应用

在子应用的 package.json 中添加如下配置,以便它能正确地作为 micro-app 的子应用运行。

    {
      "name": "subapp1",
      "version": "0.1.0",
      "private": true,
      "homepage": ".",
      "scripts": {
        "start": "set PORT=3001 && react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
      // 其它配置项保持不变
    }

运行子应用:

    npm start

效果如图:

侵入总结

接入过程非常简单,侵入性操作总结如下:

主应用

  1. 启动 micro-app
        microApp.start();

2. 添加微应用容器组件

        <micro-app name="subapp1" url="http://localhost:3001/" baseroute="/"></micro-app>

3. 添加路由指向容器组件

        import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
        import Home from './pages/Home';
        import About from './pages/About';

        function App() {
          return (
            <BrowserRouter>
              <div>
                <header>
                  <Link to="/">基座 Home</Link>
                  <Link to="/about">基座 About</Link>
                </header>
                <Routes>
                  <Route path="/" element={<Home />} />
                  <Route path="/about" element={<About />} />
                </Routes>
              </div>
            </BrowserRouter>
          );
        }

        export default App;

微应用

  1. 修改 public-path: 在微应用的构建配置中设置正确的 publicPath,确保资源路径正确。例如,在 webpack.config.js 中:
        
        output: {
          publicPath: './',
        },
  1. 添加跨域访问: 确保微应用支持跨域访问。在微应用的服务器配置中允许跨域请求。例如:
        app.use((req, res, next) => {
          res.header("Access-Control-Allow-Origin", "*");
          next();
        });

3. 自动切换路由的 basename: 根据环境自动设置路由的 basename,确保路由切换正确。例如:

        import { BrowserRouter } from 'react-router-dom';

        const basename = window.__MICRO_APP_BASE_ROUTE__ || '/';
        <BrowserRouter basename={basename}>
          {/* 其他路由配置 */}
        </BrowserRouter>

通过以上步骤,你可以轻松将 micro-app 集成到主应用中,实现微前端架构的高效开发和部署。

vue 子应用

  1. 创建一个新的 vue 应用

    使用 vue-cli 创建一个新的 Vue 项目:

        vue create my-vue-app
在项目创建过程中,选择 Vue 3 版本。

2. 修改 public-path: 在 Vue 项目的 vue.config.js 中设置正确的 publicPath,以确保资源路径正确:

        module.exports = {
          publicPath: './',
        };

3. 添加跨域访问: 确保微应用支持跨域访问。在开发服务器的配置中允许跨域请求。在 vue.config.js 中添加以下配置:

        module.exports = {
          devServer: {
            headers: {
              'Access-Control-Allow-Origin': '*',
            },
          },
        };

4. 自动切换路由的 basename: 在微应用中根据环境自动设置路由的 basename,确保路由切换正确。在 Vue 项目的 src/main.js 中进行如下配置:

        import { createApp } from 'vue';
        import App from './App.vue';
        import { createRouter, createWebHistory } from 'vue-router';
        import routes from './routes';

        const router = createRouter({
          history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || '/'),
          routes,
        });

        const app = createApp(App);
        app.use(router);
        app.mount('#app');

如图所示:

注意:

1、name 必须以字母开头,且不可以带有除中划线和下划线外的特殊符号

2、url 只是 html 地址,子应用的页面渲染还是基于浏览器地址的,关于这点请查看路由一章

3、baseroute 的作用请查看路由配置

4、子应用必须支持跨域访问,跨域配置参考这里

数据通信

数据通信是 micro-app 的一大亮点,相较于 qiankunEventBusmicro-app 提供了一种稍微简便一些的使用方式。尽管如此,使用方式仍可能显得有些 Hacky。

父传子

在基座应用的容器组件里,你可以通过 microApp.setData 方法来实现父应用向子应用的数据传递。以下是具体的使用示例:

    //  父应用
    <template>
    <div>
    <h1>基座应用</h1>
    <micro-app name="my-vue-app" url="http://localhost:8081/" @mounted="handleMounted" />
    </div>
    </template>
    <script>
    import microApp from '@micro-zoe/micro-app';
    export default {
    name: 'Container',
    methods: { handleMounted() {
    // 在子应用挂载后传递数据
    microApp.setData('my-vue-app', { message: 'Hello from the parent app!' }); }, }, };
    </script>
    <style> /* 样式可以根据需要自定义 */ </style>

<!---->

    //  子应用
    <template>
    <div>
    <h1>子应用</h1>
    <p>{{ message }}</p>
    </div>
    </template>
    <script>
    export default {
    name: 'ChildComponent',
    data() {
    return {
    message: 'No message yet',
    };
    },
    mounted() {
    // 监听来自父应用的数据变化
    this.$microApp.addDataListener((data) => { if (data && data.message) { this.message = data.message; } }); }, };
    </script>
    <style> /* 样式可以根据需要自定义 */ </style>

使用 microApp.setData 可以方便地在基座应用和子应用之间传递数据。这种方式相比于 qiankunEventBus 更加简便,但仍可能显得有些 Hacky。通过上述示例,你可以轻松实现父应用向子应用的数据通信。

子传父

子应用向父应用传递数据在 micro-app 中同样是一个重要的功能。micro-app 提供了 microApp.dispatchmicroApp.addGlobalDataListener 方法来实现子传父的功能

Micro-App 高级功能

上面介绍了 micro-app 的一些基础操作,它还提供了一些高级功能来增强微前端项目的灵活性和可维护性。

Keep-Alive

保持微应用的状态 Keep-Alive

    <micro-app name='xx' url='xx' keep-alive></micro-app>

生命周期

你可以通过以下方法来监听微应用的生命周期事件:

    /** @jsxRuntime classic */
    /** @jsx jsxCustomEvent */
    import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event'

    const App = () => {
      return (
        <micro-app
          name='xx'
          url='xx'
          onCreated={() => console.log('micro-app元素被创建')}
          onBeforemount={() => console.log('即将被渲染,只在初始化时执行一次')}
          onMounted={() => console.log('已经渲染完成,只在初始化时执行一次')}
          onAfterhidden={() => console.log('已卸载')}
          onBeforeshow={() => console.log('即将重新渲染,初始化时不执行')}
          onAftershow={() => console.log('已经重新渲染,初始化时不执行')}
          onError={() => console.log('渲染出错')}
        />
      )
    }

应用之间跳转

  • 使用  window.history  进行导航。
  • 通过数据通信控制跳转。
  • 传递路由实例方法。

隔离

JS 隔离

使用 Proxy 拦截用户的全局操作行为,防止对 window 的访问和修改,以避免全局变量污染。

CSS 隔离

提供两种隔离方式:

  1. 默认添加 CSS 选择器前缀
  2. ShadowDOM

元素隔离

micro-app 模拟实现了类似 ShadowDOM 的功能,确保元素不会逃离 <micro-app> 元素边界,子应用只能对自身的元素进行操作。

静态资源处理

使用  globalAssets  共享资源:

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

    microApp.start({
      globalAssets: {
        js: ['js地址1', 'js地址2', ...], // js地址
        css: ['css地址1', 'css地址2', ...], // css地址
      }
    })

或者使用  global  属性:

    <link rel="stylesheet" href="xx.css" global>
    <script src="xx.js" global></script>

对资源进行过滤:

    <link rel="stylesheet" href="xx.css" exclude>
    <script src="xx.js" exclude></script>
    <style exclude></style>

渲染微前端模式

  • 默认模式:每次都按顺序执行一次 JS,具有幂等性。
  • UMD 模式:只在初次渲染时执行所有 JS,对于需要频繁切换微应用的项目可以提高其性能。

插件系统

插件系统的主要作用是对 JS 进行修改,每一个 JS 文件都会经过插件系统,你可以对这些 JS 进行拦截和处理。插件系统通常用于修复 JS 中的错误或向子应用注入一些全局变量。

这个插件系统主要在中间层处理 JS,避免一些由于固定模板而无法处理的 JS 报错。总的来说,这个系统还在发展中,需要更多开发者一起共建。

项目架构:

常见问题

1、子应用一定要支持跨域吗?

是的!

如果是开发环境,可以在 webpack-dev-server 中设置 headers 支持跨域。

    devServer: {
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
    },

如果是线上环境,可以通过配置 nginx支持跨域。

2、兼容性如何

micro-app 依赖于 CustomElements 和 Proxy 两个较新的 API。

对于不支持 CustomElements 的浏览器,可以通过引入 polyfill 进行兼容,详情可参考:webcomponents/polyfills

但是 Proxy 暂时没有做兼容,所以对于不支持 Proxy 的浏览器无法运行 micro-app。

浏览器兼容性可以查看:Can I Use

总体如下:

  • PC 端:除了 IE 浏览器,其它浏览器基本兼容。
  • 移动端:ios10+、android5+

3、微应用无法渲染但没有报错

请检查路由配置是否正确,详情查看路由一章,或者下面第 4 条:jsonpFunction 是否冲突

4、webpack-jsonpfunction-冲突导致渲染失败

这种情况常见于多个应用都是通过 create-react-app 等类似脚手架创建的项目,或一个应用多次重复渲染。

因为相同的 jsonpFunction 名称会导致资源加载混乱。

解决方式:修改子应用的 webpack 配置

webpack4

    // webpack.config.js
    module.exports = {
      output: {
        ...
        jsonpFunction: `webpackJsonp_custom_app_name`,
        globalObject: 'window',
      },
    }

5、开发时每次保存文件时报错 (热更新导致报错)

在一些场景下,热更新会导致保存时报错,请关闭热更新来解决这个问题,同时我们也在尝试更好的解决方案。

6、vue3 的问题

1、样式失效

通过禁用样式隔离解决。

2、图片等静态资源无法正常加载

vue3 中需要配置 publicPath 补全资源路径,详情请查看publicPath

总结

micro-app 是由京东推出的一个新兴的微前端框架,其使用方式简洁易懂,配置方面也不需要太多操作。类似 Vue 风格的 API 使得它对新手十分友好,非常适合大家尝试和使用这个新框架。

需要注意的是,micro-app 项目自 2021 年 7 月推出以来,截至目前,其版本到达 1.X 版本。建议大家在正式使用前查看最新版的文档及更新日志,以确保使用的是最新的稳定版本。

相比之下,qiankun 虽然在某些场景下可能显得侵入性较大,但它已有多年的微前端使用经验和丰富的 Issue 解决经验。在解决实际问题方面,qiankun 可能更为稳健。

作者:洞窝-重阳