微前端就是应用分割
,独立运行
,独立部署
,将原本把所有功能集中于一个项目中的方式转变为把功能按业务划分成一个主项目和多个子项目,每个子项目负责自身功能,同时具备和其它子项目和主项目进行通信的能力,达到更细化更易于管理的目的。
总的来说微前端就是:一个完整应用划分成一个主应用和一个或多个微应用,应用间相互独立,可相互通信。
那么我将使用 微前端框架qiankun 来进行微前端项目搭建。
qiankun官方文档:https://qiankun.umijs.org/zh/guide/getting-started
一、主应用改造 —— 安装及使用 qiankun
主应用项目必须安装 qiankun:npm i qiankun -S
在主应用的src文件夹下新建一个 micros 文件夹,在micros文件夹新建index.js、app.js
1、index.js —— 主要是导出方法及生命周期钩子,用于在主应用中注册微应用
import { registerMicroApps, addGlobalUncaughtErrorHandler, start, } from "qiankun";// 微应用注册信息 import apps from "./app"; registerMicroApps(apps, { beforeLoad: (app) => { console.log("before load", app.name); return Promise.resolve(); }, afterMount: (app) => { console.log("after mount", app.name); return Promise.resolve(); }, }); addGlobalUncaughtErrorHandler((event) => { console.error(event); const { message: msg } = event if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) { console.error("微应用加载失败,请检查应用是否可运行"); } }); export default start;
这里用到了官方的几个api:
(1)registerMicroApps
:包含两个参数,第一个参数是微应用的一些注册信息,第二个参数是全局的微应用生命周期钩子。:
(2)addGlobalUncaughtErrorHandler
:全局的未捕获异常处理器,微应用发生报错的时候亦可以用这个api捕捉。
(3)start
:我们用来启动qiankun的方法,包含一个参数,具体的参数用途不再详述。
以上详细的api请点击查看官网API文档:https://qiankun.umijs.org/zh/api
2、app.js —— 主要是写一些微应用的信息
const apps = [ { name: "micro-app-order", entry: "//localhost:8088", container: "#order-container", activeRule: "/micorder", }, ]; export default apps;
app.js导出的是上面registerMicroApps
的第一个参数,是一个对象数组,其中数组每个字段的作用如下:
(1)name
:微应用的名称,后面改造微应用的时候一定要与这个name对应
(2)entry
:微应用运行的域名加端口,我用的是本地8088端口
(3)container
:启动微应用需要一个dom容器,里面就是这个dom容器的 id
(4)activeRule
:触发启动微应用的规则,当检测到url中含有activeRule的值时,将启动微应用
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
3、在主应用中添加微应用 dom 容器
<template> <div id="main-app"> <router-view/> <!-- 新添加,微应用的容器 --> <div id="order-container"></div> </div> </template>
需要注意的是:第一个:可以看到我上面的 id 改为了 main-app,这个默认的 app 最好改一下,主应用子应用不要重复,否则会有 CSS 污染问题。第二个就是微应用的容器 id : order-container 就是与我们上面定义的 apps 里的 container 里的 id 要保持一致。
4、main.js 改造 —— 主要就是导入 start 方法,然后运行,启动qiankun
...... import start from '@/micros' ...... start()
这个改造就比较简单,引入 start() 方法,然后执行即可。
做完上述改造之后,刷新一下浏览器,发现主应用和改造前并无差异!
二、微应用改造
官网写了:微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
1、导出相应的生命周期钩子
微应用需要在自己的入口 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); }
这是微应用改造前的main.js
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import '@/assets/main.css' Vue.config.productionTip = false new Vue({ router, store, render: h => h(App) }).$mount('#app')
下面我们来改造一下main.js
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import '@/assets/main.css' Vue.config.productionTip = false // 新增:用于保存vue实例 let instance = null; // 新增:动态设置 webpack publicPath,防止资源加载出错 if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } /** * 新增: * 渲染函数 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行 */ function render() { // 挂载应用 instance = new Vue({ router, store, render: (h) => h(App), }).$mount("#micro-app-child");
} /** * 新增: * bootstrap 只会在微应用初始化的时候调用一次, 下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { console.log("VueMicroApp bootstraped"); } /** * 新增: * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount(props) { console.log("VueMicroApp mount", props); render(props); } /** * 新增: * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { console.log("VueMicroApp unmount"); instance.$destroy(); instance = null; } // 新增:独立运行时,直接挂载应用 if (!window.__POWERED_BY_QIANKUN__) { render(); }
特别需要注意的是,render方法中我把$mount后的参数改为了#micro-app-child
,这是为了区分主应用和微应用中index.html
的根id,所以微应用中的public文件夹的index.html
也要改为micro-app-child
2、配置微应用的打包工具
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
const packageName = require('./package.json').name; module.exports = { output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, };
所以我们还需要对webpack配置进行改造,微应用根目录下的vue.config.js
文件
const path = require("path"); module.exports = { devServer: { // 监听端口 port: 8088, // 关闭主机检查,使微应用可以被 fetch disableHostCheck: true, // 配置跨域请求头,解决开发环境的跨域问题 headers: { "Access-Control-Allow-Origin": "*", }, }, configureWebpack: { resolve: { alias: { "@": path.resolve(__dirname, "src"), }, }, output: { // 微应用的包名,这里与主应用中注册的微应用名称一致 library: "micro-app-order", // 将你的 library 暴露为所有的模块定义下都可运行的方式 libraryTarget: "umd", // 按需加载相关,设置为 webpackJsonp_MicroAppOrde 即可 jsonpFunction: `webpackJsonp_micro-app-order`, }, }, };
主要是要设置下 output 的配置,其他的配置按原项目的即可。
3、然后还要改造一下我们的路由
路由主要的改动就是每个path
都添加了一个microPath
变量,用于检测是否由微前端改动,相应的路由守卫也要添加microPath
变量,另外微应用的login
跳转的时候也要加上microPath
判断
这里我采用 history 模式,只需要加上 base 即可
const router = new VueRouter({ mode: 'history', base: window.__POWERED_BY_QIANKUN__ ? '/micorder' : '/', routes, scrollBehavior(to, from, savedPosition) { //设置滚动行为 ...... } })
4、改造效果
做完以上步骤改造,最后重启一下我们的微应用,然后在主应用上加上一些跳转到微应用页面的路由。我们看看改造效果:
<div id="main-app"> <div class="fix-box"> <!-- 主应用首页 --> <router-link to="/">首页</router-link> <!-- 微应用商品页 --> <router-link to="/micorder/foods">商品</router-link> <!-- 微应用订单页 --> <router-link to="/micorder/ord">订单</router-link> </div> <router-view/> <!-- 新添加,微应用的容器 --> <div id="order-container"></div> </div>
子应用独立运行
主应用 demo 运行
主应用里子应用运行