微服务的主要思路
- 将应用分解为小的、互相连接的微服务,一个微服务完成某个特定功能 。
- 每个微服务都有自己的业务逻辑和适配器,不同的微服务,可以使用不同的技术去实现。
- 使用统一的网关进行调用。
微服务的主要思路是化繁为简 ,通过更加细致的划分,使得服务内部更加内聚,服务之间耦合性降低,有利于项目的团队开发和后期维护。把微服务的概念应用到前端, 前端微服务/微前端服务就诞生了,简称其为微前端。
微前端是什么
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
WEB应用面临的问题
- DX(developer experience)
- 业务领域的代码库不够独立和高度可重用
- 相同的产品功能由多个团队开发 / 产品功能难以保持统一
- 新的产品理念无法在不同的应用中快速复用 / 实现
- 快速迭代新子业务 / 干净移除将被淘汰的子业务
- UX(user experience)
- 性能体验
- 页面跳转和用户体验问题
- 多个系统在一个仓库应用中,不同子应用独立SPA模式
- 系统分为多个仓库,独立上线部署,采用MPA模式
任何新技术的产生都是为了解决现有场景和需求下的技术痛点,微前端也不例外:
- 拆分和细化:当下前端领域,单页面应用(SPA)是非常流行的项目形态之一,而随着时间的推移以及应用功能的丰富,单页应用变得不再单一而是越来越庞大也越来越难以维护,往往是改一处而动全身,由此带来的发版成本也越来越高。微前端的意义就是将这些庞大应用进行拆分,并随之解耦,每个部分可以单独进行维护和部署,提升效率。
- 整合历史系统:在不少的业务中,或多或少会存在一些历史项目,这些项目大多以采用老框架类似(Backbone.js,Angular.js 1)的B端管理系统为主,介于日常运营,这些系统需要结合到新框架中来使用还不能抛弃,对此我们也没有理由浪费时间和精力重写旧的逻辑。而微前端可以将这些系统进行整合,在基本不修改来逻辑的同时来同时兼容新老两套系统并行运行。
微前端核心价值
- 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
- 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
- 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
- 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
- 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率
微前端应用具备的能力
常见的微前端方案
iframe
优点
- 非常简单,无需任何改造
- 完美隔离,JS、CSS 都是独立的运行环境
- 不限制使用,页面上可以放多个 iframe 来组合业务
iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。
缺点
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
- 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
Web Components
很多小伙伴对 Web Components不是很了解,它是由google推出的浏览器的原生组件,具体内容查看 链接 了解
优点
- 复用性:不需要对外抛出加载和卸载的全局 API,可复用能力更强
- 标准化:W3C 的标准,未来能力会得到持续升级
- 插拔性:可以非常便捷的进行移植和组件替换
缺点
- 兼容性:对于 IE 浏览器不兼容,需要通过 Polyfill 的方式进行处理
- 学习曲线:相对于传统的 Web 开发,需要掌握新的概念和技术
基于webpack module federation
模块联邦主要是一种去中心化的思想,也可以用来做服务拆分, 实现原理比较复杂,主要涉及到以下几个方面:
模块接口定义
在需要共享的模块中,通过 module.exports 或 export 将需要共享的模块封装成一个模块接口,并将其在模块系统中注册。
共享模块的描述信息
在需要共享模块的应用程序中,通过使用 ModuleFederationPlugin 插件,将需要共享的模块的描述信息以 JSON 格式写入配置中。描述信息包括需要共享的模块名称、模块接口、提供共享模块的应用程序的 URL 等。
共享模块的加载
在需要使用共享模块的应用程序中,通过 webpack 的 container 远程加载共享模块的代码,并将其封装成一个容器。容器在当前应用程序中的作用是在容器中运行共享模块的代码,并按照描述信息将导出的模块接口暴露出来。容器本身是一个 JavaScript 运行时环境,它可以在需要使用共享模块的应用程序中被动或主动加载。
远程模块的执行
在容器中加载共享模块的代码后,容器需要将其执行,并将执行过程中产生的模块接口导出。为了实现这个目的,容器会利用 webpack 打包时在编译过程中生成的一个特殊的运行时代码,即 remoteEntry.js,通过 script 标签远程加载到当前应用程序中。在这个特殊的运行时代码中,会封装一些与容器通信的方法,例如 remote 方法,可以用于按需加载模块、获取模块接口等。 综上,webpack-module-federation 基于这些原理,实现了多个独立的应用程序之间的模块共享和远程加载,从而可以实现高度解耦、可扩展的架构。
主流微前端框架
single-spa
single-spa只是实现了加载器、路由托管。沙箱隔离并没有实现。single-spa只做了两件事情:
- 加载微应用(加载方法还得用户自己来实现)
- 管理微应用的状态(初始化、挂载、卸载)
qiankun
qiankun 是一个基于 single-spa 的 微前端 实现库,阿里系开源的微前端框架。 qiankun对single-spa方案进行完善,主要的完善点:
- 子应用资源由 js 列表修改进为一个url,大大减轻注册子应用的复杂度
- 实现应用隔离,完成js隔离方案 (window工厂) 和css隔离方案 (类vue的scoped)
- 增加资源预加载能力,预先子应用html、js、css资源缓存下来,加快子应用的打开速度,通过 import-html-entry 包解析 HTML 获取资源路径,然后对资源进行解析、加载
Mirco-App
Micro App 是京东出的一款基于 Web Component 原生组件进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度、提升工作效率。
使用qiankun搭建微前端项目实战
采用 React 作为主应用基座,接入 Vue 技术栈的子应用和 React 技术栈的完整项目 tdesign-react-starter。
主应用
使用 create-react-app 生成一个 React 的项目,初始化主应用,将它作为 qiankun 主应用基座。
- 创建微应用容器 - 用于承载微应用,渲染显示微应用;
- 注册微应用 - 设置微应用激活条件,微应用地址等等;
- 启动 qiankun;
安装依赖
bash
npm i react-router-dom -S
npm i antd -S
npm i qiankun -S
注册微应用
tsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import {
registerMicroApps,
start,
initGlobalState,
} from 'qiankun';
// 传值相关逻辑
const state = { id: 1 };
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
registerMicroApps([
{
name: 'react1App',
entry: '//localhost:3003',
container: '#micro-box',
activeRule: '/app-react1',
},
{
name: 'vue2App',
entry: '//localhost:5173',
container: '#micro-box',
activeRule: '/app-vue2',
},
]);
start();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
菜单逻辑代码
tsx
// src/app.js
import { BrowserRouter as Router, Link } from 'react-router-dom';
import { Menu } from 'antd';
const Menus = [
{
key: 'app-vue1',
label: <Link to='/app-react1'>react微应用1</Link>,
},
{
key: 'app-vue2',
label: <Link to='/app-vue2'>vue微应用2</Link>,
},
];
function App() {
return (
<Router>
<div style={{ display: 'flex' }}>
<Menu
style={{
width: 200,
height: '100vh',
}}
theme='dark'
mode='inline'
items={Menus}
/>
<div
id='micro-box'
style={{ flex: 1 }}
/>
</div>
</Router>
);
}
export default App;
实现效果
微应用react1App
可以按照 官网 安装项目。
安装依赖
bash
npm i vite-plugin-qiankun -S
配置微应用
在主应用注册好了微应用后,我们还需要对微应用进行一系列的配置。首先,我们在入口文件 main.js 中,导出 qiankun 主应用所需要的三个生命周期钩子函数(这里需要注意的是React18版本的卸载函数和vite的qiankun插件):
tsx
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import store from 'modules/store';
import App from 'layouts/index';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import 'tdesign-react/es/style/index.css';
import './styles/index.less';
const env = import.meta.env.MODE || 'development';
const baseRouterName = env === 'site' ? '/starter/react/' : '';
let root: ReactDOM.Root | null = null;
const renderApp = (props) => {
const container = props?.container;
const app = container ? container?.querySelector('#app')! : document.getElementById('app')!;
root = ReactDOM.createRoot(app);
root.render(
<Provider store={store}>
<BrowserRouter basename={baseRouterName}>
<App />
</BrowserRouter>
</Provider>,
);
};
renderWithQiankun({
mount(props) {
renderApp(props);
},
bootstrap() {
console.log('bootstrap');
},
unmount() {
root.unmount();
},
});
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
renderApp();
}
配置文件
加入vite-plugin-qiankun的配置,注释掉react方法。
javascript
import path from 'path';
import { loadEnv } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import react from '@vitejs/plugin-react';
import svgr from '@honkhonk/vite-plugin-svgr';
import qiankun from 'vite-plugin-qiankun';
const CWD = process.cwd();
export default (params) => {
const { mode } = params;
const { VITE_BASE_URL } = loadEnv(mode, CWD);
return {
/**
* 省略其他配置文件
**/
base: VITE_BASE_URL,
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
plugins: [
svgr(),
// react(), // 这里一定要注释,否则会报错
qiankun('react1App', {
useDevMode: true,
}),
mode === 'mock' &&
viteMockServe({
mockPath: './mock',
localEnabled: true,
}),
]
}
}
实现效果
注意:里面的配置需要根据自己的需求改一下。
微应用vue2App
vue这个就简单用vite初始化搭建一下。
安装依赖
bash
npm i vite-plugin-qiankun -S
注册微应用
和之前的react微应用一样,导出 qiankun 主应用所需要的三个生命周期钩子函数:
tsx
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import {
renderWithQiankun,
qiankunWindow,
} from 'vite-plugin-qiankun/dist/helper';
let app;
const render = () => {
app = createApp(App);
app.mount('#box');
};
const initQianKun = () => {
renderWithQiankun({
mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
props.setGlobalState({ id: 2 });
const { container } = props;
render(container);
},
bootstrap() {},
unmount() {
app.unmount();
},
});
};
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render();
配置文件
javascript
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import qiankun from 'vite-plugin-qiankun'
export default defineConfig({
devServer: {
headers: {
"Access-Control-Allow-Origin": "*",
},
},
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
qiankun('vue2App', {
useDevMode: true
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});