微前端qiankun改造日渐庞大的项目教程


Posted in Javascript onJune 21, 2022

项目背景

很多小伙伴在工作中都碰到过和我一样的场景,手上的某个项目越来越大,眼看着每次build时间越来越长,吐了?。在杭州某独角兽我碰到了这样的一个项目,他叫运营后台,听名字就知道,他的主要用户是运营人员。问题就是随着公司业务的越来越多,这个运营后台承担的已经不是某一块业务了,而是所有业务的运营操作的中后台都在这上面。你可以这样理解,这个系统的每个一级菜单都是一块独立的业务,相互之间没有任何瓜葛;按常规的理解,这应该是单独的每一个project比较合理,但是正因为他的用户又都是公司的同一群人,他们早已经习惯就在运营后台上去找自己的菜单进行业务操作,拒不接受在他的收藏夹里多出来好几个项目地址。那我们有没有一个办法让我们的项目更好维护,又能让用户不改变他们使用同一个项目的期望呢。这就是写这篇文章的初衷,就是微前端!!??

微前端的好处

除了可以解决上面的问题以外,你想想,只要我们把项目改造成了微前端,那每个业务都是独立的project,只不过最终用户都是在一个主项目里去使用,那我们每个project的技术栈也不用定死了,就不存在老项目的技术栈是vue,以至于后面的项目都必须要用vue了,你也可以用react,这就很香不是吗。微前端的原理就是项目被拆成了父子关系,通过基座去引用了子应用,子应用之间是互相隔离的??

qiankun

可能是你见过最完善的微前端解决方案?——官方是这么介绍的,基于single-spa,这里我就不详细介绍了,感兴趣的去看看文档,地址丢给你

改造过程

首先我先用vue2-admin-cli——我自己做的脚手架工具,创建两个vue-admin项目来演示,一个作为qiankun基座,另外一个就是我要引用的子应用。

全局安装脚手架
npm install -g vue2-admin-cli 
or
yarn global add vue2-admin-cli
创建项目
vue2-admin-cli init <project_name>
安装依赖
yarn
启动项目
yarn serve

运行起来就是这样的

微前端qiankun改造日渐庞大的项目教程

现在我们开始分别改造基座qiankun-base和子应用qiankun-vue,我想达到的效果是主应用qiankun-base只保留header sider footer的一个基本layout的布局,content部分全部加载子应用

qiankun-base

 yarn add qiankun # 或者 npm i qiankun -S

修改package.json启动命令修改启动端口

"serve": "vue-cli-service serve --port 80 --open"

src/router/index.ts修改路由模式为history 

const createRouter = () =>
  new VueRouter({
    mode: "history",
    routes: routes as any,
  });

修改vue.config.js   我这里之前用的路由模式是hash  上线配置了publicPath 导致改为history以后静态资源加载路径有问题所以修改

module.exports = {
  // publicPath: "./",  
  devServer: {
    disableHostCheck: true, // 关闭host检查
  },
};

在入口文件src/main.ts下注册微应用并启动:

import { registerMicroApps, start } from "qiankun";
registerMicroApps([
  {
    name: "qiankunVue",
    entry: "//localhost:8080", //子应用的启动端口修改为8080,基座使用80,不要相同
    container: "#qiankunVue",  //加载子应用的容器
    activeRule: "/qiankunVue", //路由匹配规则
  },
]);
// 启动 qiankun
start();

 在你要放置子应用的位置增加一个容器用于加载子应用 

src/components/layout/index.vue

<template>
  <el-container direction="vertical" style="height: 100%">
    <Header />
    <el-container style="overflow: auto">
      <el-aside width="250px">
        <Menu />
      </el-aside>
      <el-main>
        <el-breadcrumb
          separator-class="el-icon-arrow-right"
          v-if="showBreadcrumb"
        >
          <template v-for="(route, index) in matchedRoutes">
            <el-breadcrumb-item
              v-if="
                (route.meta && route.meta.breadcrumbTo === false) ||
                index === matchedRoutes.length - 1
              "
              :key="route.path"
            >
              {{ route.meta.title }}
            </el-breadcrumb-item>
            <el-breadcrumb-item
              v-else
              :key="route.path"
              :to="{ path: route.path }"
            >
              {{ route.meta.title }}
            </el-breadcrumb-item>
          </template>
        </el-breadcrumb>
        <!-- 本身的路由加载 -->
        <router-view style="margin-top: 20px" />
        <!-- 子应用加载容器 -->
        <div id="qiankunVue" style="width: 100%; height: 100%" />
      </el-main>
    </el-container>
  </el-container>
