什么是微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。、
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
微前端架构具备以下几个核心价值:
- 技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权 - 独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新 - 增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略 - 独立运行时
每个微应用之间状态隔离,运行时状态不共享
qiankun - 一套完整的微前端解决方案
特性
- 基于 single-spa
- 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
搭建微前端项目
主应用
主应用与技术栈无关,我们可以使用Vue、React、Angular、JQuery甚至ES5语法进行搭建。主应用的目的如下:
- 注册微应用;
- 为每个微应用提供dom容器;
- 启动主应用;
下面我是以vue为主应用一步一步进行讲解,先使用vue-cli3构建项目。
1.安装qiankun
$ npm install qiankun
2.注册微应用
在入口文件main.js中添加如下代码:
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
//注册应用
registerMicroApps([
{
name: "vue2App",
props: { age: 10 }, //给子应用传数据
entry: "//localhost:3001", //默认会加载这个html,解析里面的js,动态执行(子应用必须支持跨域)里面,是用fetch去请求的数据
container: "#out-main", //挂载到主应用的哪个元素下
activeRule: "/vue2", //当我劫持到路由地址为/vue2时,我就把http://localhost:3000这个应用挂载到#app-main的元素下
},
{
name: "vue3App",
entry: "//localhost:3002",
// entry: { scripts: ["//localhost:7100/main.js"] },
container: "#out-main",
activeRule: "/vue3",
},
]);
//step3 设置默认进入微应用
//setDefaultMountApp('/vue3')
//开启
start();
微应用
微应用不用额外安装qiankun即可接入主应用,大致分为下面几个步骤:
- 入口js文件平级目录下增加public-path.js文件;
- 入口js文件中引入public-path.js,修改并导出qiankun定义的三个钩子函数:bootstrap、mount、unmount;
- 使用history路由,并且路由base值要和activeRule匹配规则保持一致;
- 修改webpack配置,允许开发环境跨域及umd打包;
下面我们以vue cli3作为技术栈构建微应用
在src下增加public-path.js
qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
关于运行时 publicPath 的技术细节,可以参考 webpack 文档。
runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。
修改入口文件main.js
let instance = null;
function render(props = {}) {
const { container } = props;
window.props = props;
//会把这个应用(也就是这个界面),插入到主应用的container的元素中去
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app") : "#app"); //这里是挂载到自己的html中,主应用会拿到这个挂载的html,将其插入到主应用中
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log("[vue] vue app bootstraped");
}
export async function mount(props) {
console.log("[vue] props from main framework", props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
}
路由配置
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
/* Layout */
export const routes = [
{
path: "/",
redirect: "/list",
},
{
path: "/list",
component: () => import("@/views/protocol/index"),
},
];
const router = new VueRouter({
base: "vue2",
mode: "history",
routes,
});
export default router;
配置webpack 在vue.config.js
module.exports = {
lintOnSave: false,
devServer: {
port: "3001",
headers: {
"Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
},
},
configureWebpack: {
output: {
// library: `${name}-[name]`,
library: `vue2App`,
libraryTarget: "umd", // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
};
下面我们以vue3作为技术栈构建微应用
在src下增加public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
修改入口文件main.js
let instance = null;
function render(props = {}) {
if (instance) return;
const { container } = props;
instance = createApp(App)
.use(store)
.use(router)
.mount(container ? container.querySelector("#app") : "#app");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log("[vue] vue app bootstraped");
}
export async function mount(props) {
console.log("[vue] props from main framework", props);
render(props);
}
export async function unmount() {
//可选链操作符
instance.$destroy?.();
instance = null;
}
路由配置
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
path: "/",
redirect: "/home",
},
{
path: "/home",
component: () => import("../views/Home.vue"),
},
];
const router = createRouter({
history: createWebHistory("/vue3"),
routes,
});
export default router;
配置webpack 在vue.config.js
// const { name } = require("./package");
module.exports = {
lintOnSave: false,
devServer: {
port: "3002",
headers: {
"Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
},
},
configureWebpack: {
output: {
// library: `${name}-[name]`,
library: `vue3App`,
libraryTarget: "umd", // 把微应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
};