前端微服务无界实践 | 京东云技术团队

news2024/11/24 14:14:47

一、前言

随着项目的发展,前端SPA应用的规模不断加大、业务代码耦合、编译慢,导致日常的维护难度日益增加。同时前端技术的发展迅猛,导致功能扩展吃力,重构成本高,稳定性低。因此前端微服务应运而生。

前端微服务优势

1.复杂度可控: 业务模块解耦,避免代码过大,保持较低的复杂度,便于维护与开发效率。

2.独立部署: 模块部署,减少模块影响范围,单个模块发生错误,不影响全局,提升项目稳定性。

3.技术选型灵活: 在同一项目下可以使用市面上所有前端技术栈,也包括未来的前端技术栈。

4.扩展性,提升业务动态扩展的可能,避免资源浪费

微前端服务结构

技术对比和选型:

选型静态资源预加载子应用保活iframejs沙箱css沙箱接入成本地址
EMP×××https://github.com/efoxTeam/emp
Qiankun××中低https://qiankun.umijs.org/zh/
无界中低https://wujie-micro.github.io/doc/
micro-app××中低https://zeroing.jd.com/micro-app/

通过对比多种技术对项目的支持情况和项目接入的成本,我们最终选型无界。

二、wujie简单用法(以主应用使用vue框架为例)

主应用是vue框架可直接使用wujie-vue,react框架可直接使用wujie-react,先安装对应的插件哦

主应用改造:

// 引入无界,根据框架不同版本不同,引入不同的版本
import { setupApp, bus, preloadApp, startApp } from 'wujie-vue2'

// 设置子应用默认参数
setupApp({
    name: '子应用id(唯一值)',
    url: "子应用地址",
    exec: true,
    el: "容器",
    sync: true
})

// 预加载
preloadApp({ name: "唯一id"});

// 启动子应用
startApp({ name: "唯一id"});

子应用改造:

1、跨域

子应用如果支持跨域,则不用修改

原因:存在请求子应用资源跨域

方案:因前端应用基本是前后端分离,使用proxy代理。只需配置在子应用配置允许跨域即可

// 本地配置
server: {
    host: '127.0.0.1', // 本地启动如果主子应用没处在同一个ip下,也存在跨域的问题,需要配置
    headers: {
        'Access-Control-Allow-Credentials': true,
        'Access-Control-Allow-Origin': '*', // 如资源没有携带 cookie,需设置此属性
        'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
        'Access-Control-Allow-Methods': '*'
    }
}

// nginx 配置
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Headers 'X-Requested-With,Content-Type';
add_header Access-Control-Allow-Methods "*";

2、运行模式选择

无界有三种运行模式:单例模式、保活模式、重建模式

(1)、保活模式(长存页面)

释义:类似于vue的keep-alive性质(子应用实例和webcomponent不销毁,状态、路由都不丢失,只做热webcomponent的热插拔),子应用不想做生命周期改造,子应用切换又不想有白屏时间,可以采用保活模式。主应用上有多个入口跳转到子应用的不同页面,不能采用保活模式,因为无法改变子应用路由。

配置:只需要在主应用加载子应用的时候,配置参数添加alive:true

效果:预加载+保活模式=页面数据请求和渲染提前完成,实现瞬间打开效果

(2)、单例模式

释义:子应用页面切走,会调用window.__WUJIE_UNMOUNT销毁子应用当前实例。子应用页面如果切换回来,会调用window.__WUJIE_MOUNT渲染子应用新的子应用实例。过程相当于:销毁当前应用实例 => 同步新路由 => 创建新应用实例

配置:只需要在主应用加载子应用的时候,配置参数添加alive:false

改造生命周期

// window.__POWERED_BY_WUJIE__用来判断子应用是否在无界的环境中
if (window.__POWERED_BY_WUJIE__) {
  let instance;
  // 将子应用的实例和路由进线创建和挂载
  window.__WUJIE_MOUNT = () => {
    const router = new VueRouter({ routes });
    instance = new Vue({ router, render: (h) => h(App) }).$mount("#app");
  };
   // 实例销毁
  window.__WUJIE_UNMOUNT = () => {
    instance.$destroy();
  };
} else {
  // 子应用单独启动
  new Vue({ router: new VueRouter({ routes }), render: (h) => h(App) }).$mount("#app");
}
 

(3)、重建模式

释义:每次页面切换销毁子应用webcomponent+js的iframe。

配置:只需要在主应用加载子应用的时候,配置参数添加alive:false

无生命周期改造

备注:非webpack打包的老项目,子应用切换可能出现白屏,应尽可能使用保活模式降低白屏时间