</template>

 将主应用之前的路由配置进行修改,不渲染自己的内容了,因为要改成去加载子应用的内容才是我们想要的,我的左侧菜单栏sider也是用路由配置这份文件生成的,所以我只需要注释这些路由要渲染的components就行,让他只充当一个生成sider菜单栏的作用,但是注意要保留容器所在的layout,因为我的子应用加载容器在这里面,加载子应用之前你必须保证容器被加载了

src/router/config.ts

const routes: Array<IBaseRouter> = [
  {
    path: "/",
    redirect: "/home",
    hidden: true,
  },
  {
    path: "/login",
    name: "login",
    hidden: true,
    component: () => import("../views/Login.vue"),
  },
  //保证子应用加载时容器页面必须加载
   {
    path: "/qiankunVue/*",
    name: "qiankunVue",
    hidden: true,
    component: Layout,
  },
  {
    path: "/qiankunVue/home",
    name: "home",
    component: Layout,
    redirect: "/qiankunVue/home/index",
    meta: {
      title: "首页",
      icon: "el-icon-s-home",
    },
    children: [
      {
        path: "index",
        name: "index",
        hidden: true,
        // component: () => import("../views/Home.vue"),
        meta: {
          title: "首页",
          breadcrumb: false,
        },
      },
      {
        path: "bar/:width/:height",
        name: "bar",
        props: true,
        hidden: true,
        // component: () => import("@/components/echarts/Bar.vue"),
        meta: {
          title: "柱状图",
          activeMenu: "/home/index",
        },
      },
      {
        path: "pie/:width/:height",
        name: "pie",
        props: true,
        hidden: true,
        // component: () => import("@/components/echarts/Pie.vue"),
        meta: {
          title: "饼图",
          activeMenu: "/home/index",
        },
      },
      {
        path: "line/:width/:height",
        name: "line",
        props: true,
        hidden: true,
        // component: () => import("@/components/echarts/Line.vue"),
        meta: {
          title: "折线图",
          activeMenu: "/home/index",
        },
      },
    ],
  },
  .....
  {
    path: "*",
    redirect: "/error/404",
    hidden: true,
  },
];
export default routes;

改完之后刷新看一看,这样基座项目就改造好了,保留了基本页面的框架,中间的内容到时候都由子应用来填充就行了

微前端qiankun改造日渐庞大的项目教程

qiankun-vue

修改package.json启动命令修改启动端口

"serve": "vue-cli-service serve --port 8080 --open"

入口文件 src/main.ts 修改

let vm: any = null;
function render(props: any = {}) {
  const { container } = props;
  vm = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector("#app") : "#app");
}
// 在被qiankun引用时 修改运行时的 `publicPath`
if ((window as any).__POWERED_BY_QIANKUN__) { 
  __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// 独立运行时
if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}
导出三个生命周期函数
export async function bootstrap() {
  console.log("[vue] vue app bootstraped");
}
export async function mount(props: any) {
  console.log("[vue] props from main framework", props);
  render(props);
}
export async function unmount() {
  vm.$destroy();
  vm.$el.innerHTML = "";
  vm = null;
}
export default vm;

src/router/index.ts修改路由模式并增加base(和主应用设置的activeRule一致)

const createRouter = () =>
  new VueRouter({
    mode: "history",
    base: "/qiankunVue",
    routes: routes as any
  });

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

module.exports = {
  devServer: {
    disableHostCheck: true, // 关闭host检查
    headers: {
      "Access-Control-Allow-Origin": "*", // 防止加载时跨域
    },
  },
  configureWebpack: {
    output: {
      library: "qiankunVue",
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
    },
  },
};

基座和子应用都修改完以后刷新看看,控制台报错了

微前端qiankun改造日渐庞大的项目教程

立马查看官方文档,发现是因为我的子应用加载容器在基座的某个路由页面即我的layout里面,文档里指出必须保证微应用加载时主应用这个路由页面也加载了,就很喜欢这种文档??,于是立马改一改

注释之前qiankun-base注册子应用时的启动qiankun命令,改到路由页面layout里面启动
src/main.ts

// 启动 qiankun
//start();
src/components/layout/index.vue
import { start } from "qiankun";
 mounted() {
    if (!(window as any).qiankunStarted) {
      (window as any).qiankunStarted = true;
      start();
    }
  }

重新刷新看看,成功了??,多少有点舒服了

微前端qiankun改造日渐庞大的项目教程

接下来要做的就是把子应用再改造一下,在qiankun中就只需要展示子应用content的内容,单独运行的时候为了方便调试我们就保留layout布局。看了这么久的官方文档,我当然知道用它就可以做出判断__POWERED_BY_QIANKUN__,思路很清晰,冲他??

qiankun-vue

src/components/layout/index.vue

<template>
  <el-container direction="vertical" v-if="isQiankun">
    <el-main>
      <el-breadcrumb
        separator-class="el-icon-arrow-right"
        v-if="showBreadcrumb"
      >
        <template v-for="(route, index) in matchedRoutes">
          <el-breadcrumb-item
            v-if="
              (route.meta && route.meta.breadcrumbTo === false) ||
              index === matchedRoutes.length - 1
            "
            :key="route.path"
          >
            {{ route.meta.title }}
            <!-- {{ route.path }} -->
          </el-breadcrumb-item>
          <el-breadcrumb-item
            v-else
            :key="route.path"
            :to="{ path: route.path }"
          >
            {{ route.meta.title }}
            <!-- {{ route.path }} -->
          </el-breadcrumb-item>
        </template>
      </el-breadcrumb>
      <router-view style="margin-top: 20px" />
    </el-main>
  </el-container>
  <el-container direction="vertical" style="height: 100%" v-else>
    <Header />
    <el-container style="overflow: auto">
      <el-aside width="250px">
        <Menu />
      </el-aside>
      <el-main>
        <el-breadcrumb
          separator-class="el-icon-arrow-right"
          v-if="showBreadcrumb"
        >
          <template v-for="(route, index) in matchedRoutes">
            <el-breadcrumb-item
              v-if="
                (route.meta && route.meta.breadcrumbTo === false) ||
                index === matchedRoutes.length - 1
              "
              :key="route.path"
            >
              {{ route.meta.title }}
              <!-- {{ route.path }} -->
            </el-breadcrumb-item>
            <el-breadcrumb-item
              v-else
              :key="route.path"
              :to="{ path: route.path }"
            >
              {{ route.meta.title }}
              <!-- {{ route.path }} -->
            </el-breadcrumb-item>
          </template>
        </el-breadcrumb>
        <router-view style="margin-top: 20px" />
      </el-main>
    </el-container>
  </el-container>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import Header from "./Header.vue";
import Menu from "./Menu.vue";
// import { IBaseRouter } from "@/router/config";
@Component({
  name: "Layout",
  components: { Header, Menu },
})
export default class Layout extends Vue {
  private get showBreadcrumb() {
    return this.$route?.meta?.breadcrumbAll !== false;
  }
  private get matchedRoutes() {
    return this.$route.matched?.filter(
      (v) => v.meta?.title && v?.meta?.breadcrumb !== false
    );
  }
  private get isQiankun() {
    return (window as any).__POWERED_BY_QIANKUN__;
  }
}
</script>
<style lang="less" scoped></style>

至此完结撒花,改造结束❤️❤️,看看效果 基座正常展示子应用

微前端qiankun改造日渐庞大的项目教程

子应用单独运行也正常展示,并且丝毫不影响开发体验

微前端qiankun改造日渐庞大的项目教程

项目地址

qiankun-base qiankun基座

qiankun-vue qiankun子应用

vue-admin ## vue 中后台系统解决方案

vue2-admin-cli vue2-admin-cli是vue-admin的cli脚手架工具,支持快速搭建企业级中后台项目模板

结尾

关于父子通信这个示例就不做概述,有兴趣的可以自己看看文档

