templatediv class='zibo-custom-web' div id='zibo-custom-web' class='app-view-box'/div/div/templatescriptexport default {};/scriptstyle lang='scss'scoped.zibo- custom-web{position:relative;}/style的id属性必须是唯一的,最终的子应用内容将挂载在这里。
ContainerOther.vue代码更改:
!-- 主视图层-- div class='avue-view-contain' v-show='!isSearch' keep-alive router-view class='avue-view keep-alive' v-if='$route. meta.keepAlive isActiveRoute' v-show='!showAppVueHash' //keep-alive router-view class='avue-view' v-if='!$route.meta.keepAlive isActiveRoute' v-show='!showAppVueHash' /AppVueHash v-show='showAppVueHash' //divjs 代码:
import router from '@/router/router';import store from '@/store';import AppVueHash from '@/views/AppVueHash.vue';import {loadMicroApp} from 'qiankun';//子项目路由前缀const isChildRoute=path=website.childRoute.some(item=path.startsWith(item));const apps=[ { name: '/zibo-custom-web', entry: window.configs.VUE_APP_ZIBO_CUSTOM_URL, container: '#zibo-custom-web' , props: { data: { store, router } }, Sandbox: { strictStyleIsolation: true //开启样式隔离} }];//控制手动加载微应用ctrlMicroApp(path){ if (isChildRoute(path)) { this .showAppVueHash=true ; this.$nextTick(()={ //手动加载if(!this.mounted){ this.loadApps=apps.map(item=loadMicroApp(item)) this.mounted=true; } }) ; this .showAppVueHash=false; }这里的容器属性值必须与AppVueHash.vue组件的id值匹配。
根据URL地址判断是否为子应用。如果是子应用程序,则隐藏子应用程序容器并仅加载主应用程序的路由器视图。
首次加载ContainerOther 或路由更改时手动加载微应用。
mount() { this.init(); setTheme(this.themeName); this.ctrlMicroApp(this.$route.path) }, watch: { $route(val) { this.ctrlMicroApp(val.path);}, tagList (newVal,oldVal){ 让starting=''; const childRoute=website.childRoute.forEach((n,i)={ if(ichildRoute.length-1){starts+=`^${n}|`; }else{starts+=`^${n}`; } }) const patt=new RegExp(`${starts}`); //子应用程序选项卡之前已存在const=oldVal.some(item={ return patt) .test (item.value) }); //已添加子应用程序选项卡const=newVal.some(item={ return patt.test(item.value); }); { this .loadApps.forEach;={ app.unmount(); }) } 如果要监控选项卡更改,则需要卸载子应用程序。
3. qiankun子项目zibo-custom-web
将文件public-path.js 添加到src 目录。 if (window.__POWERED_BY_QIANKUN__) { //eslint-disable-next-line no-undef __webpack_public_path__=window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } 更改Index.html 的项目初始化容器。避免使用#app以避免与其他项目发生冲突。要修改入口文件main.js,我们建议编写一个小的驼峰式div id='appVueHash'/div。 //-------- ---启动子应用的微前端-----let router=null;let instance=null; function render({ data={} ,container }={ }) { router=new VueRouter({route, }); instance=new Vue({ router, store, data(){ return {parentRouter: data.router,parentVuex: data .store, } }, render: h( App), } ).$mount(container?container.querySelector('#appVueHash') : '#appVueHash');}if (!window.__POWERED_BY_QIANKUN__) { render();} //测试全局变量污染console.log('window .a',window.a)导出异步函数bootstrap() { console.log('vue app bootstraped');}导出异步函数mount(props) { console.log('来自主框架的props', props .data) ; render(props);} 异步函数导出{instance.$destroy();}//----- - -----子应用微前端-
------------------ 主要改动是:引入修改 publicPath 的文件和export 三个生命周期。 注意: webpack 的 publicPath 值只能在入口文件修改,之所以单独写到一个文件并在入口文件最开始引入,是因为这样做可以让下面所有的代码都能使用这个。路由文件需要 export 路由数据,而不是实例化的路由对象,路由的钩子函数也需要移到入口文件。在 mount 生命周期,可以拿到父项目传递过来的数据,router 用于跳转到主项目/其他子项目的路由,store 是父项目的实例化的 Vuex(也可以传递其他数据过来)。修改打包配置 vue.config.js:const { name } = require("./package");module.exports = { outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web", devServer: { port: 9010, // 关闭主机检查,使微应用可以被 fetch disableHostCheck: true, // 配置跨域请求头,解决开发环境的跨域问题 headers: { "Access-Control-Allow-Origin": "*", }, proxy: { "/api": { //本地服务接口地址 target: "http://192.168.10.112:10067", //cas ws: true, pathRewrite: { "^/api": "/", }, }, }, }, // 以下配置可以修复一些字体文件加载路径问题 chainWebpack: (config) => { //忽略的打包文件 config.externals({ vue: "Vue", "vue-router": "VueRouter", vuex: "Vuex", axios: "axios", "element-ui": "ELEMENT", }); config .plugin('html') .tap(args => { args[0].name = name; return args }); config.module .rule("fonts") .test(/.(ttf|otf|eot|woff|woff2)$/) .use("url-loader") .loader("url-loader") .tap((options) => ({ name: "/fonts/[name].[hash:8].[ext]" })) .end(); }, // 自定义webpack配置 configureWebpack: { output: { library: `${name}-[name]`, // 微应用的包名,这里与主应用中注册的微应用名称一致 libraryTarget: "umd", // 把子应用打包成 umd 库格式 jsonpFunction: `webpackJsonp_${name}`, //webpack打包之后保存在window中的key,各个子应用不一致 }, },}; 这里主要就两个配置,一个是允许跨域,另一个是打包成 umd 格式。为什么要打包成 umd 格式呢?是为了让 qiankun 拿到其 export 的生命周期函数。 注意: 这个 name 默认从 package.json 获取,可以自定义,只要和父项目注册时的 name 保持一致即可。 vue.config.js中 outputDir: "../sass-base-web/cicd-config/test/zibo-custom-web", 这里我子应用项目编译后会将打包文件打包到sass-base-web项目中的zibo-custom-web下。 路由动态加载 需要给子项目所有的路由都添加一个前缀,子项目的路由跳转如果之前使用的是 path 也需要修改,用name 跳转则不用。 avue-router.js: const oRouter = { path: "/zibo-custom-web", name: "RouterView", component(resolve) { require(["@/components/RouterView.vue"], resolve); }, children: [ { path: path, component(resolve) { require([`../${component}.vue`], resolve); }, name: name, meta: meta, }, ], }; aRouter.push(oRouter);4. 状态管理,主应用和微应用之间的通信 qiankun 通过 initGlobalState: 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过props 获取通信方法; onGlobalStateChange: 在当前应用监听全局状态,有变更触发 callback; setGlobalState: 按一级属性设置全局状态,微应用中只能修改已存在的一级属性; 换句话说只能修改主用于预先定义的属性,后面添加的属性无效。 官方例子:发布-订阅的设计模式: 主应用: import { initGlobalState, MicroAppStateActions } from 'qiankun';// 初始化 stateconst actions: MicroAppStateActions = initGlobalState(state);actions.onGlobalStateChange((state, prev) => { // state: 变更后的状态; prev 变更前的状态 console.log(state, prev);});actions.setGlobalState(state);actions.offGlobalStateChange(); 子应用: // 从生命周期 mount 中获取通信方法,使用方式和 master 一致export function mount(props) { props.onGlobalStateChange((state, prev) => { // state: 变更后的状态; prev 变更前的状态 console.log(state, prev); }); props.setGlobalState(state); } 如果主应用和子应用都是vue技术栈,可以在子应用中把数据传递给子应用,例如store。 props: { data: { store, router } }, 5. 各应用之间的独立仓库以及聚合管理 实际开发中项目存储在公司仓库中,以 gitLab 为例, 当子应用一多,全部放在一个仓库下面, 这时候就显得很臃肿了,也很庞大,大大的增加了维护成本,和开发效率; 我们可以通过 sh 脚本, 初始只需要克隆主仓库代码, 然后通过 sh 脚本去一键拉取所有子应用代码。 这里我将单独创建一个用来编译和打包的主仓库项目sass-base-web,仓库地址:http://192.168.1.102/zouqiongjun/sass-base-web.git。 项目根目录下,新建 script/clone-all.sh 文件,内容如下: # 子服务 gitLab 地址SUB_SERVICE_GIT=('http://192.168.1.102/zouqiongjun/big-data-web.git' 'http://192.168.1.102/zouqiongjun/zibo-custom-web.git')SUB_SERVICE_NAME=('big-data-web' 'zibo-custom-web')# 子服务if [ ! -d "sub-service" ]; then echo '创建sub-service目录...' mkdir sub-servicefiecho '进入sub-service目录...'cd sub-service# 遍历克隆微服务for i in ${!SUB_SERVICE_NAME[@]}do if [ ! -d ${SUB_SERVICE_NAME[$i]} ]; then echo '克隆微服务项目'${SUB_SERVICE_NAME[$i]} git clone ${SUB_SERVICE_GIT[$i]} fidone echo '脚本结束...'# 克隆完成 当我们启动主项目的时候,如果想要使用所有子应用的功能,子应用,需要一个一个启动,这样无论是开发还是编译都十分不便。 考虑到国内使用npm装包可能会由于网络的原因安装失败,可以让npm使用国内淘宝镜像。 执行命令:npm config set registry https://registry.npm.taobao.org。 在这个主仓库项目里面,我们只需要安装一个npm-run-all插件。 运行:yarn add npm-run-all -D或者npm i -D。 该项目下只有一个package.json文件,用于配置编译和打包命令,代码如下: { "name": "sass-big-data-web", "version": "1.0.0", "description": "`qiankun`来实现`vue`技术栈的前端微服务", "main": "index.js", "scripts": { "clone:all": "bash ./scripts/clone-all.sh", "install:zibo": "cd ./sub-service/zibo-custom-web && npm install", "install:main": "cd ./sub-service/big-data-web && npm install", "install-all": "npm-run-all install:*", "start:zibo": "cd ./sub-service/zibo-custom-web && npm run serve ",", "start:main": "cd ./sub-service/big-data-web && npm run serve", "start-all": "npm-run-all --parallel start:*", "serve-all": "npm-run-all --parallel start:*", "build:zibo": "cd ./sub-service/zibo-custom-web && npm run build", "build:main": "cd ./sub-service/big-data-web && npm run build", "build-all": "npm-run-all --parallel build:*" }, "repository": { "type": "git", "url": "http://192.168.1.102/zouqiongjun/sass-big-data-web.git" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "npm-run-all": "^4.1.5" }} 要执行yarn clone:all,需要用到bash,跳转到sass-base-web目录,右键鼠标打开Git bash窗口,如下图所示:
