0.0.33 • Published 3 years ago

@hollysys-mirco-front-end/framework v0.0.33

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

前言

由于项目未来的需要,最近在构思一个服务于前端的“微服务”架构,即微前端.

说到这里,可能有人要卧槽,一个微服务就够折腾了,怎么前端也要 "微服务" 化,放在一起开发不香吗?

考虑一下,如果你的系统是一个巨无霸系统,里面涵盖了N多模块,代码千千万,依赖稀巴烂,构建超级慢,改一个bug,发一个版,测试要从头测到晚

那我希望你能看下去,更欢迎您与我一起 maintain 这个框架

当然,如果你的系统很简单,微前端,不适合。老老实实用大锅炉吧,一锅端比较好

什么是微前端

微前端与微服务的概念类似,是将复杂的系统前端模块进行拆分细化成各个子应用,

每个子应用可以拥有独立repository仓库,独立development开发,独立build构建,独立deployment部署,

我们把这些子应用称为“微应用”,类似于微服务

我们把管理子应用的应用称为“主应用”,有点像Console、Nacos等微服务注册中心的概念

虽然看起来有点 "偷天换日" ,但是不这样做,怎么装X....

微前端的好处

1.可以将复杂系统的前端代码拆分细化到不同团队

2.每个微应用不再局限于同一种框架

3.每个微应用独立开发、构建、部署

4.主应用与微应用开发、构建、部署分离,实现真正的“可插拔式”架构

5.每个微应用独立访问时页面看起来与访问主应用一样,实现共享页面

6.方便测试,每个微应用都是独立的

7.丰富微服务,微服务不再单纯的提供API,也可输出web页面

…………

微前端的坏处

1.打破传统前端开发模式,开发人员有阵痛期

2.对开发人员技术水平有一定要求,尤其是对npm package、webpack、esm,commonjs、umd等前端模块化、mvvm框架基本原理、docker等有一定掌握

…………还有不少坏处,这里我就不列举了,害怕你们受不了

微前端入门必修知识

1.如何构建发布同一个组织机构下的npm package?这里罗列vue旗下的几个package,大家感受一下:

@vue/vue-cli,@vue/vue-router………

比如我们要做一个微前端框架,就需要在npm 上发布类似下面的包:

@hollysys-mirco-front-end/framework等等

2.如何利用构建工具,比如rollup、webpack,打包出一个library库,像ant-design、element-ui等

3.webpack library、externals概念,以及动态生成html的插件webpackHtmlPlugin

4.设计模式之工厂模式、模板模式

5.umd、commonjs、es module等前端模块规范

………这些知识如果不懂,那我建议你去查查资料,不一定为了学习微前端,而是为了你自己

微前端架构要考虑以下列几件事情:

1.主应用、各个微应用如何独立开发、构建、部署

2.主应用与微应用如何共享一套html或者说dom,以此来实现当单独访问微应用

或者从主应用内部访问微应用时,用户看到的界面效果是一致的

3.微应用该如何暴露自己,通过什么方式暴露

4.主应用如何注册微应用,何时加载微应用,何时卸载,通过什么方式加载与卸载

5.主应用如何与微应用进行信息共享或者数据通讯,比如像jwt等等

6.微应用与微应用之间如何互相依赖

7.主应用、微应用如何样式隔离、变量隔离,做到沙箱sandbox

……应该还有一些坑,还在使劲踩的过程中

解决方案

针对上述问题,我们进行一步步分析,有点鱼骨图拔刺的味道了

独立开发、构建、部署

1.利用github或者gitlab,主应用与每个微应用创建属于自己的repo。

主应用仓库类似于:https://github.com/arashiquan33/hollysys-mirco-front-end-framework

各个微应用也拥有属于自己的repo,但是就像vue、react框架那样,我们需要提供创建微应用工程的脚手架hmfe-cli(hollysysMircoFrontEnd-cli),方便微应用在几乎一样的规则下开发

