neo.mjs 6.35.0 → 6.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/apps/ServiceWorker.mjs +2 -2
- package/apps/colors/view/ViewportController.mjs +27 -17
- package/apps/portal/view/home/FeatureSection.mjs +209 -0
- package/apps/portal/view/home/parts/Colors.mjs +34 -84
- package/apps/portal/view/home/parts/Features.mjs +1 -1
- package/apps/portal/view/home/parts/Helix.mjs +36 -95
- package/apps/portal/view/home/parts/How.mjs +31 -48
- package/apps/portal/view/learn/ContentView.mjs +53 -20
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/videoMove/MainContainer.mjs +3 -4
- package/examples/videoMove/neo-config.json +2 -1
- package/package.json +2 -2
- package/resources/data/deck/learnneo/pages/benefits/Multi-Threading.md +221 -0
- package/resources/data/deck/learnneo/tree.json +12 -13
- package/resources/scss/src/apps/portal/Viewport.scss +19 -0
- package/resources/scss/src/apps/portal/home/ContentBox.scss +9 -2
- package/resources/scss/src/apps/portal/home/FeatureSection.scss +68 -0
- package/resources/scss/src/apps/portal/home/parts/Colors.scss +1 -5
- package/resources/scss/src/apps/portal/home/parts/Helix.scss +1 -7
- package/resources/scss/src/apps/portal/home/parts/How.scss +0 -22
- package/resources/scss/src/apps/portal/learn/ContentView.scss +22 -10
- package/resources/scss/src/code/LivePreview.scss +1 -0
- package/resources/scss/src/examples/videoMove/MainContainer.scss +31 -0
- package/resources/scss/theme-neo-light/design-tokens/Core.scss +1 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/Neo.mjs +5 -1
- package/src/code/LivePreview.mjs +16 -19
- package/src/component/Toast.mjs +8 -8
- package/src/component/Video.mjs +22 -28
- package/src/component/wrapper/AmChart.mjs +15 -8
- package/src/main/addon/AmCharts.mjs +1 -1
- package/src/manager/DomEvent.mjs +1 -1
- package/src/worker/App.mjs +1 -1
- package/src/worker/mixin/RemoteMethodAccess.mjs +1 -3
- package/resources/data/deck/learnneo/pages/WhyNeo-Multi-Threaded.md +0 -15
@@ -1,10 +1,10 @@
|
|
1
|
-
import
|
1
|
+
import FeatureSection from '../FeatureSection.mjs';
|
2
2
|
|
3
3
|
/**
|
4
4
|
* @class Portal.view.home.parts.How
|
5
|
-
* @extends Portal.view.home.
|
5
|
+
* @extends Portal.view.home.FeatureSection
|
6
6
|
*/
|
7
|
-
class How extends
|
7
|
+
class How extends FeatureSection {
|
8
8
|
static config = {
|
9
9
|
/**
|
10
10
|
* @member {String} className='Portal.view.home.parts.How'
|
@@ -16,56 +16,39 @@ class How extends BaseContainer {
|
|
16
16
|
*/
|
17
17
|
cls: ['portal-home-parts-how'],
|
18
18
|
/**
|
19
|
-
* @member {Object}
|
19
|
+
* @member {Object[]} contentItems
|
20
20
|
*/
|
21
|
-
|
22
|
-
/**
|
23
|
-
* @member {Object[]} items
|
24
|
-
*/
|
25
|
-
items: [{
|
21
|
+
contentItems: [{
|
26
22
|
ntype : 'container',
|
27
|
-
cls :
|
28
|
-
flex : '1',
|
29
|
-
style : {padding: '2rem'},
|
30
|
-
layout: {ntype: 'vbox', align: 'center', pack: 'center'},
|
31
|
-
items : [{
|
32
|
-
cls : ['neo-h1'],
|
33
|
-
flex: 'none',
|
34
|
-
html: 'How?',
|
35
|
-
tag : 'h1'
|
36
|
-
}, {
|
37
|
-
cls : ['neo-h2'],
|
38
|
-
flex: 'none',
|
39
|
-
html: 'How Does Neo.mjs Do It?',
|
40
|
-
tag : 'h2'
|
41
|
-
}, {
|
42
|
-
cls : ['neo-h3'],
|
43
|
-
flex: 'none',
|
44
|
-
tag : 'p',
|
45
|
-
|
46
|
-
html: [
|
47
|
-
'When a Neo.mjs app launches three webworkers are spawned: one that holds app logic, one for tracking delta DOM updates, ',
|
48
|
-
'and one for backend calls. Each webworker runs in its own thread, and thus, in its own processor core. ',
|
49
|
-
'This means these processes run in parallel: your app logic isn\'t affected by DOM changes or ',
|
50
|
-
'by Ajax or socket calls. If you have processor-intensive tasks you can easily run them in their own threads.'
|
51
|
-
].join('')
|
52
|
-
}]
|
53
|
-
}, {
|
54
|
-
ntype : 'container',
|
55
|
-
cls : 'portal-content-wrapper',
|
56
|
-
flex : '2',
|
23
|
+
cls : 'portal-content-container',
|
57
24
|
layout: 'fit',
|
58
25
|
items : [{
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
items : [{
|
63
|
-
cls : 'neo-worker-setup',
|
64
|
-
tag : 'element-loader',
|
65
|
-
vdom: {src: '../../resources/images/workers-focus.svg'}
|
66
|
-
}]
|
26
|
+
cls : 'neo-worker-setup',
|
27
|
+
tag : 'element-loader',
|
28
|
+
vdom: {src: '../../resources/images/workers-focus.svg'}
|
67
29
|
}]
|
68
|
-
}]
|
30
|
+
}],
|
31
|
+
/**
|
32
|
+
* @member {String} headline='How?'
|
33
|
+
*/
|
34
|
+
headline: 'How?',
|
35
|
+
/**
|
36
|
+
* @member {String} learnMoreRoute='#/learn/WhyNeo-Speed'
|
37
|
+
*/
|
38
|
+
learnMoreRoute: '#/learn/benefits.Multi-Threading',
|
39
|
+
/**
|
40
|
+
* @member {String} paragraph
|
41
|
+
*/
|
42
|
+
paragraph: [
|
43
|
+
'When a Neo.mjs app launches 3+ webworkers are spawned: one that holds App logic, one for calculating delta DOM ',
|
44
|
+
'updates, and one for backend calls. Each webworker runs in its own thread, and thus, in its own processor core. ',
|
45
|
+
'This means these processes run in parallel: DOM updates and transitions are not affected by your App logic ',
|
46
|
+
'and always run smooth. Every processor-intensive task runs outside the Main Thread.'
|
47
|
+
].join(''),
|
48
|
+
/**
|
49
|
+
* @member {String} subHeadline='How does Neo.mjs do it?'
|
50
|
+
*/
|
51
|
+
subHeadline: 'How does Neo.mjs do it?'
|
69
52
|
}
|
70
53
|
}
|
71
54
|
|
@@ -3,10 +3,11 @@ import LivePreview from '../../../../src/code/LivePreview.mjs';
|
|
3
3
|
import {marked} from '../../../../node_modules/marked/lib/marked.esm.js';
|
4
4
|
|
5
5
|
const
|
6
|
-
labCloseRegex
|
7
|
-
labOpenRegex
|
8
|
-
preJsRegex
|
9
|
-
preNeoRegex
|
6
|
+
labCloseRegex = /<!--\s*\/lab\s*-->/g,
|
7
|
+
labOpenRegex = /<!--\s*lab\s*-->/g,
|
8
|
+
preJsRegex = /<pre\s+data-javascript\s*>([\s\S]*?)<\/pre>/g,
|
9
|
+
preNeoRegex = /<pre\s+data-neo\s*>([\s\S]*?)<\/pre>/g,
|
10
|
+
preNeoComponentRegex = /<pre\s+data-neo-component\s*>([\s\S]*?)<\/pre>/g;
|
10
11
|
|
11
12
|
/**
|
12
13
|
* @class Portal.view.learn.ContentView
|
@@ -108,19 +109,25 @@ class ContentView extends Component {
|
|
108
109
|
async doFetchContent(record) {
|
109
110
|
let me = this,
|
110
111
|
path = me.getModel().getData('contentPath'),
|
111
|
-
content, data, html, modifiedHtml, neoDivs;
|
112
|
+
content, data, html, modifiedHtml, neoComponents, neoDivs;
|
112
113
|
|
113
|
-
path += `/pages/${record.id}.md`;
|
114
|
+
path += `/pages/${record.id.replaceAll('.', '/')}.md`;
|
114
115
|
|
115
116
|
if (record.isLeaf && path) {
|
116
|
-
data
|
117
|
-
content
|
118
|
-
content
|
119
|
-
content
|
120
|
-
modifiedHtml
|
121
|
-
|
122
|
-
|
123
|
-
|
117
|
+
data = await fetch(path);
|
118
|
+
content = await data.text();
|
119
|
+
content = me.updateContentSectionsStore(content); // also replaces ## with h2 tags
|
120
|
+
content = `# ${record.name}\n${content}`;
|
121
|
+
modifiedHtml = await me.highlightPreContent(content);
|
122
|
+
neoComponents = {};
|
123
|
+
neoDivs = {}
|
124
|
+
|
125
|
+
// Replace <pre neo-component></pre> with <div id='neo-component-x'/>
|
126
|
+
// and create a map keyed by ID, whose value is the javascript
|
127
|
+
// from the <pre>
|
128
|
+
modifiedHtml = me.extractNeoComponents(modifiedHtml, neoComponents);
|
129
|
+
|
130
|
+
// Replace <pre data-neo></pre> with <div id='neo-preview-1'/>
|
124
131
|
// and create a map keyed by ID, whose value is the javascript
|
125
132
|
// from the <pre>
|
126
133
|
modifiedHtml = me.extractNeoContent(modifiedHtml, neoDivs);
|
@@ -132,7 +139,20 @@ class ContentView extends Component {
|
|
132
139
|
|
133
140
|
me.html = html;
|
134
141
|
|
135
|
-
await me.timeout(
|
142
|
+
await me.timeout(100);
|
143
|
+
|
144
|
+
Object.keys(neoComponents).forEach(key => {
|
145
|
+
Neo.create({
|
146
|
+
appName : me.appName,
|
147
|
+
autoMount : true,
|
148
|
+
autoRender : true,
|
149
|
+
className : 'Neo.component.Base',
|
150
|
+
parentComponent: me,
|
151
|
+
parentId : key,
|
152
|
+
windowId : me.windowId,
|
153
|
+
...neoComponents[key]
|
154
|
+
})
|
155
|
+
});
|
136
156
|
|
137
157
|
Object.keys(neoDivs).forEach(key => {
|
138
158
|
// Create LivePreview for each iteration, set value to neoDivs[key]
|
@@ -161,15 +181,28 @@ class ContentView extends Component {
|
|
161
181
|
* @param {Object} map
|
162
182
|
* @returns {String}
|
163
183
|
*/
|
164
|
-
|
165
|
-
// 1. Replace <pre data-neo> with <div id='neo-
|
184
|
+
extractNeoComponents(htmlString, map) {
|
185
|
+
// 1. Replace <pre data-neo-component> with <div id='neo-learn-content-component-x'/>
|
166
186
|
// and update map with key/value pairs, where the key is the ID and the value is the <pre> contents.
|
187
|
+
// Replace the content with tokens, and create a promise to update the corresponding content
|
188
|
+
return htmlString.replace(preNeoComponentRegex, (match, preContent) => {
|
189
|
+
const key = Neo.core.IdGenerator.getId('learn-content-component');
|
190
|
+
map[key] = JSON.parse(preContent);
|
191
|
+
return `<div id="${key}"></div>`
|
192
|
+
})
|
193
|
+
}
|
167
194
|
|
168
|
-
|
169
|
-
|
195
|
+
/**
|
196
|
+
* @param {String} htmlString
|
197
|
+
* @param {Object} map
|
198
|
+
* @returns {String}
|
199
|
+
*/
|
200
|
+
extractNeoContent(htmlString, map) {
|
201
|
+
// 1. Replace <pre data-neo> with <div id='neo-pre-live-preview-x'/>
|
202
|
+
// and update map with key/value pairs, where the key is the ID and the value is the <pre> contents.
|
170
203
|
// Replace the content with tokens, and create a promise to update the corresponding content
|
171
204
|
return htmlString.replace(preNeoRegex, (match, preContent) => {
|
172
|
-
const key =
|
205
|
+
const key = Neo.core.IdGenerator.getId('pre-live-preview');
|
173
206
|
map[key] = preContent;
|
174
207
|
return `<div id="${key}"></div>`
|
175
208
|
})
|
@@ -9,11 +9,12 @@ import Viewport from '../../src/container/Viewport.mjs';
|
|
9
9
|
class MainContainer extends Viewport {
|
10
10
|
static config = {
|
11
11
|
className: 'Neo.examples.videoMove.MainContainer',
|
12
|
+
cls : ['examples-videomove-maincontainer'],
|
12
13
|
layout : {ntype: 'vbox', align: 'stretch'},
|
13
|
-
style : {padding: '50px'},
|
14
14
|
|
15
15
|
items: [{
|
16
16
|
ntype : 'container',
|
17
|
+
cls : ['video-wrapper'],
|
17
18
|
layout: {ntype: 'hbox', align: 'stretch'},
|
18
19
|
|
19
20
|
itemDefaults: {
|
@@ -23,7 +24,6 @@ class MainContainer extends Viewport {
|
|
23
24
|
|
24
25
|
items: [{
|
25
26
|
reference: 'container-1',
|
26
|
-
style : {backgroundColor: 'rgb(139,166,255)', padding: '50px'},
|
27
27
|
|
28
28
|
items: [{
|
29
29
|
module : Video,
|
@@ -31,8 +31,7 @@ class MainContainer extends Viewport {
|
|
31
31
|
url : 'https://video-ssl.itunes.apple.com/itunes-assets/Video125/v4/a0/57/54/a0575426-dd8e-2d25-bdf3-139702870b50/mzvf_786190431362224858.640x464.h264lc.U.p.m4v'
|
32
32
|
}]
|
33
33
|
}, {
|
34
|
-
reference: 'container-2'
|
35
|
-
style : {backgroundColor: 'rgb(139,166,255)', marginLeft: '50px', padding: '50px'}
|
34
|
+
reference: 'container-2'
|
36
35
|
}]
|
37
36
|
}, {
|
38
37
|
ntype : 'container',
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "neo.mjs",
|
3
|
-
"version": "6.
|
3
|
+
"version": "6.37.0",
|
4
4
|
"description": "The webworkers driven UI framework",
|
5
5
|
"type": "module",
|
6
6
|
"repository": {
|
@@ -48,7 +48,7 @@
|
|
48
48
|
"chalk": "^5.3.0",
|
49
49
|
"clean-webpack-plugin": "^4.0.0",
|
50
50
|
"commander": "^12.1.0",
|
51
|
-
"cssnano": "^7.0.
|
51
|
+
"cssnano": "^7.0.5",
|
52
52
|
"envinfo": "^7.13.0",
|
53
53
|
"fs-extra": "^11.2.0",
|
54
54
|
"highlightjs-line-numbers.js": "^2.8.0",
|
@@ -0,0 +1,221 @@
|
|
1
|
+
## How many Cores are on a Computer or Smartphone?
|
2
|
+
In case you are using a Mac, you can click on the top-left Apple Icon,
|
3
|
+
then on "About this Mac" and it will show you something like:
|
4
|
+
|
5
|
+
> Processor 3,2 GHz 8-Core Intel Xeon W
|
6
|
+
|
7
|
+
With the Apple silicon series even more: "Apple M2 Ultra" provides 24 CPU cores.
|
8
|
+
|
9
|
+
An iPhone has 6 CPU cores.
|
10
|
+
|
11
|
+
TL-BR: Every computer or smartphone has several cores available.
|
12
|
+
|
13
|
+
This means that you can run multiple threads concurrently.
|
14
|
+
|
15
|
+
> Would you build a car using just one engine cylinder?
|
16
|
+
|
17
|
+
If your answer is "Of course not! It would be way slower!",
|
18
|
+
then you should read this article carefully.
|
19
|
+
|
20
|
+
## How many Cores does a Browser use?
|
21
|
+
|
22
|
+
On its own, a Browser will just use ***one*** core per tab / window.
|
23
|
+
|
24
|
+
Meaning: your Angular or React apps look like this:
|
25
|
+
|
26
|
+

|
27
|
+
|
28
|
+
The more JavaScript tasks are running inside your app, the slower it will get.
|
29
|
+
The worst scenario is a complete UI freeze where your one core is at 100%
|
30
|
+
and all other cores are completely idle.
|
31
|
+
|
32
|
+
This is ***not*** scalable at all.
|
33
|
+
|
34
|
+
_[Side note] In case you are creating simple, small and rather static websites or apps, this setup can be sufficient._
|
35
|
+
|
36
|
+
## Web Workers API
|
37
|
+
|
38
|
+
> Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread
|
39
|
+
> of a web application. The advantage of this is that laborious processing can be performed in a separate thread,
|
40
|
+
> allowing the main (usually the UI) thread to run without being blocked/slowed down.
|
41
|
+
|
42
|
+
Source: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">MDN: Web Workers API</a>
|
43
|
+
|
44
|
+
> The W3C and WHATWG envision web workers as long-running scripts that are not interrupted by scripts that respond to
|
45
|
+
> clicks or other user interactions. Keeping such workers from being interrupted by user activities should allow
|
46
|
+
> Web pages to remain responsive at the same time as they are running long tasks in the background.
|
47
|
+
>
|
48
|
+
> The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
|
49
|
+
|
50
|
+
Source: <a target="_blank" href="https://en.wikipedia.org/wiki/Web_worker">Wikipedia: Web worker</a>
|
51
|
+
|
52
|
+
So, while JavaScript itself is single-threaded as a language, using workers enables us to use multiple cores
|
53
|
+
concurrently and end this scalability nightmare.
|
54
|
+
|
55
|
+
Let the following quote really sink in:
|
56
|
+
> The simplest use of workers is for performing a computationally expensive task without interrupting the user interface.
|
57
|
+
|
58
|
+
It leads to the question: "What is the most expensive task?"
|
59
|
+
|
60
|
+
The answer is simple: "An UI Framework or Library itself as well as the apps we build with it."
|
61
|
+
|
62
|
+
This is leading to the idea: Let us move everything we can out of the main thread,
|
63
|
+
so that this one can purely focus on what it is intended to do: manipulating the DOM.
|
64
|
+
|
65
|
+
In case your apps are no longer running in main, there is nothing left which can slow down or block your UI or create memory leaks.
|
66
|
+
|
67
|
+
This thought is leading to the following concept:
|
68
|
+
|
69
|
+
## The "An Application Worker being the Main Actor" Paradigm
|
70
|
+
<a target="_blank" href="https://github.com/surma">Surma</a>
|
71
|
+
wrote a very nice article on how the Actor Model can and should get applied to the web
|
72
|
+
<a target="_blank" href="https://surma.dev/things/actormodel/">here</a> in 2017.
|
73
|
+
At this point, we already had the Neo.mjs setup running, but sadly not open-sourced it yet.
|
74
|
+
|
75
|
+
> It struck me that the Actor Model could work on the web. The more I thought about it, the more it seems like a natural fit.
|
76
|
+
|
77
|
+
In case you are not familiar with what an "actor" means, definitely read it first.
|
78
|
+
|
79
|
+
To resolve this performance bottleneck, we want to get main threads as idle as possible, so that they can fully focus on
|
80
|
+
rendering / dynamically manipulating the DOM:
|
81
|
+
|
82
|
+

|
83
|
+
|
84
|
+
The worst case that could happen now is that your app worker will slow down and this core runs at 100%. However,
|
85
|
+
this will not affect your UI (rendering thread → main).
|
86
|
+
Probably the best solution for single page apps (SPAs) as well as multi-window apps (MWAs) looks like this:
|
87
|
+
|
88
|
+
<pre data-neo-component>
|
89
|
+
{
|
90
|
+
"cls" : "neo-worker-setup",
|
91
|
+
"tag" : "element-loader",
|
92
|
+
"vdom": {"src": "../../resources/images/workers-focus.svg"}
|
93
|
+
}
|
94
|
+
</pre>
|
95
|
+
|
96
|
+
To prevent the app worker from handling too much logic, we can optionally spawn more workers.
|
97
|
+
Each thread has its fixed scope. Let us take a quick look into each of them.
|
98
|
+
|
99
|
+
### Main Thread
|
100
|
+
|
101
|
+
The `index.html` file of your Neo.mjs App will by default have an empty body tag and only import the
|
102
|
+
`MicroLoader.mjs` file. The loader will fetch your `neo-config.json` and afterwards dynamically import the
|
103
|
+
main thread part of the framework. This part is as lightweight as possible: around 40KB in dist/production.
|
104
|
+
|
105
|
+
* Main will start the workers
|
106
|
+
* Main will apply delta-updates to the DOM
|
107
|
+
* Main will forward serialised DOM events to the App Worker
|
108
|
+
* Main can import Main Thread Addons (e.g. libraries which rely on direct DOM access)
|
109
|
+
|
110
|
+
The important part: Main is ***not*** aware of Neo.mjs Apps or Components.
|
111
|
+
It is purely focussing on mounting and updating DOM nodes to ensure that literally nothing can slow down
|
112
|
+
your UI or even freeze it.
|
113
|
+
|
114
|
+
This concept is called OMT (Off the Main Thread), and you can find quite a bunch of infos on the web.
|
115
|
+
|
116
|
+
Example-Overview: <a target="_blank" href="https://css-tricks.com/off-the-main-thread/">CSS-Tricks: Off the Main Thread</a>
|
117
|
+
|
118
|
+
### Application Worker
|
119
|
+
|
120
|
+
The most important actor is the App-Worker. After construction, it will lazy-load (dynamically import)
|
121
|
+
your main App.
|
122
|
+
|
123
|
+
* Your App will import your used Components
|
124
|
+
* Meaning: your Component instances live within the App-Worker
|
125
|
+
* View Models & View Controllers also live here
|
126
|
+
* Most parts of the Neo.mjs Framework live here
|
127
|
+
* You can directly communicate with other Actors via remote method access (RPC)
|
128
|
+
|
129
|
+
As a developer, you will probably spend 95% of your time working within this actor.
|
130
|
+
|
131
|
+
There is a catch: Workers have by design no access to the DOM.
|
132
|
+
Meaning: `window` and `window.document` are undefined.
|
133
|
+
|
134
|
+
This enforces us to use an abstraction layer to describe the DOM (often called virtual DOM or vdom).
|
135
|
+
Compared to other implementations like React, the Neo.mjs vdom is super lightweight. It is using a JSON-like
|
136
|
+
syntax => just nested objects & arrays, since we need to be able to serialise it.
|
137
|
+
|
138
|
+
In German, we would call the concept "Kindersicherung" (parental controls), which has the benefit that we
|
139
|
+
can ensure that junior developers can not mess up the real DOM with invalid operations.
|
140
|
+
|
141
|
+
Some libraries like <a target="_blank" href="https://www.solidjs.com/">SOLIDJS</a> are claiming that using
|
142
|
+
virtual DOM is a bad thing. They are referring to the React implementation of it, which is very different
|
143
|
+
to the Neo.mjs approach. While the SOLIDJS concept to directly modify DOM nodes instead is charming in
|
144
|
+
its own way, it does limit you for staying single-threaded. Their Components must live within the main thread.
|
145
|
+
|
146
|
+
### Virtual DOM Worker
|
147
|
+
|
148
|
+
Like the main thread, the vdom-worker is not aware of your Apps or Components.
|
149
|
+
|
150
|
+
Every Component has a vdom tree (new state) and a vnode tree (current state).
|
151
|
+
|
152
|
+
Once we applied all our desired changes to the vdom tree, we can start an update-cycle.
|
153
|
+
This is a triangle communication:
|
154
|
+
1. The App-Worker will send the vdom & vnode trees to the VDom-Worker
|
155
|
+
2. The VDom-Worker will transform the vdom tree into a vnode tree
|
156
|
+
3. The VDom-Worker will compare the new & old vnode trees to calculate the required delta-updates (diffing)
|
157
|
+
4. The VDom-Worker will send the deltas & the new vnode to the Main Thread
|
158
|
+
5. Main will apply the deltas (piped through `requestAnimationFrame()`) and pass the vnode to the App-Worker
|
159
|
+
6. At this point (async), the next update-cycle can start
|
160
|
+
|
161
|
+
If you think about it: This solves the problem of requiring an "immutable state tree" out of the box.
|
162
|
+
We can modify vdom trees multiple times before starting an update-cycle. Once we do, the vdom gets serialised
|
163
|
+
=> immutable and sent to the VDom-Worker. We can immediately add new changes to the vdom, which will not interfere with
|
164
|
+
the current update-cycle, but get used inside the next cycle.
|
165
|
+
|
166
|
+
### Data Worker
|
167
|
+
|
168
|
+
The main responsibility of the Data-Worker is to communicate with the Backend / Cloud.
|
169
|
+
Mostly, but not limited to:
|
170
|
+
1. Ajax Calls
|
171
|
+
2. SocketConnection messages
|
172
|
+
|
173
|
+
In case you are in need to apply expensive data-transformations before sending / after receiving data,
|
174
|
+
these transformations should happen here. Think about the data reader / writer concept.
|
175
|
+
|
176
|
+
### Canvas Worker
|
177
|
+
|
178
|
+
> Can a Worker really not access the DOM?
|
179
|
+
|
180
|
+
There is one exception, which is called
|
181
|
+
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas">OffscreenCanvas</a>.
|
182
|
+
|
183
|
+
Meaning: We can transfer the ownership of a Canvas DOM node to a worker.
|
184
|
+
|
185
|
+
This enables us to work with charting or maps libraries outside the App-Worker & outside the main thread.
|
186
|
+
|
187
|
+
Here is an example Blog-Post to show you how powerful this concept can be:</br>
|
188
|
+
<a target="_blank" href="https://itnext.io/rendering-3d-offscreen-getting-max-performance-using-canvas-workers-88c207cbcdc2?source=friends_link&sk=7ee0851ff6043c4a79248ff5a20a23fc">Rendering 3d offscreen: Getting max performance using canvas workers</a>
|
189
|
+
|
190
|
+
In the future, we might create an own OffscreenCanvas charting library for Neo.mjs.
|
191
|
+
|
192
|
+
### Task Worker
|
193
|
+
|
194
|
+
In case you have specific expensive tasks, which don't really fit well into the other actors,
|
195
|
+
you can optionally move them into the Task-Worker.
|
196
|
+
|
197
|
+
E.g. calculating Fibonacci numbers would be a good fitting example.
|
198
|
+
|
199
|
+
### Service Worker
|
200
|
+
|
201
|
+
By design, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service-Workers</a>
|
202
|
+
are responsible for caching assets (Images, CSS, JS-bundles)
|
203
|
+
|
204
|
+
> Service workers essentially act as proxy servers that sit between web applications, the browser,
|
205
|
+
> and the network (when available). They are intended, among other things, to enable the creation of
|
206
|
+
> effective offline experiences, intercept network requests, and take appropriate action based on whether
|
207
|
+
> the network is available, and update assets residing on the server. They will also allow access to push
|
208
|
+
> notifications and background sync APIs.
|
209
|
+
|
210
|
+
While Neo.mjs is not purely focussing on making the very first page load as fast as possible,
|
211
|
+
it can make all following page loads happen almost instantly.
|
212
|
+
|
213
|
+
We can cache the JS bundles which generate the desired markup (HTML) directly on the client.
|
214
|
+
There is no need to stream HTML again to the client (server side rendering => SSR),
|
215
|
+
since we already have everything in place to recreate the fully hydrated version in the blink of an eye.
|
216
|
+
|
217
|
+
Via remote method access, the App-Worker can directly communicate with the Service-Worker at run-time.
|
218
|
+
This enables us to preload / pre-cache assets on the fly.
|
219
|
+
|
220
|
+
More infos on this topic:</br>
|
221
|
+
<a target="_blank" href="https://itnext.io/predictive-offline-support-for-assets-you-have-not-used-yet-aeeccccd3754?source=friends_link&sk=e946e0f25f508e6a8cec4136400291a3">Predictive offline support for assets you have not used yet</a>
|
@@ -1,15 +1,15 @@
|
|
1
1
|
{"data": [
|
2
2
|
{"name": "Welcome!", "parentId": null, "isLeaf": true, "id": "Welcome" },
|
3
|
-
{"name": "Benefits
|
3
|
+
{"name": "Benefits", "parentId": null, "isLeaf": false, "id": "WhyNeo"},
|
4
4
|
{"name": "Introduction ", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Intro"},
|
5
|
-
{"name": "Multi-
|
5
|
+
{"name": "Multi-Threading", "parentId": "WhyNeo", "isLeaf": true, "id": "benefits.Multi-Threading"},
|
6
6
|
{"name": "Extreme Speed", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Speed"},
|
7
7
|
{"name": "Multi-Window Applications", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Multi-Window"},
|
8
8
|
{"name": "Quick Application Development", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Quick"},
|
9
9
|
{"name": "Complexity and Effort", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Effort"},
|
10
10
|
{"name": "Features and Benefits Summary", "parentId": "WhyNeo", "isLeaf": true, "id": "WhyNeo-Features"},
|
11
11
|
{"name": "Getting Started", "parentId": null, "isLeaf": false, "id": "GettingStarted", "collapsed": true},
|
12
|
-
{"name": "Setup", "parentId": "GettingStarted", "isLeaf": true, "id": "Setup"
|
12
|
+
{"name": "Setup", "parentId": "GettingStarted", "isLeaf": true, "id": "Setup"},
|
13
13
|
{"name": "Workspaces and Applications", "parentId": "GettingStarted", "isLeaf": true, "id": "2023-10-14T19-25-08-153Z"},
|
14
14
|
{"name": "Describing a View", "parentId": "GettingStarted", "isLeaf": true, "id": "DescribingTheUI"},
|
15
15
|
{"name": "Events", "parentId": "GettingStarted", "isLeaf": true, "id": "Events"},
|
@@ -20,24 +20,23 @@
|
|
20
20
|
{"name": "Tutorials", "parentId": null, "isLeaf": false, "expanded": false, "id": "Tutorials", "collapsed": true},
|
21
21
|
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "RSP", "hidden": true},
|
22
22
|
{"name": "Earthquakes", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "Earthquakes", "collapsed": true},
|
23
|
-
|
24
23
|
{"name": "Todo List", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "TodoList"},
|
25
24
|
{"name": "Guides", "parentId": null, "isLeaf": false, "expanded": false, "id": "InDepth", "collapsed": true},
|
26
25
|
{"name": "Config", "parentId": "InDepth", "isLeaf": false, "id": "Config"},
|
27
|
-
{"name": "Instance Lifecycle", "parentId": "InDepth", "isLeaf": false, "id": "InstanceLifecycle"},
|
28
|
-
{"name": "User Input (Forms)", "parentId": "InDepth", "isLeaf": false, "id": "Forms"},
|
26
|
+
{"name": "Instance Lifecycle", "parentId": "InDepth", "isLeaf": false, "id": "InstanceLifecycle", "hidden": true},
|
27
|
+
{"name": "User Input (Forms)", "parentId": "InDepth", "isLeaf": false, "id": "Forms", "hidden": true},
|
29
28
|
{"name": "Component and Container Basics", "parentId": "InDepth", "isLeaf": true, "id": "ComponentsAndContainers"},
|
30
|
-
{"name": "Layouts", "parentId": "InDepth", "isLeaf": false, "id": "Layouts"},
|
29
|
+
{"name": "Layouts", "parentId": "InDepth", "isLeaf": false, "id": "Layouts", "hidden": true},
|
31
30
|
{"name": "View Models", "parentId": "InDepth", "isLeaf": true, "id": "GuideViewModels"},
|
32
|
-
{"name": "Custom Components", "parentId": "InDepth", "isLeaf": true, "id": "CustomComponents"},
|
31
|
+
{"name": "Custom Components", "parentId": "InDepth", "isLeaf": true, "id": "CustomComponents", "hidden": true},
|
33
32
|
{"name": "Events", "parentId": "InDepth", "isLeaf": true, "expanded": false, "id": "GuideEvents"},
|
34
|
-
{"name": "Tables (Stores)", "parentId": "InDepth", "isLeaf": false, "id": "Tables"},
|
35
|
-
{"name": "Shared Bindable Data (Component Models)", "parentId": "InDepth", "isLeaf": false, "id": "InDepthComponentModels"},
|
36
|
-
{"name": "Multi-Window Applications", "parentId": "InDepth", "isLeaf": false, "id": "MultiWindow"},
|
37
|
-
{"name": "Main Thread Addons", "parentId": "InDepth", "isLeaf": false, "id": "MainThreadAddons"},
|
33
|
+
{"name": "Tables (Stores)", "parentId": "InDepth", "isLeaf": false, "id": "Tables", "hidden": true},
|
34
|
+
{"name": "Shared Bindable Data (Component Models)", "parentId": "InDepth", "isLeaf": false, "id": "InDepthComponentModels", "hidden": true},
|
35
|
+
{"name": "Multi-Window Applications", "parentId": "InDepth", "isLeaf": false, "id": "MultiWindow", "hidden": true},
|
36
|
+
{"name": "Main Thread Addons", "parentId": "InDepth", "isLeaf": false, "id": "MainThreadAddons", "hidden": true},
|
38
37
|
{"name": "Introduction", "parentId": "MainThreadAddons", "isLeaf": true, "id": "MainThreadAddonIntro"},
|
39
38
|
{"name": "Example", "parentId": "MainThreadAddons", "isLeaf": true, "id": "MainThreadAddonExample"},
|
40
|
-
{"name": "Mixins", "parentId": "InDepth", "isLeaf": false, "id": "Mixins"},
|
39
|
+
{"name": "Mixins", "parentId": "InDepth", "isLeaf": false, "id": "Mixins", "hidden": true},
|
41
40
|
{"name": "JavaScript Classes", "parentId": null, "isLeaf": false, "id": "JavaScriptClasses", "hidden": true},
|
42
41
|
{"name": "Classes, Properties, and Methods", "parentId": "JavaScriptClasses", "isLeaf": true, "id": "2023-10-07T19-18-28-517Z"},
|
43
42
|
{"name": "Overriding Methods", "parentId": "JavaScriptClasses", "isLeaf": true, "id": "2023-10-08T20-20-07-934Z"},
|
@@ -35,4 +35,23 @@ body {
|
|
35
35
|
transform: translateZ(0px);
|
36
36
|
}
|
37
37
|
}
|
38
|
+
|
39
|
+
// the style will get used inside home & learn
|
40
|
+
.neo-worker-setup {
|
41
|
+
align-items : center;
|
42
|
+
background-color: #17141c;
|
43
|
+
display : flex;
|
44
|
+
height : 100%;
|
45
|
+
justify-content : center;
|
46
|
+
padding : 20px;
|
47
|
+
width : 100%;
|
48
|
+
|
49
|
+
--fill-opacity : 0.05;
|
50
|
+
--stroke-opacity: 0.05;
|
51
|
+
|
52
|
+
&:hover {
|
53
|
+
--fill-opacity : 1;
|
54
|
+
--stroke-opacity: 1;
|
55
|
+
}
|
56
|
+
}
|
38
57
|
}
|
@@ -13,9 +13,16 @@
|
|
13
13
|
|
14
14
|
.portal-content-box-content {
|
15
15
|
padding-left: 1.3em;
|
16
|
+
margin-top: 0.75em;
|
16
17
|
}
|
17
18
|
|
18
|
-
.portal-content-box-
|
19
|
-
|
19
|
+
.portal-content-box-headline {
|
20
|
+
border-bottom: 1px solid #ececec;
|
21
|
+
padding-bottom: 0.75em;
|
22
|
+
max-width: 85%;
|
23
|
+
margin: 1em auto 0 auto;
|
24
|
+
text-align: center;
|
25
|
+
letter-spacing: 1.5px!important;
|
26
|
+
word-spacing: 1.5px;
|
20
27
|
}
|
21
28
|
}
|