前端微服务无界实践 | 京东云技术团队
一、前言
随着项目的发展,前端SPA应用的规模不断加大、业务代码耦合、编译慢,导致日常的维护难度日益增加。同时前端技术的发展迅猛,导致功能扩展吃力,重构成本高,稳定性低。因此前端微服务应运而生。
前端微服务优势
1.复杂度可控: 业务模块解耦,避免代码过大,保持较低的复杂度,便于维护与开发效率。
2.独立部署: 模块部署,减少模块影响范围,单个模块发生错误,不影响全局,提升项目稳定性。
3.技术选型灵活: 在同一项目下可以使用市面上所有前端技术栈,也包括未来的前端技术栈。
4.扩展性,提升业务动态扩展的可能,避免资源浪费
微前端服务结构
技术对比和选型:
选型 | 静态资源预加载 | 子应用保活 | iframe | js沙箱 | 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} = vconst info = {...other,url: url[env]}subMap[v.name] = infosubArr.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').urlreturn 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 });}});
七、部署
前端单页面的部署,不管怎么自动化,工具怎么变. 都是把打包好的静态文件,放到服务器的正确位置下。所以支持项目的独立部署和混合部署。
作者:京东物流 张燕燕 刘海鼎
内容来源:京东云开发者社区
前端微服务无界实践 | 京东云技术团队相关推荐
- 京东APP百亿级商品与车关系数据检索实践 | 京东云技术团队
导读 本文主要讲解了京东百亿级商品车型适配数据存储结构设计以及怎样实现适配接口的高性能查询.通过京东百亿级数据缓存架构设计实践案例,简单剖析了jimdb的位图(bitmap)函数和lua脚本应用在高性 ...
- 京东短网址高可用提升最佳实践 | 京东云技术团队
作者:京东零售 郝彦军 什么是短网址? 短网址,是在长度上比较短的网址.简单来说就是帮您把冗长的URL地址缩短成8个字符以内的短网址. 当我们在腾讯.新浪发微博时,有时发很长的网址连接,但由于微博只限 ...
- 京东物流常态化压测实践 | 京东云技术团队
作者:京东物流 刘江波 一.常态化压测建设目的 为什么做常态化压测? 目前面临主要问题,性能问题滞后发现,给大促带来不可控风险.目前日常需求频繁迭代,系统配置的变更.上下游依赖的变化.服务器资源置换等 ...
- 万物云原生下的服务进化 | 京东云技术团队
导读: 在万物云原生下的环境下,Java的市场份额也因耗资源.启动慢等缺点,导致在云原生环境里被放大而降低,通过这篇文章,读者可以更好地了解如何在云原生环境下通过升级相关版本和使用GraalVM打出原 ...
- 京东云专业安全服务介绍 | 京东云技术团队
根据LogicMonitor发布的未来云服务趋势研究报告显示,到2020年,企业在各类云产品上的支出将高于其在一般IT服务成本六倍以上,与此同时,所有企业的工作量将有83%都在云上实现,各企业将继续加 ...
- 分布式数据库 Join 查询设计与实现浅析 | 京东云技术团队
相对于单例数据库的查询操作,分布式数据查询会有很多技术难题. 本文记录 Mysql 分库分表 和 Elasticsearch Join 查询的实现思路,了解分布式场景数据处理的设计方案. 文章从常用的 ...
- 基于AIGC的京东购物助手的技术方案设想 | 京东云技术团队
灵感来源 随着AIGC的爆火,ChatGPT,GPT-4的发布,我作为一个算法工作者,深感AI发展的迅猛.最近,OpenAI的插件和联网功能陆续向用户公开,我也在第一时间试用了这些最新的功能.在Ope ...
- 烂怂if-else代码优化方案 | 京东云技术团队
0.问题概述 代码可读性是衡量代码质量的重要标准,可读性也是可维护性.可扩展性的保证,因为代码是连接程序员和机器的中间桥梁,要对双边友好.Quora 上有一个帖子: "What are so ...
- 京东云高可用业务架构建设 | 京东云技术团队
本文以 2022 年一个实际项目为基础,来演示在京东云上构建高可用业务的整个过程.公有云及私有云客户可通过使用京东云的弹性 IAAS.PAAS 服务,创建高可用.高弹性.高可扩展.高安全的云上业务环境 ...
最新文章
- 【ACM】杭电OJ 2015
- [转]IE下对文件(图片)进行base64转换
- Python+selenium 自动化-chrome驱动的下载安装
- Nuget Tips
- TWaver初学实战——炫动2D机房之设备篇
- 2015 Google code jam Qualification Round A 水
- 常用的Struts 2.0的标志(Tag)介绍
- android开发蓝牙自动连接电脑上,Android蓝牙开发之自动连接设备
- linux下qt实现vlc视频播放器,Qt封装本地视频播放器(VLC二次开发)
- elman神经网络 python实现_Rust 能取代 Python,更好的实现神经网络?
- 桌面虚拟化之盗梦空间
- 匿名页反向映射得建立
- Python入门学习—列表(FishC)
- linux怎么限制文件大小,Linux文件系统文件大小限制
- 手机浏览器哪家强,这3款口碑极佳的浏览器值得一用
- Temami防辐射服小贴士
- Cocos从零开发一个翻译插件
- 网站使用国外服务器越来越卡、越来越慢的原因
- 高频交易及化资策与区
- 创建群晖共享文件夹,并进行电脑访问