三、加载模块(主应用配置)

子应用基础信息管理

// subList.js 数据可在配置页面动态配置
const subList = [
    {
        "name":"subVueApp1",
        "exec":true,// false只会预加载子应用的资源,true时预执行子应用代码
        "alive": true,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx1-pre.com",
            "gray":"http://xxx1-gray.com",
            "prod":"http://xxx1.com"
        }
    },
    {
        "name":"subVueApp2",
        "exec":false,// false只会预加载子应用的资源,true时预执行子应用代码
        "alive": false,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx2-pre.com",
            "gray":"http://xxx2-gray.com",
            "prod":"http://xxx2.com"
        }
    }
]
export default subList;
// hostMap.js
import subList from './subList'

 const env = process.env.mode || 'pre'

// 子应用map结构
const subMap = {}
const subArr = []

// 转换子应用
export const hostMap = () => {
    subList.forEach(v => {
        const {url, ...other} = v
        const info = {
            ...other,
            url: url[env]
        }
       subMap[v.name] = info
       subArr.push(info)
    })
   return subArr
}

// 获取子应用配置信息
export const getSubMap = name => {
    return subMap[name].show ? subMap[name] : {}
}

子应用注册预加载和启动

// setupApp.js
import WujieVue from 'wujie-vue2';
import {hostMap} from './hostMap';

const { setupApp, preloadApp } = WujieVue 

const setUpApp = Vue => {
    Vue.use(WujieVue)
    hostMap().forEach(v => {
        setupApp(v)
        preloadApp(v.name)
    })
}
export default setUpApp;


// main.js
import Vue from 'vue'
import setUpApp from'@/microConfig/setupApp'
setUpApp(Vue)

配置公共函数

全子应用共享的生命周期函数,可用于执行多个子应用间相同的逻辑操作函数共同处理

// lifecycle.js
const lifecycles = {
  beforeLoad: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeLoad 生命周期`),
  beforeMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeMount 生命周期`),
  afterMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterMount 生命周期`),
  beforeUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeUnmount 生命周期`),
  afterUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterUnmount 生命周期`),
  activated: (appWindow) => console.log(`${appWindow.__WUJIE.id} activated 生命周期`),
  deactivated: (appWindow) => console.log(`${appWindow.__WUJIE.id} deactivated 生命周期`),
  loadError: (url, e) => console.log(`${url} 加载失败`, e),
};

export default lifecycles;


// subCommon.js
// 跳转到主应用指定页面
const toJumpMasterApp = (location, query) => {
    
    this.$router.replace(location, query);
    const url = new URL(window.location.href);
    url.search = query
    // 手动的挂载url查询参数
    window.history.replaceState(null, '', url.href);
}
// 跳转到子应用的页面
const toJumpSubApp = (appName, query) => {
   this.$router.push({path: appName}, query)
}
export default {
    toJumpMasterApp,
    toJumpSubApp 
}


// setupApp.js
import lifecycles from './lifecycles';
import subCommon from './subCommon';
const setUpApp = Vue => {
    ....
    hostMap().forEach(v => {
        setupApp({
            ...v,
            ...lifecycles,
            props: subCommon
        })
        preloadApp(v.name)
    })
}

主应用加载子应用页面

// 子应用页面加载
// app1.vue
<template>
    <WujieVue
        :key="update"
        width="100%"
        height="100%"
        :name="name"
        :url="appUrl"
        :sync="subVueApp1Info.sync" 
        :alive="subVueApp1Info.alive" 
        :props="{ data: dataProps ,method:{propsMethod}}"
    ></WujieVue>
</template>

<script>
import wujieVue from "wujie-vue2";
import {getSubMap} from '../../hostMap';
const name = 'subVueApp1'
export default {
    data() {
       return {
          dataProps: [],
          subVueApp1Info: getSubMap(name)
       }
    },
    computed: {
      appUrl() {
        // return getSubMap('subVueApp1').url
        return this.subVueApp1Info.url + this.$route.params.path
      }
    },
     watch: {
        // 如果子应用是保活模式,可以采用通信的方式告知路由变化
        "$route.params.path": {
          handler: function () {
            wujieVue.bus.$emit("vue-router-change", `/${this.$route.params.path}`);
          },
          immediate: true,
        },
      },
    methods: {
        propsMethod() {}
    }
}
</script>

四、子应用配置

无界的插件体系主要是方便用户在运行时去修改子应用代码从而避免去改动仓库代码

// plugins.js
const plugins = {
  'subVueApp1': [{
    htmlLoader:code => {
      return code;
    },
    cssAfterLoaders: [
      // 在加载html所有样式之后添加一个外联样式
      { src:'https://xxx/xxx.css' },
      // 在加载html所有样式之后添加一个内联样式
      { content:'img{height: 300px}' }
    ],
    jsAfterLoaders: [
      { src:'http://xxx/xxx.js' },
      // 插入一个内联脚本本
      { content:`
          window.$wujie.bus.$on('routeChange', path => {
          console.log(path, window, self, global, location)
          })`
      },
      // 执行一个回调
      {
        callback(appWindow) {
          console.log(appWindow.__WUJIE.id);
        }
      }
    ]
  }],
  'subVueApp2': [{
    htmlLoader: code=> {
      return code;
    }
  }]
};
export default plugins;
// setupApp.js
import plugins from './plugins';
const setUpApp = Vue => {
    ......
    hostMap().forEach(v => {
        setupApp({
            ...v,
            plugins: plugins[element.name]
        })
        ......
    })
}

五、数据传输和消息通信

数据交互方式

1,通过props进行传

2、通过window进线传达

3,通过事件bus进行传达

props

主应用通过data传参给子应用, 子应用通过methods方法传参给主应用

// 主应用
<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }"></WujieVue>

// 子应用
const props = window.$wujie?.props; // {data: xxx, methods: xxx}

window

利用子应用运行在主应用的iframe

类似iframe的传参和调用

// 主应用获取子应用的全局变量数据
window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;

//子应用获取主应用的全局变量数据
window.parent.xxx;

eventBus

去中心化的通信方案,方便。类似于组件间的通信

主应用

// 使用 wujie-vue
import WujieVue from"wujie-vue";
const{ bus }= WujieVue;

// 主应用监听事件
bus.$on("事件名字",function(arg1,arg2, ...){});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2,...);
// 主应用取消事件监听
bus.$off("事件名字",function(arg1,arg2, ...){});

子应用

// 子应用监听事件
window.$wujie?.bus.$on("事件名字",function(arg1,arg2, ...){});
// 子应用发送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2,...);
// 子应用取消事件监听
window.$wujie?.bus.$off("事件名字",function(arg1,arg2, ...){});

规范主子应用传递规则

规则:子应用名+事件名

主应用向子应用传参

// 主应用传参
bus.$emit('matser', options) // 主应用向所有子应用传参
bus.$emit('vite:getOptions', options) // 主应用向指定子应用传参

//子应用监听主应用事件
window?.$wujie?.bus.$on("master", (options) => {
  console.log(options)
});
//子应用监听主应用特定通知子应用事件
window?.$wujie?.bus.$on("vite:getOptions", (options) => {
  console.log(options)
});

六、路由

以 vue 主应用为例,子应用 A 的 name 为 A, 主应用 A 页面的路径为/pathA,子应用 B 的 name 为 B,主应用 B 页面的路径为/pathB为例

主应用统一props传入跳转函数

jump (location) {
  this.$router.push(location);
}

1、主应用history路由

子应用 B 为非保活应用

1、子应用A 只能跳转到子应用 B 的主应用的默认路由

function handleJump(){
   window.$wujie?.props.jump({ path:"/pathB"});
}

2、子应用A 只能跳转到子应用B 应用的指定路由(非默认路由)

// 子应用A点击跳转处理函数, 子应用B需开启路由同步
function handleJump(){
    window.$wujie?.props.jump({ path:"/pathB", query:{ B:"/test"}});
}

子应用 B 为保活应用

子应用A 只能跳转到子应用 B 的主应用的路由

可写入主应用的插件中,主应用插件根据不同的应用,引入不同方法

// 子应用 A 点击跳转处理函数
function handleJump() {
  window.$wujie?.bus.$emit("routeChange", "/test");
}

// 子应用 B 监听并跳转
window.$wujie?.bus.$on("routeChange", (path) => this.$router.push({ path }));

2、主应用hash路由

子应用 B 为非保活应用

1、子应用A 只能跳转到子应用 B 的主应用的默认路由

同子应用B为非保活应用,子应用A跳转到子应用 B 的主应用的默认路由

2、子应用A 只能跳转到子应用B 应用的指定路由(非默认路由)

主应用
jump(location,query){ 
    // 跳转到主应用B页面
    this.$router.push(location); 
    const url=new URL(window.location.href);
    url.search=query
    // 手动的挂载url查询参数
    window.history.replaceState(null,"",url.href);
}

// 子应用 B 开启路由同步能力


// 子应用A
function handleJump() {
  window.$wujie?.props.jump({ path: "/pathB" } , `?B=${window.encodeURIComponent("/test")}`});
}

子应用 B 为保活应用

同子应用B为保活应用,子应用A跳转到子应用 B 路由

// bus.js
// 在 xxx-sub 路由下子应用将激活路由同步给主应用,主应用跳转对应路由高亮菜单栏
  bus.$on('sub-route-change', (name, path) => {
      const mainName = `${name}-sub`;
      const mainPath = `/${name}-sub${path}`;
      const currentName = router.currentRoute.name;
      const currentPath = router.currentRoute.path;
    if (mainName === currentName && mainPath !== currentPath) {
        router.push({ path: mainPath });
      }
  });

七、部署

前端单页面的部署,不管怎么自动化,工具怎么变. 都是把打包好的静态文件,放到服务器的正确位置下。所以支持项目的独立部署和混合部署。

作者:京东物流 张燕燕 刘海鼎

内容来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/566828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【源码篇】基于SSM+EasyUI开发的学生后台管理系统

系统介绍 一个基于SSM的学生管理系统:代码注释详细,逻辑结构清晰,对于初学 SSM 的同学非常具有参考,及学习价值哟! 数据库中默认的管理员身份信息 账户名:admin,密码:admin 用户权限介绍&#xff1a; 管理员:具有所有管理模块的操控权限。学生:仅具有学生信息管理模块的查…

智能AI抢了元宇宙的风头?

前几天&#xff0c;微博突然出现这么一条热搜。 #ChatGPT官方APP登录美国苹果应用商店 这绝对是一条相当火爆的新闻&#xff0c;因为这意味着智能聊天机器人ChatGPT终于有自己的App了。值得一提的是&#xff0c;ChatGPT不仅仅登录了美国苹果应用商店&#xff0c;而且下载量迅…

80个10倍提升Excel技能的ChatGPT提示

你是否厌倦了在使用Excel时感觉像个新手&#xff1f;你是否想将你的技能提升到更高的水平&#xff0c;成为真正的Excel大师&#xff1f;嗯&#xff0c;如果你正在使用ChatGPT&#xff0c;那么成为Excel专家简直易如反掌。 你只需要了解一些最有用的Excel提示&#xff0c;就能在…

打造高效互联网医院系统源码:解读其核心功能及应用

随着互联网的不断普及和发展&#xff0c;互联网医院系统已经成为了现代医疗服务的一个重要组成部分。本文将介绍互联网医院系统的核心功能以及其应用&#xff0c;并提供一些互联网医院系统开发代码。 互联网医院系统是一种基于互联网技术的医疗服务平台&#xff0c;可以通过网…

Springboot +spring security,实现session并发控制及实现原理分析

一.简介 在SpringSecurity中实现会话并发控制&#xff0c;只需要配置一个会话数量就可以了&#xff0c;先介绍下如何配置会话并发控制&#xff0c;然后再。介绍下SpringSecurity 如何实现会话并发控制。 二.创建项目 如何创建一个SpringSecurity项目&#xff0c;前面文章已经…

Hive ---- 函数

Hive ---- 函数 1. 函数简介2. 单行函数1. 算术运算函数2. 数值函数3. 字符串函数4. 日期函数5. 流程控制函数6. 集合函数7. 案例演示 3. 高级聚合函数案例演示 4. 炸裂函数1. 概述2. 案例演示 5. 窗口函数1. 概述2. 常用窗口函数3. 案例演示 6. 自定义函数7. 自定义UDF函数 1.…

Unity - 记一次非正规变体优化带来的兼容性导致部分手机卡死的问题

文章目录 问题但是我咨询过 公司中台TA大佬 - 2023.4.6然后咨询 unity 技术官方 - 2023.4.6再次遇到卡死 - 2023.5.24 解决方法具体华为真机上的 DEBUG 问题 在 2023.4.6 我们的 角色展示界面 就遇到了 华为手机&#xff0c;red mi note 11 的测试手机上的 后 2023.5.24 再次遇…

SSM框架学习之spring

Spring 以下是关于Spring Boot学习的一些文档和资源&#xff0c;希望对你有帮助&#xff1a; Spring Boot官方文档&#xff1a;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ Spring Boot中文文档&#xff1a;https://www.springcloud.cc/spring-bo…

Server - 高性能的 PyTorch 训练环境配置 (PyTorch3D 和 FairScale)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/130863537 PyTorch3D 是基于 PyTorch 的 3D 数据深度学习库&#xff0c;提供了高效、模块化和可微分的组件&#xff0c;以简化 3D 深度学…

龙讯旷腾作为首批单位入驻北京昇腾人工智能计算中心

2023中关村论坛系列活动—北京人工智能产业创新发展大会圆满落幕&#xff0c;围绕北京AI产业发展&#xff0c;政产学研用各界大咖汇聚京城&#xff0c;中国科协副主席束为、北京市副市长于英杰、中国工程院院士廖湘科出席大会。会上&#xff0c;北京市门头沟区政府联合中关村发…

Kubernetes基础操作

K8S基础操作 ✨✨✨✨✨✨✨✨✨这个基础操作一切都基于各位把k8s搭建好哦&#xff0c;搭建的时候请一定一定一定&#xff08;很重要&#xff09;&#xff0c;选定一个版本&#xff0c;能避免很多错&#xff0c;然后本章节就给大家介绍了k8s最基础的操作&#xff0c;有一些复杂…

基于GPTP时间同步(时钟同步服务器)技术助力智能驾驶应用

基于GPTP时间同步&#xff08;时钟同步服务器&#xff09;技术助力智能驾驶应用 基于GPTP时间同步&#xff08;时钟同步服务器&#xff09;技术助力智能驾驶应用 智能驾驶区域网关架构并未采用车载以太网总线进行连接&#xff0c;而是采用传统的 CAN 总线、FlexRay 或 MOST 总线…

解决若依出现Error: Cannot find module ‘@/views/xxx‘问题

问题描述&#xff1a; 若依 vue 版菜单点不开&#xff0c;报错&#xff1a;Error: Cannot find module ‘/views/xxx’ 。后台、vue前端启动都没问题。但是左侧菜单点不开&#xff0c;一直在加载中。 原因&#xff1a; 路由懒加载&#xff0c;webpack版本问题&#xff0c;we…

常见淘宝API文档接口使用攻略,一文搞定

探索淘宝数据的奥秘&#xff0c;淘宝是目前国内最大的B2C电商平台之一&#xff0c;每天都会产生海量的数据。借助淘宝API技术文档&#xff0c;我们可以轻松地获取到这些数据&#xff0c;从而为电商运营和数据分析提供有力支持。 1.什么是淘宝API&#xff1f; 淘宝API&#xf…

工作分配问题——算法设计与分析(C实现)

目录 一、问题描述 二、问题分析 三、代码展示 四、结果验证 一、问题描述 问题描述&#xff1a;设有n件工作分配给n个人。将工作i分配给第j个人所需要的费用为Cij。试设计一个算法&#xff0c;为每个人都分配1件不同的工作&#xff0c;并使总费用达到最小值 算法设计&a…

nodejs+vue+elementui大学生多媒体学习系统

前端技术&#xff1a;nodejsvueelementui 前端&#xff1a;HTML5,CSS3、JavaScript、VUE(1)课程学习(包括课程分类 课程目录 课程学习等相关操作&#xff09; (2)课程评价 (3)课程统计 (4)相关信息管理(包括基本信息 课程编辑 注册登录等相关操作) 1、 node_modules文件夹(有np…

day18 - 使用直方图提高图像对比度

本期将使用图像直方图的相关知识来提高图像对比度&#xff0c;对图像进行优化&#xff0c;从而提高图像清晰度。 完成本期内容&#xff0c;你可以&#xff1a; 了解图像直方图的定义和计算方法了解直方图均衡化的原理学会使用直方图均衡化优化图像 若要运行案例代码&#xf…

SpringBoot 结合 mybatis-plus 实现分页功能

一、分页的原理 要实现分页功能方法有很多&#xff0c;但最基本的实现原理都差不多&#xff0c;所以在实现功能之前要先把原理搞明白。正如删除有 “逻辑删除” 和 “物理删除" 之分&#xff0c;分页也有 “逻辑分页” 和 “物理分页”&#xff1b; 1、逻辑分页&…

WalMiner插件之xlog解析恢复使用教程

WalMiner插件主要有两个功能&#xff0c;在此记录一下第二个功能数据页挽回&#xff08;坏块修复&#xff09;&#xff0c;学习一下关于这块的使用方法&#xff0c;方便日后回顾。 1 环境搭建 创建WalMiner的extension create extension walminer;语句解析: 该句SQL功能是安…

Linux用户管理相关命令(全)

1、Linux用户(账号)管理 查询用户(账号)信息&#xff08;判断用户(账号)是否存在&#xff09; id account新增用户(账号) useradd account设置用户(账号)密码 方式1&#xff1a; passwd account 方式2&#xff1a; echo 123|passwd --stdin account; #密码为123删除用户(账…