前言
在上一篇文章中,我们深入探讨了阿里基于 single-spa 的微前端框架 qiankun 及其核心理念和应用。然而,微前端的世界不断发展,京东的 micro-app 作为另一种解决方案,正以其基于 web-component 的设计和强大功能赢得开发者的青睐。micro-app 不仅实现了高效的前端模块化开发,还具备灵活的集成和独立部署能力,大大提升了开发和维护效率。
在本文中,我们将重点介绍 micro-app 的核心概念、关键特性及实际应用场景,帮助你更好地理解和选择这一微前端框架。希望本文能为你的前端开发提供新的视角和有价值的参考。
关于 micro-app 的核心概念
single-spa
通过监听 URL 变化事件,在路由变化时匹配并渲染相应的子应用。这一思路目前是实现微前端的主流方式。然而,single-spa
要求子应用修改渲染逻辑并暴露三个方法:bootstrap
、mount
和 unmount
,分别对应初始化、渲染和卸载。这就意味着子应用需要对其入口文件进行修改。此外,使用 qiankun
由于其基于 single-spa
进行封装,也继承了这些特点,并需要对 webpack
配置进行一定的调整。
与此不同,micro-app
并未沿袭 single-spa
的思路,而是借鉴了 WebComponent
的理念。通过将 CustomElement
与自定义的 Shadow DOM
结合,micro-app
将微前端封装成一个类 WebComponent
组件,从而实现组件化渲染。得益于自定义 Shadow DOM
的隔离特性,micro-app
不需要像 single-spa
和 qiankun
那样要求子应用修改渲染逻辑并暴露方法,也无需修改 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
效果如图:
侵入总结
接入过程非常简单,侵入性操作总结如下:
主应用
-
启动
micro-app
:inijavascript复制代码 microApp.start();
-
添加微应用容器组件:
inijsx复制代码 <micro-app name="subapp1" url="http://localhost:3001/" baseroute="/"></micro-app>
-
添加路由指向容器组件:
javascriptjsx复制代码 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;
微应用
-
修改
public-path
: 在微应用的构建配置中设置正确的publicPath
,确保资源路径正确。例如,在webpack.config.js
中:cssjavascript复制代码 output: { publicPath: './', },
-
添加跨域访问: 确保微应用支持跨域访问。在微应用的服务器配置中允许跨域请求。例如:
javascriptjavascript复制代码 app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); next(); });
-
自动切换路由的
basename
: 根据环境自动设置路由的basename
,确保路由切换正确。例如:javascriptjavascript复制代码 import { BrowserRouter } from 'react-router-dom'; const basename = window.__MICRO_APP_BASE_ROUTE__ || '/'; <BrowserRouter basename={basename}> {/* 其他路由配置 */} </BrowserRouter>
通过以上步骤,你可以轻松将 micro-app
集成到主应用中,实现微前端架构的高效开发和部署。
vue 子应用
-
创建一个新的 vue 应用
使用
vue-cli
创建一个新的 Vue 项目:luabash复制代码 vue create my-vue-app
在项目创建过程中,选择 Vue 3 版本。
-
修改
public-path
: 在 Vue 项目的vue.config.js
中设置正确的publicPath
,以确保资源路径正确:inijavascript复制代码 module.exports = { publicPath: './', };
-
添加跨域访问 : 确保微应用支持跨域访问。在开发服务器的配置中允许跨域请求。在
vue.config.js
中添加以下配置:cssjavascript复制代码 module.exports = { devServer: { headers: { 'Access-Control-Allow-Origin': '*', }, }, };
-
自动切换路由的
basename
: 在微应用中根据环境自动设置路由的basename
,确保路由切换正确。在 Vue 项目的src/main.js
中进行如下配置:javascriptjavascript复制代码 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
的一大亮点,相较于 qiankun
的 EventBus
,micro-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
可以方便地在基座应用和子应用之间传递数据。这种方式相比于 qiankun
的 EventBus
更加简便,但仍可能显得有些 Hacky。通过上述示例,你可以轻松实现父应用向子应用的数据通信。
子传父
子应用向父应用传递数据在 micro-app
中同样是一个重要的功能。micro-app
提供了 microApp.dispatch
和 microApp.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 隔离
提供两种隔离方式:
- 默认添加 CSS 选择器前缀。
- 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
可能更为稳健。
作者:洞窝-重阳