Expanding Single Page Apps into multiple Browser Windows
Tags: javascript, webdev, architecture, webperf
Published: 2020-06-19 18:50:59

# Content
1. Introduction
2. Scalable frontend architectures
3. More background infos
4. Benefits of the Shared App Worker
5. How can DOM events still work?
6. How do the Apps inside the Shared Covid App look like?
7. How can we position Popup Windows to exactly overlay an existing Component?
8. What are the use cases for the Shared Workers setup?
9. Webpack based dist versions?
10. What about Webkit / Safari?
11. Online Demo
12. What is coming next?
13. Would you like a tutorial on how to transform the Covid App into the new Shared Covid App version?
14. Close to the end game?
15.Final thoughts

## 1. Introduction
For me, it was alway a big dream, if not a life time goal, to create a meaningful Open Source project, with the potential to make a significant impact to improve the way how UI development works in general. This is it. I am very excited to not only share this article with you, but the entire code base as well (using the MIT license).

Before diving into the topic of scalable frontend architectures, please watch this 95s video first:

{% youtube n7m7ZT1kXQk %}

## 2. Scalable frontend architectures
In a nut shell:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ij67uqprj742ql4rn5ex.png)

1. In case you are working inside the UI development area, the first picture should look familiar. Everything happens inside the main thread (Browser Window). This is actually a performance problem. A big one. By default, each Browser Window is just using 1 CPU / Core, while close to every computer or mobile device has more than one available. The worst case which can happen is that one CPU is running at 100% => freezing your UI, while other CPUs are idle. Even framework background tasks, like internal garbage collection, can already mess up your beautiful CSS animations and it is hard for developers to figure out what is happening. Some project started moving isolated expensive tasks into a separate thread. Sorry, this is not enough!
2. The default neo.mjs setup for Single Page Apps (SPAs) started with the concept to move out everything that is possible from the Main Thread, so that this one is mostly idle and can fully concentrate on its primal purpose: manipulating the DOM. A tiny part (37KB) of the neo.mjs is running inside the Main Thread: the logic to create the workers, applying delta updates on the real DOM & delegating virtualised UI events to the App Worker. There are no background tasks here. Optional Main Thread addons can get dynamically imported as needed. Since this concept put a lot of weight on the App Worker, the VDom & Data Workers were added. Of course you can connect to your backend from the App worker as well.
3. The neo.mjs setup was designed right from the start keeping the Shared Workers setup in mind. From an architecture perspective, this is close to the end game. We can now optionally replace the Workers with Shared Workers, which allows us to connect multiple Main Threads (Browser Windows) to the Shared App Worker. This way our Apps can directly communicate (not even needing postMessages to do so), but it goes way beyond this, as you have seen inside the video.

## 3. More background infos
The last 2 articles are worth a look:
{% medium https://medium.com/swlh/chrome-v83-enables-js-module-support-for-sharedworkers-starting-a-new-era-for-multi-browser-dbb20366bddf %}

{% medium https://medium.com/swlh/neo-mjs-v1-2-5-support-for-sharedworkers-including-firefox-b31f144cea3f %}

## 4. Benefits of the Shared App Worker
Of course you only need to load a framework once for all connected Browser Windows.

It might sound trivial, but the biggest benefit is probably that the App Worker is using just one IdGenerator. Meaning: all Components for all of your Apps have unique IDs.
The VDom worker is using just one IdGenerator as well, which ensures that all DOM nodes for all Apps have unique IDs as well.

The combination of both makes it super easy to move Components or entire Component Trees around different Browser Windows. Even better: we can just move the virtual DOM around while keeping & re-using the same Javascript instances.

Example:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/jzjym9wdsbxexq1atlb0.png)

https://github.com/neomjs/neo/blob/dev/apps/sharedcovid/view/MainContainerController.mjs#L410

We are just removing a view from a parent view inside one Browser Window and adding it to another view of an App running inside a different Browser Window. As simple as this.

## 5. How can DOM events still work?
1. DOM events were de-coupled right from the start, since their handlers live inside the Shared App Worker. This is also the case for the non version.
2. To not pollute the DOM, neo.mjs is sticking to global event listeners. E.g. there is just 1 click listener on the document body node.
3. Workers can not access the DOM, but it is also not possible to send anything which is DOM related to a Worker. E.g. DOM events contain a target DOM element. Even simple things like a DOM Rect can not get passed. This is why DOM events get virtualised (converted into JSON).
4. When a DOM event arrives inside the App Worker, the Component Manager will map it to all Components inside the Component Tree upwards.

Now inside the Shared App Worker, there is just 1 Component Manager which contains references to all Components of all Apps inside all connected Windows.

This is the reason why this one works out of the box.

## 6. How do the Apps inside the Shared Covid App look like?
This demo App is very similar to the non shared Covid App.

All views & controllers are inside the main App:

https://github.com/neomjs/neo/tree/dev/apps/sharedcovid

I created new Apps (entry points) for each Popup Window. These Apps are as minimal as I can think of:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wwoiqmo3lpwr836kiad0.png)

=> an empty Viewport. Not even an own Controller.

We are just adding Component Trees from the Main App into the empty Viewports and their original Component Controllers can handle it.

Of course this is just one way to use the setup. You could create 2 or more advanced Apps which can communicate directly. The setup is completely flexible to enable you to build what fits best for your use case(s).

## 7. How can we position Popup Windows to exactly overlay an existing Component?
This part was actually a bit tricky. There is a Chromium bug which can mess with the positioning in case your Main Window is not on your Main Screen.

To save you from this pain point:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zqzlz28yid8n12veczkw.png)

https://github.com/neomjs/neo/blob/dev/apps/sharedcovid/view/MainContainerController.mjs#L209

This “hack” worked extremely well for me.

## 8. What are the use cases for the Shared Workers setup?
The obvious use case are Apps which are supposed to run on multiple Screens.

A Trading App would be a very good example.

**There is a lot more though:**

In case you want to create Native Apps which contain multiple Browser Windows. Think about developing an IDE. Or something like Firefox OS. You could manage Chromebooks.

On mobile: A Native Shell containing multiple WebViews. You don’t see it as a user, but many Apps already overlay multiple “Browsers” for performance reasons.

Be creative!

## 9. Webpack based dist versions?
In case you read my last article, you most likely noticed that the first demo App using Shared Workers was running inside Firefox as well.

The reason is, that only one Main Thread was connected.
So far the build processes for the App realm were always a combination of the App worker and your Apps.

Now, when we want to connect multiple Main Threads, we need a separate build for the App worker and your Apps.

I think webpack is not yet designed to handle separate builds which are supposed to play along inside the same realm.
I created the following ticket:

https://github.com/webpack/webpack/issues/11049

I could really use your help on this one!

## 10. What about Webkit / Safari?
I have some very good news for you on this one :)

The Webkit team is now willing to reconsider adding support for Shared Workers. They are looking for more input and feedback before doing it. So, in case you care about it, please take a moment and add a comment on their ticket:

https://bugs.webkit.org/show_bug.cgi?id=149850

At the same time, they put the ticket to support JS modules inside the worker scope into “inRadar”. In case you would like to use the neo.mjs dev mode directly in Safari as well, please write a comment there as well:

https://bugs.webkit.org/show_bug.cgi?id=164860

## 11. Online Demo
Please keep in mind that as long as the build processes are not getting enhanced, this one is limited to Chrome v83+. It does not work on mobile devices yet.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/guf19y4tqyvngztf6w3i.png)

There you go. Enjoy!

https://neomjs.github.io/pages/node_modules/neo.mjs/apps/sharedcovid/index.html#mainview=table

As usual, you can find all other neo.mjs online examples here:

https://neomjs.github.io/pages/

## 12. What is coming next?
I want to finish the Tutorial Part 2 on how to create the Covid App next, to give you a better chance to get up to speed.

One epic item is to support dynamic imports for the app realm. While this works out of the box for the development mode, it will be a lot of work to make this happen inside the webpack based dist env. Think about split-chunks. This one is tied to the webpack ticket (Section 9 in this article).

The main repo readme needs an update (rewrite).

At this point the neo.mjs project is in need of a real website => the online demo page is no longer sufficient.

## 13. Would you like a tutorial on how to transform the Covid App into the new Shared Covid App version?
After Part 2 of the tutorial is done, I could create a Part 3 to show you the steps in detail.

I already created a ticket for this one here:

https://github.com/neomjs/neo/issues/747

This one will be a lot of work on my end, so I will only do it in case there are at least 20 comments to show that you are interested in this one. A simple “+1” is enough.

Sounds fair?

## 14. Close to the end game?
Since I did mention this inside Section 2.3 you might be interested in knowing what else we could do to further enhance it.

I want to add 2 more changes:
1. Make the Data Worker optional
2. Make the VDom Worker optional

For “simple” Apps it might make sense to just import the logic of these 2 Workers into the App realm.

We could also create an option to spawn multiple versions of the VDom Worker, in case this one becomes a bottle neck. E.g. 2 Vdom Workers, one using odd and the second using even IDs. Easy, right?

Workers don’t have access to the DOM, but they can access Canvas directly. In case we want to work on the Charting side of things, we could create another Worker to handle this part.

## 15. Final thoughts
I hope that at this point you got an idea about the scope of neo.mjs as well as the potential moving forward. For me the current enhancement to enable SPAs running inside multiple Browser Windows is not only impressive, but mind blowing.

neo.mjs is an Open Source project (the entire code base as well as all examples are using the MIT license).
{% github neomjs/neo %}

Meaning: you can use it for free.

It will stay like this.

However, the project is in need for more contributors as well as sponsors.

A lot(!) more items & ideas are on the roadmap.

If you want to contribute to a lovely Open Source project, this would be highly appreciated.

In case the project has or will have business value for your company: signing up as a sponsor can allow me to put more time into it, resulting in a faster delivery time for new things.

**Famous last words:** Isn’t the SW architecture the hell of a way to implement Application Dialogs?

**Your feedback on this article and scalable frontend architectures in general would mean a lot to me!**

Best regards & happy coding,
Tobias