This ticket proposes a refactoring of Neo.component.Base#mount() and the removal of the hasUnmountedVdomChanges mechanism in Neo.mixin.VdomLifecycle.
Context & Problem
The current implementation of mount() contains legacy code that relies on outerHTML, which is not compatible with the default DomApiRenderer. Additionally, Neo.mixin.VdomLifecycle maintains a hasUnmountedVdomChanges_ flag. This flag requires traversing the component's parent chain on every single VDOM update while a component is unmounted.
Proposed Solution
- Refactor
mount(): Simplify Neo.component.Base#mount() to be a direct alias for this.initVnode(true).
- Remove Overhead: Delete the
hasUnmountedVdomChanges_ config, its afterSet hook, and its assignment logic in updateVdom.
Trade-off Analysis (Pros & Cons)
Pros (Why we are doing this):
- Performance on Hot Path: We eliminate the overhead of tracking
hasUnmountedVdomChanges on every VDOM update. This logic traversed the parent chain unnecessarily, impacting performance for unmounted components (e.g., inactive tabs, hidden dialogs).
- Robustness: By forcing
initVnode(true), we ensure the DOM is always mounted with the most up-to-date VDOM state, eliminating potential synchronization bugs.
- Cleanup: Removes dead/legacy code related to string-based
outerHTML mounting strategies.
Cons (What we are sacrificing):
- Loss of "Pre-render" Optimization: In the specific edge case where a developer calls
await initVnode(false) to pre-calculate the VNode tree, leaves the component completely untouched, and then calls mount(), the VNode tree will now be regenerated instead of re-used. This incurs a duplicate TreeBuilder run.
Decision Rationale
The "Pre-render Optimization" scenario is an edge case (<1% likelihood) compared to the common scenario of components updating while unmounted (e.g., hideMode: 'removeDom'). The current architecture penalized the common case (tracking overhead) to support the rare case. Furthermore, the existing mount() optimization was already partially broken for DomApiRenderer.
We have decided that broadly improving runtime performance and codebase simplicity outweighs the loss of this specific edge-case optimization.
Note: If this optimization proves critical for specific advanced use cases (e.g., complex micro-frontends), it can be re-introduced in a more targeted manner in the future without imposing a global performance penalty.
Tasks
src/component/Base.mjs:
- Refactor
mount() to return this.initVnode(true);.
- Remove legacy code/comments.
src/mixin/VdomLifecycle.mjs:
- Remove
hasUnmountedVdomChanges_ from static config.
- Remove
afterSetHasUnmountedVdomChanges.
- Remove logic setting
me.hasUnmountedVdomChanges in updateVdom.
This ticket proposes a refactoring of
Neo.component.Base#mount()and the removal of thehasUnmountedVdomChangesmechanism inNeo.mixin.VdomLifecycle.Context & Problem
The current implementation of
mount()contains legacy code that relies onouterHTML, which is not compatible with the defaultDomApiRenderer. Additionally,Neo.mixin.VdomLifecyclemaintains ahasUnmountedVdomChanges_flag. This flag requires traversing the component's parent chain on every single VDOM update while a component is unmounted.Proposed Solution
mount(): SimplifyNeo.component.Base#mount()to be a direct alias forthis.initVnode(true).hasUnmountedVdomChanges_config, itsafterSethook, and its assignment logic inupdateVdom.Trade-off Analysis (Pros & Cons)
Pros (Why we are doing this):
hasUnmountedVdomChangeson every VDOM update. This logic traversed the parent chain unnecessarily, impacting performance for unmounted components (e.g., inactive tabs, hidden dialogs).initVnode(true), we ensure the DOM is always mounted with the most up-to-date VDOM state, eliminating potential synchronization bugs.outerHTMLmounting strategies.Cons (What we are sacrificing):
await initVnode(false)to pre-calculate the VNode tree, leaves the component completely untouched, and then callsmount(), the VNode tree will now be regenerated instead of re-used. This incurs a duplicateTreeBuilderrun.Decision Rationale
The "Pre-render Optimization" scenario is an edge case (<1% likelihood) compared to the common scenario of components updating while unmounted (e.g.,
hideMode: 'removeDom'). The current architecture penalized the common case (tracking overhead) to support the rare case. Furthermore, the existingmount()optimization was already partially broken forDomApiRenderer.We have decided that broadly improving runtime performance and codebase simplicity outweighs the loss of this specific edge-case optimization.
Note: If this optimization proves critical for specific advanced use cases (e.g., complex micro-frontends), it can be re-introduced in a more targeted manner in the future without imposing a global performance penalty.
Tasks
src/component/Base.mjs:mount()toreturn this.initVnode(true);.src/mixin/VdomLifecycle.mjs:hasUnmountedVdomChanges_fromstatic config.afterSetHasUnmountedVdomChanges.me.hasUnmountedVdomChangesinupdateVdom.