qiankun+vue3 使用模块联邦bug记录

402 阅读3分钟

前言

容器和子应用都是vue-cli5搭建的,使用的是vue3+element-plus

host代码

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <div v-if="isLoadingComponent">Loading HelloWorld.vue</div>
    <HelloWorld msg="Welcome " />
    <el-button>本地测试按钮</el-button>
  </div>
</template>

<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
import lodash from 'lodash';

const isLoadingComponent = ref(true);
const HelloWorld = defineAsyncComponent(() =>
// eslint-disable-next-line
  import('app_exposes/HelloWorld.vue').finally(() => {
    console.log('finally');
    isLoadingComponent.value = false;
  }));
</script>

host配置

const { defineConfig } = require("@vue/cli-service");
const webpack = require("webpack");
const pkg = require("./package.json");

const appName = pkg.name;
const publicPath = `/${appName}`;

module.exports = defineConfig({
  publicPath,
  pages: {
    index: {
      entry: './src/main.ts',
    },
  },
  devServer: {
    // 关闭主机检查,使微应用可以被 fetch
    allowedHosts: "*", // webpack@4设置 disableHostCheck: true
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    experiments: {
      topLevelAwait: true,
    },
    optimization: {
      splitChunks: {
        cacheGroups: {
          defaultVendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            chunks: 'async',
            reuseExistingChunk: true,
          },
          common: {
            name: 'chunk-common',
            minChunks: 2,
            priority: -20,
            chunks: 'async',
            reuseExistingChunk: true,
          },
        },
      },
    },
    output: {
      library: appName,
      libraryTarget: "umd",
      chunkLoadingGlobal: `webpackJsonp_${appName}`, // webpack@4设置 jsonpFunction: `webpackJsonp_${appName}`
    },
    plugins: [
      new webpack.container.ModuleFederationPlugin({
        name: "app_general",
        filename: "remote.js",
        remotes: {
          app_exposes: "app_exposes@http://localhost:5552/remoteEntry.js",
        },
        shared: {
          vue: { singleton: true },
        },
      }),
    ],
  },
  transpileDependencies: true,
});

remote配置

const { defineConfig } = require("@vue/cli-service");
const webpack = require("webpack");
const pkg = require("./package.json");
const appName = pkg.name;
// const publicPath = '/' + appName;
const publicPath = "auto";

module.exports = defineConfig({
  publicPath,
  pages: {
    index: {
      entry: './src/main.ts',
    },
  },
  devServer: {
    // 关闭主机检查,使微应用可以被 fetch
    allowedHosts: "*", // webpack@4设置 disableHostCheck: true
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          defaultVendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            chunks: 'async',
            reuseExistingChunk: true,
          },
          common: {
            name: 'chunk-common',
            minChunks: 2,
            priority: -20,
            chunks: 'async',
            reuseExistingChunk: true,
          },
        },
      },
    },
    experiments: {
      topLevelAwait: true,
    },
    output: {
      library: appName,
      libraryTarget: "umd",
      chunkLoadingGlobal: `webpackJsonp_${appName}`, // webpack@4设置 jsonpFunction: `webpackJsonp_${appName}`
    },
    plugins: [
      new webpack.container.ModuleFederationPlugin({
        name: "app_exposes",
        filename: "remoteEntry.js",
        library: { type: "umd", name: "app_exposes" },
        exposes: {
          "./HelloWorld.vue": "./src/components/HelloWorld.vue",
          "./AboutView.vue": "./src/views/AboutView.vue",
          "./helloWord": "./src/components/helloWord.js",
        },
        shared: {
          vue: { singleton: true },
          lodash: { singleton: true, eager: true },
          // 其他共享的依赖模块
        },
      }),
    ],
  },

  transpileDependencies: true,
});

遇到的bug

  1. host应用加载remote应用一直报错404 (先单独跑两个子应用)

image.png

解决:

// 给remote配置
 publicPath:'auto' (设置别的路径,在引入的时候依旧报错404 待解决)
//host配置
const pkg = require("./package.json");

const appName = pkg.name;
const publicPath = `/${appName}`;

