How Fre's `sibling()` Function Traverses Sibling Fibers in the Reconciliation Loop
Fre's sibling() function performs a post-order depth-first traversal by bubbling side-effects, committing dirty fibers, and climbing the tree to find the next work unit until the root is reached.
Fre is a lightweight, React-like library that manages UI updates through a Fiber architecture. At the heart of its rendering engine lies the sibling() function in src/reconcile.ts, which determines how the reconciler moves between nodes after processing a fiber's children. Understanding this traversal mechanism is essential for grasping how Fre implements cooperative scheduling and incremental rendering.
Where sibling() Fits in the Reconciliation Pipeline
The reconciliation process begins in the reconcile() function, which repeatedly calls capture() to process fibers until the scheduler's shouldYield() indicates a pause is needed. When capture() finishes processing a fiber's children, it invokes sibling() to locate the next unit of work:
// In src/reconcile.ts - capture() logic
if (fiber.isComp) {
// Component-specific reconciliation
} else {
updateHost(fiber as FiberHost);
}
return fiber.child || sibling(fiber); // Child first, then sibling traversal
The outer reconciliation loop drives the process:
while (fiber && !shouldYield()) {
fiber = capture(fiber) as any;
}
When capture() returns null (signaling the frame is complete), the scheduler yields control back to the browser.
Step-by-Step Traversal Logic in src/reconcile.ts
The sibling() implementation (located at lines 18-30 in src/reconcile.ts) follows a strict post-order traversal pattern that processes children before moving sideways or upward.
1. Bubbling Side Effects
Upon entry, sibling() immediately calls bubble(fiber) to execute any layout effects or cleanup callbacks associated with the completed fiber. This ensures component lifecycles are processed in the correct order before moving to the next node.
2. Committing Dirty Fibers
The function checks the fiber.dirty flag to determine if the fiber requires DOM updates:
- If
fiber.dirtyis set, the flag is cleared andcommit(fiber)is invoked to apply changes to the real DOM - The function returns
nullimmediately after committing, signaling the current frame should end - This pause allows the scheduler to batch DOM writes and maintain frame budget constraints
3. Moving to the Next Sibling
If the fiber is not dirty, sibling() checks for fiber.sibling. When a sibling reference exists, that fiber is returned immediately, allowing the reconciler to process the next node at the same tree depth.
4. Climbing the Tree
When no sibling exists, the algorithm enters a climbing loop:
- It sets
fiber = fiber.parentand repeats the bubble-check-commit cycle - This continues until either a sibling is found at a higher level or the root is reached
- If the root is reached without finding siblings,
nullis returned, indicating the entire tree has been traversed
Post-Order Depth-First Traversal Pattern
The traversal strategy implemented by sibling() is a post-order depth-first search that moves right-to-left across the Fiber tree:
- Down:
capture()descends intofiber.childfirst, processing entire subtrees before moving sideways - Sideways:
sibling()moves to the next sibling only after the current branch is complete - Up: When siblings are exhausted, the algorithm climbs to parents, ensuring parent effects run after all children are processed
This pattern guarantees that component cleanup and layout effects execute in the correct hierarchical order, and that DOM commits happen only after a fiber and its entire subtree have been reconciled.
Practical Example: Component Tree Walking
Consider a component structure where sibling() manages the traversal flow:
import { render, useState, useEffect } from 'fre'
function Parent() {
return (
<div>
<ChildA />
<ChildB />
</div>
)
}
function ChildA() {
useEffect(() => console.log('ChildA committed'), [])
return <span>A</span>
}
function ChildB() {
useEffect(() => console.log('ChildB committed'), [])
return <span>B</span>
}
render(<Parent />, document.getElementById('root'))
Traversal sequence:
capture()entersParent, thenChildA- After reconciling
ChildA,sibling()findsChildBand returns it - After
ChildBfinishes,sibling()climbs toParent, finds no sibling, and continues upward - At the root,
sibling()returnsnull, ending the frame
The bubble() calls ensure ChildA's effect runs before ChildB's, and both run before the parent completes.
Summary
sibling()insrc/reconcile.tsmanages post-order traversal by processing side-effects, committing dirty fibers, and climbing the tree when siblings are exhausted- The function returns
nulleither when committing a dirty fiber (to yield to the scheduler) or when reaching the root (indicating traversal completion) - Post-order depth-first traversal ensures children are fully processed before parents, maintaining correct effect ordering
- The combination of
capture()(downward) andsibling()(sideways/upward) creates Fre's cooperative scheduling system that respects browser frame budgets
Frequently Asked Questions
What is a Fiber in Fre?
A Fiber is a lightweight JavaScript object representing a unit of work in Fre's rendering pipeline. Each Fiber corresponds to a React element or DOM node and contains references to its child, sibling, and parent, along with state, props, and effect flags like dirty.
How does sibling() know when to stop traversing?
sibling() stops when it reaches the root of the Fiber tree without finding any remaining siblings. At that point, it returns null, which signals to the reconcile() loop that the entire tree has been processed and the current frame is complete.
What is the difference between capture() and sibling() in Fre?
capture() handles the downward phase of reconciliation, entering into child fibers and performing component updates or host node creation. sibling() handles the upward and sideways phase, moving to next siblings after children are complete or climbing to parents when a branch is finished.
When does sibling() trigger a DOM commit?
sibling() triggers a DOM commit via commit(fiber) whenever the current fiber has the dirty flag set to true. This typically happens after state updates or initial renders. After committing, sibling() returns null to allow the scheduler to pause work and maintain performance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →