neo.mjs 10.0.0-alpha.1 → 10.0.0-alpha.2
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 +127 -121
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/package.json +31 -3
- package/src/DefaultConfig.mjs +2 -2
- package/src/Main.mjs +1 -1
- package/src/main/DeltaUpdates.mjs +62 -2
- package/src/main/DomAccess.mjs +0 -59
- package/src/vdom/Helper.mjs +38 -50
package/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
<p align="center">
|
2
|
-
<img height="100"src="https://raw.githubusercontent.com/neomjs/pages/main/resources_pub/images/logo/neo_logo_text_primary.svg">
|
2
|
+
<img height="100"src="https://raw.githubusercontent.com/neomjs/pages/main/resources_pub/images/logo/neo_logo_text_primary.svg" alt="Neo.mjs Logo">
|
3
3
|
</p>
|
4
4
|
</br>
|
5
5
|
<p align="center">
|
@@ -11,104 +11,114 @@
|
|
11
11
|
<a href="./CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-green.svg?logo=GitHub&logoColor=white" alt="PRs Welcome"></a>
|
12
12
|
</p>
|
13
13
|
|
14
|
-
# Build Ultra-Fast,
|
15
|
-
|
14
|
+
# Build Ultra-Fast, Desktop-Like Web Apps. Period. :zap:
|
15
|
+
🚀 **Break Free from UI Freezes — Experience True Multithreading & Uncompromised Responsiveness.**
|
16
16
|
|
17
|
-
Neo.mjs
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
💻 ***Neo.mjs v10 isn't an upgrade — it's a new operating system for the web. Where others optimize at the margins, we reinvented the engine.***
|
18
|
+
|
19
|
+
Imagine web applications that never jank, no matter how complex the logic, how many real-time updates they handle, or how
|
20
|
+
many browser windows they span. Neo.mjs is engineered from the ground up to deliver **desktop-like fluidity and scalability**.
|
21
|
+
**While it excels for Single Page Apps (SPAs), Neo.mjs is simply the best option for browser-based multi-window applications**,
|
22
|
+
operating fundamentally different from traditional frameworks.
|
23
|
+
|
24
|
+
By leveraging a **pioneering Off-Main-Thread (OMT) architecture**, Neo.mjs ensures your UI remains butter-smooth, even during computationally intensive tasks like complex data processing or advanced graphics rendering. The main thread is kept free for one purpose: **flawless user interactions and seamless DOM updates.**
|
21
25
|
|
22
26
|
<p align="center">
|
23
|
-
<a href="https://youtu.be/pYfM28Pz6_0"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo33s.png"></a>
|
24
|
-
<a href="https://youtu.be/aEA5333WiWY"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo-movie.png"></a>
|
27
|
+
<a href="https://youtu.be/pYfM28Pz6_0"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo33s.png" alt="Neo.mjs Performance Demo 1 (YouTube Video)"></a>
|
28
|
+
<a href="https://youtu.be/aEA5333WiWY"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo-movie.png" alt="Neo.mjs Performance Demo 2 (YouTube Video)"></a>
|
25
29
|
</p>
|
26
30
|
|
27
31
|
</br></br>
|
28
|
-
##
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
## 🌟 Key Features
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
🌀 **Dynamic Component Management**:</br>
|
99
|
-
Unmount, move, and remount components across the UI or even in separate browser windows
|
100
|
-
— without losing the component’s state or logic. This **runtime flexibility** is a game-changer, **preserving JS instances** while still updating the UI dynamically.
|
101
|
-
|
102
|
-
:dependabot: **No npm dependency hell**:</br>
|
103
|
-
Neo.mjs apps do not need any dependencies at all, just some dev dependencies for tooling.
|
32
|
+
## 🚀 Why Choose Neo.mjs? Solving the Toughest UI Challenges
|
33
|
+
Traditional single-threaded frontend frameworks often struggle with performance bottlenecks and UI freezes, especially for
|
34
|
+
large-scale, data-intensive, or real-time applications. Neo.mjs offers a fundamentally different solution, designed for
|
35
|
+
**uncompromising performance, enhanced security, and superior developer experience.**
|
36
|
+
|
37
|
+
1. **Eliminate UI Freezes with True Multithreading**:
|
38
|
+
> *"The browser's main thread should be treated like a neurosurgeon: only perform precise, scheduled operations with zero distractions."*</br></br>
|
39
|
+
— Neo.mjs Core Philosophy
|
40
|
+
|
41
|
+
Neo.mjs's OMT architecture inherently prevents UI freezes. With v10's optimized rendering pipeline, your UI will remain even *more*
|
42
|
+
consistently responsive, even during intense data processing or complex graphics rendering. It achieves an astonishing
|
43
|
+
rate of **over 40,000 delta updates per second** in optimized environments. This translates to an engine with vast untapped
|
44
|
+
potential, limited only by user interaction, not the framework.
|
45
|
+
|
46
|
+
2. **Unmatched Developer Experience: Transpilation-Free ESM**:
|
47
|
+
Say goodbye to complex build steps for development. Neo.mjs apps run **natively as ES Modules directly in the browser**.
|
48
|
+
This means **zero builds or transpilations** in dev mode, offering instant reloads and an **unmatched debugging experience**.
|
49
|
+
You modify code, and your app updates in real-time.
|
50
|
+
|
51
|
+
3. **Inherent Security by Design**:
|
52
|
+
By prioritizing direct DOM API manipulation over string-based methods (like `innerHTML`), Neo.mjs fundamentally reduces
|
53
|
+
the attack surface for vulnerabilities like Cross-Site Scripting (XSS), building a more robust and secure application from the ground up.
|
54
|
+
|
55
|
+
4. **Declarative, Consistent, & Reusable Architecture**:
|
56
|
+
Neo.mjs's unique **unified class config system** allows you to define components, layouts, and logic in a clean, declarative,
|
57
|
+
and highly consistent way. This significantly reduces boilerplate, improves maintainability, and makes complex UI composition surprisingly straightforward.
|
58
|
+
|
59
|
+
5. **Scalability for Enterprise & Beyond**:
|
60
|
+
Whether building sophisticated enterprise dashboards, data-intensive Gen AI interfaces, or desktop-like multi-window applications,
|
61
|
+
Neo.mjs's modular, worker-driven architecture effortlessly scales. Components are persistent, stateful instances that can be unmounted,
|
62
|
+
moved, and even remounted across browser windows without losing their logic or state. This is key to preventing the "re-rendering madness"
|
63
|
+
common in other frameworks.
|
64
|
+
|
65
|
+
</br></br>
|
66
|
+
## 📊 Real-World Win: Crushing UI Lag in Action
|
67
|
+
Imagine a developer building a stock trading app with live feeds updating every millisecond. Traditional frameworks often choke,
|
68
|
+
freezing the UI under the data flood. With Neo.mjs, the heavy lifting happens in worker threads, keeping the main thread free.
|
69
|
+
Traders get real-time updates with zero lag, and the app feels like a native desktop tool. Now, imagine extending this with
|
70
|
+
**multiple synchronized browser windows**, each displaying different real-time views, all remaining butter-smooth.
|
71
|
+
That’s Neo.mjs in action—solving problems others can’t touch.
|
72
|
+
|
73
|
+
</br></br>
|
74
|
+
## 🌟 Key Features (and How They Supercharge Your App)
|
75
|
+
|
76
|
+
* **Persistent Component Instances**: Components maintain their state and logic even when their DOM is removed or moved.
|
77
|
+
No more wasteful re-creations – just surgical, efficient updates.
|
78
|
+
|
79
|
+
* **Reactive State Management**: Built-in reactivity ensures dynamic, efficient updates between components and state providers,
|
80
|
+
all handled off the main thread.
|
81
|
+
|
82
|
+
* **Hierarchical State Management**: Seamlessly manage state between parent and child components with nested state providers.
|
83
|
+
Components intelligently bind to the closest provider, combining data for powerful, maintainable patterns.
|
84
|
+
|
85
|
+
* **Clean Architecture (MVVM-inspired)**: View controllers ensure a clear separation of concerns, isolating business logic
|
86
|
+
from UI components for easier maintenance, testing, and team collaboration.
|
87
|
+
|
88
|
+
* **Multi-Window & Single-Page Applications (SPAs)**: Easily build and manage complex applications that require multiple
|
89
|
+
browser windows or traditional SPAs, all powered by the same underlying multi-threaded architecture without requiring any native shell.
|
90
|
+
|
91
|
+
* **No npm Dependency Hell**: Neo.mjs apps run with **zero runtime dependencies**, just a few dev dependencies for tooling.
|
92
|
+
This means smaller bundles, fewer conflicts, and a simpler dependency graph.
|
93
|
+
|
94
|
+
* **Cutting-Edge Use Cases**: Ideal for **data-intensive applications, real-time dashboards, web-based IDEs, banking
|
95
|
+
applications, and complex multi-window Gen AI interfaces** where performance and responsiveness are non-negotiable.
|
96
|
+
|
97
|
+
<p align="center">
|
98
|
+
<img src="./resources/images/workers-focus.svg" alt="Neo.mjs Worker Architecture Diagram - Shows Main Thread, App Worker, VDom Worker, Canvas Worker, Data Worker, Service Worker, Backend connections.">
|
99
|
+
</p>
|
100
|
+
*Diagram: A high-level overview of Neo.mjs's multi-threaded architecture (Main Thread, App Worker, VDom Worker, Canvas Worker, Data Worker, Service Worker, Backend). Optional workers fade in on hover on neomjs.com.*
|
101
|
+
|
104
102
|
</br></br>
|
105
|
-
##
|
106
|
-
|
103
|
+
## 🔍 Neo.mjs vs. The Rest: Key Differentiators
|
104
|
+
Wondering how Neo.mjs stacks up against React, Angular, or Vue.js? Here’s the breakdown:
|
105
|
+
|
106
|
+
| Feature | Neo.mjs | React / Angular / Vue.js |
|
107
|
+
| :------------------------ | :--------------------------------------------- | :------------------------------------------ |
|
108
|
+
| **UI Responsiveness** | **Guaranteed smooth**: Heavy tasks off-main-thread; main thread free for UI. | **Prone to jank**: Main thread handles all logic + UI, easily blocked. |
|
109
|
+
| **Multithreading** | Native OMT architecture for core app logic, VDom, data, & graphics. | Single-threaded by default; requires complex workarounds (e.g., Web Workers for *specific* tasks). |
|
110
|
+
| **Dev Mode Experience** | **No transpilation, instant reloads**: Native ES Modules directly in browser. | Build tools (Webpack, Babel) required for dev; slower reloads. |
|
111
|
+
| **Component Persistence** | State survives DOM changes; instances move across windows. | Full re-renders common; state often lost on unmount unless managed externally. |
|
112
|
+
| **Security** | Direct DOM API, inherently XSS-resistant by design. | Relies heavily on careful string sanitization; higher XSS risk if not diligent. |
|
113
|
+
| **Multi-Window Apps** | Seamless, browser-native multi-window support. | Complex to achieve; hacky or unsupported natively. |
|
114
|
+
| **Bundle Size** | Zero runtime dependencies for lean apps. | Can be large with many third-party dependencies. |
|
115
|
+
|
116
|
+
**Neo.mjs Edge**: True multithreading, a no-build development mode, and a scalable, secure architecture combine to deliver a framework that's faster to build with and fundamentally faster and more stable to run.
|
107
117
|
|
108
118
|
</br></br>
|
109
|
-
## 📦 Declarative Class Configuration
|
110
|
-
|
111
|
-
This simplifies class creation, reduces boilerplate code, and improves maintainability.
|
119
|
+
## 📦 Declarative Class Configuration: Build Faster, Maintain Easier
|
120
|
+
|
121
|
+
Neo.mjs’s class config system allows you to define and manage classes in a declarative and reusable way. This simplifies class creation, reduces boilerplate code, and improves maintainability.
|
112
122
|
|
113
123
|
```javascript
|
114
124
|
import Component from '../../src/component/Base.mjs';
|
@@ -116,65 +126,61 @@ import Component from '../../src/component/Base.mjs';
|
|
116
126
|
class MyComponent extends Component {
|
117
127
|
static config = {
|
118
128
|
className : 'MyComponent',
|
119
|
-
myConfig_ : 'defaultValue',
|
120
|
-
domListeners: {
|
129
|
+
myConfig_ : 'defaultValue', // Reactive property
|
130
|
+
domListeners: { // Direct DOM event binding
|
121
131
|
click: 'onClick'
|
122
132
|
}
|
123
133
|
}
|
124
134
|
|
135
|
+
// Automatically called when myConfig_ changes
|
125
136
|
afterSetMyConfig(value, oldValue) {
|
126
|
-
console.log('myConfig changed:', value, oldValue)
|
137
|
+
console.log('myConfig changed:', value, oldValue);
|
127
138
|
}
|
128
139
|
|
140
|
+
// Handled in the App Worker, main thread remains free
|
129
141
|
onClick(data) {
|
130
|
-
console.log('Clicked!', data)
|
142
|
+
console.log('Clicked!', data);
|
131
143
|
}
|
132
144
|
}
|
133
145
|
|
134
146
|
export default Neo.setupClass(MyComponent);
|
135
147
|
```
|
136
|
-
With Neo.mjs’s class config system, you can:
|
137
148
|
|
138
|
-
|
139
|
-
* Easily extend and reuse configurations across classes.
|
140
|
-
* Keep your codebase organized and scalable.
|
141
|
-
|
142
|
-
For more details, check out the <a href="https://neomjs.com/dist/production/apps/portal/index.html#/learn/gettingstarted.Config">Class Config System documentation</a>.
|
149
|
+
For more details, check out the [Class Config System documentation](https://neomjs.com/dist/production/apps/portal/index.html#/learn/gettingstarted.Config).
|
143
150
|
|
144
151
|
</br></br>
|
145
|
-
## 🚀
|
146
|
-
Quick Start
|
152
|
+
## 🚀 Jump In: Your First Neo.mjs App in Minutes
|
147
153
|
|
148
|
-
Run
|
154
|
+
Run this command:
|
149
155
|
|
150
156
|
```bash
|
151
157
|
npx neo-app@latest
|
152
158
|
```
|
153
|
-
This one-liner sets up everything you need to start building with Neo, including:
|
154
|
-
- A new app workspace.
|
155
|
-
- A pre-configured app shell.
|
156
|
-
- A local development server.
|
157
|
-
- Opening your app inside a new browser window
|
158
159
|
|
159
|
-
|
160
|
+
This one-liner sets up everything you need to start building with Neo.mjs, including:
|
161
|
+
|
162
|
+
* A new app workspace.
|
163
|
+
* A pre-configured app shell.
|
164
|
+
* A local development server.
|
165
|
+
* Launching your app in a new browser window—all in one go.
|
160
166
|
|
161
|
-
:
|
167
|
+
:book: More details? Check out our [Getting Started Guide](./.github/GETTING_STARTED.md)
|
162
168
|
|
163
|
-
:
|
164
|
-
<a href="https://neomjs.com/dist/production/apps/portal/#/learn/tutorials.Earthquakes">Earthquakes Tutorial</a>
|
169
|
+
:student: Make sure to dive into the [Learning Section](https://neomjs.com/dist/production/apps/portal/#/learn/gettingstarted.Setup)
|
165
170
|
|
166
171
|
Next steps:
|
167
|
-
* :star: **Explore exciting Examples here**: <a href="https://neomjs.com/dist/production/apps/portal/#/examples">Neo.mjs Examples</a>
|
168
|
-
* Many more are included inside the repos <a href="https://github.com/neomjs/neo/tree/dev/apps">apps</a>
|
169
|
-
& <a href="https://github.com/neomjs/neo/tree/dev/examples">examples</a> folders.
|
170
|
-
* :blue_book: All Blog Posts are listed here: <a href="https://neomjs.com/dist/production/apps/portal/#/blog">Neo.mjs Blog</a>
|
171
|
-
</br></br>
|
172
|
-
## :handshake: Join the Community
|
173
172
|
|
174
|
-
:
|
173
|
+
* :star: **Experience stunning Demos & Examples here**: [Neo.mjs Examples Portal](https://neomjs.com/dist/production/apps/portal/#/examples)
|
174
|
+
* Many more are included inside the repos [apps](https://github.com/neomjs/neo/tree/dev/apps)
|
175
|
+
& [examples](https://github.com/neomjs/neo/tree/dev/examples) folders.
|
176
|
+
* :blue_book: All Blog Posts are listed here: [Neo.mjs Blog](https://neomjs.com/dist/production/apps/portal/#/blog)
|
177
|
+
|
178
|
+
</br></br>
|
179
|
+
## :handshake: Join the Community
|
175
180
|
|
176
|
-
:
|
181
|
+
:speech_balloon: Have questions? Join our [Slack channel](https://join.slack.com/t/neomjs/shared_invite/zt-6c50ueeu-3E1~M4T9xkNnb~M_prEEOA) and connect with other developers.
|
177
182
|
|
183
|
+
:hammer_and_wrench: Want to contribute? Check out our [Contributing Guide](https://github.com/neomjs/neo/blob/dev/CONTRIBUTING.md).
|
178
184
|
|
179
185
|
</br></br>
|
180
|
-
Copyright (c) 2015 - today,
|
186
|
+
Copyright (c) 2015 - today, [Tobias Uhlig](https://www.linkedin.com/in/tobiasuhlig/)
|
package/ServiceWorker.mjs
CHANGED
package/apps/portal/index.html
CHANGED
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name" : "neo.mjs",
|
3
|
-
"version" : "10.0.0-alpha.
|
4
|
-
"description" : "The
|
3
|
+
"version" : "10.0.0-alpha.2",
|
4
|
+
"description" : "Neo.mjs: The multi-threaded UI framework for building ultra-fast, desktop-like web applications with uncompromised responsiveness, inherent security, and a transpilation-free dev mode.",
|
5
5
|
"type" : "module",
|
6
6
|
"repository" : {
|
7
7
|
"type": "git",
|
@@ -32,14 +32,42 @@
|
|
32
32
|
"javascript",
|
33
33
|
"frontend",
|
34
34
|
"framework",
|
35
|
+
"performance",
|
36
|
+
"ui-responsiveness",
|
37
|
+
"no-jank",
|
38
|
+
"smooth-ui",
|
39
|
+
"scalability",
|
40
|
+
"enterprise",
|
41
|
+
"multi-threaded",
|
42
|
+
"desktop-like",
|
43
|
+
"native-like",
|
44
|
+
"web-app",
|
35
45
|
"offscreencanvas",
|
36
46
|
"sharedworker",
|
37
47
|
"webworker",
|
48
|
+
"virtual-dom",
|
38
49
|
"ecmascript",
|
39
50
|
"css",
|
40
51
|
"json",
|
41
52
|
"react-alternative",
|
42
|
-
"angular-alternative"
|
53
|
+
"angular-alternative",
|
54
|
+
"zero-build",
|
55
|
+
"no-transpilation",
|
56
|
+
"security",
|
57
|
+
"xss-resistant",
|
58
|
+
"realtime",
|
59
|
+
"data-visualization",
|
60
|
+
"single-page-application",
|
61
|
+
"spa",
|
62
|
+
"generative-ai",
|
63
|
+
"genai",
|
64
|
+
"ai-interfaces",
|
65
|
+
"json-blueprints",
|
66
|
+
"low-code-ui",
|
67
|
+
"multi-window",
|
68
|
+
"pwa",
|
69
|
+
"progressive-web-app",
|
70
|
+
"predictive-caching"
|
43
71
|
],
|
44
72
|
"author" : "Tobias Uhlig",
|
45
73
|
"license" : "MIT",
|
package/src/DefaultConfig.mjs
CHANGED
@@ -276,12 +276,12 @@ const DefaultConfig = {
|
|
276
276
|
useVdomWorker: true,
|
277
277
|
/**
|
278
278
|
* buildScripts/injectPackageVersion.mjs will update this value
|
279
|
-
* @default '10.0.0-alpha.
|
279
|
+
* @default '10.0.0-alpha.2'
|
280
280
|
* @memberOf! module:Neo
|
281
281
|
* @name config.version
|
282
282
|
* @type String
|
283
283
|
*/
|
284
|
-
version: '10.0.0-alpha.
|
284
|
+
version: '10.0.0-alpha.2'
|
285
285
|
};
|
286
286
|
|
287
287
|
Object.assign(DefaultConfig, {
|
package/src/Main.mjs
CHANGED
@@ -17,12 +17,37 @@ class DeltaUpdates extends Base {
|
|
17
17
|
* @protected
|
18
18
|
*/
|
19
19
|
className: 'Neo.main.DeltaUpdates',
|
20
|
+
/**
|
21
|
+
* @member {Number} countDeltas=0
|
22
|
+
* @protected
|
23
|
+
*/
|
24
|
+
countDeltas: 0,
|
25
|
+
/**
|
26
|
+
* @member {Number} countDeltasPer250ms=0
|
27
|
+
* @protected
|
28
|
+
*/
|
29
|
+
countDeltasPer250ms: 0,
|
30
|
+
/**
|
31
|
+
* @member {Number} countUpdates=0
|
32
|
+
* @protected
|
33
|
+
*/
|
34
|
+
countUpdates: 0,
|
35
|
+
/**
|
36
|
+
* @member {Boolean} renderCountDeltas_=false
|
37
|
+
* @protected
|
38
|
+
*/
|
39
|
+
renderCountDeltas_: false,
|
20
40
|
/**
|
21
41
|
* @member {Boolean} singleton=true
|
22
42
|
*/
|
23
43
|
singleton: true
|
24
44
|
}
|
25
45
|
|
46
|
+
/**
|
47
|
+
* @member {Number} logDeltasIntervalId=0
|
48
|
+
* @protected
|
49
|
+
*/
|
50
|
+
logDeltasIntervalId = 0
|
26
51
|
/**
|
27
52
|
* Private property to store the dynamically loaded renderer module.
|
28
53
|
* @member {Neo.main.render.DomApiRenderer|Neo.main.render.DomApiRenderer|null} #renderer=null
|
@@ -44,8 +69,14 @@ class DeltaUpdates extends Base {
|
|
44
69
|
construct(config) {
|
45
70
|
super.construct(config);
|
46
71
|
|
72
|
+
let me = this;
|
73
|
+
|
74
|
+
if (Neo.config.renderCountDeltas) {
|
75
|
+
me.renderCountDeltas = true
|
76
|
+
}
|
77
|
+
|
47
78
|
// Initiate the asynchronous loading of the renderer here.
|
48
|
-
|
79
|
+
me.#_readyPromise = (async () => {
|
49
80
|
try {
|
50
81
|
let module;
|
51
82
|
|
@@ -55,7 +86,7 @@ class DeltaUpdates extends Base {
|
|
55
86
|
module = await import('./render/DomApiRenderer.mjs')
|
56
87
|
}
|
57
88
|
|
58
|
-
|
89
|
+
me.#renderer = module.default
|
59
90
|
} catch (err) {
|
60
91
|
console.error('DeltaUpdates: Failed to load renderer module:', err);
|
61
92
|
throw err // Re-throw to propagate initialization failures
|
@@ -63,6 +94,35 @@ class DeltaUpdates extends Base {
|
|
63
94
|
})()
|
64
95
|
}
|
65
96
|
|
97
|
+
/**
|
98
|
+
* Triggered after the renderCountDeltas config got changed
|
99
|
+
* @param {Boolean} value
|
100
|
+
* @param {Boolean} oldValue
|
101
|
+
* @protected
|
102
|
+
*/
|
103
|
+
afterSetRenderCountDeltas(value, oldValue) {
|
104
|
+
let me = this,
|
105
|
+
{logDeltasIntervalId} = me,
|
106
|
+
node;
|
107
|
+
|
108
|
+
if (value) {
|
109
|
+
if (logDeltasIntervalId === 0) {
|
110
|
+
me.logDeltasIntervalId = setInterval(() => {
|
111
|
+
node = document.getElementById('neo-delta-updates');
|
112
|
+
|
113
|
+
if (node) {
|
114
|
+
node.innerHTML = String(me.countDeltasPer250ms * 4)
|
115
|
+
}
|
116
|
+
|
117
|
+
me.countDeltasPer250ms = 0
|
118
|
+
}, 250)
|
119
|
+
}
|
120
|
+
} else {
|
121
|
+
logDeltasIntervalId && clearInterval(logDeltasIntervalId);
|
122
|
+
me.logDeltasInterval = 0
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
66
126
|
/**
|
67
127
|
* @param {HTMLElement} node
|
68
128
|
* @param {String} nodeName
|
package/src/main/DomAccess.mjs
CHANGED
@@ -54,21 +54,6 @@ class DomAccess extends Base {
|
|
54
54
|
* @protected
|
55
55
|
*/
|
56
56
|
className: 'Neo.main.DomAccess',
|
57
|
-
/**
|
58
|
-
* @member {Number} countDeltas=0
|
59
|
-
* @protected
|
60
|
-
*/
|
61
|
-
countDeltas: 0,
|
62
|
-
/**
|
63
|
-
* @member {Number} countDeltasPer250ms=0
|
64
|
-
* @protected
|
65
|
-
*/
|
66
|
-
countDeltasPer250ms: 0,
|
67
|
-
/**
|
68
|
-
* @member {Number} countUpdates=0
|
69
|
-
* @protected
|
70
|
-
*/
|
71
|
-
countUpdates: 0,
|
72
57
|
/**
|
73
58
|
* Remote method access for other workers
|
74
59
|
* @member {Object} remote
|
@@ -102,11 +87,6 @@ class DomAccess extends Base {
|
|
102
87
|
'windowScrollTo'
|
103
88
|
]
|
104
89
|
},
|
105
|
-
/**
|
106
|
-
* @member {Boolean} renderCountDeltas_=false
|
107
|
-
* @protected
|
108
|
-
*/
|
109
|
-
renderCountDeltas_: false,
|
110
90
|
/**
|
111
91
|
* @member {Boolean} singleton=true
|
112
92
|
* @protected
|
@@ -114,12 +94,6 @@ class DomAccess extends Base {
|
|
114
94
|
singleton: true
|
115
95
|
}
|
116
96
|
|
117
|
-
/**
|
118
|
-
* @member {Number} logDeltasIntervalId=0
|
119
|
-
* @protected
|
120
|
-
*/
|
121
|
-
logDeltasIntervalId = 0
|
122
|
-
|
123
97
|
/**
|
124
98
|
* @returns {HTMLElement}
|
125
99
|
*/
|
@@ -143,10 +117,6 @@ class DomAccess extends Base {
|
|
143
117
|
|
144
118
|
let me = this;
|
145
119
|
|
146
|
-
if (Neo.config.renderCountDeltas) {
|
147
|
-
me.renderCountDeltas = true
|
148
|
-
}
|
149
|
-
|
150
120
|
me.initGlobalListeners();
|
151
121
|
|
152
122
|
// Set up our aligning callback which is called when things change which may
|
@@ -218,35 +188,6 @@ class DomAccess extends Base {
|
|
218
188
|
document.head.appendChild(script)
|
219
189
|
}
|
220
190
|
|
221
|
-
/**
|
222
|
-
* Triggered after the renderCountDeltas config got changed
|
223
|
-
* @param {Boolean} value
|
224
|
-
* @param {Boolean} oldValue
|
225
|
-
* @protected
|
226
|
-
*/
|
227
|
-
afterSetRenderCountDeltas(value, oldValue) {
|
228
|
-
let me = this,
|
229
|
-
{logDeltasIntervalId} = me,
|
230
|
-
node;
|
231
|
-
|
232
|
-
if (value) {
|
233
|
-
if (logDeltasIntervalId === 0) {
|
234
|
-
me.logDeltasIntervalId = setInterval(() => {
|
235
|
-
node = document.getElementById('neo-delta-updates');
|
236
|
-
|
237
|
-
if (node) {
|
238
|
-
node.innerHTML = String(me.countDeltasPer250ms * 4)
|
239
|
-
}
|
240
|
-
|
241
|
-
me.countDeltasPer250ms = 0
|
242
|
-
}, 250)
|
243
|
-
}
|
244
|
-
} else {
|
245
|
-
logDeltasIntervalId && clearInterval(logDeltasIntervalId);
|
246
|
-
me.logDeltasInterval = 0
|
247
|
-
}
|
248
|
-
}
|
249
|
-
|
250
191
|
/**
|
251
192
|
* @param {Object} data
|
252
193
|
* @returns {Promise<void>}
|
package/src/vdom/Helper.mjs
CHANGED
@@ -46,14 +46,14 @@ class Helper extends Base {
|
|
46
46
|
* @returns {Object} deltas
|
47
47
|
* @protected
|
48
48
|
*/
|
49
|
-
compareAttributes(
|
50
|
-
|
51
|
-
attributes, delta, value, keys, styles, add, remove;
|
52
|
-
|
49
|
+
compareAttributes({deltas, oldVnode, vnode, vnodeMap}) {
|
50
|
+
// Do not compare attributes for component references
|
53
51
|
if (oldVnode.componentId && (oldVnode.id === vnode.id || oldVnode.componentId === vnode.id)) {
|
54
52
|
return deltas
|
55
53
|
}
|
56
54
|
|
55
|
+
let attributes, delta, value, keys, styles, add, remove;
|
56
|
+
|
57
57
|
if (vnode.vtype === 'text' && vnode.innerHTML !== oldVnode.innerHTML) {
|
58
58
|
deltas.default.push({
|
59
59
|
action : 'updateVtext',
|
@@ -265,7 +265,7 @@ class Helper extends Base {
|
|
265
265
|
if (me.isMovedNode(childNode, oldVnodeMap)) {
|
266
266
|
me.moveNode({deltas, insertDelta, oldVnodeMap, vnode: childNode, vnodeMap})
|
267
267
|
} else {
|
268
|
-
me.insertNode({deltas, index: i + insertDelta, oldVnodeMap, vnode: childNode, vnodeMap})
|
268
|
+
me.insertNode({deltas, index: i + insertDelta, oldVnodeMap, vnode: childNode, vnodeMap})
|
269
269
|
}
|
270
270
|
|
271
271
|
if (oldChildNode && vnodeId === vnodeMap.get(oldChildNodeId)?.parentNode.id) {
|
@@ -380,12 +380,12 @@ class Helper extends Base {
|
|
380
380
|
}
|
381
381
|
});
|
382
382
|
|
383
|
-
//
|
383
|
+
// Relevant for vtype='text'
|
384
384
|
if (Object.keys(node.attributes).length < 1) {
|
385
385
|
delete node.attributes
|
386
386
|
}
|
387
387
|
|
388
|
-
//
|
388
|
+
// Relevant for vtype='text'
|
389
389
|
if (Object.keys(node.style).length < 1) {
|
390
390
|
delete node.style
|
391
391
|
}
|
@@ -396,10 +396,10 @@ class Helper extends Base {
|
|
396
396
|
/**
|
397
397
|
* Creates a flat map of the tree, containing ids as keys and infos as values
|
398
398
|
* @param {Object} config
|
399
|
-
* @param {Neo.vdom.VNode} config.vnode
|
400
|
-
* @param {Neo.vdom.VNode} [config.parentNode=null]
|
401
399
|
* @param {Number} [config.index=0]
|
402
400
|
* @param {Map} [config.map=new Map()]
|
401
|
+
* @param {Neo.vdom.VNode} [config.parentNode=null]
|
402
|
+
* @param {Neo.vdom.VNode} config.vnode
|
403
403
|
* @returns {Map}
|
404
404
|
* {String} id vnode.id (convenience shortcut)
|
405
405
|
* {Number} index
|
@@ -407,18 +407,15 @@ class Helper extends Base {
|
|
407
407
|
* {Neo.vdom.VNode} vnode
|
408
408
|
* @protected
|
409
409
|
*/
|
410
|
-
createVnodeMap(
|
411
|
-
let {vnode, parentNode=null, index=0, map=new Map()} = config,
|
412
|
-
id;
|
413
|
-
|
410
|
+
createVnodeMap({index=0, map=new Map(), parentNode=null, vnode}) {
|
414
411
|
if (vnode) {
|
415
|
-
id = vnode.id || vnode.componentId;
|
412
|
+
let id = vnode.id || vnode.componentId;
|
416
413
|
|
417
414
|
map.set(id, {id, index, parentNode, vnode});
|
418
415
|
|
419
416
|
vnode.childNodes?.forEach((childNode, index) => {
|
420
|
-
this.createVnodeMap({
|
421
|
-
})
|
417
|
+
this.createVnodeMap({index, map, parentNode: vnode, vnode: childNode})
|
418
|
+
})
|
422
419
|
}
|
423
420
|
|
424
421
|
return map
|
@@ -435,9 +432,8 @@ class Helper extends Base {
|
|
435
432
|
* @returns {Map}
|
436
433
|
* @protected
|
437
434
|
*/
|
438
|
-
findMovedNodes(
|
439
|
-
let
|
440
|
-
id = vnode?.id;
|
435
|
+
findMovedNodes({movedNodes=new Map(), oldVnodeMap, vnode, vnodeMap}) {
|
436
|
+
let id = vnode?.id;
|
441
437
|
|
442
438
|
if (id) {
|
443
439
|
if (this.isMovedNode(vnode, oldVnodeMap)) {
|
@@ -454,6 +450,27 @@ class Helper extends Base {
|
|
454
450
|
return movedNodes
|
455
451
|
}
|
456
452
|
|
453
|
+
/**
|
454
|
+
* For delta updates to work, every node inside the live DOM needs a unique ID.
|
455
|
+
* Text nodes need to get wrapped into comment nodes, which contain the ID to ensure consistency.
|
456
|
+
* As the result, we need a physical index which counts every text node as 3 nodes.
|
457
|
+
* @param {Neo.vdom.VNode} parentNode
|
458
|
+
* @param {Number} logicalIndex
|
459
|
+
* @returns {Number}
|
460
|
+
*/
|
461
|
+
getPhysicalIndex(parentNode, logicalIndex) {
|
462
|
+
let physicalIndex = logicalIndex,
|
463
|
+
i = 0;
|
464
|
+
|
465
|
+
for (; i < logicalIndex; i++) {
|
466
|
+
if (parentNode.childNodes[i]?.vtype === 'text') {
|
467
|
+
physicalIndex += 2 // Accounts for <!--neo-vtext--> wrappers
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
471
|
+
return physicalIndex
|
472
|
+
}
|
473
|
+
|
457
474
|
/**
|
458
475
|
* Only import for the DOM API based mount adapter.
|
459
476
|
* @returns {Promise<void>}
|
@@ -493,22 +510,7 @@ class Helper extends Base {
|
|
493
510
|
movedNodes = me.findMovedNodes({oldVnodeMap, vnode, vnodeMap}),
|
494
511
|
delta = {action: 'insertNode', parentId},
|
495
512
|
hasLeadingTextChildren = false,
|
496
|
-
physicalIndex =
|
497
|
-
i = 0,
|
498
|
-
siblingVnode;
|
499
|
-
|
500
|
-
// Calculate physicalIndex for DOM insertion and hasLeadingTextChildren flag
|
501
|
-
// This loop processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
|
502
|
-
// up to the logical insertion point.
|
503
|
-
for (; i < index; i++) {
|
504
|
-
siblingVnode = parentNode.childNodes[i];
|
505
|
-
|
506
|
-
// If we encounter a text VNode before the insertion point, adjust physicalIndex
|
507
|
-
if (siblingVnode?.vtype === 'text') {
|
508
|
-
physicalIndex += 2; // Each text VNode adds 2 comment nodes to the physical count
|
509
|
-
hasLeadingTextChildren = true
|
510
|
-
}
|
511
|
-
}
|
513
|
+
physicalIndex = me.getPhysicalIndex(parentNode, index); // Processes the children of the *NEW* parent's VNode in the *current* state
|
512
514
|
|
513
515
|
Object.assign(delta, {hasLeadingTextChildren, index: physicalIndex});
|
514
516
|
|
@@ -568,21 +570,7 @@ class Helper extends Base {
|
|
568
570
|
movedParentNode = movedNode.parentNode,
|
569
571
|
{childNodes} = movedParentNode,
|
570
572
|
delta = {action: 'moveNode', id: vnode.id, parentId},
|
571
|
-
physicalIndex =
|
572
|
-
i = 0,
|
573
|
-
siblingVnode;
|
574
|
-
|
575
|
-
// Calculate physicalIndex for DOM insertion.
|
576
|
-
// This loop processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
|
577
|
-
// up to the logical insertion point.
|
578
|
-
for (; i < index; i++) {
|
579
|
-
siblingVnode = parentNode.childNodes[i];
|
580
|
-
|
581
|
-
if (siblingVnode?.vtype === 'text') {
|
582
|
-
// Each text VNode adds 2 comment nodes to the physical count
|
583
|
-
physicalIndex += 2
|
584
|
-
}
|
585
|
-
}
|
573
|
+
physicalIndex = this.getPhysicalIndex(parentNode, index); // Processes the children of the *NEW* parent's VNode in the *current* state (parentNode.childNodes)
|
586
574
|
|
587
575
|
Object.assign(delta, {index: physicalIndex + insertDelta});
|
588
576
|
deltas.default.push(delta);
|