How Fre's capture() Function Processes Fibers During Reconciliation
Fre's capture() function in src/reconcile.ts acts as the step-function of the reconciler, identifying fiber types, executing component logic or updating DOM nodes, and returning the next fiber to traverse until the cooperative scheduler yields.
The Fre library implements a modern, React-like fiber architecture for building user interfaces. At the heart of its reconciliation phase lies the capture() function, which orchestrates how each node in the fiber graph is processed, updated, and traversed during the render cycle.
What Is the capture() Function?
The capture() function serves as the primary workhorse inside Fre's reconciliation loop. Defined in [src/reconcile.ts](https://github.com/frejs/fre/blob/master/src/reconcile.ts), it receives a single fiber node and performs the necessary computations to advance the virtual tree's state. After processing the current fiber, it returns the next fiber to visit—either a child, sibling, or an ancestor's sibling—enabling the depth-first traversal of the component tree without recursion.
Step-by-Step Fiber Processing
When capture() processes a fiber, it follows a strict sequence of type checks and update routines. Each step determines how the reconciler treats the current node.
1. Fiber Type Identification
First, capture() distinguishes between function components and host elements by checking the fiber's type property. It sets the isComp flag based on whether the type is a function.
fiber.isComp = isFn(fiber.type) // src/reconcile.ts#L86‑L88
This classification determines which update path the reconciler follows for the remainder of the function.
2. Short-Circuiting Memoized Components
If the fiber represents a memoized component whose props have not changed, capture() immediately bails out to avoid unnecessary work. It resets the memo flag and jumps to the next sibling, skipping the entire subtree.
if (isMemo(fiber)) {
fiber.memo = false
return sibling(fiber) // src/reconcile.ts#L90‑L93
}
This optimization prevents redundant re-renders of pure components.
3. Executing Function Components
For standard function components, capture() invokes updateHook(), which prepares the hook cursor, creates a fragment node, executes the component function, and reconciles the returned children.
const isMatchSuspenseOrErrorBoundary = updateHook(fiber) // src/reconcile.ts#L94‑L96
if (isMatchSuspenseOrErrorBoundary) return isMatchSuspenseOrErrorBoundary
If the component throws a Promise, suspenseRender() creates a Suspense boundary; if it throws an error, errorBoundaryRender() establishes an ErrorBoundary. These returns exit the current capture cycle to handle the boundary state.
4. Handling Host Elements
When capture() encounters a non-component fiber—such as a DOM element—it delegates to updateHost(). This function creates or updates the real DOM node and then reconciles the fiber's children.
updateHost(fiber as FiberHost) // src/reconcile.ts#L98‑L100
Host processing bridges the virtual representation with the actual browser DOM.
5. Advancing the Traversal
Finally, capture() determines the next fiber to process. It returns the first child to dive deeper into the tree, or if no child exists, it calls sibling() to find the next sibling or an ancestor's sibling.
return fiber.child || sibling(fiber) // src/reconcile.ts#L101‑L102
The sibling() function walks up the tree until it locates the next valid node or reaches the root, effectively implementing the depth-first traversal pattern.
The Cooperative Scheduling Loop
The capture() function operates within a controlled loop inside reconcile(), which respects the browser's scheduling constraints. The surrounding code repeatedly invokes capture() until the work is complete or the scheduler signals a yield.
while (fiber && !shouldYield()) {
fiber = capture(fiber) as any
}
This integration with shouldYield() from src/schedule.ts enables Fre to implement cooperative scheduling, allowing the renderer to pause and resume work to maintain frame rates and responsiveness.
Practical Code Examples
Basic Component Rendering
When you mount a component, capture() processes the function component first, then the resulting host elements.
import { render } from 'fre'
import { h } from 'fre/h'
function Counter({ start }) {
const [count, setCount] = useState(start)
return h('button', { onClick: () => setCount(c => c + 1) }, count)
}
const container = document.getElementById('root')
render(h(Counter, { start: 0 }), container)
The render() call initiates reconciliation, triggering capture() to process the Counter fiber, execute its hook logic, and then traverse the button host element.
Manual Component Updates
You can trigger reconciliation manually by updating props and calling update().
// Assuming rootFiber is the fiber returned from render()
rootFiber.props.children = h(Counter, { start: 10 })
update(rootFiber)
This marks the fiber dirty, causing capture() to re-execute the component with the new start value and diff the resulting subtree.
Suspense Boundary Handling
When a component throws a Promise during execution, capture() intercepts it to establish a Suspense boundary.
function AsyncComp() {
const data = fetchData() // Returns a Promise
return h('div', null, data) // Throws the Promise
}
render(
h(Suspense, { fallback: h('span', null, 'Loading') },
h(AsyncComp, null)
),
container
)
During capture(), when AsyncComp throws the Promise, the function calls suspenseRender() to swap the primary subtree with the fallback UI until the Promise resolves.
Key Source Files Involved
Several modules collaborate to support the capture() workflow:
src/reconcile.ts– Implementscapture(),sibling(), and the diffing algorithm that drives the reconciliation process.src/type.ts– Defines theFiberinterface andTAGconstants used to distinguish node types during traversal.src/schedule.ts– Providesschedule()andshouldYield()for cooperative scheduling around thecapture()loop.src/commit.ts– Performs DOM mutations aftercapture()marks fibers dirty and work completes.src/h.ts– Supplies type-checking utilities likeisMemo,isFn, and JSX element creation helpers.
Summary
capture()insrc/reconcile.tsis the central step-function that processes each fiber during Fre's reconciliation phase.- It identifies fiber types using
isFn()to route components throughupdateHook()and host elements throughupdateHost(). - Memoized components are short-circuited early by returning
sibling(fiber)when props haven't changed. - The function handles error boundaries and Suspense by catching thrown Promises or errors during
updateHook(). - Traversal advances via
fiber.childorsibling(), enabling depth-first walks while integrated with cooperative scheduling viashouldYield().
Frequently Asked Questions
Where is the capture() function defined in Fre?
The capture() function is defined in src/reconcile.ts. It serves as the core work unit processor during the reconciliation loop, handling type checking, component execution, and traversal logic for the fiber graph.
How does capture() handle Suspense boundaries?
When a function component throws a Promise during execution inside updateHook(), capture() catches this and returns the result of suspenseRender(). This creates a Suspense boundary that renders a fallback UI until the asynchronous operation completes, effectively pausing the subtree's rendering.
What happens when capture() encounters a memoized component?
If isMemo(fiber) returns true and the props haven't changed, capture() resets the memo flag and immediately returns sibling(fiber). This skips the entire subtree reconciliation, preventing unnecessary recomputation of pure components.
How does capture() decide which fiber to process next?
After processing the current fiber, capture() returns fiber.child if a child exists to continue depth-first traversal. If no child exists, it calls sibling(fiber) to find the next sibling or walk up the tree to find an ancestor's sibling, ensuring complete tree coverage without recursion.
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 →