用微前端框架qiankun配置项目的实战

参考文档:

qiankun官方文档      csdn文档1  csdn文档2    github地址

umi-qiankun 的教程请移步 umi 官网 和 umi-qiankun 的官方 demo

一、过程

1.使用vue-cli创建主应用

vue create qiankun-main

选择2.x版本,选择eslint等等配置,完成后初始化并启动项目。vue-cli建的项目vue版本是2.6,所以不要用ts。路由需要是history模式。

遇到错误:关于使用vue create创建项目的时候出现了 command failed: pnpm install --reporter silent --shamefully-hoist 报错

2.主应用需要安装qiankun插件   

npm i qiankun -S

3.配置些简单的eslint 和 prettierrc规则

注意  "vue/multi-word-component-names": "off",  要添加,否则创建view下其他vue文件如index.vue时会报错。

// in .eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    "plugin:vue/essential",
    // "plugin:prettier/recommended",  // 还没配置prettier就先注释掉
  ],
  parserOptions: {
    parser: "@babel/eslint-parser",
    ecmaVersion: 2020,
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
  },
  overrides: [
    {
      files: ["*.vue"], // 匹配views和二级目录中的index.vue
      rules: {
        "vue/multi-word-component-names": "off",
      }, //给上面匹配的文件指定规则
    },
  ],
};
// in .prettierrc.js

module.exports = {
  // 一行最多 120 字符..
  printWidth: 120,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 末尾需要有逗号
  trailingComma: 'all',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // vue 文件中的 script 和 style 内不用缩进
  vueIndentScriptAndStyle: false,
  // 换行符使用 lf
  endOfLine: 'lf',
};

4.配置新路由

删除无用代码比如HomeView.vue等文件,重新配置路由。

配置些简单的路由:

// in src/router/index.ts

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/home/index.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "home",
    component: Home,
  },
  {
    path: "/about",
    name: "about",
    component: () => import("../views/about/index.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;

5.编写存放微应用的区域

参考上方文档1。定义子应用的加载位置,如 id 为 root-view 的一个盒子。

我选择在 src/app.vue 中存放:

<template>
  <div id="app">
    <nav>
      <router-link to="/home">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/app1">app1</router-link>
    </nav>
    <router-view />
    <div class="app-content">
      <!-- 微应用所在 -->
      <div id="root-view"></div>
    </div>
  </div>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

其中子应用就是存放在 <div id="root-view"></div> 中。id 为 root-view 对应第六点 microApps 中的配置 container 项,将子应用挂载到 id="root-view" 的 div 中。

6. 注册微应用并启动

可以上方参考官方文档项目实践部分,或者文档1实战部分

src下新建modules文件夹,里面新建micro-app.js。 这是注册微应用的代码。

// in src/modules/micro-app.js

// 在主应用中注册微应用    各子应用信息如下
const microApps = [
    {
      name: "module-app1", // name都不能重复
      entry: "http://localhost:8081", // 定义子应用入口,基于qiankun的应用一般直接写子应用的入口html地址即可。
      activeRule: "/app1", // url变化后, 所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中。要和子项目中配置的拦截到对应子项目的标识保持一致(  base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : '/',)。
      container: "#root-view", // 定义子应用的加载位置,如 id为root-view的一个盒子
      sandbox: {
        strictStyleIsolation: true, // 开启样式隔离
      },
    },
    // {
    //   name: "module-app2",
    //   entry: "http://localhost:8082",
    //   activeRule: "/app2",
    //   container: "#root-view",
    //   sandbox: {
    //     strictStyleIsolation: true, // 开启样式隔离
    //   },
    // },
  ];
  export default microApps;
  

导出了一个子应用数组,会作为参数传入到注册函数registerMicroApps中。

启动:

在  src/main.js  页添加相应代码:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import microApps from '../src/modules/micro-app';
// // 引入qiankun注册子应用和启动的接口函数
import { registerMicroApps, start } from 'qiankun';

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");


// 引入微应用入口配置
registerMicroApps(microApps, {
  // 注册一些全局生命周期钩子,如进行日志打印,如果不需要可以不传
    beforeMount() {
      console.log(21, "qiankun-beforeMount");
    },
});
// 启动qiankun,并开启预加载
start({
    prefetch: true,
    sandbox: {
        strictStyleIsolation: true, // 开启样式隔离
    },
});

注意: start() 中 strictStyleIsolation: true 开启严格的样式隔离模式,这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。即使主子应用页面样式都没 scoped 也互不影响。(详见官网)。

隔离前页面结构为:

隔离之后页面结构为(可见多了一个Shadow root 节点, 是Shadow tree 的根节点):

7.在vue.config.js文件添加反向代理配置

const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
    transpileDependencies: true,
    lintOnSave: false,
    //反向代理
    devServer: {
        // 环境配置
        host: "0.0.0.0",
        port: 8080,
        https: false,
        // hotOnly: false,
        open: false, //配置自动启动浏览器
        proxy: {
            // 配置多个代理(配置一个 proxy: 'http://localhost:4000' )
            // "/service": {
            //     target: `http://172.16.21.153:6081/`,
            // },
        },
    },
});

