[v2]: cursor based chore handling #323
wmertens
started this conversation in
Proposals For Qwik
Replies: 2 comments
-
|
Additional things:
|
Beta Was this translation helpful? Give feedback.
0 replies
-
|
Implementation:
optimizations:
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Qwik Cursors Architectural Document
This document outlines some core architectural concepts of Qwik, specifically focusing on its sparse virtual DOM (vDOM) implementation, chore execution mechanism, and cursor management for both server-side rendering (SSR) and browser environments.
1. Sparse Virtual DOM (vDOM)
Qwik utilizes a sparse vDOM, which represents only the DOM nodes that are of interest (i.e., potentially mutable). This contrasts with traditional vDOM implementations that often represent the entire tree.
1.1. vNode Structure
Each node in the sparse vDOM is represented by a
vNode. AvNodehas the following key properties:type: string | Function: Can be either a string representing an HTML tag name (e.g.,'div','span') or a function representing a inline component or a Qwik component.varProps: Props: An object containing properties that can change during thevNode's lifetime (e.g., attributes, styles).constProps: Props: An object containing properties that cannot change after thevNodeis created. Meaning, scalar values won't change, and references to objects are immutable but their internal state can change. If these objects are reactive, they can still trigger DOM attribute updates.qKey: string: A unique identifier for thevNodewithin its parent, used for efficient tracking and updates.parent: vNode | null: A reference to the parentvNodein the tree.children: vNode[]: An array of childvNodes.depth: number: The depth of thevNodein the tree, including any skipped nodes. This can be used for ancestry detection.sequentialScope: unknown[] | null: An array used to store the state of component hooks (e.g.,useTask$).dirty: number: A bitfield used to indicate which chores need to be performed on thisvNode.jsxOutput: JSXOutput | null: Stores the result of rendering the component's JSX, used during theNODE_DIFFchore.dirtyChildren: Set<VNode> | null: A set containing childvNodes that are currently marked dirty.1.2. vNode Types
VNode: The base class for allvNodes.SsrVNode: Inherits fromVNodeand addsstreamed: booleanto indicate if the opening tag for this node has begun streaming during SSR.DomVNode: Inherits fromVNodeand addselement: Element | nullto store a reference to the actual DOM element it represents in the browser.2. Chore Execution Mechanism
Qwik uses a chore-based system to manage updates to the vDOM and the DOM. Chores represent specific tasks that need to be performed on a
vNode. Chores are indicated by thedirtybitfield.2.1. Chore Order
Chores are executed in a specific order:
TASK: Runs any dirtyuseTask$callbacks attached to thevNode'ssequentialScope. These tasks are render-blocking by default, meaning they must complete before further rendering can proceed.NODE_DIFF:vNodewith the storedjsxOutput.vNode's structure (children, props) to match the newjsxOutput.journal(only for DOM environments).vNodes as needed.vNodes as dirty if necessary.Promiseencountered during the process.NODE_PROP: Applies changed properties from thevNode'svarPropsto the corresponding DOM element (only for DOM environments). This does not block children processing, but is blocked by parent processing and is therefore a chore.COMPONENT:type(props)).Promisereturned by the component function.vNodeforNODE_DIFFwith the result stored injsxOutput.RECOMPUTE_AND_SCHEDULE_EFFECTS:vNode'ssequentialScope.2.2. Chore Execution Rules
TASK,NODE_DIFF,NODE_PROP,COMPONENT,RECOMPUTE_AND_SCHEDULE_EFFECTS).vNodes can only be processed if their parentvNodeis currently clean (i.e., all its chores have been completed).2.3. Efficient Dirty Marking
vNode, it is marked dirty by setting the corresponding bit in thedirtybitfield.vNodeis also added to its parent'sdirtyChildrenset.vNodebecomes clean (all its chores completed), it removes itself from its parent'sdirtyChildrenset.vNodeis marked dirty, this dirty status is propagated upwards to the topmost ancestorvNodethat is already dirty.vNodereceives acursor.3. Cursors
A
cursoris a reference to avNodethat enables efficient traversal and manipulation of the vDOM tree. It has a low or high priority.3.1. Cursor Lifecycle
cursoralways indicates the topmost dirtyvNodein its subtree, except where it was created recursively.cursorinvolves executing the dirty chores of the referencedvNodein the defined order.vNodeare complete, itsdirtyChildren(if any) are processed.vNodereturns aPromiseduring processing (e.g., duringNODE_DIFForCOMPONENT), a newcursoris created for that child (using the same journal in DOM environments), it is removed from thedirtyChildrenset, and processing continues with the next sibling.cursoris considered done when all its chores and those of all its children (excluding any recursively created cursors) have been completed.3.2. Cursor Propagation
vNodeto become dirty (except forNODE_PROPchores), all active cursors under that ancestor are stopped. These stops are awaited and if they are not done, their intermediatevNodes are added to thedirtyChildrenset of their respective parents.vNodethen receives a newcursor.3.3. Parallel Processing
4. Environment-Specific Considerations
4.1. DOM Environment
journalis used to store the changes to be applied to the DOM during theNODE_DIFFandNODE_PROPchores.journalis committed to the DOM only when acursoris done (i.e., all its chores and those of its children are complete).vNodedepth (deepest first) to ensure that deletions can skip journal entries corresponding to nodes below the deleted node.4.2. Server-Side Rendering (SSR) Environment
journalis used in SSR.Suspenseboundaries. This means that there is a single reference to the currently streamingvNode.vNodes can cause parentvNodes to change without error, as long as they didn't begin streaming yet.Suspenseboundary is encountered and the firstPromisewithin it takes longer than a timeout to resolve, an out-of-ordervNode(e.g., a placeholder) is created and included in the initial HTML stream. This out-of-order node will be filled in later when thePromiseresolves.Suspenseboundary are done or when the OoO node is streamed.Beta Was this translation helpful? Give feedback.
All reactions