Create Blazing Fast Multi-Threading User Interfaces Outside Node.js
Most front-end developers are missing out on the next level of what’s possible in modern browsers

Contents
- Introduction
- How Can WebWorkers Help?
- Multi-Screen Apps
- Multi-Screen Apps on Mobile
- How Can We Include Our App code in a Worker?
- What Are Remote Methods?
- What’s the Problem With Templates?
- Reducing the DOM by 80%+ on Average
- ES8+ Code Directly Inside the Browser
- Get Your App Documentation Out of the Box
- Got curious? What is neo.mjs?
- How to Get Up to Speed?
- What’s Coming Next?
1. Introduction
The web moves forward at a great speed — but do you?
When Angular and React were introduced, browsers had poor support for ES6+ features. As a result, the entire UI development got moved into node.js.
Browsers caught up and can now handle JavaScript modules as well as several ESnext features on their own — so it’s time for a change.
While it might be convenient to use custom files like .vue, your Browser can’t 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’t debug your real code.
Most apps today still use the following setup:

Some are using WebWorker
s, to move out single expensive tasks, but this is not nearly enough. The main thread is responsible for DOM manipulations — the rendering queue. Getting that as idle and lightweight as possible must be the main goal.
2. How Can WebWorkers Help?
What we want to get out of this dilemma are the following setups:

A framework and your app logic can and should run inside a worker.
With this setup, there are zero background tasks inside the main thread. Nothing can slow down or even freeze your UI transitions.
With a simple switch to use SharedWorker
s instead of normal workers, we can even connect multiple main threads.
3. Multi-Screen Apps
While this is a niche use, it can create an amazing UX to expand single-page apps into multiple browser windows.
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 keeping the same JavaScript instances.
4. Multi-Screen Apps on Mobile
This will be a big deal. Many mobile apps are using a native shell, containing multiple WebView
s. The SharedWorker
s setup can work here as well, resulting in loading framework and app-related code only once and, more importantly, to move component trees around WebView
s 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 in a Worker?
Your index.html
file will look like this (dev mode):

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
and generate the Worker
s for you, register remote methods and load your app code into the app worker.
https://github.com/neomjs/neo/blob/dev/src/worker/Manager.mjs
If you take a closer look into the files, you’ll 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 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 SharedWorker
s as well.
If you run your own code inside a Worker
(inside the neo.mjs
context the app worker), you will notice that:
window
is undefinedwindow.document
is undefined
You simply cannot access the real DOM. This makes using a virtual DOM mandatory.
Still, there are edge use cases where you want to directly access the DOM. Scrolling is a good one:

https://github.com/neomjs/neo/blob/dev/src/main/DomAccess.mjs#L402
As the file name suggests, Neo.main.DomAccess
is only available inside the main thread. It’s not imported into the app worker.

All you need to do is add 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’ll be mapped to the Neo namespace out of the box.

https://github.com/neomjs/neo/blob/dev/src/calendar/view/MonthComponent.mjs#L216
It’s as easy as that.
7. What’s the Problem With Templates?

Angular, React, and Vue all use pseudo XML string based templates. These templates have to be 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. They can slow down your productivity (e.g. scoping) and limit you.
While this topic is certainly controversial, neo.mjs got rid of templates. Instead, persistent JSON-like structures are in place:

https://github.com/neomjs/neo/blob/dev/src/calendar/view/MonthComponent.mjs#L99
JSON-like means: nested JS objects & arrays, which you can change any way you want during the full component lifecycle. They do not contain variables, if-statements, or loops.
You may agree that JavaScript is perfect to work with these structures.
You never need to parse them:

https://github.com/neomjs/neo/blob/dev/src/calendar/view/MonthComponent.mjs#L232
Mapping config changes to the vdom
is fairly trivial. You can add flags to specific nodes and use 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%+ on Average
This removeDom
attribute that we just mentioned is incredibly powerful. I just used it to enhance card layouts to remove all inactive cards from the DOM by default.
You can also change it using a config, if that’s something you’d like to do.
While nodes which have the style display:’none’
will be excluded from browser layout calculations, they’re still around.
Removing them reduces the DOM, which then reduces the memory footprint of your main thread a lot.
This is an easy way to further boost performance.

As you can see, the calendar has 4 main views (Day, Week, Month, Year), but only one is inside the DOM.
The SideBar
will be removed after collapsing it.
The SettingsContainer
will be removed after collapsing it.
Settings contain a TabPanel
with five tabs. Only one tab body is inside the DOM at any time.
Your JavaScript 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 want.
Your state will keep in place, meaning that, in this case, we can change settings for views, which are no longer inside the DOM.
9. ES8+ Code Directly Inside the Browser
Take a look at the following screenshot:

The important things to note here are:
- You can see the threads (Main, App, Data, Vdom).
- The WeekComponent.mjs file is located inside the App Worker.
- You can see the real code (this is not a source map).
- 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 JavaScript modules are fully supported inside all major browsers, they’re still not supported inside the worker scope for Firefox and Safari. The development 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.

11. What’s neo.mjs?
neo.mjs is an open-source project (the entire code base, as well as all examples and demo apps, use the MIT license).
Website app:

Repository:
This means you can use it for free and it will stay like this. However, the project is in need of more contributors as well as sponsors.
Many more items and ideas are on the roadmap.
If you want to contribute to a fantastic open-source project, you would be appreciated.
If 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 and drop implementation afterward.
Once drag and drop is in place, the next item is in-app dialogs. We have to be able 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 — easier access to column filtering, moving columns around, hiding columns, etc.
You can definitely influence the roadmap!
Feel free to use the issues tracker. All feedback is appreciated!
Best regards and happy coding!