以上就是微前端qiankun改造日渐庞大的项目教程的详细内容,更多关于微前端qiankun改造庞大项目的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js 数值项目的格式化函数代码
May 14 Javascript
jqPlot 图表中文API使用文档及源码和在线示例
Feb 07 Javascript
JavaScript实现向select下拉框中添加和删除元素的方法
Mar 07 Javascript
详解vue与后端数据交互(ajax):vue-resource
Mar 16 Javascript
javascript中this用法实例详解
Apr 06 Javascript
AngularJS自定义指令实现面包屑功能完整实例
May 17 Javascript
webpack学习教程之前端性能优化总结
Dec 05 Javascript
编写React组件项目实践分析
Mar 04 Javascript
Puppeteer 爬取动态生成的网页实战
Nov 14 Javascript
ES6常用小技巧总结【去重、交换、合并、反转、迭代、计算等】
Dec 21 Javascript
es6数组includes()用法实例分析
Apr 18 Javascript
使用Vant完成通知栏Notify的提示操作
Nov 11 Javascript
JavaScript架构localStorage特殊场景下二次封装操作
Jun 21 #Javascript
js前端图片加载异常兜底方案
Jun 21 #Javascript
JavaScript中10个Reduce常用场景技巧
Jun 21 #Javascript
js前端面试常见浏览器缓存强缓存及协商缓存实例
Jun 21 #Javascript
JavaScript前端面试组合函数
Jun 21 #Javascript
Vue2项目中对百度地图的封装使用详解
JavaScript原型链中函数和对象的理解
You might like
PHP和JAVA中的重载(overload)和覆盖(override) 介绍
2012/03/01 PHP
PHP 魔术变量和魔术函数详解
2015/02/25 PHP
php计算给定时间之前的函数用法实例
2015/04/03 PHP
Laravel5.7框架安装与使用学习笔记图文详解
2019/04/02 PHP
如何解决Jquery库及其他库之间的$命名冲突
2013/09/15 Javascript
基于js与flash实现的网站flv视频播放插件代码
2014/10/14 Javascript
JS实现往下不断流动网页背景的方法
2015/02/27 Javascript
JavaScript实现向OL列表内动态添加LI元素的方法
2015/03/21 Javascript
JQuery页面地址处理插件jqURL详解
2015/05/03 Javascript
jQuery prototype冲突的2种解决方法(附demo示例下载)
2016/01/21 Javascript
BootStrap 实现各种样式的进度条效果
2016/12/07 Javascript
jQuery焦点图左右转换效果
2016/12/12 Javascript
canvas实现刮刮卡效果
2017/03/14 Javascript
ReactNative踩坑之配置调试端口的解决方法
2017/07/28 Javascript
JavaScript cookie原理及使用实例
2020/05/08 Javascript
基于Vue sessionStorage实现保留搜索框搜索内容
2020/06/01 Javascript
解决Vue的项目使用Element ui 走马灯无法实现的问题
2020/08/03 Javascript
在elementui中Notification组件添加点击事件实例
2020/11/11 Javascript
uni-app 自定义底部导航栏的实现
2020/12/11 Javascript
Python实现把回车符\r\n转换成\n
2015/04/23 Python
Python脚本简单实现打开默认浏览器登录人人和打开QQ的方法
2016/04/12 Python
Python基于回溯法子集树模板解决选排问题示例
2017/09/07 Python
rabbitmq(中间消息代理)在python中的使用详解
2017/12/14 Python
对python tkinter窗口弹出置顶的方法详解
2019/06/14 Python
python实现对服务器脚本敏感信息的加密解密功能
2019/08/13 Python
原生python实现knn分类算法
2019/10/24 Python
深入了解如何基于Python读写Kafka
2019/12/31 Python
Python如何实现的二分查找算法
2020/05/27 Python
解决python中0x80072ee2错误的方法
2020/07/19 Python
Pyinstaller打包Scrapy项目的实现步骤
2020/09/22 Python
python中复数的共轭复数知识点总结
2020/12/06 Python
简单介绍HTML5中的文件导入
2015/05/08 HTML / CSS
健身场所或家用健身设备:Life Fitness
2017/11/01 全球购物
设计毕业生简历中的自我评价
2013/10/01 职场文书
安全生产目标责任书
2014/04/14 职场文书
家庭经济困难证明
2015/06/23 职场文书