neo.mjs 10.0.1 → 10.1.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/.github/RELEASE_NOTES/v10.0.2.md +12 -0
- package/.github/RELEASE_NOTES/v10.1.0.md +17 -0
- package/README.md +37 -14
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/learn/comparisons/NeoVsAngular.md +26 -30
- package/learn/comparisons/NeoVsReact.md +94 -30
- package/learn/comparisons/NeoVsVue.md +37 -51
- package/package.json +1 -1
- package/resources/scss/theme-dark/button/Base.scss +10 -10
- package/src/DefaultConfig.mjs +2 -2
- package/src/collection/Base.mjs +9 -9
- package/src/component/Abstract.mjs +3 -3
- package/src/data/RecordFactory.mjs +5 -3
- package/src/draggable/tree/DragZone.mjs +1 -1
- package/src/grid/Body.mjs +1 -1
- package/src/main/DeltaUpdates.mjs +0 -4
- package/src/main/render/DomApiRenderer.mjs +1 -1
- package/src/selection/Model.mjs +12 -2
- package/src/tree/List.mjs +17 -13
- package/src/util/vdom/TreeBuilder.mjs +1 -1
- package/src/vdom/Helper.mjs +2 -1
@@ -0,0 +1,12 @@
|
|
1
|
+
**Neo.mjs v10.0.2 Release Notes**
|
2
|
+
|
3
|
+
This release includes several enhancements and fixes since v10.0.1.
|
4
|
+
|
5
|
+
**Key Updates:**
|
6
|
+
|
7
|
+
* **`README.md` & Comparison Enhancements**: Significant updates to the `README.md`, including a new section "🚀 Inside v10: A New Era of Frontend Architecture" with links to a five-part blog series. The architectural comparison table "🔍 Architectural Deep Dive: Neo.mjs vs. Main-Thread Frameworks" has been expanded, and detailed comparison documents (`NeoVsAngular.md`, `NeoVsReact.md`, `NeoVsVue.md`) have been updated to provide enhanced insights.
|
8
|
+
* **Data Model Enhancements**: Default values for `records` now optionally support functions, enabled by changes in `data.RecordFactory`.
|
9
|
+
* **DOM Rendering**: `DomApiRenderer` has been improved for creating void attributes, enhancing rendering accuracy.
|
10
|
+
* **Core Component Fix**: Re-added the lost `parentId` default value to `component.Abstract`, which resolves an issue with the tooltip singleton.
|
11
|
+
* **Dark Theme**: Improved the styling for ghost buttons
|
12
|
+
* **Tree List** Polished drag&drop support for the edge case where stores are not using `id` as the keyProperty.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
**Neo.mjs v10.1.0 Release Notes**
|
2
|
+
|
3
|
+
This release includes important fixes and minor improvements, primarily focusing on VDOM optimization, collection performance, and selection model improvements.
|
4
|
+
|
5
|
+
**Key Updates:**
|
6
|
+
|
7
|
+
* **VDOM Optimization & `neo-ignore` Refinement (Addresses #7114):**
|
8
|
+
The internal handling of the `neo-ignore` flag has been significantly improved. Previously, `neo-ignore` could interfere with the VDOM's structural integrity, potentially leading to rendering anomalies, **especially in complex layouts like grids, where elements might overlap**. Now, a dedicated `neoIgnore: true` flag is added to VDOM nodes, preserving the original `componentId` and `id`. This ensures the VDOM engine can correctly identify and skip sub-trees during diffing, leading to more robust and efficient asymmetric VDOM updates and resolving visual rendering issues **such as those observed in grid components**.
|
9
|
+
|
10
|
+
* **Collection Performance Enhancements (Addresses #7115):**
|
11
|
+
Several methods within `src/collection/Base.mjs` (e.g., `clear`, `findBy`, `pop`, `remove`) have been optimized by replacing `this.getCount()` method calls with direct access to `this.count`. This change streamlines collection operations, leading to improved performance.
|
12
|
+
|
13
|
+
* **Enhanced Selection Model (Addresses #7116):**
|
14
|
+
The `src/selection/Model.mjs` has been updated to provide more comprehensive data during selection changes. A new `getController()` method has been added, and the `selectionChange` event now includes the `records` array, offering richer context for selection handling and better integration with view controllers.
|
15
|
+
|
16
|
+
* **Minor Fixes & Improvements (Addresses #7117):**
|
17
|
+
This release also includes minor cleanups and documentation improvements, such as an updated JSDoc comment in `src/grid/Body.mjs` and the removal of a debug `console.log` statement in `src/main/DeltaUpdates.mjs`.
|
package/README.md
CHANGED
@@ -62,6 +62,24 @@ large-scale, data-intensive, or real-time applications. Neo.mjs offers a fundame
|
|
62
62
|
moved, and even remounted across browser windows without losing their logic or state. This is key to preventing the "re-rendering madness"
|
63
63
|
common in other frameworks.
|
64
64
|
|
65
|
+
</br></br>
|
66
|
+
## 🚀 Inside v10: A New Era of Frontend Architecture
|
67
|
+
|
68
|
+
The v10 release marks a significant evolution of the Neo.mjs core, introducing a new functional component model and a revolutionary two-tier reactivity system. We've rebuilt the engine to provide an even more powerful and intuitive developer experience, making it simpler than ever to build complex, performant applications.
|
69
|
+
|
70
|
+
To understand the depth of these changes and the philosophy behind them, we've published a five-part blog series that dives deep into the architecture of v10:
|
71
|
+
|
72
|
+
1. **[A Frontend Love Story: Why the Strategies of Today Won't Build the Apps of Tomorrow](./learn/blog/v10-post1-love-story.md)**
|
73
|
+
* *An introduction to the core problems in modern frontend development and the architectural vision of Neo.mjs.*
|
74
|
+
2. **[Deep Dive: Named vs. Anonymous State - A New Era of Component Reactivity](./learn/blog/v10-deep-dive-reactivity.md)**
|
75
|
+
* *Explore the powerful two-tier reactivity system that makes the "memoization tax" a thing of the past.*
|
76
|
+
3. **[Beyond Hooks: A New Breed of Functional Components for a Multi-Threaded World](./learn/blog/v10-deep-dive-functional-components.md)**
|
77
|
+
* *Discover how functional components in a multi-threaded world eliminate the trade-offs of traditional hooks.*
|
78
|
+
4. **[Deep Dive: The VDOM Revolution - JSON Blueprints & Asymmetric Rendering](./learn/blog/v10-deep-dive-vdom-revolution.md)**
|
79
|
+
* *Learn how our off-thread VDOM engine uses simple JSON blueprints for maximum performance and security.*
|
80
|
+
5. **[Deep Dive: The State Provider Revolution](./learn/blog/v10-deep-dive-state-provider.md)**
|
81
|
+
* *A look into the powerful, hierarchical state management system that scales effortlessly.*
|
82
|
+
|
65
83
|
</br></br>
|
66
84
|
## 📦 Batteries Included: A Comprehensive Component Library
|
67
85
|
|
@@ -84,6 +102,8 @@ That’s Neo.mjs in action — solving problems others can’t touch.
|
|
84
102
|
* **Persistent Component Instances**: Components maintain their state and logic even when their DOM is removed or moved.
|
85
103
|
No more wasteful re-creations – just surgical, efficient updates.
|
86
104
|
|
105
|
+
* **New in v10: Functional Components & A Modern Hook System**: Embrace a modern, hook-based development style with `defineComponent`, `useConfig`, and `useEvent`. This new paradigm, built on top of our robust class system, offers a familiar and intuitive way to build components while benefiting from the unparalleled performance of our multi-threaded architecture. Best of all, it's free from the "memoization tax" (`useMemo`, `useCallback`) that plagues other frameworks.
|
106
|
+
|
87
107
|
* **Reactive State Management**: Leveraging `Neo.state.Provider`, Neo.mjs offers natively integrated, hierarchical state management.
|
88
108
|
Components declare their data needs via a concise `bind` config. These `bind` functions act as powerful inline formulas, allowing
|
89
109
|
Components to automatically react to changes and combine data from multiple state providers within the component hierarchy.
|
@@ -116,6 +136,10 @@ That’s Neo.mjs in action — solving problems others can’t touch.
|
|
116
136
|
tree across workers, live-modify component configurations directly in the browser console, and observe real-time UI updates,
|
117
137
|
all without complex tooling setup.
|
118
138
|
|
139
|
+
* **Asymmetric VDOM & JSON Blueprints**: Instead of a complex, class-based VNode tree, your application logic deals with simple, serializable JSON objects. These blueprints are sent to a dedicated VDOM worker for high-performance diffing, ensuring your main thread is never blocked by rendering calculations. This architecture is not only faster but also inherently more secure and easier for AI tools to generate and manipulate.
|
140
|
+
|
141
|
+
* **Async-Aware Component Lifecycle**: With the `initAsync()` lifecycle method, components can handle asynchronous setup (like fetching data or lazy-loading modules) *before* they are considered "ready." This eliminates entire classes of race conditions and UI flicker, allowing you to build complex, data-dependent components with confidence.
|
142
|
+
|
119
143
|
<p align="center">
|
120
144
|
<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.">
|
121
145
|
</p>
|
@@ -123,20 +147,19 @@ That’s Neo.mjs in action — solving problems others can’t touch.
|
|
123
147
|
*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.*
|
124
148
|
|
125
149
|
</br></br>
|
126
|
-
## 🔍 Neo.mjs vs.
|
127
|
-
|
128
|
-
|
129
|
-
| Feature
|
130
|
-
|
|
131
|
-
| **
|
132
|
-
| **
|
133
|
-
| **
|
134
|
-
| **
|
135
|
-
| **
|
136
|
-
| **
|
137
|
-
|
138
|
-
|
139
|
-
**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.
|
150
|
+
## 🔍 Architectural Deep Dive: Neo.mjs vs. Main-Thread Frameworks
|
151
|
+
The true power of Neo.mjs lies in its foundational architectural choices, which solve problems that other frameworks can only mitigate. Here’s a more detailed breakdown:
|
152
|
+
|
153
|
+
| Feature | Neo.mjs Approach | Typical Main-Thread Framework Approach (React, Vue, Angular) | The Neo.mjs Advantage |
|
154
|
+
| :--- | :--- | :--- | :--- |
|
155
|
+
| **Core Architecture** | **Multi-Threaded by Design**: App logic, VDOM diffing, and rendering are split across a dedicated App Worker, VDOM Worker, and the Main Thread. | **Single-Threaded**: All application logic, state management, rendering, and user interactions compete for the same Main Thread resources. | **Guaranteed UI Responsiveness**. By isolating expensive computations, Neo.mjs ensures the main thread is always free to respond to user input, eliminating UI jank and freezes at an architectural level. |
|
156
|
+
| **Reactivity Model** | **Direct & Granular Hybrid**: A powerful two-tier system combines imperative "push" (`afterSet`) and declarative "pull" (`Effect`) reactivity. | **React**: Inverted model (the entire component function re-runs). **Vue/Angular**: Highly optimized, direct "pull" model. | **Performant by Default**. Eliminates the "memoization tax" (`useMemo`, etc.) required in React. More powerful than pure pull systems for orchestrating complex business logic. |
|
157
|
+
| **Component Lifecycle** | **Stable & Persistent**: Instances are created once and persist through UI changes. Features a rich lifecycle with `construct`, `initAsync`, and `afterSetMounted`. | **React**: Ephemeral (functional components are re-executed on every render). **Vue/Angular**: More stable, but lack pre-ready async hooks for complex setup. | **Robust & Predictable**. `initAsync` solves async setup (data fetching, module loading) *before* the first render, preventing UI flicker. Persistence enables complex stateful apps and multi-window operations. |
|
158
|
+
| **State Management** | **Surgical Subscriptions**: The integrated `StateProvider` allows components to subscribe *only* to the precise state slices they need, completely bypassing intermediate components. | **React**: Context API re-renders all consumers by default, requiring manual optimization. **Vue/Angular**: Optimized state managers (Pinia, NgRx) are still bound by the main thread. | **Scalable & Decoupled**. More performant for global state changes by default. Architecturally cleaner, avoiding props drilling and the performance traps of React's Context. |
|
159
|
+
| **DOM Updates** | **Asymmetric & Off-Thread**: Simple, serializable JSON objects (blueprints) are sent to the VDOM worker for diffing. The Main Thread only receives and applies minimal, pre-calculated patches. | VDOM diffing and DOM manipulation are computationally expensive tasks that occur on the main thread, directly competing with user interactions. | **Faster, More Secure, and AI-Friendly**. Off-thread diffing is faster. Using direct DOM APIs instead of `innerHTML` is more secure. Simple JSON blueprints are trivial for AI to generate and manipulate. |
|
160
|
+
| **Dev Experience** | **Zero-Builds Development**: Native ES Modules run directly in the browser. No transpilation or bundling is needed for development. | **Build-Heavy**: Relies on tools like Vite, Webpack, or the Angular CLI, which add complexity, require source maps, and introduce delays. | **Unparalleled Simplicity & Debugging Clarity**. What you write is what you debug. Instant feedback and the absence of complex build toolchains lead to a faster, more intuitive workflow. |
|
161
|
+
|
162
|
+
**The Bottom Line**: Where other frameworks optimize operations on the main thread, Neo.mjs moves them off the main thread entirely. This fundamental difference results in a framework that is not just faster, but architecturally more scalable, robust, and resilient to complexity.
|
140
163
|
|
141
164
|
</br></br>
|
142
165
|
## ⚙️ Declarative Class Configuration: Build Faster, Maintain Easier
|
package/ServiceWorker.mjs
CHANGED
package/apps/portal/index.html
CHANGED
@@ -28,21 +28,21 @@ This is where the two frameworks diverge significantly, each offering unique tra
|
|
28
28
|
* Communication between workers and the Main Thread happens via asynchronous message passing.
|
29
29
|
* **Benefit:** This architecture keeps the Main Thread almost entirely free and responsive, preventing UI freezes even during heavy computations or complex application logic. It inherently leverages multi-core CPUs for parallel processing, leading to superior UI responsiveness and performance under heavy load.
|
30
30
|
|
31
|
-
### 2. Rendering
|
32
|
-
|
33
|
-
* **Angular:
|
34
|
-
* Angular uses templates
|
35
|
-
*
|
36
|
-
|
37
|
-
|
38
|
-
*
|
39
|
-
* **Immutability Considerations:** While Angular doesn't enforce immutability, performance optimizations like `OnPush` change detection often benefit significantly from immutable data patterns. This can introduce a cognitive burden for developers to manage data immutably for optimal performance.
|
40
|
-
|
41
|
-
* **Neo.mjs: Off-Thread, Scoped VDOM & Atomic Insertion**
|
42
|
-
* Neo.mjs uses a Virtual DOM defined by plain JavaScript objects. The diffing process happens in a VDom Worker, keeping the Main Thread free.
|
31
|
+
### 2. Rendering & Reactivity: A Hybrid, Off-Thread Approach
|
32
|
+
|
33
|
+
* **Angular: Main-Thread Rendering & Zone.js Change Detection**
|
34
|
+
* Angular uses templates compiled into renderable instructions. Its change detection relies on **Zone.js** to monkey-patch asynchronous APIs, which broadly detects when an application's state *might* have changed.
|
35
|
+
* This triggers a change detection cycle on the main thread. While highly optimized, this can still be a bottleneck, and developers often need to manually implement the `OnPush` strategy to improve performance.
|
36
|
+
|
37
|
+
* **Neo.mjs: Off-Thread Rendering & A Two-Tier Reactivity System**
|
38
|
+
* Neo.mjs uses a Virtual DOM defined by plain JavaScript objects, with the entire diffing process happening in a dedicated **VDom Worker**. This keeps the main thread free from heavy rendering calculations.
|
43
39
|
* **Scoped VDOM (Encapsulation & Performance):** Neo.mjs's VDOM is **scoped by default**. When a parent component renders, its children are represented by simple `{componentId: '...'}` placeholders. This provides two key advantages: 1) **Performance:** A parent's update never processes the complex VDOM of its children, keeping update payloads extremely small. 2) **Encapsulation:** It is architecturally impossible for a parent to accidentally manipulate a child's internal VDOM structure, enforcing clear ownership.
|
44
40
|
* **Atomic Insertion:** For insertions, the Main Thread receives a VNode structure and uses `DomApiRenderer` to **build the entire new DOM subtree in memory**, completely detached from the live document. This fully constructed fragment is then inserted into the live DOM in a **single, atomic operation**.
|
45
|
-
* **
|
41
|
+
* **Two-Tier Reactivity vs. Zone.js:** Instead of Zone.js's broad detection, Neo.mjs uses a precise, two-tier system:
|
42
|
+
1. **Classic Components (Imperative "Push"):** For the 100+ components in the core library, changes to reactive configs trigger `afterSet` hooks. These hooks perform surgical, imperative updates directly to the component's VDOM.
|
43
|
+
2. **Functional Components (Declarative "Pull"):** For modern functional components, the `createVdom` function is wrapped in an `Effect`. When its dependencies change, only this function is re-executed to generate a new VDOM structure.
|
44
|
+
|
45
|
+
This hybrid system provides the architectural robustness needed for a massive component library and the modern developer experience of functional components, all while being performant by default.
|
46
46
|
|
47
47
|
### 3. Component Model & State Management
|
48
48
|
|
@@ -55,19 +55,16 @@ This is where the two frameworks diverge significantly, each offering unique tra
|
|
55
55
|
* This functional approach uses hooks like `useConfig()` for state, providing a clean, declarative way to build UI while benefiting from Neo.mjs's underlying fine-grained reactivity.
|
56
56
|
* **State Management:** Features integrated state providers and a unified config system for managing and sharing bindable data across the component tree, often simplifying cross-component communication compared to traditional DI or prop passing.
|
57
57
|
|
58
|
-
### 4.
|
58
|
+
### 4. Developer Experience: Prescriptive Tooling vs. Unparalleled Flexibility
|
59
59
|
|
60
|
-
* **Angular:
|
61
|
-
* Angular
|
62
|
-
* **Implication:** This leads to slower development server startup times, requires source maps for debugging
|
60
|
+
* **Angular: The "Straightjacket" - A Prescriptive, Build-Heavy Workflow**
|
61
|
+
* Angular is famous for its highly opinionated and prescriptive nature. It dictates specific patterns (e.g., NgModules, decorators, strict DI) and relies heavily on its CLI for a mandatory and often complex build process (Webpack, TypeScript compilation, AOT compilation), even for development.
|
62
|
+
* **Implication:** This leads to slower development server startup times, requires source maps for debugging, and can introduce a steep learning curve. While this rigidity can enforce consistency, it often limits flexibility and makes it challenging to deviate from "the Angular way."
|
63
63
|
|
64
|
-
* **Neo.mjs:
|
65
|
-
* Neo.mjs champions a **"zero builds" instant development mode
|
66
|
-
* **Benefit:** This offers unparalleled speed and debugging clarity. Code changes are reflected instantly
|
67
|
-
*
|
68
|
-
* **`dist/esm`:** Deploys as native ES Modules, preserving the dev mode's file structure for efficient modular loading in modern browsers.
|
69
|
-
* **`dist/production`:** Generates highly optimized, thread-specific bundles using Webpack for maximum compatibility and minification.
|
70
|
-
* **Dynamic Module Loading:** Neo.mjs uniquely supports dynamically loading code-based modules (even with arbitrary `import` statements) from different environments at runtime, a powerful feature for plugin architectures or user-generated code that most other frameworks struggle with due to their static build graphs.
|
64
|
+
* **Neo.mjs: "Structured Freedom" - A Zero-Builds, Direct Workflow**
|
65
|
+
* Neo.mjs champions a **"zero builds" instant development mode**. Developers work directly with native ES Modules in the browser, eliminating the need for transpilation or bundling during development.
|
66
|
+
* **Benefit:** This offers unparalleled speed and debugging clarity. Code changes are reflected instantly. Developers work directly with the real code in the browser's dev tools, eliminating the need for source maps and vastly simplifying debugging.
|
67
|
+
* While Neo.mjs provides a robust architecture, it offers significant flexibility within that structure (e.g., plain JS for VDOM, choice of functional or class components), allowing developers more freedom without sacrificing consistency.
|
71
68
|
|
72
69
|
### Other Considerations:
|
73
70
|
|
@@ -78,13 +75,12 @@ This is where the two frameworks diverge significantly, each offering unique tra
|
|
78
75
|
* **Ecosystem & Maturity:** Angular has a large, mature ecosystem backed by Google. Neo.mjs has a smaller but dedicated community, with a focus on framework-level solutions and integrated features.
|
79
76
|
* **Dependency Management (Batteries Included):** Angular projects often involve a large `node_modules` directory and can lead to complex dependency trees and version conflicts. Neo.mjs, in contrast, is a "batteries included" framework. It literally has zero real runtime dependencies outside of its own core. This native ES Module approach and integrated framework, significantly reduces this complexity, offering a much leaner and more controlled dependency management experience.
|
80
77
|
|
81
|
-
## Conclusion: Why Neo.mjs Offers
|
78
|
+
## Conclusion: Why Neo.mjs Offers a More Modern and Flexible Architecture
|
82
79
|
|
83
|
-
While Angular is a powerful and widely adopted framework, Neo.mjs offers fundamental architectural advantages that can lead to superior technical performance
|
80
|
+
While Angular is a powerful and widely adopted framework, Neo.mjs offers fundamental architectural advantages that can lead to superior technical performance, responsiveness, and a more streamlined developer experience.
|
84
81
|
|
85
82
|
* **Unblocked Main Thread & Inherent Performance:** Neo.mjs's unique worker-based architecture fundamentally shifts application logic off the Main Thread. This ensures the UI remains fluid and responsive, even during heavy computations, leading to inherently higher performance ceilings without the need for extensive manual optimizations.
|
86
|
-
* **
|
87
|
-
* **
|
88
|
-
* **Streamlined Development Workflow:** The "zero builds" development mode and native ES Module approach offer a significantly faster and more transparent development experience compared to Angular's mandatory build process.
|
83
|
+
* **More Precise and Efficient Reactivity:** By using a surgical, effect-based system instead of broad, zone-based change detection, Neo.mjs is more performant by default and requires less manual tuning from the developer.
|
84
|
+
* **Superior Developer Experience:** The "zero builds" development mode offers a significantly faster, simpler, and more transparent development workflow compared to Angular's mandatory and complex build process.
|
89
85
|
|
90
|
-
The choice between them depends on the specific application's needs. For applications where guaranteed Main Thread responsiveness, high performance under load,
|
86
|
+
The choice between them depends on the specific application's needs. For teams heavily invested in the Angular ecosystem, it remains a robust choice. However, for applications where guaranteed Main Thread responsiveness, high performance under load, and a modern, flexible development workflow are paramount, Neo.mjs presents a compelling and technically superior alternative.
|
@@ -43,31 +43,98 @@ This is where the two frameworks diverge significantly, each offering unique tra
|
|
43
43
|
1. **Performance:** A parent's update never processes the complex VDOM of its children, keeping update payloads extremely small and efficient.
|
44
44
|
2. **Encapsulation:** It is architecturally impossible for a parent to accidentally reach into and manipulate a child's internal VDOM structure. This enforces clear ownership and prevents a wide class of bugs.
|
45
45
|
|
46
|
-
### 3.
|
47
|
-
|
48
|
-
* **React:
|
49
|
-
* When a
|
50
|
-
* This
|
51
|
-
|
52
|
-
|
53
|
-
|
46
|
+
### 3. Reactivity & Execution Model: The 'Inverted' Paradigm vs. Direct Granularity
|
47
|
+
|
48
|
+
* **React: The Inverted Reactivity Model & The `memo` Tax**
|
49
|
+
* React's model is "inverted" because the **entire component function is the unit of reactivity**. When a state change occurs, React re-executes the entire function. This brute-force approach then triggers a cascading re-render of **all its child components**, regardless of whether their own props have changed.
|
50
|
+
* This creates a significant performance problem known as "unnecessary re-renders." To fight this, developers are forced to pay the `memo` tax: wrapping components in `React.memo()`, manually memoizing functions with `useCallback()`, and objects with `useMemo()`. This adds significant boilerplate, increases complexity, and is a notorious source of bugs.
|
51
|
+
|
52
|
+
```javascript
|
53
|
+
// A typical "optimized" React component, demonstrating the memoization tax
|
54
|
+
const MyComponent = React.memo(({ onButtonClick, user }) => {
|
55
|
+
// This component is wrapped in React.memo to prevent re-renders
|
56
|
+
return <button onClick={onButtonClick}>{user.name}</button>;
|
57
|
+
});
|
58
|
+
|
59
|
+
const App = () => {
|
60
|
+
const [count, setCount] = useState(0);
|
61
|
+
|
62
|
+
// We must wrap this in useCallback to prevent MyComponent from re-rendering
|
63
|
+
const handleClick = useCallback(() => { /* ... */ }, []);
|
64
|
+
|
65
|
+
// We must wrap this in useMemo to ensure the object reference is stable
|
66
|
+
const user = useMemo(() => ({ name: 'John Doe' }), []);
|
67
|
+
|
68
|
+
return (
|
69
|
+
<div>
|
70
|
+
<button onClick={() => setCount(c => c + 1)}>App Clicks: {count}</button>
|
71
|
+
<MyComponent onButtonClick={handleClick} user={user} />
|
72
|
+
</div>
|
73
|
+
);
|
74
|
+
};
|
75
|
+
```
|
76
|
+
|
77
|
+
* **Neo.mjs: Direct & Granular Reactivity (Performant by Default)**
|
78
|
+
* Neo.mjs's model is fundamentally more efficient. The **individual config property is the unit of reactivity**.
|
54
79
|
* When a config value changes, only the specific `createVdom` effects that depend on that *exact* piece of state are queued for re-execution. There are no cascading re-renders. If a parent's `createVdom` re-runs, but the configs passed to a child have not changed, the child component's `createVdom` function is **never executed**.
|
55
|
-
* **Benefit (Zero Manual Optimization):** This fine-grained reactivity completely eliminates the need for manual memoization
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
80
|
+
* **Benefit (Zero Manual Optimization):** This fine-grained reactivity completely eliminates the need for manual memoization. The framework is performant by design.
|
81
|
+
|
82
|
+
```javascript
|
83
|
+
// The Neo.mjs equivalent, performant by default without manual optimization
|
84
|
+
import {defineComponent, useConfig} from 'neo.mjs/src/functional/_export.mjs';
|
85
|
+
|
86
|
+
const MyComponent = defineComponent({
|
87
|
+
// The user config is passed in from the parent
|
88
|
+
createVdom({user}) {
|
89
|
+
// This will only log when the component *actually* re-renders
|
90
|
+
console.log('Rendering MyComponent');
|
91
|
+
return {tag: 'div', text: user.name};
|
92
|
+
}
|
93
|
+
});
|
94
|
+
|
95
|
+
export default defineComponent({
|
96
|
+
createVdom() {
|
97
|
+
const [count, setCount] = useConfig(0);
|
98
|
+
|
99
|
+
// No useMemo needed. useConfig provides a stable reference for the user object.
|
100
|
+
const [user] = useConfig({name: 'John Doe'});
|
101
|
+
|
102
|
+
return {
|
103
|
+
cn: [{
|
104
|
+
tag: 'button',
|
105
|
+
onclick: () => setCount(prev => prev + 1),
|
106
|
+
text: `App Clicks: ${count}`
|
107
|
+
}, {
|
108
|
+
module: MyComponent,
|
109
|
+
// Pass the stable user object to the child.
|
110
|
+
// MyComponent will not re-render when `count` changes.
|
111
|
+
user
|
112
|
+
}]
|
113
|
+
}
|
114
|
+
}
|
115
|
+
});
|
116
|
+
```
|
117
|
+
|
118
|
+
### 4. Component Lifecycle: Ephemeral vs. Stable
|
119
|
+
|
120
|
+
* **React: Ephemeral Components**
|
121
|
+
* In React, a functional component is not a persistent instance but an ephemeral function that is re-executed on every render. This conflates the concepts of component definition, rendering, and lifecycle management into a single, repeatedly-run block of code.
|
122
|
+
* Side effects and lifecycle events are managed with hooks like `useEffect`, which run *after* the render has committed to the screen. This can lead to UI flicker (e.g., render a loading state, then fetch data, then re-render the final state) and requires careful management of dependency arrays to prevent infinite loops or stale closures.
|
123
|
+
|
124
|
+
* **Neo.mjs: Stable & Persistent Instances**
|
125
|
+
* In Neo.mjs, both class-based and functional components are **stable, persistent instances** that are created once. This "stable chassis" provides a robust and predictable lifecycle that is separate from the render logic.
|
126
|
+
* **`construct()` / `init()`**: These run only once when the instance is created, providing a clear place for one-time setup.
|
127
|
+
* **`initAsync()`**: This powerful hook runs *after* construction but *before* the component is considered "ready." It allows for asynchronous setup (like data fetching or lazy-loading modules) to complete *before* the first render, eliminating UI flicker and race conditions at an architectural level.
|
128
|
+
* **`afterSetMounted()`**: This hook fires every time the component is physically mounted or unmounted from the DOM, providing a reliable way to manage DOM-specific event listeners or integrations. This persistence allows components to be moved between containers or even browser windows without being destroyed.
|
129
|
+
|
130
|
+
### 5. State Management: Context API vs. Surgical State Providers
|
131
|
+
|
132
|
+
* **React: The Context Problem**
|
133
|
+
* React's primary built-in solution for avoiding "props drilling" is the Context API. However, it has a major performance drawback: when a context value changes, **every single component** that consumes that context re-renders by default, even if it only cares about a small, unchanged part of the context value. This forces developers back into the world of manual memoization to prevent performance degradation.
|
134
|
+
|
135
|
+
* **Neo.mjs: The State Provider Solution**
|
136
|
+
* Neo.mjs's architecture makes props drilling an obsolete anti-pattern. The integrated `state.Provider` allows a deeply nested component to subscribe *only* to the precise slice of state it needs via its `bind` config or a `useConfig` hook.
|
137
|
+
* This creates a direct, performant, and surgical link between the state and the component that needs it, completely bypassing all intermediate components. It is more akin to a selector in a dedicated state management library, but it's a native, architectural feature of the framework.
|
71
138
|
|
72
139
|
### Other Considerations:
|
73
140
|
|
@@ -76,12 +143,9 @@ A key differentiator between the frameworks is how they handle growing applicati
|
|
76
143
|
* **`dist/esm`:** Deploys as native ES Modules, preserving the dev mode's file structure for efficient modular loading in modern browsers.
|
77
144
|
* **`dist/production`:** Generates highly optimized, thread-specific bundles using Webpack for maximum compatibility and minification.
|
78
145
|
* **`dist/development`:** A bundled but unminified environment for debugging production-specific issues or for TypeScript preference, serving as a bridge to traditional build-based workflows.
|
79
|
-
* **Dynamic Module Loading:** Neo.mjs uniquely supports dynamically loading code-based modules (even with arbitrary `import` statements) from different environments at runtime, a powerful feature for plugin architectures or user-generated code that most other frameworks struggle with.
|
80
146
|
|
81
147
|
* **JSX vs. Plain Objects:** React uses JSX (requiring a build step for UI definition). Neo.mjs uses plain JavaScript objects for VDOM (no JSX compilation needed for VDOM definition).
|
82
|
-
* **Side Effects:** Both frameworks advocate for managing side effects outside the pure render function. React uses `useEffect`. Neo.mjs uses separate `Neo.core.Effect` instances or dedicated hooks for side effects.
|
83
148
|
* **Ecosystem & Maturity:** React has a massive, mature ecosystem with abundant libraries, tools, and community support. Neo.mjs has a smaller but dedicated community, with a focus on framework-level solutions and integrated features.
|
84
|
-
* **Learning Curve:** React's initial learning curve can be gentle, but mastering its performance optimizations (memoization, context, custom hooks) can be complex. Neo.mjs has a steeper initial learning curve due to its worker-based architecture, but once understood, it offers inherent performance benefits.
|
85
149
|
* **Dependency Management (Batteries Included):** React projects often involve a large `node_modules` directory and can lead to complex dependency trees and version conflicts, a common pain point often referred to as "dependency hell." Neo.mjs, in contrast, is a "batteries included" framework. It literally has zero real runtime dependencies outside of its own core. This native ES Module approach and integrated framework significantly reduces this complexity, offering a much leaner and more controlled dependency management experience.
|
86
150
|
|
87
151
|
## Conclusion: Why Neo.mjs Offers Significant Technical Advantages Over React
|
@@ -89,7 +153,7 @@ A key differentiator between the frameworks is how they handle growing applicati
|
|
89
153
|
For applications where guaranteed Main Thread responsiveness, high performance under load, leveraging multi-core processing, and long-term maintainability are paramount, Neo.mjs presents a compelling and technically superior alternative.
|
90
154
|
|
91
155
|
* **Unblocked Main Thread & Inherent Performance:** Neo.mjs's unique worker-based architecture fundamentally shifts application logic off the Main Thread. This ensures the UI remains fluid and responsive even during heavy computations, leading to inherently higher performance ceilings without the need for extensive manual optimizations common in React.
|
92
|
-
* **
|
93
|
-
* **
|
156
|
+
* **Architecturally Superior Reactivity:** By avoiding React's "inverted reactivity model," Neo.mjs eliminates the need for manual memoization, resulting in cleaner code, fewer bugs, and a more intuitive developer experience.
|
157
|
+
* **Robust Lifecycle for Complex Apps:** The stable component lifecycle, especially with `initAsync`, provides an architectural solution for asynchronous challenges that React can only handle with post-render side effects, often leading to a less optimal user experience.
|
94
158
|
|
95
|
-
The choice between them depends on the specific application's needs. For applications where
|
159
|
+
The choice between them depends on the specific application's needs. For simple websites or applications where the development team is already heavily invested in the React ecosystem, React remains a viable choice. For applications where performance, scalability, and long-term maintainability are critical, Neo.mjs offers a fundamentally more robust and powerful architecture.
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# Neo.mjs vs Vue.js
|
2
2
|
|
3
|
-
Neo.mjs is a comprehensive JavaScript ecosystem for building high-performance, multi-threaded web applications. Unlike frameworks like Vue.js, which
|
3
|
+
Neo.mjs is a comprehensive JavaScript ecosystem for building high-performance, multi-threaded web applications. Unlike frameworks like Vue.js, which have perfected their architecture for a single-threaded browser environment, Neo.mjs is a self-contained system with **zero runtime dependencies** built on a fundamentally different, multi-threaded paradigm. It provides a complete, out-of-the-box solution that includes four distinct development and deployment environments, from a revolutionary zero-builds development mode to thread-optimized production bundles.
|
4
4
|
|
5
|
-
This article provides a focused comparison between the Neo.mjs ecosystem and Vue.js. While both frameworks share a commitment to
|
5
|
+
This article provides a focused comparison between the Neo.mjs ecosystem and Vue.js. While both frameworks share a commitment to a superb developer experience and a highly efficient reactive model, their underlying philosophies on how to achieve peak performance and scalability diverge. We will explore their approaches to **architecture, rendering, and reactivity**, highlighting the trade-offs between Vue's best-in-class, single-threaded model and Neo.mjs's holistic, worker-based paradigm.
|
6
6
|
|
7
7
|
## Foundational Concepts: A Shared Heritage
|
8
8
|
|
@@ -10,62 +10,48 @@ Despite their architectural differences, both frameworks build upon foundational
|
|
10
10
|
|
11
11
|
* **Component-Based Architecture:** Both frameworks champion building UIs as a composition of reusable components. Neo.mjs extends this with `Neo.core.Base`, allowing any class-based entity (controllers, models, etc.) to leverage the framework's powerful class system, even without a UI.
|
12
12
|
* **Declarative UI:** Developers describe *what* the UI should look like for a given state, and the framework handles *how* to update the DOM.
|
13
|
-
* **
|
14
|
-
* **
|
13
|
+
* **Fine-Grained Reactivity:** Both frameworks feature exceptionally efficient reactivity systems that avoid the "unnecessary re-render" problems found in other libraries. They automatically track dependencies and ensure that only the necessary parts of the UI are updated when state changes.
|
14
|
+
* **Modern APIs:** Both support modern development patterns with APIs for managing state and side effects (Hooks in Neo.mjs, Composition API in Vue).
|
15
15
|
|
16
16
|
## Key Differences: Architectural & Rendering Strategies
|
17
17
|
|
18
18
|
This is where the two frameworks diverge significantly, each offering unique trade-offs and advantages.
|
19
19
|
|
20
|
-
### 1. Overall Architecture: Main
|
20
|
+
### 1. Overall Architecture: Main-Thread Optimized vs. Multi-Thread Native
|
21
21
|
|
22
|
-
* **Vue.js: Main Thread
|
22
|
+
* **Vue.js: A Master of the Main Thread**
|
23
23
|
* Vue applications run entirely on the Main JavaScript Thread. All component logic, state management (e.g., Pinia), VDOM reconciliation, and direct DOM manipulation occur on this single thread.
|
24
|
-
* **Implication:** Vue is exceptionally well-optimized for
|
24
|
+
* **Implication:** Vue is exceptionally well-optimized for this environment. Its reactivity system and template compiler are designed to minimize the work done on the main thread. However, it is still fundamentally bound by the single-threaded nature of JavaScript. Very complex computations or large, synchronous state updates can still potentially block the Main Thread, impacting the user experience in data-heavy, high-frequency update scenarios.
|
25
25
|
|
26
|
-
* **Neo.mjs:
|
27
|
-
* Neo.mjs's defining feature is its multi-threaded architecture
|
28
|
-
*
|
29
|
-
* **
|
26
|
+
* **Neo.mjs: A "Backend-in-the-Browser" Architecture**
|
27
|
+
* Neo.mjs's defining feature is its multi-threaded architecture, which treats the client-side application with the same rigor as a backend system.
|
28
|
+
* **App Worker (The "Frontend Backend"):** Application logic, component instances, state, and business logic run in a dedicated App Worker.
|
29
|
+
* **VDom Worker (The "Rendering Service"):** The VDOM diffing occurs in a separate VDom Worker.
|
30
|
+
* **Main Thread (The "Painting Client"):** The Main Thread's primary job is to apply pre-calculated DOM patches.
|
31
|
+
* **Benefit:** This architecture keeps the Main Thread almost entirely free and responsive, preventing UI freezes even during heavy computations. It's not just an optimization; it's a paradigm shift that leverages multi-core CPUs for true parallelism.
|
30
32
|
|
31
|
-
### 2.
|
33
|
+
### 2. Reactivity Model: Pure "Pull" vs. a Hybrid "Push/Pull" System
|
32
34
|
|
33
|
-
* **Vue.js:
|
34
|
-
* Vue
|
35
|
-
*
|
36
|
-
* **Compiler Intelligence:** The compiler can detect static parts of a template and hoist them out of the render function, so they are created only once. It also applies other optimizations, such as patching flags, to help the runtime VDOM diffing algorithm take fast paths and skip unnecessary checks. This makes Vue's rendering extremely efficient on the Main Thread.
|
35
|
+
* **Vue.js: Elegant and Pure "Pull" Reactivity**
|
36
|
+
* Vue's reactivity system (using `ref` and `reactive`) is a masterpiece of developer ergonomics. It is a pure "pull" system: your templates or `watchEffect` functions "pull" from reactive sources, and Vue automatically tracks these dependencies to trigger updates.
|
37
|
+
* For side effects, developers use `watch` or `watchEffect`, which are powerful tools for observing state changes and reacting to them.
|
37
38
|
|
38
|
-
* **Neo.mjs:
|
39
|
-
* Neo.mjs
|
40
|
-
|
41
|
-
|
42
|
-
* **Scoped VDOM (Encapsulation & Performance):** The VDom Worker sends only the "deltas" (a minimal set of change instructions) back to the Main Thread. For insertions, `DomApiRenderer` **builds the entire new DOM subtree in memory**, completely detached from the live document, and inserts it in a **single, atomic operation**. Furthermore, Neo.mjs's VDOM is **scoped by default**. When a parent component renders, its children are represented by simple `{componentId: '...'}` placeholders. This provides two key advantages:
|
43
|
-
1. **Performance:** A parent's update never processes the complex VDOM of its children, keeping update payloads extremely small and efficient.
|
44
|
-
2. **Encapsulation:** It is architecturally impossible for a parent to accidentally reach into and manipulate a child's internal VDOM structure. This enforces clear ownership and prevents a wide class of bugs.
|
39
|
+
* **Neo.mjs: The Two-Tier Hybrid System**
|
40
|
+
* Neo.mjs's reactivity is arguably more powerful because it's a hybrid system designed for architectural flexibility. Every reactive property simultaneously powers two paradigms:
|
41
|
+
1. **The "Pull" System (Declarative):** Used in functional components, the `createVdom()` function is an `Effect` that "pulls" from state dependencies. This is very similar in spirit to Vue's `setup` and is fantastic for expressing the UI as a pure function of its state.
|
42
|
+
2. **The "Push" System (Imperative):** This is the unique part. Every reactive config also has optional `afterSet<PropertyName>()` hooks. This is more than just a `watch`er; it's a powerful, class-based tool for validation, transformation, and orchestrating complex business logic. For example, changing a grid column's `dataIndex` could trigger a store reload, update headers, and recalculate summary rows, all within a clean, predictable `afterSetDataIndex` hook.
|
45
43
|
|
46
|
-
|
44
|
+
This hybrid approach gives developers the choice between elegant, declarative rendering and powerful, imperative control for any given state change, all within a single, unified model.
|
47
45
|
|
48
|
-
|
49
|
-
* Vue's reactivity system is one of its most celebrated features. When a piece of reactive state (e.g., from `ref` or `reactive`) changes, Vue automatically tracks which components depend on it and triggers updates only for those specific components.
|
50
|
-
* It avoids the "cascading re-render" problem of React by default. If a parent component re-renders, it will not unnecessarily re-render child components whose props have not changed. This is a significant performance advantage and a core part of Vue's design.
|
46
|
+
### 3. Component Lifecycle: Optimized for Rendering vs. Built for Persistence
|
51
47
|
|
52
|
-
* **
|
53
|
-
*
|
54
|
-
* When a config value changes, only the specific `createVdom` effects that depend on that *exact* piece of state are queued for re-execution. There are no cascading re-renders. If a parent's `createVdom` re-runs, but the configs passed to a child have not changed, the child component's `createVdom` function is **never executed**.
|
55
|
-
* **Benefit (Zero Manual Optimization):** Like Vue, this fine-grained reactivity eliminates the need for manual memoization (`memo`, `useCallback`, `useMemo`) that plagues React development. The framework handles dependency tracking automatically and precisely, delivering optimal performance out-of-the-box.
|
48
|
+
* **Vue.js: A Rich, Main-Thread Lifecycle**
|
49
|
+
* Vue provides a comprehensive set of lifecycle hooks (`onMounted`, `onUnmounted`, etc.) that give developers fine-grained control over a component's life within the main-thread environment. This is excellent for managing side effects related to the DOM, such as integrating third-party libraries.
|
56
50
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
* **Vue.js: Optimized for Main-Thread Scalability**
|
62
|
-
* Vue is designed to scale gracefully on the Main Thread. Its reactivity system and compiler optimizations ensure that as an application grows, performance remains high. State management with Pinia is also highly optimized.
|
63
|
-
* However, the fundamental limitation remains: all work competes for the same single thread. For extremely data-intensive applications, like real-time financial dashboards or complex graphical editors, the Main Thread can still become a bottleneck, no matter how optimized the framework is.
|
64
|
-
|
65
|
-
* **Neo.mjs: Built-in Efficiency and Linear Effort**
|
66
|
-
* Neo.mjs's architecture is designed to handle this scenario effortlessly. Multiple state changes are automatically batched into a single, de-duplicated update cycle via the `EffectBatchManager`.
|
67
|
-
* In a complex dashboard scenario, if a value in a global `StateProvider` changes, only the `createVdom` effects in components that *directly depend on that specific value* will re-run. All other components remain untouched. The crucial difference is that this logic runs in the **App Worker**, and the VDOM diffing runs in the **VDom Worker**, leaving the Main Thread free.
|
68
|
-
* This leads to a **linear relationship between complexity and effort**. As you add more components, you don't need to add more performance optimizations. The framework's core design ensures that updates are always surgical and efficient, allowing developers to focus on features instead of fighting the rendering engine. This is a direct result of the sophisticated, multi-layered batching and aggregation built into the framework's core.
|
51
|
+
* **Neo.mjs: A Stable Lifecycle for Asynchronous and Multi-Window Apps**
|
52
|
+
* Neo.mjs's lifecycle is designed to solve problems that are architecturally difficult in a single-threaded world.
|
53
|
+
* **`initAsync()`**: This hook runs *after* construction but *before* the component is considered "ready." It allows for asynchronous setup (like data fetching or lazy-loading modules) to complete *before* the first render. This eliminates entire classes of UI flicker and race conditions at an architectural level.
|
54
|
+
* **`afterSetMounted()` & True Persistence**: A Neo.mjs component is a stable, persistent instance. It can be unmounted from the DOM (`mounted: false`) and later remounted without being destroyed. `afterSetMounted` fires reliably for both state changes. This persistence is the key to enabling true multi-window applications, where a component instance can be moved from one browser window to another while retaining its full state and logic.
|
69
55
|
|
70
56
|
### Other Considerations:
|
71
57
|
|
@@ -74,19 +60,19 @@ A key differentiator between the frameworks is how they handle growing applicati
|
|
74
60
|
* **`dist/esm`:** Deploys as native ES Modules, preserving the dev mode's file structure.
|
75
61
|
* **`dist/production`:** Generates highly optimized, thread-specific bundles using Webpack.
|
76
62
|
* **`dist/development`:** A bundled but unminified environment for debugging production-specific issues.
|
77
|
-
* **Dynamic Module Loading:** Neo.mjs uniquely supports dynamically loading code-based modules (even with arbitrary `import` statements) from different environments at runtime, a powerful feature for plugin architectures.
|
78
63
|
|
79
64
|
* **Templates vs. Plain Objects:** Vue primarily uses HTML-like templates in SFCs (requiring a build step). Neo.mjs uses plain JavaScript objects for VDOM (no compilation needed for VDOM definition).
|
80
65
|
* **Ecosystem & Maturity:** Vue has a massive, mature ecosystem with a rich collection of libraries, tools (like Vite and Vue Devtools), and extensive community support. Neo.mjs has a smaller but dedicated community, with a focus on framework-level solutions and integrated features.
|
81
|
-
* **Learning Curve:** Vue is famous for its gentle learning curve and excellent documentation. Neo.mjs has a steeper initial learning curve due to its worker-based architecture, but once understood, it offers inherent performance benefits that don't require manual tuning.
|
82
66
|
* **Dependency Management (Batteries Included):** Vue projects, while often leaner than React, still rely on `node_modules` and a build toolchain. Neo.mjs is a "batteries included" framework with zero real runtime dependencies outside of its own core. This native ES Module approach significantly reduces complexity and dependency management overhead.
|
83
67
|
|
84
|
-
## Conclusion: Why Neo.mjs Offers
|
68
|
+
## Conclusion: Why Neo.mjs Offers a Different Path to Performance
|
69
|
+
|
70
|
+
For many websites and standard business applications, Vue's performance and developer experience are best-in-class for the main-thread paradigm. It is an exceptional framework.
|
85
71
|
|
86
|
-
For applications where guaranteed
|
72
|
+
Neo.mjs offers a different solution for a different class of problems. For applications where guaranteed UI fluidity is a non-negotiable business requirement, or for those pushing the boundaries of complexity with data-intensive UIs or multi-window environments, Neo.mjs provides a fundamentally more robust and scalable architecture.
|
87
73
|
|
88
|
-
* **
|
89
|
-
* **
|
90
|
-
* **
|
74
|
+
* **True Parallelism:** Neo.mjs doesn't just optimize tasks on the main thread; it runs them in parallel on separate threads, providing a higher performance ceiling.
|
75
|
+
* **Architectural Solutions for Complex Problems:** The hybrid reactivity model and the async-aware, persistent lifecycle are designed to solve complex orchestration and asynchronous challenges at the framework level.
|
76
|
+
* **Future-Proofing for Complexity:** By building on a multi-threaded foundation, Neo.mjs is architecturally prepared for the increasingly complex and data-intensive applications of the future.
|
91
77
|
|
92
|
-
The choice
|
78
|
+
The choice depends on the project's goals. If you are building a highly interactive SPA and hitting the limits of the main thread, or if you are envisioning a true multi-window desktop-like experience on the web, Neo.mjs offers a compelling and powerful alternative.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name" : "neo.mjs",
|
3
|
-
"version" : "10.0
|
3
|
+
"version" : "10.1.0",
|
4
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" : {
|
@@ -48,28 +48,28 @@ $text-color : #bbb;
|
|
48
48
|
--button-use-gradients : true;
|
49
49
|
|
50
50
|
// {module: Button, ui: --ghost}
|
51
|
-
--button-ghost-background-color :
|
51
|
+
--button-ghost-background-color : transparent;
|
52
52
|
--button-ghost-background-color-active : #373a3c;
|
53
53
|
--button-ghost-background-color-disabled : inherit;
|
54
|
-
--button-ghost-background-color-hover :
|
54
|
+
--button-ghost-background-color-hover : transparent;
|
55
55
|
--button-ghost-background-image : none;
|
56
56
|
--button-ghost-badge-background-color : var(--button-badge-color);
|
57
57
|
--button-ghost-badge-color : var(--button-badge-background-color);
|
58
|
-
--button-ghost-border :
|
58
|
+
--button-ghost-border : none;
|
59
59
|
--button-ghost-border-active : 1px #{$border-style} #282828;
|
60
60
|
--button-ghost-border-disabled : inherit;
|
61
|
-
--button-ghost-border-hover : 1px #{$border-style} #
|
61
|
+
--button-ghost-border-hover : 1px #{$border-style} #64B5F6;
|
62
62
|
--button-ghost-border-pressed : 1px #{$border-style} #5d83a7;
|
63
|
-
--button-ghost-glyph-color : var(--button-
|
64
|
-
--button-ghost-glyph-color-active : var(--button-
|
63
|
+
--button-ghost-glyph-color : var(--button-text-color);
|
64
|
+
--button-ghost-glyph-color-active : var(--button-text-color);
|
65
65
|
--button-ghost-glyph-color-disabled : inherit;
|
66
|
-
--button-ghost-glyph-color-hover : var(--button-
|
66
|
+
--button-ghost-glyph-color-hover : var(--button-text-color);
|
67
67
|
--button-ghost-opacity-disabled : var(--neo-disabled-opacity);
|
68
68
|
--button-ghost-ripple-background-color : inherit;
|
69
|
-
--button-ghost-text-color : var(--button-
|
70
|
-
--button-ghost-text-color-active : var(--button-
|
69
|
+
--button-ghost-text-color : var(--button-text-color);
|
70
|
+
--button-ghost-text-color-active : var(--button-text-color);
|
71
71
|
--button-ghost-text-color-disabled : inherit;
|
72
|
-
--button-ghost-text-color-hover : var(--button-
|
72
|
+
--button-ghost-text-color-hover : var(--button-text-color);
|
73
73
|
|
74
74
|
// {module: Button, ui: --secondary}
|
75
75
|
--button-secondary-background-color : var(--button-text-color);
|
package/src/DefaultConfig.mjs
CHANGED
@@ -299,12 +299,12 @@ const DefaultConfig = {
|
|
299
299
|
useVdomWorker: true,
|
300
300
|
/**
|
301
301
|
* buildScripts/injectPackageVersion.mjs will update this value
|
302
|
-
* @default '10.0
|
302
|
+
* @default '10.1.0'
|
303
303
|
* @memberOf! module:Neo
|
304
304
|
* @name config.version
|
305
305
|
* @type String
|
306
306
|
*/
|
307
|
-
version: '10.0
|
307
|
+
version: '10.1.0'
|
308
308
|
};
|
309
309
|
|
310
310
|
Object.assign(DefaultConfig, {
|
package/src/collection/Base.mjs
CHANGED
@@ -412,7 +412,7 @@ class Collection extends Base {
|
|
412
412
|
* Removes all items and clears the map
|
413
413
|
*/
|
414
414
|
clear() {
|
415
|
-
this.splice(0, this.
|
415
|
+
this.splice(0, this.count)
|
416
416
|
}
|
417
417
|
|
418
418
|
/**
|
@@ -429,7 +429,7 @@ class Collection extends Base {
|
|
429
429
|
clearSilent() {
|
430
430
|
let me = this;
|
431
431
|
|
432
|
-
me._items.splice(0, me.
|
432
|
+
me._items.splice(0, me.count);
|
433
433
|
me.map.clear()
|
434
434
|
}
|
435
435
|
|
@@ -817,10 +817,10 @@ class Collection extends Base {
|
|
817
817
|
* @param {Object} fn.item The current collection item
|
818
818
|
* @param {Object} scope=this The scope in which the passed function gets executed
|
819
819
|
* @param {Number} start=0 The start index
|
820
|
-
* @param {Number} end=this.
|
820
|
+
* @param {Number} end=this.count The end index (up to, last value excluded)
|
821
821
|
* @returns {Array} Returns an empty Array in case no items are found
|
822
822
|
*/
|
823
|
-
findBy(fn, scope=this, start=0, end=this.
|
823
|
+
findBy(fn, scope=this, start=0, end=this.count) {
|
824
824
|
let me = this,
|
825
825
|
items = [],
|
826
826
|
i = start;
|
@@ -1041,7 +1041,7 @@ class Collection extends Base {
|
|
1041
1041
|
* @returns {Object}
|
1042
1042
|
*/
|
1043
1043
|
last() {
|
1044
|
-
return this._items[this.
|
1044
|
+
return this._items[this.count -1]
|
1045
1045
|
}
|
1046
1046
|
|
1047
1047
|
/**
|
@@ -1102,7 +1102,7 @@ class Collection extends Base {
|
|
1102
1102
|
* @returns {Object} The removed element from the collection; undefined if the collection is empty.
|
1103
1103
|
*/
|
1104
1104
|
pop() {
|
1105
|
-
let mutation = this.splice(this.
|
1105
|
+
let mutation = this.splice(this.count -1, 1);
|
1106
1106
|
return mutation.removedItems[0]
|
1107
1107
|
}
|
1108
1108
|
|
@@ -1122,7 +1122,7 @@ class Collection extends Base {
|
|
1122
1122
|
*/
|
1123
1123
|
remove(key) {
|
1124
1124
|
this.splice(0, Array.isArray(key) ? key : [key]);
|
1125
|
-
return this.
|
1125
|
+
return this.count
|
1126
1126
|
}
|
1127
1127
|
|
1128
1128
|
/**
|
@@ -1132,7 +1132,7 @@ class Collection extends Base {
|
|
1132
1132
|
*/
|
1133
1133
|
removeAt(index) {
|
1134
1134
|
this.splice(index, 1);
|
1135
|
-
return this.
|
1135
|
+
return this.count
|
1136
1136
|
}
|
1137
1137
|
|
1138
1138
|
/**
|
@@ -1310,7 +1310,7 @@ class Collection extends Base {
|
|
1310
1310
|
*/
|
1311
1311
|
unshift(item) {
|
1312
1312
|
this.splice(0, 0, item);
|
1313
|
-
return this.
|
1313
|
+
return this.count
|
1314
1314
|
}
|
1315
1315
|
}
|
1316
1316
|
|
@@ -87,11 +87,11 @@ class Abstract extends Base {
|
|
87
87
|
*/
|
88
88
|
parentComponent_: null,
|
89
89
|
/**
|
90
|
-
*
|
91
|
-
* @
|
90
|
+
* The parent component id or document.body
|
91
|
+
* @member {String} parentId_='document.body'
|
92
92
|
* @reactive
|
93
93
|
*/
|
94
|
-
parentId_:
|
94
|
+
parentId_: 'document.body',
|
95
95
|
/**
|
96
96
|
* Optionally add a state.Provider to share state data with child components
|
97
97
|
* @member {Object|null} stateProvider_=null
|
@@ -32,7 +32,7 @@ class RecordFactory extends Base {
|
|
32
32
|
}
|
33
33
|
|
34
34
|
/**
|
35
|
-
* Assigns model
|
35
|
+
* Assigns model-based default values to a data object
|
36
36
|
* @param {Object} data
|
37
37
|
* @param {Neo.data.Model} model
|
38
38
|
* @returns {Object}
|
@@ -40,11 +40,13 @@ class RecordFactory extends Base {
|
|
40
40
|
assignDefaultValues(data, model) {
|
41
41
|
model.fieldsMap.forEach((field, fieldName) => {
|
42
42
|
if (Object.hasOwn(field, 'defaultValue')) {
|
43
|
+
const defaultValue = Neo.isFunction(field.defaultValue) ? field.defaultValue() : field.defaultValue;
|
44
|
+
|
43
45
|
// We could always use Neo.assignToNs() => the check is just for improving the performance
|
44
46
|
if (model.hasNestedFields) {
|
45
|
-
Neo.assignToNs(fieldName,
|
47
|
+
Neo.assignToNs(fieldName, defaultValue, data, false)
|
46
48
|
} else if (data[fieldName] === undefined) {
|
47
|
-
data[fieldName] =
|
49
|
+
data[fieldName] = defaultValue
|
48
50
|
}
|
49
51
|
}
|
50
52
|
});
|
@@ -73,7 +73,7 @@ class DragZone extends BaseDragZone {
|
|
73
73
|
let {owner} = this;
|
74
74
|
|
75
75
|
if (!(this.leafNodesOnly && !record.isLeaf)) {
|
76
|
-
return owner.getVdomChild(owner.getItemId(record.
|
76
|
+
return owner.getVdomChild(owner.getItemId(record[owner.getKeyProperty()]), owner.vdom)
|
77
77
|
}
|
78
78
|
|
79
79
|
return null
|
package/src/grid/Body.mjs
CHANGED
@@ -954,7 +954,7 @@ class GridBody extends Component {
|
|
954
954
|
/**
|
955
955
|
* @param {Object} data
|
956
956
|
* @param {Object[]} data.fields Each field object contains the keys: name, oldValue, value
|
957
|
-
* @param {Neo.data.Model} data.model
|
957
|
+
* @param {Neo.data.Model} data.model The model instance of the changed record
|
958
958
|
* @param {Object} data.record
|
959
959
|
*/
|
960
960
|
onStoreRecordChange({fields, record}) {
|
@@ -370,10 +370,6 @@ class DeltaUpdates extends Base {
|
|
370
370
|
let me = this,
|
371
371
|
node = DomAccess.getElementOrBody(delta.id);
|
372
372
|
|
373
|
-
if (!node) {
|
374
|
-
console.log('node not found', delta.id);
|
375
|
-
}
|
376
|
-
|
377
373
|
if (node) {
|
378
374
|
Object.entries(delta).forEach(([prop, value]) => {
|
379
375
|
switch (prop) {
|
@@ -53,7 +53,7 @@ const DomApiRenderer = {
|
|
53
53
|
// Apply Attributes
|
54
54
|
Object.entries(vnode.attributes).forEach(([key, value]) => {
|
55
55
|
if (voidAttributes.has(key)) {
|
56
|
-
domNode
|
56
|
+
domNode.toggleAttribute(key, value === 'true' || value === true)
|
57
57
|
} else if (key === 'value') {
|
58
58
|
domNode.value = value
|
59
59
|
} else if (value !== null && value !== undefined) {
|
package/src/selection/Model.mjs
CHANGED
@@ -157,6 +157,14 @@ class Model extends Base {
|
|
157
157
|
super.destroy(...args)
|
158
158
|
}
|
159
159
|
|
160
|
+
/**
|
161
|
+
* Important for mapping listeners to view controllers
|
162
|
+
* @returns {Neo.controller.Component|null}
|
163
|
+
*/
|
164
|
+
getController() {
|
165
|
+
return this.view.getController()
|
166
|
+
}
|
167
|
+
|
160
168
|
/**
|
161
169
|
* @returns {Array} this.items
|
162
170
|
*/
|
@@ -226,8 +234,9 @@ class Model extends Base {
|
|
226
234
|
items = [items]
|
227
235
|
}
|
228
236
|
|
229
|
-
let me
|
230
|
-
{view}
|
237
|
+
let me = this,
|
238
|
+
{view} = me,
|
239
|
+
records = [...items]; // Potential records
|
231
240
|
|
232
241
|
// We hold vdom ids for now, so all incoming selections must be converted.
|
233
242
|
items = items.map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item);
|
@@ -256,6 +265,7 @@ class Model extends Base {
|
|
256
265
|
view.onSelect?.(items);
|
257
266
|
|
258
267
|
me.fire('selectionChange', {
|
268
|
+
records,
|
259
269
|
selection: itemCollection
|
260
270
|
})
|
261
271
|
}
|
package/src/tree/List.mjs
CHANGED
@@ -64,9 +64,9 @@ class Tree extends Base {
|
|
64
64
|
* @member {Object} _vdom
|
65
65
|
*/
|
66
66
|
_vdom:
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
{cn: [
|
68
|
+
{tag: 'ul', cls: ['neo-list-container', 'neo-list'], tabIndex: -1, cn: []}
|
69
|
+
]}
|
70
70
|
}
|
71
71
|
|
72
72
|
/**
|
@@ -188,12 +188,15 @@ class Tree extends Base {
|
|
188
188
|
{folderCls, itemCls} = me,
|
189
189
|
cls = [itemCls],
|
190
190
|
contentCls = [itemCls + '-content'],
|
191
|
+
keyProperty = me.getKeyProperty(),
|
191
192
|
itemVdom;
|
192
193
|
|
193
194
|
if (record.iconCls) {
|
194
|
-
|
195
|
-
|
196
|
-
|
195
|
+
if (Array.isArray(record.iconCls)) {
|
196
|
+
contentCls.push(...record.iconCls)
|
197
|
+
} else {
|
198
|
+
contentCls.push(record.iconCls)
|
199
|
+
}
|
197
200
|
}
|
198
201
|
|
199
202
|
if (record.isLeaf) {
|
@@ -209,7 +212,7 @@ class Tree extends Base {
|
|
209
212
|
itemVdom = {
|
210
213
|
tag: 'li',
|
211
214
|
cls,
|
212
|
-
id : me.getItemId(record
|
215
|
+
id : me.getItemId(record[keyProperty]),
|
213
216
|
cn : [{
|
214
217
|
tag : 'span',
|
215
218
|
cls : contentCls,
|
@@ -416,16 +419,17 @@ class Tree extends Base {
|
|
416
419
|
* @param {Object} data
|
417
420
|
*/
|
418
421
|
onItemClick(node, data) {
|
419
|
-
let me
|
420
|
-
{items}
|
421
|
-
i
|
422
|
-
len
|
423
|
-
|
422
|
+
let me = this,
|
423
|
+
{items} = me.store,
|
424
|
+
i = 0,
|
425
|
+
len = items.length,
|
426
|
+
keyProperty = me.getKeyProperty(),
|
427
|
+
path = data.path.map(e => e.id),
|
424
428
|
item, record, tmpItem, vnodeId;
|
425
429
|
|
426
430
|
for (; i < len; i++) {
|
427
431
|
tmpItem = items[i];
|
428
|
-
vnodeId = me.getItemId(tmpItem
|
432
|
+
vnodeId = me.getItemId(tmpItem[keyProperty]);
|
429
433
|
|
430
434
|
if (path.includes(vnodeId)) {
|
431
435
|
record = tmpItem;
|
@@ -51,7 +51,7 @@ class TreeBuilder extends Base {
|
|
51
51
|
if (currentItem.componentId) {
|
52
52
|
// Prune the branch only if we are at the boundary AND the child is not part of a merged update
|
53
53
|
if (depth === 1 && !mergedChildIds?.has(currentItem.componentId)) {
|
54
|
-
output[childKey].push({
|
54
|
+
output[childKey].push({...currentItem, neoIgnore: true});
|
55
55
|
return // Stop processing this branch
|
56
56
|
}
|
57
57
|
// Expand the branch if it's part of a merged update, or if the depth requires it
|
package/src/vdom/Helper.mjs
CHANGED
@@ -255,7 +255,8 @@ class Helper extends Base {
|
|
255
255
|
// Case 2: Both nodes are placeholders for the same component
|
256
256
|
(childNode.componentId && childNode.componentId === oldChildNode.componentId)
|
257
257
|
)) {
|
258
|
-
if (childNode.
|
258
|
+
if (childNode.neoIgnore) {
|
259
|
+
delete childNode.neoIgnore;
|
259
260
|
continue
|
260
261
|
}
|
261
262
|
|