主-从 应用架构的一种实现,目的是通过主应用提供的标准接口来实现从应用二次开发(二方/三方),以实现主应用的平台性开放能力。
基于能力特性,servkit能够做:
- 对于平台性的前端应用(通常是复杂的),可以做小程序基础框架和SDK;
- 对于复杂的前端巨应用,可以做微前端的架构实践;
- 对于简单的前端页面,可以作为基础的RPC通信库;
能力特性:
- 微应用架构:主应用-从应用
- 三方应用开放能力
- 微应用
- 独立运行环境(iframe)
- 同一运行环境
- 定制化SDK
- 服务/API管理
- 权限管理:应用粒度、服务粒度、API粒度
- 微应用
- 声明式服务API
- 声明式服务事件
- RPC通信协议
-
GUI应用H5化
在GUI程序的领域,传统的原生开发逐步被H5 WEB技术所取代,因为H5表现出了非常优秀的开发体验和生产效率,这在PC平台尤为明显;那么H5 WEB技术面临的是更为复杂的软件工程和系统程序。
-
云端化
云端化将一直保持主旋律,当前在终端操作的软件系统会越来越多的向云端发展,而WEB技术作为云端的主要技术之一,WEB前端领域会面临更多传统软件系统的开发挑战,工程规模、系统复杂度、架构设计等(在工程化上已经能看到一些变化在应对这些挑战,比如TypeScript,Webpack等)。
-
开放能力
SAAS服务应该是开放的。对于范服务的提供商,通过提供开放能力,让上下游生态、周边生态、行业细分生态等能够接入,达到能力互补/能力创新(服务场景拓宽);一体化是目的,开放和封闭都是实现路径,但是封闭的一体化方案,ROI可能较低,自建意味更多的人力、财力、时间,对于客户也缺少服务弹性。
在这个背景下,在技术角度看,要应对的是巨型应用;在业务角度看,要应对的是开放能力。而这些也会是WEB领域发展的共性问题,所以servkit针对这些问题,尝试提供一个通用处理方案;巨型应用使用微前端架构(SOA)思路,开放能力使用小程序架构思路。
npm install servkit
或者yarn add servkit
IFrame页面间通信是最常见的场景,这种场景分为承载页(打开iframe页面)和内容页(iframe页面),servkit在这个场景提供了:
- 语义化操作:iframe的打开、页面间通信,而无需关注底层琐碎代码的开发;
- 规范化通信:基于提供的service机制,规范了RPC通信间协议,也提供了通信间的类型检测;
相互间的关系:
承载页 -> sappMGR -> service decl <- sappSDK <- IFrame
打开页面:
// 在承载页面都通过sappMGR进行操作
import { sappMGR } from 'servkit';
import { CommonService } from 'servkit-service-decl';
import { CommonServiceImpl } from './service/CommonServiceImpl';
sappMGR.create(
{
// 页面的ID
id: 'com.page.demo',
// 页面的版本
version: '1.0.0',
// 页面的名称
name: 'demo',
// 页面的地址
url: 'https://www.demo.com',
// 页面的可选参数
options: {
}
},
{
// 页面布局相关配置
layout: {
// iframe元素挂载的容器DOM元素
container: domElement,
},
// 配置承载页面向iframe页面提供的服务,iframe页面可直接调用相应的服务API
services: [
// 注册CommonService
{
// 服务声明,类型声明会和iframe进行共享,保证了API的类型检查
decl: CommonService,
// 服务实现,实现只存在与承载页代码之中,iframe页面不感知
impl: CommonServiceImpl
},
],
}).then((app) => {
// 页面创建成功,app为对应iframe的抽象体;
// 可通过app直接与iframe通信,以及显示、隐藏和关闭操作
}).catch((e) => {
// 页面创建失败
});
声明服务:
// CommonService.ts
// 服务声明通常单独放在一个npm包里面,能够给实现方和使用方进行共享使用
import { ServService, ServEventer, anno, ServAPIArgs, ServAPIRetn, API_UNSUPPORT } from 'servkit';
// 声明一个服务class,该class定义了IFrame间的通信语义,类型声明
@anno.decl({
// 服务id
id: 'demo.service.common',
// 服务版本
version: '1.0.0',
})
export class CommonService extends ServService {
// 声明一个服务notify型api,notify型的api不带有返回数据;
// 参数为一个字符串;
@anno.decl.notify()
message(args: ServAPIArgs<string>): ServAPIRetn {
return API_UNSUPPORT();
}
// 声明一个服务api,具有返回数据;
// 参数为一个字符串,返回boolean值;
@anno.decl.api()
confirm(args: ServAPIArgs<string>): ServAPIRetn<boolean> {
return API_UNSUPPORT();
}
}
实现服务:
// CommonService.ts
// 服务声明通常单独放在一个npm包里面,能够给实现方和使用方进行共享使用
import { CommonService } from 'servkit-service-decl';
import { anno, ServAPIArgs, ServAPIRetn, API_SUCCEED, DeferredUtil } from 'servkit';
// 通过antd实现message和confirm
import message from 'antd/lib/message';
import 'antd/lib/message/style/css';
import Modal from 'antd/lib/modal';
import 'antd/lib/modal/style/css';
// 实现一个服务class
@anno.impl()
export class CommonServiceImpl extends CommonService {
// 实现message接口
message(args: ServAPIArgs<string>): ServAPIRetn {
message.success(args);
return API_SUCCEED();
}
// 实现confirm接口
confirm(args: ServAPIArgs<string>): ServAPIRetn<boolean> {
const deffered = DeferredUtil.create<boolean>();
Modal.confirm({
title: '确认',
content: args,
onCancel: () => {
deffered.resolve(false);
},
onOk: () => {
deffered.resolve(true);
}
})
return deffered;
}
}
启动页面:
// 在内容页面都通过sappSDK进行操作
import { sappSDK } from 'servkit';
import { IFrameCommonService } from 'servkit-service-decl';
import { IFrameCommonServiceImpl } from './service/CommonServiceImpl';
sappSDK
.setConfig(
{
// 配置iframe页面向承载页面提供的服务,承载页面可直接调用相应的服务API
services: [
// 注册IFrameCommonService
{
// 服务声明,类型声明会和iframe进行共享,保证了API的类型检查
decl: IFrameCommonService,
// 服务实现,实现只存在与承载页代码之中,iframe页面不感知
impl: IFrameCommonServiceImpl
},
],
})
// 启动内容页面
.start()
.then((app) => {
// 页面创建成功,app为对应iframe的抽象体;
// 可通过app直接与承载页进行通信,以及关闭操作
}).catch((e) => {
// 页面创建失败
});
页面通信:
import { sappSDK } from 'servkit';
import { CommonService } from 'servkit-service-decl';
// 获取服务
const common = await sappSDK.service(CommonService);
// 调用服务API
common.message('调用服务');
这里只展示了从IFrame向承载页的单向通信,但servkit提供了IFrame页面和承载页面的双向通信机制,IFrame自身也可以向承载页暴露服务,具体使用与上述例子类似(不同点在于承载页通过sappMGR.create后的app获取服务)。
基于基座应用上搭建微应用也是典型的场景,这里基座应用负责提供基础能力,而微应用以HTML片段(或者JSBundle链接)形式暴露,由基座应用进行管理,并运行在基座应用的上下文,利用基座应用的能力API做业务开发;针对两种场景,servkit做了归一化。
相互间的关系:
基座应用 -> sappMGR -> service decl <- sappSDK <- 微应用
打开微应用:
// 在承载页面都通过sappMGR进行操作
import { sappMGR } from 'servkit';
import { CommonService } from 'servkit-service-decl';
import { CommonServiceImpl } from './service/CommonServiceImpl';
sappMGR.create(
{
// 页面的ID
id: 'com.page.demo',
// 页面的版本
version: '1.0.0',
// 页面的名称
name: 'demo',
// 页面的地址,基于url提供的html片段
url: 'https://www.demo.com',
// 或者直接基于html片段
html: '<html><script src="xxxx"></script></html>',
// 应用类型为异步加载类型
type: ESappType.ASYNC_LOAD
// 页面的可选参数
options: {
}
},
{
// 页面布局相关配置
layout: {
// 微应用的容器DOM元素
container: domElement,
},
// 配置承载页面向iframe页面提供的服务,iframe页面可直接调用相应的服务API
services: [
// 注册CommonService
{
// 服务声明,类型声明会和iframe进行共享,保证了API的类型检查
decl: CommonService,
// 服务实现,实现只存在与承载页代码之中,iframe页面不感知
impl: CommonServiceImpl
},
],
}).then((app) => {
// 页面创建成功,app为对应iframe的抽象体;
// 可通过app直接与iframe通信,以及显示、隐藏和关闭操作
}).catch((e) => {
// 页面创建失败
});
声明服务:
// CommonService.ts
// 服务声明通常单独放在一个npm包里面,能够给实现方和使用方进行共享使用
import { ServService, ServEventer, anno, ServAPIArgs, ServAPIRetn, API_UNSUPPORT } from 'servkit';
// 声明一个服务class,该class定义了IFrame间的通信语义,类型声明
@anno.decl({
// 服务id
id: 'demo.service.common',
// 服务版本
version: '1.0.0',
})
export class CommonService extends ServService {
// 声明一个服务notify型api,notify型的api不带有返回数据;
// 参数为一个字符串;
@anno.decl.notify()
message(args: ServAPIArgs<string>): ServAPIRetn {
return API_UNSUPPORT();
}
// 声明一个服务api,具有返回数据;
// 参数为一个字符串,返回boolean值;
@anno.decl.api()
confirm(args: ServAPIArgs<string>): ServAPIRetn<boolean> {
return API_UNSUPPORT();
}
}
实现服务:
// CommonService.ts
// 服务声明通常单独放在一个npm包里面,能够给实现方和使用方进行共享使用
import { CommonService } from 'servkit-service-decl';
import { anno, ServAPIArgs, ServAPIRetn, API_SUCCEED, DeferredUtil } from 'servkit';
// 通过antd实现message和confirm
import message from 'antd/lib/message';
import 'antd/lib/message/style/css';
import Modal from 'antd/lib/modal';
import 'antd/lib/modal/style/css';
// 实现一个服务class
@anno.impl()
export class CommonServiceImpl extends CommonService {
// 实现message接口
message(args: ServAPIArgs<string>): ServAPIRetn {
message.success(args);
return API_SUCCEED();
}
// 实现confirm接口
confirm(args: ServAPIArgs<string>): ServAPIRetn<boolean> {
const deffered = DeferredUtil.create<boolean>();
Modal.confirm({
title: '确认',
content: args,
onCancel: () => {
deffered.resolve(false);
},
onOk: () => {
deffered.resolve(true);
}
})
return deffered;
}
}
微应用启动:
// 异步微应用需要通过SappSDK进行注册
import { SappSDK } from 'servkit';
import { IAsyncAppCommonService } from 'servkit-service-decl';
import { IAsyncAppCommonServiceImpl } from './service/CommonServiceImpl';
import { CommonService } from 'servkit-service-decl';
// 注册异步应用,这里的id需要与基座应用中对应
SappSDK.declAsyncLoad('demo.service.common', {
// 微应用的启动函数
bootstrap: (sdk) => {
// 后续通过sdk进行服务调用
sdk.setConfig(
{
// 配置微应用向基座应用提供的服务,基座应用可直接调用相应的服务API
services: [
// 注册IAsyncAppCommonService
{
// 服务声明
decl: IAsyncAppCommonService,
// 服务实现
impl: IAsyncAppCommonServiceImpl
},
],
}).start().then((app) => {
render();
const common = await sappSDK.service(CommonService);
// 调用基座应用提供的服务API
common.message('调用服务');
}).catch((e) => {
// 启动失败
});
},
// 微应用的卸载函数
deBootstrap: () => {
// 卸载应用
unmount();
},
});
npm start
,基础例子演示
基于servkit的一个完整微应用拆分例子,包括了iframe微应用和微应用(保持了独立的特性,但仍然在一个页面内)。
微前端解决的问题:
- 架构层面:
- 巨型应用的拆分手段
- 拆分后微应用的治理手段:运行机制、生命周期、通信能力等
- 工程层面:
- 微应用的独立性:独立构建、独立发布、独立更新
- 组织层面:
- 形成独立团队,独立运作(工程层面实现后,改点基本达成)
小程序面临的问题:
- 独立性:基于SDK API,完全独立运作
- 安全性:环境隔离、权限管控
- 便利性:二方/三方开发简便
single-spa 在架构层面提供的能力比较基础,在工程层面没有相关涉及。如果要用到实际项目上,还需要做较多的额外内容,比如应用间通信。
qiankun 在 single-spa 之上提供更为完整的技术方案(比如运行沙盒、样式隔离等),在架构层面和工程层面更为成熟,是微前端开源体系中较好的选择。但是不适合做小程序体系,在安全性和独立性上不能适用于三方应用。