qiankun引入vue3子项目实践

qiankun引入vue3子项目实践

背景

vue3优点就不再赘述了,反正就是很多(性能优化、优雅使用上ts、Composition API...),最主要的能使用前沿的技术架构,这是每一位技术的执念。

怎么样无感知切换到使用vue3开发成了当前的问题,前段时间有同事提出了可以通过qiankun嵌入,okokok!那就是它了,在我们老项目中通过qiankun引入一个vue3子服务来写业务模块,同时新老项目也可以通过qiankun内置方式来进行数据交互,如下图:

[图片上传失败...(image-44c6bb-1679899817625)]

准备工作

创建vue3子服务项目

这里我们使用webpack方式创建,暂时不要使用vite,因为vite暂时不支持qiankun框架.

创建过程不在赘述,可参考:https://segmentfault.com/a/1190000039255646

创建完成之后我们新建两个路由地址,如下:

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/Home.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/home",
    name: "Home2",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default { router, routes };

下面我们就以/home以及/about路由为例在老项目中引入。

安装

主服务安装

cnpm i qiankun

创建子服务容器

主服务增加router地址/qiankunVue3/*来承载vue3子项目,接入之后主服务所有/qiankunVue3的路由地址都将由子服务渲染。

下面代码省略了引入部分,导出的micoapp会合并到主服务的router.routes对象中

主服务增加一个routes对象:

const qiankunVue3Layout = () => import(/* webpackChunkName: "micoapp" */ '@/views/common/qiankunVue3Layout.vue')

let micoapp = [
  {
    path: '/qiankunVue3/*',
    name: '乾坤Vue3',
    meta: {
      keepAlive: false,
      isMicoApp: true
    },
    component: qiankunVue3Layout
  }
]

export default micoapp

其中新建qiankunVue3Layout.vue简写之后的代码如下,由<section id="qiankunVue3Dom" />代替<router-view>来承载子服务

// qiankunVue3Layout.vue
<template>
  <div class="cfpa-home-wrapper" id="main">
    <!--头部-->
    <v-header v-if="showHeader"></v-header>
        <!--侧边栏-->
    <v-aside v-if="isSHowMenu"></v-aside>
    <div class="cfpa-content" :class="{'cfpa-content-collapse':collapse, 'cfpa-header-show': showHeader, 'cfpa-content-fullScreen': !isSHowMenu}">
      <!--tag栏-->
      <v-tags v-if="isSHowMenu"></v-tags>
      <div class="cfpa-content-box">
        <!--子服务渲染dom,这个id在后续将会用到-->
        <section id="qiankunVue3Dom" />
      </div>
    </div>
  </div>
</template>

<script>
...省略
export default {
  ...
}
</script>
<style lang="scss" scoped>
</style>

注册子服务

主服务新建src/qiankun.js

import store from '@/store'
import { registerMicroApps, initGlobalState } from 'qiankun'

/** location.pathname是否以 routerPrefix 前缀开头*/
function genActiveRule(routerPrefix, currentRoute = '') {
  return location => location.pathname.startsWith(routerPrefix)
}

// 主应用共享出去的数据
let msg = {
  state: store.state,
  token: store.state.userInfo.token,
  isQiankun: true,
  system: 'fmas'
}
// 注册子应用
registerMicroApps(
  [
    {
    /**
     * name: 子服务有唯一性 - 这个需要与子服务webpack name一致
     * entry: 子服务入口 - 通过该地址加载微应用
     * container: 子服务挂载节点 - 微应用加载完成后将挂载在该节点上 - 与上述qiankunVue3Layout.vue id一致
     * activeRule: 子服务触发的路由规则 - 触发路由规则后将加载该微应用 - 与上述创建子服务路由前缀一致
     * props 共享数据到子服务
     * sandbox 开启沙箱
     */
      name: 'xxx-vue3',  // 根据实际情况来
      entry: '//localhost:10200',  
      container: '#qiankunVue3Dom', 
      activeRule: genActiveRule('/qiankunVue3'),
      props: msg, // 共享数据到子服务
      sandbox: {
        strictStyleIsolation: true
      }
    }
  ],
  {
    // 挂载前回调
    beforeLoad: [
      app => {
        console.log('before load', app)
      }
    ], 
    // 挂载后回调
    beforeMount: [
      app => {
        console.log('before mount', app)
      }
    ], 
    // 卸载后回调
    afterUnmount: [
      app => {
        console.log('after unload', app)
      }
    ] 
  }
)

src/main.js中引入

import './qiankun'

配置子服务

在主应用注册好了微应用后,我们还需要对微应用进行一系列的配置。首先,我们在 Vue 的入口文件 main.js 中,导出 qiankun 主应用所需要的三个生命周期钩子函数,另外,webpack 默认的 publicPath 为 "" 空字符串,会基于当前路径来加载资源。我们在主应用中加载微应用时需要重新设置 publicPath,这样才能正确加载微应用的相关资源。增加如下代码:

子服务新增@src/public-path.js

// @src/public-path.js

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

子服务修改:src/main.ts

// src/main.ts

/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createApp } from "vue";
import App from "./App.vue";
import "./public-path.js";
import router from "./router";
import store from "./store";

import { createRouter, createWebHistory } from "vue-router";

// eslint-disable-next-line no-unused-vars
let instance = null;
let Router = null;

