{"id":13601434,"url":"https://github.com/wl-ui/wl-mfe","last_synced_at":"2025-04-05T01:09:09.123Z","repository":{"id":38245303,"uuid":"259228691","full_name":"wl-ui/wl-mfe","owner":"wl-ui","description":"基于vue3+koa2+qiankun2的微前端后台管理系统项目实战","archived":false,"fork":false,"pushed_at":"2023-01-06T06:29:40.000Z","size":8258,"stargazers_count":533,"open_issues_count":113,"forks_count":136,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-29T00:12:16.560Z","etag":null,"topics":["koa2","micro-frontends","node","qiankun","qiankun-vue","vue3","wl-micro-frontends"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wl-ui.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-27T06:44:15.000Z","updated_at":"2025-03-12T15:06:48.000Z","dependencies_parsed_at":"2023-02-05T13:02:05.987Z","dependency_job_id":null,"html_url":"https://github.com/wl-ui/wl-mfe","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wl-ui%2Fwl-mfe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wl-ui%2Fwl-mfe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wl-ui%2Fwl-mfe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wl-ui%2Fwl-mfe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wl-ui","download_url":"https://codeload.github.com/wl-ui/wl-mfe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271532,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["koa2","micro-frontends","node","qiankun","qiankun-vue","vue3","wl-micro-frontends"],"created_at":"2024-08-01T18:01:02.680Z","updated_at":"2025-04-05T01:09:09.107Z","avatar_url":"https://github.com/wl-ui.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# wl-mfe \n\n基于 vue3.0-beta 及 qiankun2.0 极速尝鲜！微前端进阶实战项目。   \n项目地址：[wl-mfe](https://github.com/wl-ui/wl-mfe)\n\n微前端实战详细入门教程及解放方案请转至我另一篇文章：[微前端实战看这篇就够了 - Vue项目篇](https://juejin.im/post/5e1824296fb9a02fde20fec9)。   \n项目地址：[wl-micro-frontends [wl-qiankun]](https://github.com/hql7/wl-micro-frontends)  \u0026\u0026  [在线访问](http://mfe.wlui.com.cn/)  \n部署教程：[使用各种姿势舒服的部署微前端项目](https://juejin.cn/post/6921649095610204173/)\n\n### 最终效果\n![wl-mfe](http://wlsy.oss-cn-hangzhou.aliyuncs.com/QQ%E5%9B%BE%E7%89%8720200428174450.png)\n\n## 项目启动\n```js\nnpm run yinit    // 使用yarn下载依赖，推荐\nnpm run cinit    // 使用cnpm下载依赖\nnpm run init     // 或 使用npm下载依赖\n\nnpm run serve    // 运行全部项目\nyarn serve y     // yarn运行全部项目\n\nnpm run build     // 打包全部项目\nyarn build y      // 打包全部项目\n\nnpm run publish   // 执行发布脚本\n```\n注意：如果下载报错，报 bin/sh 找不到start命令，那你可能是mac or linux，那就进入目录一个一个下载运行吧。   \n另：执行批量服务耗时较久，请耐心等待，init与build成功会在控制台提示，serve稍加等待或刷新浏览器即可。\n\n## 实战详解todo\n\n- [x] 主应用基座构建\n- [x] 子应用构建\n- [x] 微应用间通信\n- [x] 跨应用通信与vuex结合\n- [x] 发布上线\n\n## 主应用基座构建\n主应用需要用到elementui，暂时使用vue2.0+qiankun2.0版本。vue3.0beta体验在下面【子应用构建】章节\n\n主应用项目主要在5个文件：`utils`文件夹，`app.vue`，`appRegister.js`，`main.js`，`render.js`\n\n### 前提条件\n```js\ncnpm i qiankun -S\n```\n在主应用下载qiankun，注意使用2.0以上版本\n\n### 改造主应用app.vue\n```js\n\u003ctemplate\u003e\n  \u003cdiv class=\"main-container-view\"\u003e\n    \u003cel-scrollbar class=\"wl-scroll\"\u003e\n      \u003c!-- qiankun2.0  container 模式--\u003e\n      \u003cdiv id=\"subapp-viewport\" class=\"app-view-box\"\u003e\u003c/div\u003e\n      \u003c!-- qiankun1.0  render 模式--\u003e\n      \u003cdiv v-html=\"appContent\" class=\"app-view-box\"\u003e\u003c/div\u003e\n      \u003cdiv v-if=\"loading\" class=\"subapp-loading\"\u003e\u003c/div\u003e\n    \u003c/el-scrollbar\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n  export default {\n    name: \"rootView\",\n    props: {\n      loading: Boolean,\n      appContent: String\n    }\n  };\n\u003c/script\u003e\n```\n注意这里，qiankun2.0是根据 `container`字段对应的dom id来注册子应用盒子的，因此只用qiankun2.0的话不需要考虑render注测子应用盒子的情况，下面那两个dom和script里的`props`都可以不要！只留一个`\u003cdiv id=\"subapp-viewport\"\u003e\u003c/div\u003e`即可！    \n另外：注册子应用时每个子应用都可以指定一个不同的`container`，因此如果想做每个子应用的keep-alive，则可能需要每个子应用对应一个`\u003cdiv id=\"subapp-viewport-ui\"\u003e\u003c/div\u003e`，`\u003cdiv id=\"subapp-viewport-blog\"\u003e\u003c/div\u003e`盒子\n\n### 将实例化vue方法提取至render.js\n```js\nimport Vue from \"vue\"\nimport router from './router'\nimport store from './store'\nimport App from './App.vue'\n\n/**\n * @name 提取vue示例化方法\n */\nexport function vueRender() {\n  Vue.config.productionTip = false\n  new Vue({\n    router,\n    store,\n    render: h =\u003e h(App)\n  }).$mount(\"#main-container\");  \n}\n```\n为什么要仅仅将这段代码从`main.js`摘出呢？一方面是尽量清洁main.js；另一方面，就是为了兼容qiankun1.0的render方法。    \n因为qiankun1.0需要在注册vue实例时显式的将`appContent`传入app.vue，如果你不用qiankun1.0版本,则完全不需要以下代码：\n```js\n/**\n * @description 实例化vue，并提供子应用 render函数模式的装载能力\n * @description 如果使用qiankun2.0 版本，只需正常实例化vue即可 不需要存在此render函数\n * @param {Object} param0 \n * @description {String} appContent 子应用内容\n * @description {Boolean} loading 是否显示加载动画（需手动实现loading效果）\n * @param {Boolean} notCompatible true则不兼容qiankun1.0 【此参数为示例添加，实际应用自酌】\n */\nexport function vueRender({ appContent, loading }, notCompatible) {\n  Vue.config.productionTip = false\n\n  // 实际上本实例只用到此if内的代码\n  // 本文件其他代码只为做兼容qiankun1.0 render挂载子应用的参考\n  if (notCompatible) {\n    new Vue({\n      router,\n      store,\n      render: h =\u003e h(App)\n    }).$mount(\"#main-container\");\n    return;\n  }\n\n  return new Vue({\n    router,\n    store,\n    data() {\n      return {\n        appContent,\n        loading,\n      };\n    },\n    render(h) {\n      return h(App, {\n        props: {\n          appContent: this.content,\n          loading: this.loading\n        }\n      });\n    }\n  }).$mount('#main-container');\n}\n\nlet app = null;\n\n/**\n * @name 提供render装载子应用方法\n * @param {Object} param0 \n * @description {String} appContent 子应用内容\n * @description {Boolean} loading 是否显示加载动画（需手动实现loading效果）\n */\nexport default function render({ appContent, loading }) {\n  if (!app) {\n    app = vueRender({ appContent, loading });\n  } else {\n    app.appContent = appContent;\n    app.loading = loading;\n  }\n}\n```\n此处是给兼容qiankun1.0 registerMicroApps方法render字段一种方案，事实上升级到2.0完全无压力，因此建议不需要留下臃肿的render方法。\n\n### 将注册子应用的逻辑抽离到appRegister.js\n下面用了一个方法将qiankun需要用到的方法全部包装起来，以便后续将注册子应用放到获取后端注册表数据后执行。\n```js\n/**\n * @name 启用qiankun微前端应用\n * @param {*} list \n * @param {*} defaultApp \n */\nconst useQianKun = (list, defaultApp) =\u003e {\n  /**\n  * @name 注册子应用\n  * @param {Array} list subApps\n  */\n  registerMicroApps(\n    [\n       {\n        name: 'subapp-ui', // 子应用app name 推荐与子应用的package的name一致\n        entry: '//localhost:6751', // 子应用的入口地址，就是你子应用运行起来的地址\n        container: '#yourContainer', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】\n        activeRule: '/ui', // 子应用的路由前缀\n      },\n    ],\n    {\n      beforeLoad: [\n        app =\u003e {\n          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);\n        },\n      ],\n      beforeMount: [\n        app =\u003e {\n          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);\n        },\n      ],\n      afterUnmount: [\n        app =\u003e {\n          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);\n        },\n      ],\n    },\n  )\n\n  /**\n   * @name 设置默认进入的子应用\n   * @param {String} 需要进入的子应用路由前缀\n   */\n  setDefaultMountApp('ui');\n  /**\n   * @name 启动微前端\n   */\n  start();\n  /**\n   * @name 微前端启动进入第一个子应用后回调函数\n   */\n  runAfterFirstMounted(() =\u003e {\n    console.log('[MainApp] first app mounted');\n  });\n}\n```\n\n结合请求后端注册表，并给子应用分发路由及数据改造后的完整代码：\n\n```js\nimport { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from \"qiankun\";\nimport store from \"./store\";\n/**\n * @name 导入render函数兼容qiakun1.0装载子应用方法，如果使用2.0container装载则不需要此方法,此处留着注释代码提供兼容qiankun1.0的示例\n * @description 此处留下注释代码仅为提供兼容qiankun1.0示例\n */\n// import render from './render';\n/**\n * @name 导入接口获取子应用注册表\n */\nimport { getAppConfigsApi } from \"./api/app-configs\"\n/**\n * @name 导入消息组件\n */\nimport { wlMessage } from './plugins/element';\n/**\n * @name 导入想传递给子应用的方法，其他类型的数据皆可按此方式传递\n * @description emit建议主要为提供子应用调用主应用方法的途径\n */\nimport emits from \"./utils/emit\"\n/**\n * @name 导入qiankun应用间通信机制appStore\n */\nimport appStore from './utils/app-store'\n/**\n * @name 声明子应用挂载dom，如果不需要做keep-alive，则只需要一个dom即可；\n */\nconst appContainer = \"#subapp-viewport\";\n/**\n * @name 声明要传递给子应用的信息\n * @param data 主应要传递给子应用的数据类信息\n * @param emits 主应要传递给子应用的方法类信息\n * @param utils 主应要传递给子应用的工具类信息（只是一种方案）\n * @param components 主应要传递给子应用的组件类信息（只是一种方案）\n */\nlet props = {\n  data: store.getters,\n  emits\n}\n\n/**\n * @name 请求获取子应用注册表并注册启动微前端\n */\ngetAppConfigsApi().then(({ data }) =\u003e {\n  // 验证请求错误\n  if (data.code !== 200) {\n    wlMessage({\n      type: 'error',\n      message: \"请求错误\"\n    })\n    return;\n  }\n  // 验证数据有效性\n  let _res = data.data || [];\n  if (_res.length === 0) {\n    wlMessage({\n      type: 'error',\n      message: \"没有可以注册的子应用数据\"\n    })\n    return;\n  }\n  // 处理菜单并存入主应用Store\n  store.dispatch('menu/setMenu', _res);\n  // 处理子应用注册表数据。详细数据见 master mock\n  let apps = []; // 子应用数组盒子\n  let defaultApp = null; // 默认注册应用\n  let isDev = process.env.NODE_ENV === 'development'; // 根据开发环境|线上环境加载不同entry\n  _res.forEach(i =\u003e {\n    apps.push({\n      name: i.module, // 子应用名\n      entry: isDev ? i.devEntry : i.depEntry, // 根据环境注册生产环境or开发环境地址\n      container: appContainer,  // 绑定dom\n      activeRule: i.routerBase, // 绑定子应用路由前缀\n      props: { ...props, routes: i.children, routerBase: i.routerBase } // 将props及子应用路由，路由前缀由主应用下发\n    })\n    if (i.defaultRegister) defaultApp = i.routerBase; // 记录默认启动子应用\n  });\n  // 启用qiankun微前端应用\n  useQianKun(apps, defaultApp);\n})\n\n/**\n * @name 启用qiankun微前端应用\n * @param {*} list \n * @param {*} defaultApp \n */\nconst useQianKun = (list, defaultApp) =\u003e {\n  /**\n  * @name 注册子应用\n  * @param {Array} list subApps\n  */\n  registerMicroApps(\n    list,\n    {\n      beforeLoad: [\n        app =\u003e {\n          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);\n        },\n      ],\n      beforeMount: [\n        app =\u003e {\n          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);\n        },\n      ],\n      afterUnmount: [\n        app =\u003e {\n          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);\n        },\n      ],\n    },\n  )\n\n  /**\n   * @name 设置默认进入的子应用\n   * @param {String} 需要进入的子应用路由前缀\n   */\n  setDefaultMountApp(defaultApp);\n\n  /**\n   * @name 启动微前端\n   */\n  start();\n\n  /**\n   * @name 微前端启动进入第一个子应用后回调函数\n   */\n  runAfterFirstMounted(() =\u003e {\n    console.log('[MainApp] first app mounted');\n  });\n}\n\n/**\n * @name 启动qiankun应用间通信机制\n */\nappStore(initGlobalState);\n```\n### 注册应用间通信机制 utils文件夹\n\u003e 上面注册子应用时，我们看到代码里有传给子应用的`props`和一个`appStore`通信函数。\n1. 关于`props`，看过我上个文章的朋友都知道我将props分为那几个模块，实际上，我真正用到的可能就是主应用请求获取下来的`routes`和`routerbase`下发给子应用。\n2. 关于`appStore`方法，我是将官方通信机制提取至utils文件夹下的`app-store.js`文件，并和vuex相结合。代码如下：\n```js\nimport store from \"@/store\";\n\n/**\n * @name 启动qiankun应用间通信机制\n * @param {Function} initGlobalState 官方通信函数\n * @description 注意：主应用是从qiankun中导出的initGlobalState方法，\n * @description 注意：子应用是附加在props上的onGlobalStateChange, setGlobalState方法（只用主应用注册了通信才会有）\n */\nconst appStore = (initGlobalState) =\u003e {\n  /**\n   * @name 初始化数据内容\n   */\n  const { onGlobalStateChange, setGlobalState } = initGlobalState({\n    msg: '来自master初始化的消息',\n  });\n\n  /**\n   * @name 监听数据变动\n   * @param {Function} 监听到数据发生改变后的回调函数\n   * @des 将监听到的数据存入vuex\n   */\n  onGlobalStateChange((value, prev) =\u003e { \n    console.log('[onGlobalStateChange - master]:', value, prev);\n    store.dispatch('appstore/setMsg', value.msg)\n  });\n\n  /**\n   * @name 改变数据并向所有应用广播\n   */\n  setGlobalState({\n    ignore: 'master',\n    msg: '来自master动态设定的消息',\n  });\n}\n\nexport default appStore;\n```\n【注意：如未在主应用注册通信，则在子应用也获取不到通信方法】\n\n### 改造main.js\n终于我们来到了最后一步，主应用一切改造完成之后，我们将其引入到main.js并执行：\n```js\n/**\n * @name 统一注册外部插件、样式、服务等\n */\nimport './install'\n\n/**\n * @name 微前端基座主应用vue实例化\n * @description 为了兼容 qiankun1.0 的render函数装载子应用能力\n * @description 2.0版本正常实例化vue即可，不需要此render函数\n * @description qiankun registerMicroApps方法 render用到，如果使用container装载子应用，无需此render函数\n * @deprecated 本示例只针对 qiankun2.0 因此只留下注释后的代码在此提醒各位读者如何兼容qiankun1.0\n */\n/* import render from './render';\nrender({ loading: true }) */\nimport { vueRender } from './render'\nvueRender({}, true)\n\n/**\n * @name 注册微应用并启动微前端\n */\nimport './appRegister'\n```\n\n## 子应用构建\n\n子应用使用vue3.0beta尝鲜，大部分时间都用在找3.0的api上，还有许多未解决的问题，比如往vue实例上挂载方法，手动注销vue是啥api，怎么注册插件比如elementUI等，后续会慢慢补充。    \n这里使用vue3.0beta实现demo效果已经没问题！\n\n### vuecli初始化项目并升级至vue3.0beta\n默认你已经装了vuecli3.0以上版本\n```js\nvue crate subapp-ui\n\ncd subapp-ui\n\n// 在此之前都是正常创建项目，到这里执行下面命令会以插件的形式将项目升级至3.0\nvue add vue-next \n```\n在这里不单独赘述vue3.0beta的特性，对此网上有许多文章。我们在实践我们微前端的需求实际应用中取逐渐解开它的神秘面纱！\n\n### 改造子应用vue.config.js文件\n注意设置publicPath、端口号与注册子应用时一致    \n注意开发时开启headers跨域头信息   \n注意output按照规定格式打包    \n```js\nconst { name } = require(\"./package\");\nconst port = 6751; // dev port\nconst dev = process.env.NODE_ENV === \"development\";\n\nmodule.exports = {\n  publicPath: dev ? `//localhost:${port}` : \"/\",\n  filenameHashing: true,\n  devServer: {\n    hot: true,\n    disableHostCheck: true,\n    port,\n    overlay: {\n      warnings: false,\n      errors: true\n    },\n    headers: {\n      \"Access-Control-Allow-Origin\": \"*\"\n    }\n  },\n  // 自定义webpack配置\n  configureWebpack: {\n    output: {\n      // 把子应用打包成 umd 库格式\n      library: `${name}-[name]`,\n      libraryTarget: \"umd\",\n      jsonpFunction: `webpackJsonp_${name}`\n    }\n  }\n};\n\n```\n### 在main.js同级添加 public-path.js\n```js\nif (window.__POWERED_BY_QIANKUN__) {\n  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;\n}\n```\n### 在main.js同级添加 life-cycle.js 统一设置子应用生命周期逻辑\n我在这里区分微前端环境和单独运行的加载机制，并引入官方通信方法    \n\u003e 注意：3.0beta的实例化方法为 createApp,并且注册路由是通过连续use的方法，详见下放代码：    \n\u003e 注意：3.0的router实例化方法为 createRouter, 注意history模式通过createWebHistory方法实现，并且此方法接受一个参数表示路由前缀   \n\u003e 注意：3.0的vuex倒是变化不大，但暂未弄明白3.0的mapGetters，mapActions的使用方法\n```js\nimport { createApp } from \"vue\";\nimport { createRouter, createWebHistory } from \"vue-router\";\nimport App from \"./App.vue\";\nimport store from \"./store\";\nimport selfRoutes from \"./router/routes\";\n\n/**\n * @name 导入自定义路由匹配方法\n */\nimport routeMatch from \"./router/routes-match\";\n/**\n * @name 导入官方通信方法\n */\nimport appStore from \"./utils/app-store\";\n\nconst __qiankun__ = window.__POWERED_BY_QIANKUN__;\nlet router = null;\nlet instance = null;\n\n/**\n * @name 导出生命周期函数\n */\nconst lifeCycle = () =\u003e {\n  return {\n    /**\n     * @name 微应用初始化\n     * @param {Object} props 主应用下发的props\n     * @description  bootstrap 只会在微应用初始化的时候调用一次，下次微应用重新进入时会直接调用 mount 钩子，不会再重复触发\n     * @description 通常我们可以在这里做一些全局变量的初始化，比如不会在 unmount 阶段被销毁的应用级别的缓存等\n     */\n    async bootstrap(props) {\n      console.log('props:', props)\n      /* props.emits.forEach(i =\u003e {\n        Vue.prototype[`$${i.name}`] = i;\n      }); */\n    },\n    /**\n     * @name 实例化微应用\n     * @param {Object} props 主应用下发的props\n     * @description 应用每次进入都会调用 mount 方法，通常我们在这里触发应用的渲染方法\n     */\n    async mount(props) {\n      // 注册应用间通信\n      appStore(props);\n      // 注册微应用实例化函数\n      render(props);\n    },\n    /**\n     * @name 微应用卸载/切出\n     */\n    async unmount() {\n      instance.$destroy?.();\n      instance = null;\n      router = null;\n    },\n    /**\n     * @name 手动加载微应用触发的生命周期\n     * @param {Object} props 主应用下发的props\n     * @description 可选生命周期钩子，仅使用 loadMicroApp 方式手动加载微应用时生效\n     */\n    async update(props) {\n      console.log(\"update props\", props);\n    }\n  };\n};\n\n/**\n * @name 子应用实例化函数\n * @param {Object} props param0 qiankun将用户添加信息和自带信息整合，通过props传给子应用\n * @description {Array} routes 主应用请求获取注册表后，从服务端拿到路由数据\n * @description {String} 子应用路由前缀 主应用请求获取注册表后，从服务端拿到路由数据\n */\nconst render = ({ routes, routerBase, container } = {}) =\u003e {\n  router = createRouter({\n    history: createWebHistory(__qiankun__ ? routerBase : \"/\"),\n    routes: __qiankun__ ? routeMatch(routes, routerBase) : selfRoutes\n  });\n  instance = createApp(App).use(router).use(store).mount(container ? container.querySelector(\"#app\") : \"#app\");\n};\n\nexport { lifeCycle, render };\n```\n\n### 在 utils/app-store.js 编写应用间的通信逻辑处理\n```js\nimport store from \"@/store\";\nimport { DataType } from \"wl-core\"\n\n/**\n * @name 声明一个常量准备将props内的部分内容储存起来\n */\nconst STORE = {};\n\n/**\n * @name 启动qiankun应用间通信机制\n * @param {Object} props 官方通信函数\n * @description 注意：主应用是从qiankun中导出的initGlobalState方法，\n * @description 注意：子应用是附加在props上的onGlobalStateChange, setGlobalState方法（只用主应用注册了通信才会有）\n */\nconst appStore = props =\u003e {\n  /**\n   * @name 监听应用间通信，并存入store\n   */\n  props?.onGlobalStateChange?.(\n    (value, prev) =\u003e {\n      console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev)\n      store.dispatch('appstore/setMsg', value.msg)\n    },\n    true\n  );\n  /**\n   * @name 改变并全局广播新消息\n   */\n  props?.setGlobalState?.({\n    ignore: props.name,\n    msg: `来自${props.name}动态设定的消息`,\n  });\n\n  /**\n   * @name 将你需要的数据存起来，供下面setState方法使用\n   */\n  STORE.setGlobalState = props?.setGlobalState;\n  STORE.name = props.name;\n};\n\n/**\n * @name 全局setState方法，修改的内容将通知所有微应用\n * @param {Object} data 按照你设定的内容格式数据 \n */\nconst setState = (data) =\u003e {\n  if (!DataType.isObject(data)) {\n    throw Error('data必须是对象格式');\n  }\n  STORE.setGlobalState?.({\n    ignore: STORE.name,\n    ...data\n  })\n}\n\nexport {\n  setState\n}\nexport default appStore;\n```\n这里分别导出了`setState`，`appStore`两个方法，`appStore`在上面`life-cycle.js`生命周期文件中注册全局通信使用，那么`setState`我们又要在哪里使用呢？我们继续往下看\n\n### 改造子应用的main.js\n将生命周期函数导出，并提供单独运行逻辑\n```js\nimport \"./public-path\";\nimport { lifeCycle, render } from \"./life-cycle\";\n\n/**\n * @name 导出微应用生命周期\n */\nconst { bootstrap, mount, unmount } = lifeCycle();\nexport { bootstrap, mount, unmount };\n\n/**\n * @name 单独环境直接实例化vue\n */\nconst __qiankun__ = window.__POWERED_BY_QIANKUN__;\n__qiankun__ || render();\n\n```\n### 在子应用的某个.vue文件中实践一下吧\n这里在`views/index.vue`做实战演练\n要求：\n1. 能使用 Vue 3.0 beta 基本特性体验\n2. 接收全局消息，并能向其他微应用发布消息\n直接上代码：\n```js\n\u003ctemplate\u003e\n  \u003cdiv class=\"home\"\u003e\n    \u003cdiv class=\"msg-box\"\u003e\n      \u003cdiv class=\"msg-title\"\u003e这里是子应用：\u003c/div\u003e\n      \u003cdiv class=\"msg-context\"\u003e{{selfMsg}}\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"msg-box\"\u003e\n      \u003cdiv class=\"msg-title\"\u003e来自其他微应用的消息：\u003c/div\u003e\n      \u003cdiv class=\"msg-context\"\u003e{{vuexMsg}}\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"msg-box\"\u003e\n      \u003cdiv class=\"msg-ipt-box\"\u003e\n        \u003cinput class=\"msg-ipt\" type=\"text\" v-model=\"formMsg\" placeholder=\"请输入你想广播的话\" /\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"msg-btn-box\"\u003e\n        \u003cbutton class=\"msg-btn\" @click=\"handleVuexMsgChange\"\u003e发送广播\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport { ref, computed, getCurrentInstance } from \"vue\";\nimport { setState } from \"@/utils/app-store\";\n\nexport default {\n  name: \"Home\",\n  setup() {\n    /**\n     * @name 通过getCurrentInstance方法得到当前上下文\n     */\n    const { ctx } = getCurrentInstance();\n    /**\n     * @name 定义一个初始数据\n     */\n    const selfMsg = ref(\"subapp-ui\");\n    /**\n     * @name 定义一个计算属性，返回vuex中的数据\n     */\n    const vuexMsg = computed(() =\u003e ctx.$store.getters.msg);\n    /**\n     * @name 定义一个表单元素v-model绑定的变量\n     */\n    const formMsg = ref(\"\");\n\n    /**\n     * @name 定义一个广播事件\n     */\n    const handleVuexMsgChange = () =\u003e {\n      /**\n       * @name 注意：在setup内部使用定义的变量，需要用**.value取值！\n       */\n      setState({\n        msg: formMsg.value\n      });\n    };\n\n    // 注意变量和事件都要return出来\n    return {\n      selfMsg,\n      vuexMsg,\n      formMsg,\n      handleVuexMsgChange\n    };\n  }\n};\n\u003c/script\u003e\n```\n## 发布上线\n\n### 使用脚本文件提高发布效率\n在根目录执行`npm run publish`会执行发布脚本，根据提示选择要发布到的服务器和要发布的应用，按指示选择后回车执行即可。\n注意为保持发布脚本的精简，默认你要发布的应用已经打包出了dist目录。\n\n### 常规多端口nginx配置\n根据`qiankun`的子应用注册规则，给每个子应用分配一个端口，nginx正常配置监听多个端口即可。\n详细配置见`_nginx`目录下`general-port.conf`\n\n### 双端口nginx配置（git切换到dual-port分支）\n有些项目应用场景及客户要求限制，无法根据子应用的数量无节制的开放端口，因此尝试将主应用独立一个端口，子应用共用一个端口的nginx配置。\n详细配置见`_nginx`目录下`dual-port.conf`\n\n\u003e 使用双端口nginx配置需要对前面教程里的配置做部分改动\n#### 修改主应用中registerMicroApps注册子应用的数据\n```js\n registerMicroApps(\n    [\n       {\n        name: 'subapp-ui', // 子应用app name 推荐与子应用的package的name一致\n        entry: 'http://192.168.1.100:2751/ui', // 子应用的入口地址\n        container: '#yourContainer', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】\n        activeRule: '/ui', // 子应用的路由前缀\n      },\n    ],\n )\n```\n\u003e注意: entry由端口地址变成了端口地址+此子应用的路径（//localhost:2751/ui）。且注意这个/ui路径后面要讲到\n\n#### 修改子应用\n1. 取消public-path.js，不再使用这个打包路径，下面这段代码删除\n```js\nif (window.__POWERED_BY_QIANKUN__) {\n  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;\n}\n```\n2. 修改vue.config.js文件publicPath字段,并和注册时的entry保持一致\n```js\nmodule.exports = {\n  publicPath: 'http://192.168.1.100:2751/ui'\n  ...\n}\n```\n3. 子应用的路由前缀应为‘/ui’，和前面两个保持一致\n```js\nconst render = ({ routerBase } = {}) =\u003e {\n  router = new VueRouter({\n    base: __qiankun__ ? routerBase : \"/\",\n    mode: \"history\",\n    routes: []\n  });\n```\n\n4. 配置nginx（见：_nginx/dual-port.conf）\n\n#### 此项目部署阿里云练手教程\n1. 将全部微应用和config、_server应用install\n2. 打包所有微应用\n3. 将config/deploy.js的服务器ip账号密码端口路径改为你的\n4. 在根目录执行npm run publish选择服务器和子应用，后点击回车部署到服务器\n5. 登陆服务器，将_nginx/wl-mfe.conf inclouds到你的nginx.conf重启nginx使其生效\n6. 将_server复制到服务器中并使用pm2运行\n7. 检查服务器防火墙和安全组有没有开放2750,2751,3000端口\n\u003e 至此即可通过nginx的配置实现一个端口下对所有子应用资源进行匹配转发。\n\n[双端口部署微前端线上预览](http://47.98.136.80:2750/)\n\n到这里已经完成了一个简单使用的 vue3.0 + qiankun2.0 微前端应用实践，快来上手试试吧！\n项目地址：[Github](https://github.com/wl-ui/wl-mfe);\n\n### 单端口nginx配置\n需求场景承接双端口配置，更近一步，有些极端发布环境只给开放一个端口，或者禁止开放跨域要求主应用和所有子应用做成同域！\n详细配置见`_nginx`目录下`single-port.conf`. （单端口思路大致如此，暂未进行测试）\n\n#### 修改主应用中registerMicroApps注册子应用的数据\n```js\n registerMicroApps(\n    [\n       {\n        name: 'subapp-ui', // 子应用app name 推荐与子应用的package的name一致\n        entry: 'http://192.168.1.100:2750/ui', // 子应用的入口地\n        container: '#yourContainer', // 挂载子应用内容的dom节点 `# + dom id`【见上面app.vue】\n        activeRule: '/ui', // 子应用的路由前缀\n      },\n    ],\n )\n```\n\u003e注意: entry由端口地址变成了主应用端口地址+此子应用的路径（//localhost:2750/ui）。注意和双端口差别\n\n#### 修改子应用\n基本和双端口一直，唯一的区别是publicPath变成了主应用端口+子应用路径\n```js\nmodule.exports = {\n  publicPath: 'http://192.168.1.100:2750/ui'\n  ...\n}\n```\n\u003e 至此即可通过nginx的配置实现主子应用同端口。（见：_nginx/single-port.conf）\n\n#### 单端口配置虽未测试但可预见的问题\n1. 可能造成qiankun检查不到子应用导出的生命周期\n2. 可能造成在子应用路由中刷新变成独立运行子应用\n3. 可能进入子应用却未进入主应用造成白屏\n\u003e 这些问题如果发生，可通过调整nginx配置等来实现单端口运行主+子应用。因为这是被理论和实践皆已证明的。\n\n## 注意事项\n1. 在主应用中使用window.history.pushState();跳转，在vue子应用中，使用router-link跳转会报错；使用router.push()会造成刷新；使用router.replace无异常\n\n## 友情链接\n\n### 微前端 \u0026 qiankun\n[可能是你见过最完善的微前端解决方案](https://juejin.im/post/5d560292e51d4561a60d9dd9)   \n[微前端的核心价值](https://juejin.im/post/5de7a80ef265da33d451e183)   \n[目标是最完善的微前端解决方案 - qiankun 2.0](https://juejin.im/post/5e97ddc76fb9a03c90379b8d)   \n[qiankun](https://github.com/umijs/qiankun)\n\n### vue3.0beta\n[Vue 3.0 全家桶抢先体验](https://juejin.im/post/5e99c21b6fb9a03c590dfea8#heading-11)\n\n### 打赏咖啡\n如果你有心，可以请作者喝杯咖啡，或者推荐一份好工作\n\n\u003cdiv\u003e\n  \u003cimg src=\"http://wlsy.oss-cn-hangzhou.aliyuncs.com/apply.jpg\" height=\"330\" width=\"220\" /\u003e\n  \u003cimg src=\"http://wlsy.oss-cn-hangzhou.aliyuncs.com/wx.jpg\" height=\"330\" width=\"220\" /\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwl-ui%2Fwl-mfe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwl-ui%2Fwl-mfe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwl-ui%2Fwl-mfe/lists"}