当前位置: 首页 > 软件库 > Web应用开发 > Web框架 >

single-spa-portal-example

授权协议 MIT License
开发语言 JavaScript
所属分类 Web应用开发、 Web框架
软件类型 开源软件
地区 不详
投 递 者 哈襦宗
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

single-spa-portal-example

The goal of this project is to provide an example of how to build a portal like app which consists of multiple single page applications (SPA's). Each SPA should be self contained with its own build process. It should be individually deployable without the need to deploy the whole application if there are changes to any individual app.

This example is based on simple-single-spa-webpack-example but will provide further features like:

  • Isolate SPA's with their own build process
  • Load SPA's on demand with SystemJS
  • Provide a way to communicate between each SPA
  • Get assets (like images, fonts, css, etc.) coming from different servers to work
  • Support multiple Angular versions without zone.js conflicts. For details see Multiple Angular Apps
  • Support Angular AOT Builds

How to run this project

  1. Clone this project
  2. Jump into each app folder and do:
    • npm install
    • npm run watch:portal
  3. Then start the portal with:
    • npm install
    • npm run watch
  4. Open up http://localhost:9000 in a web browser.

npm tasks

watch:portal: Builds the app as UMD module with singleSPA as middleware to consume the app by the portal. Changes are automatically detected.

build:portal: Releases the app as UMD module and outputs all contents to a folder. You can upload the produced file in production to your webserver. Hint: The Angular 6 example is being build with AOT. You can use npm run build:portal -- --env.analyzeBundle to see that there is no compiler.js inside the bundle.

watch:standalone: If you just want to develop the single app without the whole portal, you can use this task. Check the console log to see which port the app is being served from. This task is OPTIONAL! For now this task only exists on the Vue project to serve as an example.

build:standalone: As with the watch:standlone taks, this builds the app as stand alone version (no portal needed). This task is OPTIONAL! For now this task only exists on the Vue project to serve as an example.

inter-app-communication

This topic has been discussed multiple times (i.e. here or here). There may be many solutions to solve this problem. In this repository I want you to show a solution that meets the following requirements:

  • Each app is a self contained system. No app knows the internal state of another app or their data model. In short, each app is treated as a black box and can be maintained by a different team.
  • Each app must be able to have a complex state.
  • When you navigate between apps, the state must not be lost (because of mount/unmount).

To meet these requirements I have decided for an event system where each app can or can not listen to events that other apps send. This enables each app to keep their isolated state and modify only their own state based on events from other apps (and probably resend other events). No app needs direct access to the state of another app.

Furthermore I needed to split the apps into two parts. One is the normal app itself (GUI, Framework, etc.), the other is a "communication layer" which is exported as separate module and loaded/instantiated by the portal regardless of the app state. This allows each app to listen and react to events even if they aren't mounted.

Each app can process these events in whatever way they like. The only requirement is that all apps agree on one event format to send and receive these events.

For this example I have decided to just go with redux since it basically does exactly what I need. Throw events and process events. But this system works with whatever technic you like.

Here is a graphic which illustrates what actually happens:

The main parts are:

StoreApps: Contains state + business logic. Implements a dispatch method which can be called by the GlobalEventDistributor if an global event occurs.

GUIApps: singleSPA middleware + UI code like HTML, CSS, Controller, etc. + Framework like React or Angular

GlobalEventDistributor: Can be used to register stores. Sends dispatch events to all stores. (Observer pattern)

How it works:

  1. The root-application boots and loads all stores and instantiates them. This is necessary since we need the communication layer (store) to be active at all times. Even if the whole application is not mounted yet. Otherwise no app specific events getting processed.
  2. When an app is being mounted, the root-application passes the already instantiated store belonging to the individual app down. The root-application also passes a reference to the GlobalEventDistributor to the app.
  3. Now you can happily send all your global events to the GlobalEventDistributor with the dispatch() method and all other events to the local store.

Cons:

As already mentioned, the biggest disadvantage is that all stores have to be loaded when the root-application loads. The reason for this is that we are building a project that will have a huge application state being entirely in the browser. The user will likely input 1h of data without any server communication and once he is done, he will save everything with one click.This must not necessarily be your use-case. For example if you are only interested in inter-app-communication with any currently active app, you may not need to load all states beforehand but rather load them while the apps mount.

Multiple Angular Apps

The big issue with Angular 2+ is, that it (or third party libraries which Angular depends on) pollute the global window object. One such library is Zone.js. Zone.js monkey patches all async events and add its reference to the window object. If you have multiple Angular apps running, Angular will complain that Zone.js is already loaded.

One possible solution is to separate Zone.js from all Angular apps and load Zone.js only once in the portal. This may not be the best solution because as soon as you have multiple different Angular versions as apps in the portal, it may be possible that some of them require different Zone.js versions. Which may break everything at that point. Even though it is not a great solution, it may be the best solution we have with the current state of angular.

The other solution I found is to load every Angular app in its own iframe. Doing that, every Angular app runs completely isolated. You can then set the render target for Angular to the parent window. With this solution Angular runs in a complete isolated context but renders all content to the main DOM. Sadly this is also not the perfect solution since you open up many other issues you have to deal with. A few examples are:

  • You need to manually put all CSS styles from the iframe to the parent window
  • The angular router can no longer access the browser URL to update it according to the app routing
  • You can't use third party UI libraries that depent on document events. (i.e. a dropdown component that wants to know when you click on the document to close it.)

In the future we may have better solutions like Angular elements to deal with this issue, until then our best bet is to put a single Zone.js instance into the portal app. Which is exactly what I did in this example project.

Forks

 相关资料
  • 框架 客户端框架 下列框架实现了这种(或类似的)模式: Piral Open Components qiankun Luigi Frint.js 服务端框架 下列框架实现这种(或类似的)模式: Mosaic PuzzleJs Podium Micromono 帮助(Helper)库 帮助库或是提供共享依赖、路由事件的基础架构,或是将不同的微前端及其生命周期组织起来 。 下面的库可用于削减模板代码:

  • 介绍 RxJava(以及它派生出来的RxGroovy和RxScala)中有一个名为Single的Observable变种。 Single类似于Observable,不同的是,它总是只发射一个值,或者一个错误通知,而不是发射一系列的值。 因此,不同于Observable需要三个方法onNext, onError, onCompleted,订阅Single只需要两个方法: onSuccess - Si

  • 检查列表是否只有一个元素并返回它。 语法 (Syntax) List.single 例子 (Example) void main() { var lst = new List(); lst.add(12); print("The list has only one element: ${lst.single}"); } 它将产生以下output - The lis

  • single 限制 Observable 只有一个元素,否出发出一个 error 事件 single 操作符将限制 Observable 只产生一个元素。如果 Observable 只有一个元素,它将镜像这个 Observable 。如果 Observable 没有元素或者元素数量大于一,它将产生一个 error 事件。

  • Single Single 是 Observable 的另外一个版本。不像 Observable 可以发出多个元素,它要么只能发出一个元素,要么产生一个 error 事件。 发出一个元素,或一个 error 事件 不会共享附加作用 一个比较常见的例子就是执行 HTTP 请求,然后返回一个应答或错误。不过你也可以用 Single 来描述任何只有一个元素的序列。 如何创建 Single 创建 Sing

  • single 函数签名: single(a: Function): Observable 发出通过表达式的单一项。 示例 示例 1: 发出通过断言的第一个数字 ( StackBlitz | jsBin | jsFiddle ) // RxJS v6+ import { from } from 'rxjs'; import { single } from 'rxjs/operators'; //