微应用脚手架模板类似于:https://github.com/arashiquan33/hollysys-mirco-front-end-app-vue-example

在拥有各自的仓库后,就可以进行独立开发,并且每个工程集成webpack或者rollup这样的工具进行独立构建打包

至于部署,可结合docker完成或者根据你们整体架构的方式来进行

应用间依赖

说到依赖,就不得不提npm package,大多数情况下我们都在使用别人写的package,这次我们翻身做主人,创建属于自己的package!!!

你一定看到过类似于这样的package:@webpack/webpack-cli,@webpack/webpack-devserve .....

没错,我们就要实现像这样带组织机构的package,进行统一管理,要搞就要搞的像回事!

主框架的package:@hollysys-mirco-front-end/framework

提示 要发布一个npm package,需要在工程目录下package.json动动手脚,具体可以查看 https://www.npmjs.com 文档

package.json 部分内容:

  "name": "@hollysys-mirco-front-end/framework",  //包名称
  "author": {
    "name": "quantianchao",
    "email": "304033826@qq.com"
  },
  "version": "0.0.31", // 包版本
  "description": "微前端基座,负责管理微前端各个应用",
  "main": "dist/hollysys-mirco-front-end-framework.js", //入口文件
  "types": "types/hollysys-mirco-front-end-framework.d.ts",
  "module": "dist/hollysys-mirco-front-end-framework.js",
  

各个微应用的package:@hollysys-mirco-front-end/app-vue-example

提示 app-vue-example是其中一个微应用的名称,通过dependenices来依赖主框架

package.json 部分内容:

  "name":@hollysys-mirco-front-end/app-vue-example", //包名称
  "version": "0.0.25", //版本
  "description": "微前端应用示例",
  "main": "lib/hollysys-mirco-front-end-app-vue-example.umd.js",
  "module": "lib/hollysys-mirco-front-end-app-vue-example.umd.js",
   "dependencies": {
      "@hollysys-mirco-front-end/framework": "^0.0.32",  // 依赖主框架
      "core-js": "^3.6.5",
      "vue": "^2.6.11"
   },
   

app-vue-example微应用可以通过 dependenices 依赖来引入主应用framework暴露的一切

说到这来,可能有人要问,那要每个微应用都通过 dependenices 来依赖 framework

打包的时候岂不是重复打入了framework 代码? 这部分内容,我们可以通过 webpack externals 来完成,,这部分一会介绍framework的时候会详细说.

anyway,应用之间的依赖关系我们通过npm package 来解决。

主应用如何注册微应用,何时加载微应用,何时卸载,通过什么方式加载与卸载

咋一看,这部分我们要解决的内容很多,看起来很复杂,没关系,我们来捋一捋,我们可以通过抽象来把这事情讲清楚,大家就懂

1.抽象微应用,定义每个微应用该有的一些属性和方法

//抽象类
abstract class AbstractHollysysMircoFrontEndApp {
 
   constructor(name: string, mountTo: HTMLElement,props:any) {
       this.name = name;
       this.mountTo = mountTo;
       this.props=props;
   }
   
   //微应用名称
   private name: string;

   //挂载的DOM节点,即微应用在html中显示到哪个区域
   private mountTo: HTMLElement;
   
   //应用接收外部传递的props共享信息
   private props:any


   //定义一系列 hook,微应用可自行实现hook,返回Promise,解决函数体内有可能出现的异步情况
   
   //应用安装前
   abstract beforeInstall(): Promise<any>;

  //应用安装
   abstract install():Promise<any>;

    //应用卸载
   abstract uninstall():Promise<any>;

    //应用卸载前
   abstract beforeUninstall():Promise<any>;
}

由于javascript语言的特性以及多数前端开发人员的习惯,我们可通过构造函数参数的形式来定义一个微应用Class

