LearnNewsExamplesServices
Frontmatter
id8132
titleRefactor Component mount() and remove VdomLifecycle overhead
stateClosed
labels
discussionairefactoringarchitecture
assigneestobiu
createdAtDec 17, 2025, 1:51 AM
updatedAtDec 17, 2025, 2:10 AM
githubUrlhttps://github.com/neomjs/neo/issues/8132
authortobiu
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtDec 17, 2025, 2:10 AM

Refactor Component mount() and remove VdomLifecycle overhead

Closed v11.17.0 discussionairefactoringarchitecture
tobiu
tobiu commented on Dec 17, 2025, 1:51 AM

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

  1. Refactor mount(): Simplify Neo.component.Base#mount() to be a direct alias for this.initVnode(true).
  2. 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

  1. src/component/Base.mjs:
    • Refactor mount() to return this.initVnode(true);.
    • Remove legacy code/comments.
  2. src/mixin/VdomLifecycle.mjs:
    • Remove hasUnmountedVdomChanges_ from static config.
    • Remove afterSetHasUnmountedVdomChanges.
    • Remove logic setting me.hasUnmountedVdomChanges in updateVdom.
tobiu added the discussion label on Dec 17, 2025, 1:51 AM
tobiu added the ai label on Dec 17, 2025, 1:51 AM
tobiu added the refactoring label on Dec 17, 2025, 1:51 AM
tobiu added the architecture label on Dec 17, 2025, 1:51 AM
tobiu assigned to @tobiu on Dec 17, 2025, 1:52 AM
tobiu referenced in commit 02b25aa - "Refactor Component mount() and remove VdomLifecycle overhead #8132" on Dec 17, 2025, 2:10 AM
tobiu closed this issue on Dec 17, 2025, 2:10 AM