module.exports = defineConfig({
  publicPath,
}
  1. host引入element组件样式失败

image.png 解决方法host和remote都配置,remote共享自己的vue

shared: { vue: { singleton: true }, },

shared: {
  // 模块名称或路径
  moduleName: {
    // 是否将模块共享为单例模式
    singleton: true,

    // 共享模块的版本要求
    requiredVersion: '^1.0.0',

    // 是否使用严格的版本匹配(必需满足指定的版本)
    strictVersion: true,

    // 是否在主应用程序加载前立即加载共享模块
    eager: true,

    // 是否允许共享模块在多个实例中共存
    // 默认情况下,模块联邦会尝试合并同一个名称的模块
    // 如果设置为 true,则每个应用程序将独立加载自己的实例
    shareKey: 'dependency@1.0.0',
    
    // 共享模块的名称的别名,用于在引入模块时使用
    import: 'dependency-alias',

    // 共享模块的版本范围,只有一个共享模块匹配该范围时才能加载
    // 例如,"dependency@^1.0.0"
    requiredVersion: '^1.0.0',

    // 是否禁用共享模块,即不将其提供给其他应用程序
    // 默认情况下,所有共享的模块都是可用的
    // 如果设置为 false,则禁用该共享模块
    eager: false,
    
    // 自定义共享模块的初始化逻辑
    // 使用 shared module 的初始化函数替代默认的初始化逻辑
    init: 'module-init-function',
    
    // 手动指定分享模块的提供者
    // 默认情况下,模块联邦会自动检测分享模块的提供者
    // 通过指定这个选项,可以手动设置共享模块的提供者
    // 例如,provider: () => require('module-provider')
    provider: 'module-provider',

    // 是否允许共享模块在 fallback 中被选择作为备选项
    // 默认情况下禁止备用共享模块为提供者的选择
    fallback: true,
    
    // 是否使用 shared module 的引导逻辑
    // 默认情况下,模块联邦会为每个共享模块生成自动的引导逻辑
    // 如果设置为 false,将禁用自动生成的引导逻辑
    singleton: false,

    // 用于生成 shared module 引导逻辑的文件名称
    // 默认为 'remoteEntry.js'
    filename: 'shared-module-remoteEntry.js',
  },
  // ... 其他模块配置
}
  1. shared vue 引入报错

image.png 参考webpack官网解答webpack.docschina.org/concepts/mo… 需要host和remote异步引入vue

首先在host和remote创建init.ts

import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import locale from 'element-plus/lib/locale/lang/zh-cn';
import App from './App.vue';
import router from './router';
import 'element-plus/dist/index.css';

let app: any = null;
function render(props: any) {
  const { container } = props;
  app = createApp(App);

  app.use(router)
    .use(ElementPlus, { locale })
    .mount(container ? container.querySelector('#app') : '#app');
}

if (!(window as any).__POWERED_BY_QIANKUN__) {
  
  render({});
}

async function bootstrap() {
  console.log('bootstrap');
}


async function mount(props: any) {
  console.log('mount-general', props);
  render(props);
}
async function unmount() {
  console.log('unmount-general');
  app.unmount();
}

function update() {
  console.log('update-general');
}
export {
  render,
  app,
  mount,
  update,
  unmount,
  bootstrap,
}
import './public-path';
const {
  mount,
  unmount,
  bootstrap,
  update,
} = await import('./init');
export {
  update,
  mount,
  unmount,
  bootstrap,
}

异步导出报错找不到生命周期

  1. 异步导出插件 安装@babel/plugin-syntax-top-level-await 并且配置vue.config.js
  configureWebpack: {
    experiments: {
      topLevelAwait: true,
    },
   }

experiments 配置选项用于启用Webpack中的实验性功能。其中,topLevelAwait 是 experiments 配置选项的一个可用设置,用于启用顶层的异步等待(Top-level Await)语法支持。

在Webpack中,顶层异步等待是指在模块的顶层作用域中使用await关键字等待一个Promise的执行结果。这样做可以简化异步编程,使代码更加直观和易读。