export  class HollysysMircoFrontEndApp {
    constructor(appConstructorArguments:AppConstructorArguments) {
       let {name,beforeInstall,install,uninstall,beforeUninstall,pathPrefix} = appConstructorArguments;
       this.name=name;
       this.pathPrefix=pathPrefix;
       this.beforeInstall=beforeInstall;
       this.install=install;
       this.uninstall=uninstall;
       this.beforeUninstall=beforeUninstall;
    }
    
    //微应用名称
    private name: string;

    public beforeInstall:() => Promise<void>;

    public install:(installFunctionArguments:InstallFunctionArguments) => Promise<void>;
   
    public uninstall:() => Promise<void>;

    public beforeUninstall:() => Promise<void>;
  
}

2.实现一个具体的微应用,通过npm package 引入 @hollysys-mirco-front-end/framework 暴漏的微应用Class

以上述示例中app-vue-example为例,实例化一个HollysysMircoFrontEndApp,将vue对象的实例化包裹到微应用的安装方法内,最后把这个HollysysMircoFrontEndApp实例暴漏出去:

 import Vue from "vue";
 import App from "./App.vue";
 import {
  HollysysMircoFrontEndApp,
  HollysysMircoFrontEndAppManager
} from "@hollysys-mirco-front-end/framework";

const hollysysMircoFrontEndAppVueExample = new HollysysMircoFrontEndApp({
      name: process.env.npm_package_name,
      beforeInstall: function() {
        return Promise.resolve();
      },
      install: function({ mountTo, props,routerBasePath='' }) {
        let routes = getRoutes(routerBasePath);
        let router = new VueRouter({
          routes // (缩写) 相当于 routes: routes
        });
        this.instance = new Vue({
          props,
          router,
          render: h => h(App)
        }).$mount(mountTo);
        return Promise.resolve();
      },
      uninstall: function() {
        this.install = null;
        return Promise.resolve();
      },
      beforeUninstall: function() {
         return Promise.resolve();
      }
});

//很关键,把自己暴漏出去
export default hollysysMircoFrontEndAppVueExample;

这个例子其实很好的证明了,作为微应用的开发者,你只需要把你利用vue/react/angular等框架的初始化代码包裹到install方法就好,其他和之前的开发模式一模一样

3.定义应用管理者(单例),管理微应用的注册、下载、安装、卸载等等.

  • 注册微应用(registerApp)
  • 启动工程 (bootstrap)
  • 监听浏览器url-hash变化,找出当前hash对应的已经注册的微应用 (addHashchangeListener)
  • 卸载旧应用 (uninstallApp)
  • 下载新的微应用 (downloadApp)
  • 安装微应用 (installApp)
 export class HollysysMircoFrontEndAppManager {
 
    //私有化构造函数,不能通过new 来实例对象,只能通过getInstance()方法 
    private constructor(){}
 
     //缓存单例
    private static instance:HollysysMircoFrontEndAppManager;

    /**
     * 获取单例
     */
    public static getInstance(): HollysysMircoFrontEndAppManager {
        if (this.instance == null) {
            this.instance = new HollysysMircoFrontEndAppManager();
        }
        return HollysysMircoFrontEndAppManager.instance;
    }
    
    //微应用挂载的节点ID
    private  _appMountTo='#mountTo';

    private  _menuMountTo='#menu';

    private _isBootstrapCompleted:boolean=false;

    //已经注册的所有微应用
    private  _hasRegisterApps: RegisterAppArguments[]=[];


    /**
     * @description 注册应用
     * @param registerAppArguments
     */
    public registerApp(registerAppArguments: RegisterAppArguments) {
        this._hasRegisterApps.push(registerAppArguments);
    }

    /**
     * @description 获取注册的应用
     */
    public getRegisterApps():RegisterAppArguments[]{
        return this._hasRegisterApps;
    }

    public  downloadApp(registerAppArguments:RegisterAppArguments):Promise<any>{}

    /**
     * @description 下载装配应用
     * @param appConfig
     */
    public  async installApp(app: HollysysMircoFrontEndApp) {
        await app.beforeInstall();
        await app.install({
            mountTo:this._appMountTo,
            props:{a:1}
        });
    }

    /**
     * @description 卸载应用
     * @param appConfig
     */
    public  uninstallApp() {}

    /**
     * @description 启动
     */
    public bootstrap(){}

    /**
     * @description 绑定hashchange
     */
    private addHashchangeListener(){}

    /**
     * @description 浏览器hashchange变化的回调函数
     */
    private async hashChangeHandler(){}
}

