微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略,下面这张图来了解下。
qiankun
qiankun 蚂蚁金服基于single-spa 的微前端解决方案,生产可用。
特性
基于 single-spa 封装,提供了更加开箱即用的 API。
技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
样式隔离,确保微应用之间样式互相不干扰。
JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
主应用搭建
选择用 vue-cli 初始化了主应用,不了解的可自行阅读官方文档
项目中引入 qiankun :
$ yarn add qiankun # 或者 npm i qiankun -S
复制代码
注册微应用
定义需要加载的微应用
// src/micro/apps.ts
//此时我们还没有微应用,所以暂时为空
const apps: any = [
];
export default apps;
复制代码
注册微应用并对外暴露方法
// src/micro/index.ts
import {
registerMicroApps,
addGlobalUncaughtErrorHandler,
start,
} from “qiankun”;
import NProgress from “nprogress”;
import { Message } from ‘element-ui’;
import ‘nprogress/nprogress.css’;
NProgress.configure({ parent: ‘.scrollbar.scroll’ });
export default function (apps: []) {
registerMicroApps(apps, {
beforeLoad: () => {
// 加载微应用前,加载进度条
NProgress.start();
return Promise.resolve();
},
afterMount: () => {
NProgress.done();
return Promise.resolve();
},
});
addGlobalUncaughtErrorHandler((event: any) => {
const { msg } = event as any;
NProgress.done();
// 加载失败时提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
Message.error('微应用加载失败,请检查应用是否可运行');
}
});
start();
}
复制代码
由于我们的微应用可能是登录后,根据用户左侧菜单权限而生成该用户的微应用。所以,暴露方法以及入参方便登录完成调用注册微应用。
启动微应用
import startQiankun from “@/micro”;
startQiankun(…); //在需要启动的地方调用传入数据就行
复制代码
这边是我在全局路由守卫处启动,供大家参考。
//router
import Vue from ‘vue’;
import VueRouter, { RouteConfig } from ‘vue-router’;
import store from “@/store”;
import { getToken } from “@/utils/auth”;
import startQiankun from “@/micro”;
import apps from “@/micro/apps”;
Vue.use(VueRouter);
const routes: Array = [
{
path: ‘/login’,
name: ‘login’,
component: () => import(’@/views/login/index.vue’)
},
{
path: ‘/’,
name: ‘main’,
component: () => import(’@/views/Layout/index.vue’),
children: [
{
path: ‘’,
name: ‘Home’,
component: () => import(’@/views/Home.vue’)
}
]
},
{
path: ‘*’,
name: ‘redirect’,
redirect: ‘/’
}
];
const createRouter: any = () => new VueRouter({
mode: “history”,
routes,
});
const router: any = createRouter()
/**
const whiteList = [‘login’];
router.beforeEach((to: any, from: any, next: any) => {
const token = getToken(‘token’);
if (token) { //token存在
if (to.name === ‘login’) { //如果login直接跳转首页
return next({ path: ‘/’ });
}
if (!store.state.hasInited) { //防止反复addRoutes预设的值
store.dispatch(‘addRouters’).then((res) => {
router.addRoutes(res);
startQiankun(apps);
store.state.hasInited = true;
next({ …to, replace: true });
})
return;
}
next();
} else if (whiteList.includes(to.name)) { //白名单直接放行
next();
} else { //token不存在
next({ path: ‘/login’, query: { redirect: to.path } });
}
});
export default router;
复制代码
vue子应用搭建
在主应用中配置需要接入的子应用
// micro/apps.ts
import app from “./shared”; //分享给子应用的数据
/*
export default apps;
复制代码
配置子应用
在主应用配置好注册的微应用后,我们需要对子应用进行配置,让子应用能接入到主应用中。
1、vue子应用的入口main.js配置
// public-path.js
if (window.POWERED_BY_QIANKUN) {
// 动态设置 webpack publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
}
// main.js
import Vue from ‘vue’;
import App from ‘./App.vue’;
import VueRouter from “vue-router”;
import ‘./registerServiceWorker’;
import routes from ‘./router’;
import store from ‘./store’;
import ‘./public-path’
Vue.use(VueRouter)
Vue.config.productionTip = false;
let instance = null;
let router = null;
function render() {
router = new VueRouter({
// 运行在主应用中时,基础路由地址配置为 /vue
base: window.POWERED_BY_QIANKUN ? “/vue” : “/”,
mode: “history”,
routes,
});
instance = new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
}
/**
export async function bootstrap() {
}
/**
/**
复制代码
2、配置webpack打包策略
// vue.config.js
const path = require(“path”);
module.exports = {
//配置静态文件host路径
publicPath: ‘http://localhost:10300’,
devServer: {
// 监听端口
port: 10300,
overlay: {
warnings: false,
errors: false
},
// 关闭主机检查,使微应用可以被 fetch
disableHostCheck: true,
// 配置跨域请求头,解决开发环境的跨域问题
headers: {
“Access-Control-Allow-Origin”: “*”,
}
},
configureWebpack: {
resolve: {
alias: {
“@”: path.resolve(__dirname, “src”),
},
},
output: {
// 微应用的包名,这里与主应用中注册的微应用名称一致
library: “vue-project”,
// 将你的 library 暴露为所有的模块定义下都可运行的方式
libraryTarget: “umd”,
// 按需加载相关,设置为 webpackJsonp_vue-projec 即可
jsonpFunction: webpackJsonp_vue-project
,
},
},
};
复制代码
此时,已经完整地配置完我们的微前端了,只是当前只接入了一个子应用。根据以上代码可总结出以及几个重点:
1、主应用中注册子应用: registerMicroApps addGlobalUncaughtErrorHandler start qiankun中三个重要的api搭配使用。
2、子应用入口留给主应用调用 bootstrap mount unmount 的声明。以及 window.POWERED_BY_QIANKUN 的定义。
3、重新配置子应用的打包策略。
启动主应用和子应用效果:
空路由时匹配的是主应用的页面,当路由切换到 /vue 时加载了子应用包括 /vue/about 也加载了子应用中的 /about
路由。 打开控制台,可以看到我们所执行的生命周期钩子函数
还可以看到子应用渲染到主应用的指定节点中
react子应用搭建
在主应用中增加需要接入的子应用
// micro/apps.ts
import app from “./shared”; //分享给子应用的数据
/*
export default apps;
复制代码
子应用配置
使用 create-react-app 初始化了react应用
在根目录新增 .env 文件,增加以下配置
PORT=10100
BROWSER=none
复制代码
1、react入口配置
// public-path.js
if (window.POWERED_BY_QIANKUN) {
// 动态设置 webpack publicPath,防止资源加载出错
// eslint-disable-next-line no-undef
webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
}
//index.js
import React from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
import App from ‘./App’;
import * as serviceWorker from ‘./serviceWorker’;
import “./public-path”;
let root = document.getElementById(“root”);
function render() {
ReactDOM.render(
root
);
}
if (!window.POWERED_BY_QIANKUN) {
render();
}
export async function bootstrap() {
console.log(“ReactMicroApp bootstraped”);
}
/**
/**
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
复制代码
2、配置webpack打包策略
利用 react-app-rewired 修改打包配置。
//config-overrides.js
const path = require(“path”);
module.exports = {
webpack: (config) => {
// 微应用的包名,这里与主应用中注册的微应用名称一致
config.output.library = react-project
;
// 将你的 library 暴露为所有的模块定义下都可运行的方式
config.output.libraryTarget = “umd”;
// 按需加载相关,设置为 webpackJsonp_react-project 即可
config.output.jsonpFunction = webpackJsonp_react-project
;
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "src"),
};
return config;
},
devServer: function (configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
// 关闭主机检查,使微应用可以被 fetch
config.disableHostCheck = true;
// 配置跨域请求头,解决开发环境的跨域问题
config.headers = {
“Access-Control-Allow-Origin”: “*”,
};
// 配置 history 模式
config.historyApiFallback = true;
return config;
};
},
};
复制代码
到此react的子应用也接入完成了,可以启动看看效果
还有没使用webpack的应用接入、Angular等应用的接入就不多说了,感兴趣的可以自行找资料。
nginx部署打包的文件
nginx的安装和使用就不介绍了,不熟悉的可自行百度。
nginx配置
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
error_page 404 /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 10300;
server_name localhost;
location / {
root html/vue;
index index.html index.htm;
try_files $uri $uri/ /index.html;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 10100;nginx
server_name localhost;
location / {
root html/react;
index index.html index.htm;
try_files $uri $uri/ /index.html;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 10400;
server_name localhost;
location / {
root html/static;
index index.html index.htm;
try_files $uri $uri/ /index.html;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
复制代码
线上为了安全考虑, Access-Control-Allow-Origin 不应该配置为*,应配置指定的域名。简单说明上图 nginx 的配置
html/dist 为我们主应用,配置的80端口, html/static 简单写下的无webpack打包的项目。
每个server都配置了不同框架的前端包。
注意配置的访问地址得和主应用apps中的一致。
有想了解更多的朋友可以去https://www.jianshu.com/p/86bb74768d12 看一下。