文章目录
一. 什么是微前端
-
“微前端架构”就是构建基于微服务的前端应用架构。
-
其思想是将前端应用切分为一系列可以单独部署的松耦合的应用,然后将这些应用组装起来创建单个面向用户的应用程序。
二. 微前端的优势
- 降低代码耦合
- 独立开发、独立部署
- 增量升级:微前端是一种非常好的实施渐进式重构的手段和策略
- 独立运行时,每个微应用之间状态隔离,运行时状态不共享
- 团队可以按照业务垂直拆分更高效
三. 微前端的多种实现
3.1 iframe
iframe
天然具备微前端的基因。我们只需将单体的前端应用,按照业务模块进行拆分,分别部署。最后通过 iframe
进行动态加载即可。
<html>
<head>
<title>微前端-ifame</title>
</head>
<body>
<h1>我是容器</h1>
<iframe id="mfeLoader"></iframe>
<script type="text/javascript">
const routes = {
'/': 'https://app.com/index.html',
'/app1': 'https://app1.com/index.html',
'/app2': 'https://app2.com/index.html',
};
const iframe = document.querySelector('#mfeLoader');
iframe.src = routes[window.location.pathname];
</script>
</body>
</html>
优点:
- 实现简单
- 天然具备隔离性
缺点:
- 主页面和 iframe 共享最大允许的 HTTP 链接数。
- iframe 阻塞主页面加载。
- 浏览器的后退按钮无效
iframe子窗口调用父窗口的方法
在iframe的页面中,通过JavaScript编写代码来调用父组件的方法。可以使用window.parent
或window.top
来引用父窗口对象,然后调用父窗口的方法。
例如,假设父组件中有一个名为"handleClick"的方法,可以在iframe的页面中使用以下代码来调用它:
window.parent.handleClick();
// 或者
window.top.handleClick();
iframe父窗口调用子窗口方法
在父窗口中,通过JavaScript获取iframe的引用,然后使用contentWindow
属性访问子窗口的对象
var iframe = document.getElementById('myIframe');
var childWindow = iframe.contentWindow;
childWindow.handleClick();
iframe子窗口向父窗口通信
postMessage
用于在不同的域之间发送消息。它允许你发送消息到父窗口,并接收来自父窗口的消息。
在 iframe 中:
window.parent.postMessage('Hello from iframe!', 'http://example.com');
在父窗口中:
window.addEventListener('message', function(event) {
if (event.origin !== 'http://example.com') return; // 验证消息来源
console.log('Received message from iframe:', event.data);
}, false);
iframe的父窗口传递参数给子窗口
方法一:父窗口可以使用window.postMessage
方法向子窗口发送消息
var iframe = document.getElementById('myIframe');
iframe.contentWindow.postMessage('Hello from parent!', '*');
在子窗口中,可以使用以下代码监听消息:
window.addEventListener('message', function(event) {
if (event.origin !== 'http://example.com') return; // 验证消息来源
console.log('Received message from parent:', event.data);
}, false);
方法二:使用URL查询参数:如果父窗口和子窗口处于同一域下,并且没有跨域限制,父窗口可以通过修改iframe的src
属性,将参数作为URL
查询参数传递给子窗口。在父窗口中,可以使用以下代码将参数作为URL查询参数传递给子窗口:
var iframe = document.getElementById('myIframe');
iframe.src = 'child.html?param1=value1¶m2=value2';
在子窗口中,可以通过JavaScript获取URL查询参数:
var param1 = getUrlParam('param1'); // 获取URL查询参数的方法,可以根据实际情况实现
var param2 = getUrlParam('param2'); // 获取URL查询参数的方法,可以根据实际情况实现
3.2 服务端模板组合
常见的实现方式是,服务端根据路由动态渲染特定页面的模板文件。架构图如下:
优点:
- 实现简单
- 技术栈独立
缺点:
- 需要额外配置 Nginx
- 前后端分离不彻底
3.3 微前端框架 single-spa
借助 single-spa
,开发者可以为不同的子应用使用不同的技术栈,比如子应用 A 使用 vue
开发,子应用 B 使用 react
开发,完全没有历史债务。
single-spa 的实现原理并不难,从架构上来讲可以分为两部分:子应用
和容器应用
。
子应用与传统的单页应用的区别在于:
- 不需要 HTML 入口文件,
- js 入口文件导出的模块,必须包括 bootstrap、mount 和 unmount 三个方法。
容器应用主要负责注册应用,当 url 命中子应用的路由时激活并挂载子应用,或者当子应用不处于激活状态时,将子应用从页面中移除卸载。其核心方法有两个:
registerApplication
注册并下载子应用start
启动处于激活状态的子应用。
容器应用代码
<html>
<body>
<script src="single-spa-config.js"></script>
</body>
</html>
single-spa-config.js
代码如下:
import * as singleSpa from 'single-spa';
const appName = 'app1';
const app1Url = 'http://app1.com/app1.js'
// loadJS 方法是伪代码,表示加载 app1.js。开发者需要自己实现,或者借助 systemJS 来实现。
singleSpa.registerApplication('app1',() => loadJS(app1Url), location => location.pathname.startsWith('/app1'))
singleSpa.start();
子应用代码:
//app1.js
let domEl;
export function bootstrap(props) {
return Promise
.resolve()
.then(() => {
domEl = document.createElement('div');
domEl.id = 'app1';
document.body.appendChild(domEl);
});
}
export function mount(props) {
return Promise
.resolve()
.then(() => {
domEl.textContent = 'App 1 is mounted!'
});
}
export function unmount(props) {
return Promise
.resolve()
.then(() => {
domEl.textContent = '';
})
}
优点:
- 纯前端解决方案
- 可以使用多种技术栈
- 完善的生态
缺点:
- 上手成本高
- 需要改造现有应用
- 跨应用的联调变得复杂
3.4 微前端框架 qiankun
qiankun
是一个基于 single-spa
的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap
、mount
、unmount
三个生命周期钩子,以供主应用在适当的时机调用。
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
优点:
- 简单:qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造
- 解耦/技术栈无关
- 完善的生态
缺点:
- 上手成本高
- 需要改造现有应用
- 跨应用的联调变得复杂