微前端系列 - 阿里qiankun框架搭建项目(带视频)

1,038 阅读2分钟

qiankun 是一个基于 single-spa 的微前端实现库,拥有的特点:JS沙箱,样式隔离,元素隔离,数据通信,预加载,HTML Entry。

qiankun与single-spa的区别有:

  • qiankun 是HTML Entry,single-spa是 JS Entry

  • qiankun 有JS沙箱隔离(js沙箱先检测window.Proxy,否则降级为window快照),CSS样式隔离用Shawdom;single-spa不带JS和CSS沙箱隔离,需要用户自己设计

乾坤介绍

项目地址

B站视频地址

主项目配置

src/main.js 修改

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { registerMicroApps, start } from "qiankun";

registerMicroApps([
  {
    name: "vueapp1",
    entry: "//localhost:8031",
    container: "#container",
    activeRule: "/mypage1",
  },
  {
    name: "vueapp2",
    entry: "//localhost:8032",
    container: "#container",
    activeRule: "/mypage2",
  },
]);
createApp(App).use(router).mount("#app");
start();

src/router 修改

import { createRouter, createWebHistory } from "vue-router";
import HelloWorld from "./components/HelloWorld";

const routes = [
  {
    path: "/",
    name: "home",
    component: HelloWorld,
  },
];
const router = createRouter({
  history: createWebHistory(),
  routes,
});

//主应用使用的嵌套路由
router.beforeEach((to, from, next) => {
  if (!window.history.state.current) window.history.state.current = to.fullPath;
  if (!window.history.state.back) window.history.state.back = from.fullPath;
  // 手动修改history的state
  return next();
});

export default router;

src/App.vue 修改

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png" />
    <br />
    <router-link to="/">基座: Go to Home</router-link>
    <br />
    <router-link to="/mypage1">基座: 去子应用1</router-link>
    <br />
    <router-link to="/mypage2">基座: 去子应用2</router-link>
    <br />
    <router-view></router-view>
    <div id="container"></div>
  </div>
</template>

子项目配置

vue.webpack.js 修改

const { name } = require("./package");
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 8031,
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
    },
  },
});

package.json eslint 增加全局变量

"eslintConfig": {
    ...
    "globals": {
      "__webpack_public_path__": true
    },
    ...
  },

src/public-path.js 修改

动态修改子项目图片等素材地址,子项目中图片地址一般是相对根域名的,但是嵌入到主项目中,需要改变为绝对子项目域名,不然找不到图片

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

main.js 修改

import "./public-path";
import Vue from "vue";
import App from "./App.vue";
import routes from "./router";
import VueRouter from "vue-router";

Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;

  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/mypage1/" : "/",
    mode: "history",
    routes,
  });
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$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.$el.innerHTML = "";
  instance = null;
  router = null;
}

router.js 修改

这里只能导出配置了,不然每次加载子项目会是同一个vue-router对象

import Vue from "vue";
import VueRouter from "vue-router";
import HelloWorld from "./components/HelloWorld";
import Hello from "./components/Hello";
import World from "./components/World";

Vue.use(VueRouter);

const routes = [
  { path: "/", component: HelloWorld },
  { path: "/hello", component: Hello },
  { path: "/world", component: World },
];

export default routes;

App.vue 修改

<template>
  <div id="app">
    <img alt="Vue logo" style="height: 40px" src="./assets/big.jpg" />
    <br />
    <router-link to="/">子应用1:首页</router-link>
    <br />
    <router-link to="/hello">子应用1: hello 页</router-link>
    <br />
    <router-link to="/world">子应用1:world 页</router-link>
    <br />
    <router-view></router-view>
  </div>
</template>

问题

1、主项目报错,主项目是 vue3 + vue-router@4

[Vue Router warn]: Error with push/replace State DOMException: Failed to execute 'replaceState' on 'History': A history state object with URL 'http://localhost:8080undefined/' cannot be created in a document with origin 'http://localhost:8080' and URL 'http://localhost:8080/mypage1/'.

解决:在router.js中增加下面代码,页面跳转的时候一些内容的丢失。解决办法也查找了一些资料

//主应用使用的嵌套路由
router.beforeEach((to, from, next) => {
  if (!window.history.state.current) window.history.state.current = to.fullPath;
  if (!window.history.state.back) window.history.state.back = from.fullPath;
  // 手动修改history的state
  return next();
});

2、子项目 __webpack_public_path__ not defined

解决: package.json 中增加全局变量 __webpack_public_path__

"eslintConfig": {
    ...
    "globals": {
      "__webpack_public_path__": true
    },
    ...
  },