8.在微应用进行相应配置:

参考官方文档:

①在 src 目录新增  public-path.js 文件

// 在qiankun环境下,修正加载路径
if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

②入口文件 main.js 修改:

import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import routes from './router';
import store from './store';

Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
    console.log();
    const { container } = props;
    router = new VueRouter({
        base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : '/',
        mode: 'history',
        routes,
    });

    instance = new Vue({
        router,
        store,
        render: (h) => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时(在非qiankun环境下,直接执行渲染)
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

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

/**
 * 挂载函数
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
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;
}
/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
    console.log('update props', props);
}

注意,要修改 src/router/index.ts 文件,此时该文件导出的应该是路由信息数组:

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/home/index.vue";
import About from "../views/about/index.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "home",
    component: Home,
  },
  {
    path: "/about",
    name: "about",
    // component: () => import("../views/about/index.vue"),
    component: About,
  },
];

// const router = new VueRouter({
//   mode: "history",
//   base: process.env.BASE_URL,
//   routes,
// });

export default routes;

否则会报错:Uncaught TypeError: routes.forEach is not a function。

注意作为子应用,其页面不能懒加载,不要这样写:  // component: () => import("../views/about/index.vue")。否则进入到该页面后报错ChunkLoadError: Loading chunk src_views_about_index_vue failed.

别忘记做第四点。

③ 打包配置修改(vue.config.js):

const { defineConfig } = require("@vue/cli-service");
const { name } = require('./package');
module.exports = defineConfig({
    transpileDependencies: true,
    lintOnSave: false,
    devServer: {
        port: 8081, // 与注册的微应用的entry地址端口号一致
        headers: {
            'Access-Control-Allow-Origin': '*'
        }
    },
    configureWebpack: {
        output: {
            library: `${name}-[name]`,
            libraryTarget: 'umd', // 把微应用打包成 umd 库格式
            chunkLoadingGlobal: `webpackJsonp_${name}`,
        },
    },
});

注意:官方文档里是jsonpFunction: `webpackJsonp_${name}`,。这样会报错:

 搜索 报错 configuration has an unknown property ‘jsonpFunction‘ ,见文档

webpack  在2020-10-10发布的webpack 5中已将 output.jsonpFunction 更名为 output.chunkLoadingGlobal 。所以要把 jsonpFunction 改为 chunkLoadingGlobal 。

④ 注意要配置第三点,别忘了 libraryTarget ,否则报跨域的错和 TypeError: application 'module-app1' died in status LOADING_SOURCE_CODE: Failed to fetch。

参考文档1   参考文档2

9.同时启动主应用和微应用

成功如下:

10.qiankun- 应用间的通讯

参考文档1   参考文档2

使用官方提供的应用间通信方式 - Actions 通信

qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:

        setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。
        onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
        offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。

​​​​​​这几个函数的文档

实践步骤:

1. 在主应用的 src目录下新建 actions 文件夹,里面新建index.js文件,用于写入initGlobalState函数

// in src\actions\index.js

import { initGlobalState } from "qiankun";
// import store from '../store';

const initialState = {
    //这里写初始化数据
    test: "测试",
    mes2: 0,
    app1Msg: 0, // 子应用app1的数据
};

// 初始化 state
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((state, prev) => {
    //监听公共状态的变化
    console.log("主应用: 变更前");
    console.log(prev);
    console.log("主应用: 变更后");
    console.log(state);
    // store.commit('setProject', state); // 这里可以把公共状态存到主应用的vuex里了
});

export default actions;

导出了actions对象,里面有三个通信方法。

2.在主应用的组件中使用actions

在一个挂载了子应用的页面中:

// in src\views\app1\index.vue

<template>
    <div class="app1">
        <div style="margin-bottom: 40px">
            <p>本主应用消息1:{{ mes1 }}</p>
            <button @click="sendMes1">点击向子应用发送消息1</button>
        </div>
        <div>
            <p>本主应用消息2:{{ mes2 }}</p>
            <button @click="sendMes2">点击向子应用发送消息2</button>
        </div>
        <p>接收到的消息:{{ app1Msg }}</p>
    </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import actions from "../../actions";

const mes1 = ref(""); // 有vuex等状态管理的话可以设置为vuex里的值,并在vuex中用actions.onGlobalStateChange和store.commit改变相应的值
const mes2 = ref(0);
const app1Msg = ref(0);
const sendMes1 = () => {
    mes1.value += 1;
    actions.setGlobalState({ test: mes1.value }); //通过setGlobalState改变全局状态
};

const sendMes2 = () => {
    mes2.value += 1;
    actions.setGlobalState({ mes2: mes2.value }); //通过setGlobalState改变全局状态
};

// 数据要存入vuex的话可以在src\actions\index.js中进行监听并用store.commit改变相应的值 
actions.onGlobalStateChange((state, prev) => {
    console.log(31, "主应用监听子应用发来的信息", state, prev);
    if (state.app1Msg !== prev.app1Msg) {
        app1Msg.value = state.app1Msg;
    }
}, true);
onMounted(() => {
    console.log(10, "主应用app1地址");
});
</script>

改变数据时触发actions.setGlobalState函数,监听数据变化时触发了actions.onGlobalStateChange函数。数据要存入vuex的话也可以在src\actions\index.js中进行监听并用store.commit改变相应的值(见注释)。

3.在子应用的 src目录下新建 actions 文件夹,里面新建index.js文件,用于写入actions实例,此实例将用于存入主应用返回的props和通信方法:

// in src\actions\index.js

function emptyAction() {
    // 设置一个actions实例
    // 提示当前使用的是空 Action
    console.warn('Current execute action is empty!');
}

class Actions {
    // 默认值为空 Action
    actions = {
        onGlobalStateChange: emptyAction,
        setGlobalState: emptyAction,
    };

    /**
     * 设置 actions
     */
    setActions(actions) {
        this.actions = actions;
    }

    /**
     * 映射
     */
    onGlobalStateChange(...args) {
        this.actions.onGlobalStateChange(...args);
        return;
    }

    /**
     * 映射
     */
    setGlobalState(...args) {
        this.actions.setGlobalState(...args);
        return;
    }
}

const actions = new Actions();
export default actions;

4.在子应用的main.js的mounted的生命周期里注入actions实例

import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import routes from './router';
import store from './store';
import actions from './actions';

Vue.config.productionTip = false;

let router = null;
let instance = null;
function render(props = {}) {
    const { container } = props;
    router = new VueRouter({
        base: window.__POWERED_BY_QIANKUN__ ? '/app1/' : '/',
        mode: 'history',
        routes,
    });

    instance = new Vue({
        router,
        store,
        render: (h) => h(App),
    }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时(在非qiankun环境下,直接执行渲染)
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

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

/**
 * 挂载函数
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
    actions.setActions(props); // 把actions实例的actions对象值设置为props,这样actions对象值就有props里面的onGlobalStateChange等方法了,之后可以在vue页面调用。相当于将action对象绑到Vue原型上,为了项目中其他地方使用方便Vue.prototype.$actions = actions 、 Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange、Vue.prototype.$setGlobalState = props.setGlobalState
    console.log('[vue] props from main framework', props);
    render(props);
}

/**
 * 卸载函数
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
    router = null;
}
/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
    console.log('update props', props);
}

注入后actions就拥有了props(包括了通信方法)。之后在任意页面就可以引入此action以使用通信方法。参考文档2 中将action对象绑到Vue原型上也是同样的效果。

5.在子应用组件中

// in src\views\home\index.vue

<template>
    <div class="home">
        <h1>子应用app1</h1>
        <div style="margin-bottom: 40px">
            <p>接收到的消息1:{{ mes1 }}</p>
            <p>接收到的消息2:{{ mes2 }}</p>
        </div>
        <div>
            <p>子应用自己的数据:{{ app1Msg }}</p>
            <button @click="butClick">点击向父应用发送消息</button>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import actions from '../../actions';

const mes1 = ref('');
const mes2 = ref(0);
const app1Msg = ref(0);
const butClick = () => {
    app1Msg.value += 1;
    actions.setGlobalState({ app1Msg: app1Msg.value });
};

onMounted(() => {
    actions.onGlobalStateChange((state, prev) => {
        console.log(28, '子应用监听主应用发来的信息', state, prev);
        if (state.test !== prev.test) {
            mes1.value = state.test;
        }
        if (state.mes2 !== prev.mes2) {
            mes2.value = state.mes2;
        }
    }, true);
});
</script>

跟父组件差不多的写法。

成功展示如下:

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在实际项目中,qiankun可以用于构建微前端架构。首先,需要在主应用项目中安装qiankun,可以使用命令yarn add qiankun或者npm i qiankun -S进行安装。 接下来,将原本将所有功能集中于一个项目中的方式转变为将功能按业务划分成一个主项目和多个子项目。每个子项目负责自身的功能,并且具备和其他子项目和主项目进行通信的能力,以实现更细化和易于管理的目标。这可以通过使用微前端框架qiankun来实现。 在实际项目中,可以使用qiankun来构建一个主项目,比如使用vue3作为主项目,并使用vue-qiankun-master作为主应用。这样可以实现微前端的功能,并且可以方便地集成和管理多个子项目。 通过qiankun配置和使用,可以实现不同子项目之间的路由管理、状态管理以及跨项目的通信。这样,不同子项目可以独立开发、独立部署,并且可以在主项目中集成和展示。同时,qiankun还提供了一些插件和工具,可以帮助开发者更方便地进行微前端架构的开发和管理。 总结起来,qiankun实战中可以用于构建微前端架构,将功能按业务划分为主项目和多个子项目,并实现它们之间的通信和集成。通过使用qiankun,可以更好地实现细化和易于管理的项目架构。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [微前端vue项目实战 -- 乾坤qiankun框架 (一)](https://blog.csdn.net/csl125/article/details/122990142)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值