共享HTML

1.@hollysys-mirco-front-end/framework 这个package 需要打包出一个可以共享的html,里面定义整个应用看起来应该有的模样

2.微应用在开发时,可通过webpack 插件 HtmlWebpackPlugin 来指定微应用应该使用的html。例如:

vue.config.js中这样配置

config.plugin("html").tap(args => {
        args[0].template =
          "node_modules/@hollysys-mirco-front-end/framework/dist/standalone.html";
        return args;
});

如果是webpack.config.js

new HtmlWebpackPlugin({
  title: 'hollysys-mirco-front-end-app-standalone',
  template: 'node_modules/@hollysys-mirco-front-end/framework/dist/standalone.html',
  filename:'standalone.html',
})

微应用如何暴漏自己

1.前端模块化目前有好几种方案,amd/commonjs/es module/umd/system.js 等,这几种方案中,umd算是兼容性最好的

2.微应用通过export语法把实例化后的 HollysysMircoFrontEndApp 导出,再配合webpack打包library的功能,打包出一个脚本文件

3.以上述app-vue-example微应用为例,打包出一个 hollysys-mirco-front-end-app-vue-example.umd.js 脚本

可查看 https://github.com/arashiquan33/hollysys-mirco-front-end-app-vue-example 这个示例

如何下载微应用并且获取微应用实例

1.目前框架采用了动态创建script标签来加载微应用打包出来的js脚本,脚本加载完成后,获取应用注册时配置的脚本可以暴漏出来的变量,以此来获取微应用暴漏的 HollysysMircoFrontEndApp对象

注册微应用:

 //获取实例
 const hollysysMircoFrontEndAppManager =  HollysysMircoFrontEndAppManager.getInstance();
 
 //注册微应用
 hollysysMircoFrontEndAppManager.registerApp({
     name:'@hollysys-mirco-front-end/app-vue-example',
     number:'0.0.24',
     appModuleName:'hollysysMircoFrontEndAppVueExample',
     appModuleUrl:'https://unpkg.com/@hollysys-mirco-front-end/app-vue-example@0.0.24/lib/hollysys-mirco-front-end-app-vue-example.umd.js',
     routerBasePath:'/app-vue-example'
 })
    public  downloadApp(registerAppArguments:RegisterAppArguments):Promise<any>{
           return new Promise((resolve,reject)=>{
                //获取head的标签
                var head= document.getElementsByTagName('head')[0];
                //创建script标签
                var script= document.createElement('script');
                //属性赋值
                script.type= 'text/javascript';
                //添加src属性值
                script.src= registerAppArguments.appModuleUrl;
                head.appendChild(script);
                //回调
                script.onload =  function(e) {
                    const module = window[registerAppArguments.appModuleName];
                    resolve(module);
                };
           })
    }

2.后期会引入system.js模块系统来做到不用创建script标签来加载微应用,防止出现变量冲突的情况

运行主应用

1.yarn install

2.yarn serve

可查看目前已经做好的一个demo

该demo中注册了一个微应用 https://www.npmjs.com/package/@hollysys-mirco-front-end/app-vue-example

运行微应用

请以至 https://github.com/arashiquan33/hollysys-mirco-front-end-app-vue-example

结语

目前框架还在持续丰富中....