function render(props: any = {}) {
  const { container } = props;
  // 如果是乾坤环境
  if ((window as any).__POWERED_BY_QIANKUN__) {
    const routes = router.routes;
    // 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
    const base = (window as any).__POWERED_BY_QIANKUN__ ? "/qiankunVue3" : "/";
    Router = createRouter({
      history: createWebHistory(base),
      routes,
    });
    instance = createApp(App)
      .use(store)
      .use(Router)
      .mount(container ? container.querySelector("#app") : "#app");
  } else {
    instance = createApp(App).use(store).use(router.router).mount("#app");
  }
}

// 独立运行时,直接挂载应用
if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap(props: any) {
  console.log("VueMicroApp bootstrap", props);
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props: any) {
  console.log("VueMicroApp mount", props);
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props: any) {
  console.log("VueMicroApp unmount", props);
  // instance.$destroy();
  instance = null;
  Router = null;
}

在配置好了入口文件 main.js 后,我们还需要配置 webpack,使 main.js 导出的生命周期钩子函数可以被 qiankun 识别获取。

子服务修改: vue.config.js

// vue.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require("path");

module.exports = {
  devServer: {
    // 监听端口
    port: 10200,
    // 关闭主机检查,使微应用可以被 fetch
    disableHostCheck: true,
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    // 这一块是新增本次新增的
    output: {
      // 子服务的包名,这里与主应用中注册的微应用名称一致
      library: "xxx-vue3",  // 根据实际情况来
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: "umd",
      // 按需加载相关,设置为 webpackJsonp_子服务名称 即可
      jsonpFunction: `webpackJsonp_xxx-vue3`,  // 根据实际情况来
    },
  },
};

启动

在主服务qiankunVue3Layout.vue启动qiankun

主服务修改:qiankunVue3Layout.vue

<template>
  <div class="cfpa-home-wrapper" id="main">
    <!--头部-->
    <v-header v-if="showHeader"></v-header>
        <!--侧边栏-->
    <v-aside v-if="isSHowMenu"></v-aside>
    <div class="cfpa-content" :class="{'cfpa-content-collapse':collapse, 'cfpa-header-show': showHeader, 'cfpa-content-fullScreen': !isSHowMenu}">
      <!--tag栏-->
      <v-tags v-if="isSHowMenu"></v-tags>
      <div class="cfpa-content-box">
        <!--子服务渲染dom,这个id在后续将会用到-->
        <section id="qiankunVue3Dom" />
      </div>
    </div>
  </div>
</template>

<script>
...
import { start } from 'qiankun'
export default {
  ...
  mounted() {
    if (!window.qiankunStarted) {
      window.qiankunStarted = true
      start()
    }
  }
}
</script>
<style lang="scss" scoped>
  ...
</style>

最后重新启动主服务和子服务。访问主服务/qiankunVue3/home

[图片上传失败...(image-d25168-1679899817625)]

坑点

1.建vue3项目时,选择通过webpack打包,因为vite暂时没有支持乾坤框架

2.报错 application '*****' died in status SKIP_BECAUSE_BROKEN: [qiankun]: Target container with #qiankunDom not existed while fmas mounting!

a.首先查一下 注册子服务步骤中,下列字段是否正确

[图片上传失败...(image-e9ac62-1679899817625)]

b. 是否在 配置主服务步骤中 main.js中导出3个生命周期函数

[图片上传失败...(image-14bbd0-1679899817625)]

c. 是否在 配置主服务步骤中配置webpack

[图片上传失败...(image-b9e340-1679899817625)]

流水线发版改造

目标

主服务子服务项目使用同一个git服务及同一个流水线,实现一键提交,一键发版。

实现思路

将子服务拷贝到主服务根目录里,并且将子服务的出包路径修改为主服务的出包路径(eg: 主服务出包路径为/dist,则子服务的出包路径为../dist/包名),打包构建时先构建主服务在构建子服务

流水线构建命令:

rm -rf node_modules && rm -rf dist && npm install && npm run build:dev && cd xxx-vue3 && rm -rf node_modules && npm install && npm run build:dev

构建完成之后会生成一个类似如下结构的包

[图片上传失败...(image-c2ea30-1679899817625)]

此时我们将这个包部署到服务器之后就可以通过 https://....com/访问当主服务,通过https://....com/子服务包名 访问当子服务

至此,我们只需要修改主服务引入子服务的入口地址为https://....com/子服务包名 既可。

实现步骤

将子服务拷贝至主服务根目录(子服务不需要单独git服务,如子服务有git,删除掉子服务.git文件夹),如图

[图片上传失败...(image-4dc00a-1679899817625)]

修改子服务静态资源路径及出包路径(这里子服务包名为vue3Serve):

// vue.config.js
...
publicPath:'/vue3Serve/',
outputDir:"../dist/vue3Serve",
...

修改主服务注册子服务时的入口

// qinnkun.js

{
      name: 'vue3Serve',
      entry: process.env.VUE_APP_MICO_FMAS_FRONT_VUE3, // 只修改了这行
      container: '#qiankunVue3Dom', 
      activeRule: genActiveRule('/qiankunVue3'),
      props: qiankunActions.initialState,
      sandbox: {
        strictStyleIsolation: true, 
        experimentalStyleIsolation: true, 
      },
},

项目如下:

本地://localhost:10200/vue3Serve

dev:https://.....-dev/com.cn/vue3Serve

test:https://......-test/com.cn/vue3Serve

生产:https://........com.cn/vue3Serve

修改流水线编排构建命令

rm -rf node_modules && rm -rf dist && npm install && npm run build:dev && cd xxx-vue3 && rm -rf node_modules && npm install && npm run build:dev

完事!
作者: 快落的小海疼

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容