重塑前端开发:如何利用 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 可能更为稳健。

作者:洞窝-重阳

相关推荐
Myli_ing5 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维22 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
I_Am_Me_36 分钟前
【JavaEE进阶】 JavaScript
开发语言·javascript·ecmascript
雯0609~43 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
前端百草阁1 小时前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue