Create blazing fast multithreading User Interfaces outside of nodejs

Tobias Uhlig

Aug 5 · 9 min read

While many frontend developers are familiar with Angular, React or Vue, they are missing out on the next level of what is possible inside modern Browsers.


  1. Introduction
  2. How can WebWorkers help?
  3. Multi Screen Apps
  4. Multi Screen Apps on mobile?
  5. How can we include our App code into a worker?
  6. What are remote methods?
  7. What is the problem with templates?
  8. Reducing the DOM by 80%+ in average
  9. ES8+ code directly inside the Browser
  10. Get your App documentation out of the box
  11. Got curious? What is neo.mjs?
  12. How to get up to speed?
  13. What is coming next?

1. Introduction

The Web is moving forward at a fast speed. Are you too?

When Angular and React got introduced, Browsers had a poor support for ES6+ features. As a result, the entire UI development got moved into nodejs.

Browsers catched up and can handle JS modules as well as several ESnext features on their own, so it is time for a change.

While it might be convenient to use custom files like .vue, your Browser can not understand them. You need a build process, transpilation or at least a hot module replacement to get your code changes into the Browser. This takes time and you can not debug your real code.

Most Apps today still use the following setup:

Image for post

Some Apps are using WebWorkers, to move out single expensive tasks, but this is by far not enough. The main thread is responsible for DOM manipulations → the rendering queue. Getting it as idle and light-weight as possible needs to be a main goal.

2. How can WebWorkers help?

What we want to get out of this dilemma are the following setups:

Image for post

A framework and your App logic can and should run inside a Worker.

With this setup, there are literally zero background tasks inside the main thread. Nothing can slow down your UI transitions or even freeze your UIs.

With a simple switch to use SharedWorkers instead of normal workers, we can even connect multiple main threads.

3. Multi Screen Apps

While this is a niche, it can create an amazing UX to expand Single Page Apps into multiple Browser Windows.

Please watch this 95s demo video:

All Browser Windows share the backend data.

All Windows can communicate directly (without a backend connection).

You can move entire Component Trees around, even with keeping the same JS instances.

4. Multi Screen Apps on mobile?

This will be a big deal. Many mobile Apps are using a native shell, containing multiple WebViews. The SharedWorkers setup can work here as well, resulting in loading framework & App related code only once and more importantly to move Component Trees around WebViews as well.

The Webkit Team (Safari) is thinking about picking this topic up again.

GitHub has added weight to the ticket:

You really should do the same!

5. How can we include our App code into a worker?

Your index.html file will look like this (dev mode):

Image for post

You just pull in the main thread code of the framework (40KB).

You don’t need to manually create this file.

All you need to do is:

npx neo-app

or clone the repo and use the create-app program.

This will import the WorkerManager & generate the Workers for you, register remote methods and load your App code into the App worker.

If you take a closer look into the files, you will notice that all virtual dom updates get queued into requestAnimationFrame.

You can create a similar setup on your own or let neo.mjs take care of this part for you.

6. What are remote methods?

In case you want to communicate between Workers or to the main thread, you might need an abstraction layer around the required postMessages.

This can be a lot of work, especially in case you want to optionally support SharedWorkers as well.

If you run your own code inside a Worker (inside the neo.mjs context the App Worker), you will notice that:

  1. window is undefined
  2. window.document is undefined

You simply can not access the real DOM.

This makes using virtual DOM mandatory.

Still, there are edge use cases where you want to directly access the DOM. Scrolling is a good one.

Image for post

As the file name suggests, Neo.main.DomAccess is only available inside the main thread. It does not get imported into the App Worker.

Image for post

All you need to do is adding the methods you want to expose to different workers or the main thread.

Now, inside your scope (the App Worker), you can call these remote methods as promises. They will get mapped into the Neo namespace out of the box.

Image for post

As easy as this.

7. What is the problem with templates?

Image for post

Angular, React & Vue are using pseudo XML string based templates.

These templates need to get parsed, which is expensive. You can do this at build time (e.g. Svelte), but then you can no longer easily modify them at build time. It is possible (e.g. manually manipulating a JSX output), but at this point no longer consistent to use.

Templates are a mix of dom markup, loops, if-statements and variables. It can slow down your productivity (e.g. scoping) and limit you.

While this topic is controversial for sure, neo.mjs got rid of templates.

Instead, persistent JSON-like structures are in place.

Image for post

JSON-like means: nested JS objects & arrays, which you can change any way you want during the full component life cycle.

They do not contain variables, if-statements or loops.

You might agree, that JS is just perfect to work with these structures.

You never need to parse them.

Image for post

Mapping config changes to the vdom is fairly trivial.

You can add flags to specific nodes and use the VdomUtil to fetch them.

You can add the removeDom flag to any node, which will remove the node (or tree) from the real DOM while keeping your vdom structure in place.

8. Reducing the DOM by 80%+ in average

This just mentioned removeDom attribute is incredibly powerful. I just enhanced card layouts to remove all inactive cards from the DOM by default.

You can change it using a config, in case you like to.

While nodes which have the style display:’none’ will get excluded from Browser layout calculations, they are still around.

Removing them reduces the the DOM, which then reduces the memory footprint of your main thread. A lot.

This is an easy way to further boost the performance.

Image for post

As you can see, the calendar has 4 main views (Day, Week, Month, Year), but only one is inside the DOM.

The SideBar will get removed after collapsing it.

The SettingsContainer will get removed after collapsing it.

Settings contain a TabPanel with 5 tabs, only 1 tab body is inside the DOM at any time.

Your JS instances of all views still exist. You can still map changes to the virtual dom and put it back at any given time. In different spots of your App, if you like to.

Your state will keep in place, meaning in this use case: you can change settings for views, which are no longer inside the DOM.

9. ES8+ code directly inside the Browser

The main item of this article.

Take a look at the following screenshot:

Image for post

The important things are:

  1. You can see the threads (Main, App, Data, Vdom)
  2. The WeekComponent.mjs file is located inside the App Worker
  3. You can see the real code (this is not a source map)
  4. You can see the custom class system enhancements: A full blown config system.

This leads to an unmatched debugging experience:

Change your code, reload the Browser and this is it.

No builds or transpilations are needed.

While JS modules are fully supported inside all major Browsers, they are still not supported inside the Worker Scope for Firefox & Safari. The dev teams are working on it.

For neo.mjs, there are Webpack based dist versions in place, which do run fine in Firefox & Safari.

The important part is, that Chrome fully supports it, so you can use the dev mode there and once it is bug free, test the dist/development version in other Browsers.

10. Get your App documentation out of the box

While many libs or frameworks provide a Docs App, this one will only provide documentation views for the framework files.

Using neo.mjs, you will also get documentation views for your own App related code. All you need to do is adding doc comments to your configs, methods and events.

Image for post

11. Got curious? What is neo.mjs?

neo.mjs is an Open Source project (the entire code base as well as all examples & demo apps are using the MIT license).

Website App:

Image for post


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 will allow me to put more time into it, resulting in a faster delivery time for new things.

12. How to get up to speed?

The probably easiest way to learn neo.mjs, is following the first 2 tutorials on how to create the Covid Dashboard App from scratch.

12. What is coming next?

Right now, I am focussing on the new Calendar Component for the v1.4 release. The goal is to create an excellent UX, ahead of the GMail or native MacOS calendars.

You can take a look at the current progress here:

As the next step, I will polish the events a bit more and start with the drag & drop implementation afterwards.

Once DD is in place, the next item are in app dialogs. We have to grab them by the header to move them around or resize them.

A demo to move dialogs around different Browser Windows is possible and should be stunning.

Once this is done, I will further enhance the Grid / Table implementations. An easier access to column filtering, moving columns around, hiding columns etc.

You can definitely influence the roadmap!

Feel free to use the issues tracker:

Feedback appreciated!

Best regards & happy coding,

Author: admin

Leave a Reply

Your email address will not be published.