Skip to content

微前端-qiankun

一、解决子应用 vue-router 不一致导致 url 出现 undefined 的问题

js
// 父应用的vue-router配置的beforeEach钩子函数中添加以下代码

if (!history.state.current) {
  Object.assign(history.state, { current: from.fullPath });
}

二、解决 single spa 超时报错的问题

  • 问题:通常发生在打开页面之后没有关闭,很长时间后又点击页面的时候会出现
  • 解决:监控错误日志,然后重新加载页面
js
const consoleError = () => {
  // 重写console.error,可以捕获更全面的错误信息
  const oldError = console.error;
  console.error = function (message) {
    try {
      if (
        message &&
        message.includes &&
        message.includes("single-spa minified message #31")
      ) {
        location.reload();
      }
    } catch (error) {
      console.log(error);
    }
  };
};

// 全局启用
consoleError();

三、子应用 vite 构建的插件兼容

使用 vite-plugin-qiankun 插件

js
// 子应用
// main.ts 入口文件
import { qiankunWindow,renderWithQiankun } from "vite-plugin-qiankun/dist/helper";

const initQiankun = () => {
	renderWithQiankun({
		bootstrap() {
			console.log('子应用初始化')
		 },
		mount(props) {
			console.log('子应用挂载', props)
			// 部门逻辑或者注册应用。。。

			render(props)
		 }
		 unmount() {
			console.log('子应用卸载')
			app.unmount()
		 }
		 update(props) {
			console.log('子应用更新', props)
		 }
	})
}

if (qiankunWindow.__POWERED_BY_QIANKUN__) {
  initQiankun();
} else {
  // 单独使用 vue的渲染函数
  render();
}


// 路由文件
// 判断是否为子应用,进行构建资源和路由检测
let isMicroSubApp = qiankunWindow.__POWERED_BY_QIANKUN__;
if (!isMicroSubApp && import.meta.env.VITE_ENV === 'production') {
	// 生产环境
	isMicroSubApp = true
}
// 子路由的应用标识
const workspace = 'micro-admin'
// 路由base
const base = isMicroSubApp ? `/${workspace}/` : '/'

应用构建配置

js
// vite.config.ts
import qiankun from "vite-plugin-qiankun";

export default defineConfig({
	const name = 'vue-app-admin';
	plugins: [
		qiankun(name, {
			useDevMode: true
		})
	]
})

四、父子应用资源路径冲突问题

父应用构建无需处理

子应用构建配置

所有静态资源加上子应用标识和 __static__

js
// vite.config.ts
import qiankun from "vite-plugin-qiankun";

export default defineConfig((mode) => {
  const name = "vue-app-admin";
  const workspace = "micro-admin";
  const workspaceStatic = `${workspace}/__static__`;
  return {
    plugins: [
      qiankun(name, {
        useDevMode: true,
      }),
    ],
    build: {
      sourcemap: true,
      assetsDir: `${workspaceStatic}/`,
      rollupOptions: {
        output: {
          chunkFileNames: `${workspaceStatic}/[name].[hash].js`,
          entryFileNames: `${workspaceStatic}/[name].[hash].js`,
          assetFileNames: `${workspaceStatic}/[name].[hash].[ext]`,
        },
      },
    },
  };
});

父应用定义子应用的入口文件和激活标识

js
// childApp.ts
const subApp = [
  {
    name: "vue-app-admin",
    entry: "/micro-admin/__static__/",
    activeRule: "/micro-admin",
    $meta: {
      tile: "微前端-子应用",
    },
  },
  // ......
];

网关配置子应用

js
// 定位到各自的子应用机器
"/micro-admin/__static__";

五、解决多个子应用共用一个 dom 节点的问题

js
// qiankun.ts
import { registerMicroApps, start } from "qiankun";
import childApps from "./childApps";
import singleSpa from "single-spa";

const genActiveRule = (routePrefix) => {
	if (typeof routePrefix === 'string') {
		return (location) => location.pathname.startsWith(routePrefix);
	}
	return (location) => routePrefix.includes(location.pathname);
}


const apps = childApps.map((item) => {
	return {
		...item,
		activeRule: genActiveRule(item.activeRule),
		container: '#subapp-viewport',
		props: {
			routerBase: item.activeRule, // 下发基础路由
			getGlobalState: globalState.getGlobalState, // 下发全局状态
		}
	}
})

registerMicroApps(apps, {
	beforeLoad: (app) => {
		// 解决切换卡顿问题
		singleSpa.setMountMaxTimeout(100000, false, 50000)
		console.log('before load-加载前', app.name)
	}
	beforeMount: (app) => {
		console.log('before mount-挂载前', app.name)
	}
	afterMount: (app) => {
		console.log('after mount-挂载后', app.name)
	}
	beforeUnmount: (app) => {
		console.log('before unload-销毁前', app.name)
	}
	afterUnmount: (app) => {
		console.log('after unload-销毁后', app.name)

		// 可以处理切换子应用,全局弹出框代码的污染问题
		const name = app.name
		const names = names.split('-')
		if (names.length > 0) {
			const childName = names[names.length - 1]
			const nodeList = document.querySelectorAll(`.${childName}-message`)
			if (nodeList.length > 0) {
				nodeList.forEach(node => {
					node.remove()
				})
			}
		}
	}
})

start({
	sandbox: {

	}
})

六、权限控制

使用全局 window 变量来解决和 sessionStorage 存储来解决

七、样式全局污染问题

使用不同的子应用给不同的 UI 库样式前缀进行解决

Released under the MIT License.