欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Vue3 + Vite + 乾坤微前端实践

最编程 2024-04-06 09:14:47
...

什么是微前端?

微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署

qiankun

qiankun是一个基于single-spa的微前端实现库,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造

项目背景

vue3的生态日渐成熟,于是乎一狠心新产品直接上vue3。由于已有基于qiankun的生产项目,也算比较熟悉,新产品的微前端方案也就同样选择了qiankun,容易踩坑的地方不少,下面直接上干货

项目构成如下:

  • 项目A:  子应用 Vue2 + Webpack
  • 项目B:  子应用 Vue3 + Vite
  • 项目C:  主应用 Vue3 + Vite

项目A,Webpack老项目介入

按照官网教程即可

  1. 安装依赖 npm i qiankun
  2. 配置基础路径
  3. main.ts 中导出qiankun的生命周期钩子,mount 时加载,unmount 时销毁
let instance = null;
function render(props) {
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount("#app");
}

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

export async function mount(props) {
  render(props);
}

export async function unmount(props) {
  instance?.$destroy();
}

项目B,新的子应用

目前为止官方还没有支持vite,直接使用的话只能解决build后的集成,经过一系列的尝试,最终选择使用了 vite-plugin-qiankun 插件接入

  1. 安装依赖 npm i vite-plugin-qiankun
  2. 在 vite.config.ts 中添加插件
import qiankun from 'vite-plugin-qiankun';

defineConfig({
    base: 'http://localhost:3002/',
    server: {
      port: 3002,
      cors: true,
      origin: 'http://localhost:3002'
    },
    plugins: [
      vue(),
      qiankun('flow-graph', {
        useDevMode: true
      })
    ]
})

需要注意的点有几个:

  • base 需要使用绝对路径,做为子应用时需要跨域访问
  • useDevMode: true 这个地方,引用作者的原话:

因为开发环境作为子应用时与热更新插件(可能与其他修改html的插件也会存在冲突)有冲突,所以需要额外的调试配置

  • server.origin 需要使用绝对路径,否则会引起dev时静态资源无法访问 这个地方要多说几句,如果静态资源放在public目录下,使用如下的写法加载,在应用单独运行和发布时是没有问题的,但是在dev时作为子应用无法正确加载资源
<img src="/img/add.png" />

资源放置在 assets 目录下,使用如下的相对路径引用,再配合 server.origin 经测试在dev和prod时作为子应用都可以正确加载资源

<img src="@/assets/add.png" />
  1. main.ts 中添加qiankun的生命周期钩子,同样是 mount 时加载,unmount 时销毁
import {
  renderWithQiankun,
  qiankunWindow
} from 'vite-plugin-qiankun/dist/helper';

let app: VueApp<Element>;
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  createApp(App).use(router).use(ElementPlus).use(createPinia()).mount('#app');
} else {
  renderWithQiankun({
    mount(props) {
      console.log('--mount');

      app = createApp(App);
      app
        .use(router)
        .use(ElementPlus)
        .use(createPinia())
        .mount(
          (props.container
            ? props.container.querySelector('#app')
            : document.getElementById('app')) as Element
        );
    },
    bootstrap() {
      console.log('--bootstrap');
    },
    update() {
      console.log('--update');
    },
    unmount() {
      console.log('--unmount');
      app?.unmount();
    }
  });
}

这里要注意的是qiankun使用 window.__POWERED_BY_QIANKUN__ 判断是否在子应用环境中,而插件使用 qiankunWindow.__POWERED_BY_QIANKUN__ 进行判断

项目C,主应用

第一步,先跑起来

  1. 安装依赖 npm i qiankun
  2. App.vue 添加id为 subApp 的div
  3. main.ts中注册子应用路由
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'data-source',
    entry: 'http://localhost:3001/',
    container: '#subApp',
    activeRule: '/data-source'
  },
  {
    name: 'flow-graph',
    entry: 'http://localhost:3002/',
    container: '#subApp',
    activeRule: '/flow-graph'
  }
]);

start();

这时路由地址变化,子应用就会对应加载到div的位置上了

二级路由加载

我们在实际应用中很多时候都是在二级路由页面中加载子应用,现在只需要稍加改造就可以了

  1. 添加二级路由页面
<script setup lang="ts">
import { start } from 'qiankun';
import { onMounted } from 'vue';

onMounted(() => {
  if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start({ sandbox: { experimentalStyleIsolation: true } });
  }
});
</script>

<template>
  <div class="layout">
    <el-menu
      class="navigation-menu-left"
      active-text-color="#ffd04b"
      background-color="#545c64"
      text-color="#fff"
      router
    >
      <el-menu-item index="/flow-graph">流程</el-menu-item>
      <el-menu-item index="/data-source">数据</el-menu-item>
    </el-menu>
    <div class="main-container">
      <router-view />
      <div id="subApp" class="sub-app-container" />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.layout {
  .navigation-menu-left {
    width: 100px;
  }

  .main-container {
    position: absolute;
    left: 100px;
    top: 0;
    height: 100%;
    width: calc(100% - 100px);

    .sub-app-container {
      height: 100%;
      width: 100%;
    }
  }
}
</style>

这里在路由页面 mounted 时调用qiankun的 start,删除 main.ts 里的 start 调用,因为需要子应用的容器加载之后再进行调用

  1. 在主应用中注册二级路由
{
    path: '/data-source/:chapters*',
    component: () => import('@/layout/index.vue')
},
{
    path: '/flow-graph/:chapters*',
    component: () => import('@/layout/index.vue')
}

这里要注意的是路由使用通配符*无法正确加载,使用参数通配符 :chapters* 可以正确加载

配置

加载子应用需要配置绝对路径的base,因为vite不支持运行时publicPath,只能在打包时写死base配置,如果添加在每个子应用的 .env 配置文件中,修改起来十分麻烦,项目采用monorepo时的两个解决方案

  1. .env 配置提出到根目录,在 vite.config.ts 按如下配置:
{
    envDir: '../../',
    base: `${loadEnv(mode, resolve(process.cwd(), '../../')).VITE_APP_BASE}:${
      loadEnv(mode, resolve(process.cwd(), '../../')).VITE_APP_DATA_SOURCE_PORT
    }`,
}

配置 envDir 路径,并且读取时也添加相对路径,这样来使所有项目读取公共的 .env 配置文件

  1. 在根目录新建一个类似如下的自定义配置文件,直接读取:
const dev = {
  microApps: [
    {
      name: 'data-source',
      entry: 'http://localhost:3001/',
      container: '#subApp',
      activeRule: '/data-source'
    },
    {
      name: 'flow-graph',
      entry: 'http://localhost:3002/',
      container: '#subApp',
      activeRule: '/flow-graph'
    }
  ]
};

const prod = {
  icroApps: [
    {
      name: 'data-source',
      entry: 'http://192.168.1.22:3001/',
      container: '#subApp',
      activeRule: '/data-source'
    },
    {
      name: 'flow-graph',
      entry: 'http://192.168.1.22:3002/',
      container: '#subApp',
      activeRule: '/flow-graph'
    }
  ]
};

export default (mode: string) => ({
  port: 3000,
  ...(mode === 'development' ? dev : prod)
});

小结

项目搭建的过程和容易踩坑的地方都记录下来了,希望本文能成为最全面的 vite + qiankun 搭建指导。