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

前言

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

安装与快速上手

react 基座应用

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

css 复制代码
npx create-react-app main-app

2、安装micro-app依赖

css 复制代码
npm i @micro-zoe/micro-app --save

3、在入口处引入

javascript 复制代码
// index.js
import microApp from '@micro-zoe/micro-app'

microApp.start()

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

javascript 复制代码
// 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,以便嵌入子应用。

javascript 复制代码
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 创建子应用。

bash 复制代码
bash复制代码
npx create-react-app subapp1
cd subapp1

2、配置子应用

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

json 复制代码
json复制代码
{
  "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"
  },
  // 其它配置项保持不变
}

运行子应用:

sql 复制代码
bash复制代码
npm start

效果如图:

侵入总结

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

主应用

  1. 启动 micro-app

    ini 复制代码
    javascript复制代码
    microApp.start();
  2. 添加微应用容器组件

    ini 复制代码
    jsx复制代码
    <micro-app name="subapp1" url="http://localhost:3001/" baseroute="/"></micro-app>
  3. 添加路由指向容器组件

    javascript 复制代码
    jsx复制代码
    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 中:

    css 复制代码
    javascript复制代码
    output: {
      publicPath: './',
    },
  2. 添加跨域访问: 确保微应用支持跨域访问。在微应用的服务器配置中允许跨域请求。例如:

    javascript 复制代码
    javascript复制代码
    app.use((req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*");
      next();
    });
  3. 自动切换路由的 basename : 根据环境自动设置路由的 basename,确保路由切换正确。例如:

    javascript 复制代码
    javascript复制代码
    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 项目:

    lua 复制代码
    bash复制代码
    vue create my-vue-app

    在项目创建过程中,选择 Vue 3 版本。

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

    ini 复制代码
    javascript复制代码
    module.exports = {
      publicPath: './',
    };
  3. 添加跨域访问 : 确保微应用支持跨域访问。在开发服务器的配置中允许跨域请求。在 vue.config.js 中添加以下配置:

    css 复制代码
    javascript复制代码
    module.exports = {
      devServer: {
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
      },
    };
  4. 自动切换路由的 basename : 在微应用中根据环境自动设置路由的 basename,确保路由切换正确。在 Vue 项目的 src/main.js 中进行如下配置:

    javascript 复制代码
    javascript复制代码
    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 方法来实现父应用向子应用的数据传递。以下是具体的使用示例:

xml 复制代码
//  父应用
<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>
xml 复制代码
//  子应用
<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

ini 复制代码
javascript复制代码
<micro-app name='xx' url='xx' keep-alive></micro-app>

生命周期

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

ini 复制代码
javascript复制代码
/** @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 共享资源:

javascript 复制代码
javascript复制代码
// 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 属性:

ini 复制代码
html复制代码
<link rel="stylesheet" href="xx.css" global>
<script src="xx.js" global></script>

对资源进行过滤:

xml 复制代码
html复制代码
<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 支持跨域。

css 复制代码
devServer: {
  headers: {
    'Access-Control-Allow-Origin': '*',
  },
},复制代码Error复制成功

如果是线上环境,可以通过配置 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

javascript 复制代码
// webpack.config.js
module.exports = {
  output: {
    ...
    jsonpFunction: `webpackJsonp_custom_app_name`,
    globalObject: 'window',
  },
}复制代码Error复制成功

webpack5

复制代码

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

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

6、vue3 的问题

1、样式失效

通过禁用样式隔离解决。

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

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

总结

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

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

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

作者:洞窝-重阳

相关推荐
编程猪猪侠23 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞27 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架