servkit
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.16-alpha.0 • Public • Published

    servkit

    主-从 应用架构的一种实现,目的是通过主应用提供的标准接口来实现从应用二次开发(二方/三方),以实现主应用的平台性开放能力。

    基于能力特性,servkit能够做:

    • 对于平台性的前端应用(通常是复杂的),可以做小程序基础框架和SDK;
    • 对于复杂的前端巨应用,可以做微前端的架构实践;
    • 对于简单的前端页面,可以作为基础的RPC通信库;

    能力特性:

    • 微应用架构:主应用-从应用
    • 三方应用开放能力
      • 微应用
        • 独立运行环境(iframe)
        • 同一运行环境
      • 定制化SDK
      • 服务/API管理
      • 权限管理:应用粒度、服务粒度、API粒度
    • 声明式服务API
    • 声明式服务事件
    • RPC通信协议

    为什么要开发 servkit ?

    • 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页面)和内容页(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();
        },
    });

    Example

    自带例子

    npm start,基础例子演示

    servkit-example

    基于servkit的一个完整微应用拆分例子,包括了iframe微应用和微应用(保持了独立的特性,但仍然在一个页面内)。

    为什么没有使用 qiankun、single-spa ?

    微前端解决的问题:

    1. 架构层面:
      • 巨型应用的拆分手段
      • 拆分后微应用的治理手段:运行机制、生命周期、通信能力等
    2. 工程层面:
      • 微应用的独立性:独立构建、独立发布、独立更新
    3. 组织层面:
      • 形成独立团队,独立运作(工程层面实现后,改点基本达成)

    小程序面临的问题:

    1. 独立性:基于SDK API,完全独立运作
    2. 安全性:环境隔离、权限管控
    3. 便利性:二方/三方开发简便

    single-spa 在架构层面提供的能力比较基础,在工程层面没有相关涉及。如果要用到实际项目上,还需要做较多的额外内容,比如应用间通信。

    qiankun 在 single-spa 之上提供更为完整的技术方案(比如运行沙盒、样式隔离等),在架构层面和工程层面更为成熟,是微前端开源体系中较好的选择。但是不适合做小程序体系,在安全性和独立性上不能适用于三方应用。

    Install

    npm i servkit

    DownloadsWeekly Downloads

    29

    Version

    1.0.16-alpha.0

    License

    MIT

    Unpacked Size

    4.98 MB

    Total Files

    15

    Last publish

    Collaborators

    • lywzx
    • builditnow