微前端不是单纯的前端框架或者工具,而是一套架构体系,这个概念最早在2016年底被提出,可以参考在Google上搜索Micro-Frontends,排名靠前的http://micro-frontends.org的博客文章,提出了早起的微前端模型。
前短短是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一应用转变为把多个小型前端应用聚合唯一的应用。各个前端应用还可以独立开发、独立部署。
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员。团队的增多、变迁,从一个普通应用变成一个巨星应用,随之而来的应用不可维护的问题。这类问题在企业级Web应用中尤其常见。
微前端的实现意味着对前端应用的拆分。拆分应用的目的并不只是为了在结构上好看,它还可以提升开发效率。比如10万行代码拆分成10个项目,每个项目1万行代码,要独立维护每个项目就会容易得多。而我们只需要实现应用的自治,及实现应用的独立运行和部署,就可以在某种程度上实现为前端架构的目的。
微前端帮助开发者解决了实际的问题,但是对于每个业务来说,是否适合使用为前端,以及是否正确的使用微前端,还是需要遵循以下一些原则:
只有将真个能力体系搭建完善,才能说是整个微前端体系流程的完善。
除此之外,他也有一系列的缺点:
当发现使用为前端反而是效率变低,简单的变更复杂那说明为前端并不适用。
从技术实践上,为前端架构可以采用以下几种方式进行:
iframe
作为容器来容纳其他前端应用。Web components
技术,来构建跨框架的前端应用。实施的方式虽然多,但都是依据场景而采用。在有些场景下,可能没有合适的方式;在有些场景下,则可以同时使用多中方式来实现。
在这种模式的为前端架构中,基座承担了微前端应用的基础与技术核心。基座模式,是由一个主应用和一系列业务自应用构成的系统,并由这个主应用来管理其他子应用,包括从子应用的生命周期管理到应用间的通信机制。
基座模式中的主应用,类似于API Gateway
的概念,他作为系统的统一入口,负责将对应的请求指向对应的服务。子应用,则是负责各个子模块的业务实现,其架构如图所示。
这个主应用,既可以只带有单纯的基座功能,也可以带有业务功能。它所处理的业务功能指的是核心部分的业务功能,如:
● 用户的登录、注册管理。
● 系统的统一鉴权管理。
● 导航菜单管理。
● 路由管理。
● 数据管理。
● 通信代理。
● ……
作为应用的基础核心,它还需要:
● 维护应用注册表。在应用注册表上表明系统有多少个服务、能否找到对应的应用等。
● 管理其他子应用。如在何时加载应用、何时卸载应用等。
要实现这种模式的微前端架构,只需要设计好对应的应用加载机制即可,因此在实施的时候也比较方便。
中心化微前端架构的典型代表是:乾坤
去中心化自组织模式指的是,系统内部各子系统之间能自行按照某种规则形成一定的结构或功能。采用这种模式可以使系统内的各种前端应用,都各自拥有一个小型的基座管理功能,也相当于每个应用都可以是基座。
在采用基座模式时,用户要想访问A应用需要先加载主应用,然后才能加载A应用。采用自组织模式时,用户想要访问A应用则只访问A应用,不需要加载主应用,这也因此使它拥有了更高的自主性。
不过多数时候,我们并不需要自组织模式的微前端架构,因为它设计起来复杂、拥有大量的重复代码。
去中心化的微前端设计有一个很好的实现,就是在2020年出现的基础webpack的模块联邦。
● Single-Spa:最早的微前端框架(2018年),兼容多种前端技术栈,核心只做路由劫持和应用加载。
○ 本身没有处理样式隔离、JavaScript 执行隔离
● Qiankun:诞生于2019年,基于 Single-Spa,阿里系开源微前端框架。对vite支持不是很好
○ single-spa + sandbox + import-html-entry
○ 本身解决了样式隔离、JavaScript 执行隔离
○ 接入简单
● 2020年 webpack5 模块联邦
● 2020 年 EMP 基于 Module Federation(模块联邦),接入成本低,解决第三方依赖包的问题
● Icestark:阿里飞冰微前端框架,兼容多种前端技术栈。
● qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
● 使用简单
○ 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
○ HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
● 功能完备:几乎包含所有构建微前端系统时所需要的基本能力。
○ 样式隔离,确保微应用之间样式互相不干扰。
○ JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
○ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
○ umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
● 生产可用:qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。目前 qiankun 已在蚂蚁内部服务了超过 200+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
● 架构思想:中心化基座模式的微前端典型代表,由一个主应用和一系列业务子应用构成的系统,并由这个主应用来管理其他子应用,包括从子应用的生命周期管理到应用间的通信机制。
首先安装vue-vli命令行工具:npm install -g @vue/cli
创建main、app1、app2
三个项目:
vue create main
(选择vue3带router版本)app1
、app2
在主项目中,添加div容器。
app.vue:
注册子应用:
main.ts:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps,setDefaultMountApp , start } from "qiankun";
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
registerMicroApps([
{
name: 'app1',
entry: '//localhost:8082',
container: '#container',
activeRule: '/app1',
},
{
name: 'app2',
entry: '//localhost:8083',
container: '#container',
activeRule: '/app2',
},
]);
setDefaultMountApp("app1")
// 启动 qiankun
start();
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
store,
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[app1] vue app bootstraped');
}
export async function mount(props) {
console.log('[app1] props from main framework', props);
render(props);
}
export async function unmount() {
console.log('[app1] unmount');
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',//支持跨域
},
port:8082
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
app2如同app1一样配置。注意启动端口配置成:8083。
分别启动main、app1、app2三个项目;然后打开主应用地址。
主应用和微应用部署到同一个服务器(同一个 IP 和端口)
registerMicroApps([
{
name: 'app1',
entry: '/child/app1',
container: '#container',
activeRule: '/app1',
},
{
name: 'app2',
entry: '/child/app2',
container: '#container',
activeRule: '/app2',
},
]);
vue.config.js
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
port:8082
},
publicPath:"/child/app1/", // 主要调整的这里
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
router/index.js
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : '/child/app1/',
routes
})
app2同样配置。
main、app1、app2执行npm run build打包;将app1和app2中打包生成的dist目录,复制到main/dist/child中,并且重命名为app1和app2,调整后目录结构如图:
本地建立http服务,http-server dist
,这时就能正常访问;
还需要解决刷新
http://127.0.0.1:8081/app1
时的404问题。
源码