How Fre's render() Function Works: A Deep Dive into the Main Entry Point

Fre's render() function in src/reconcile.ts bootstraps the rendering process by accepting a virtual node and DOM container, creating an initial Fiber tree, and queuing the first reconciliation task for the scheduler.

The render() function serves as the primary public API in the frejs/fre repository, translating virtual DOM descriptions into concrete browser UI. As implemented in src/reconcile.ts, this main entry point bridges declarative component trees with the underlying Fiber-based reconciliation engine. It initializes the work-in-progress tree and delegates execution to the cooperative scheduler, initiating the full reconciliation lifecycle.

The Anatomy of Fre's render() Function

Source Location and Signature

The render() implementation resides at lines 21-30 of src/reconcile.ts. This function receives two critical arguments: a virtual node (vnode) representing the root of your UI tree, and a real DOM container (node) where the application mounts.

// Conceptual signature based on src/reconcile.ts
function render(vnode: VNode, node: HTMLElement): void

Upon invocation, render() immediately captures the container's existing first child as currentDom. This reference enables server-side hydration support by allowing Fre to reuse existing DOM nodes rather than recreating them from scratch.

Root Fiber Initialization

Inside render(), the function constructs a rootFiber object—a lightweight data structure representing the initial unit of work. This fiber's props.children property is populated with the supplied virtual node, effectively wrapping your application root in a work unit that the scheduler can process. The root fiber acts as the starting point for the entire reconciliation traversal.

From render() to the Scheduler

Enqueuing Work with update()

Immediately after fiber creation, render() invokes update(rootFiber) at lines 32-36 of src/reconcile.ts. The update() function performs two critical operations: it marks the fiber as dirty (indicating pending changes) and pushes a task into the internal task queue. This transition moves the work from the synchronous setup phase into the asynchronous scheduling system.

Cooperative Time-Slicing

The scheduler implementation in src/schedule.ts manages execution through schedule(callback), which appends the reconciliation callback to a queue and triggers startTransition. The flush function then repeatedly executes tasks while shouldYield() returns false, checking against a deadline time slice.

This cooperative multitasking approach ensures that rendering yields control to the browser when approaching frame boundaries, preventing layout thrashing and maintaining responsive user interactions during heavy component trees.

The Reconciliation Work Loop

The capture() Phase

Once scheduled, the reconciliation loop begins at reconcile(fiber) in src/reconcile.ts (lines 39-44). The reconcile function walks the fiber tree by invoking capture() on each node (lines 86-101). This phase determines whether the current fiber represents a component (function or class) or a host element (DOM node), routing each to specialized handlers.

Component Processing via updateHook()

For component fibers, capture() delegates to updateHook() (lines 56-70). This function:

  • Resets the hook cursor for fresh state management
  • Prepares a fragment node to contain return values
  • Executes the component function and recursively reconciles its children
  • Intercepts thrown Promise objects to trigger Suspense fallbacks
  • Catches other errors to activate ErrorBoundary recovery

Host Element Handling via updateHost()

Host elements traverse through updateHost() (lines 72-92), which handles concrete DOM operations. This function creates or reuses existing DOM nodes via createElement, updates properties and attributes, and manages special modes like SVG context switching. When currentDom is present from server rendering, updateHost performs hydration diffing instead of full node creation.

Diffing and Commit

The Diff Algorithm

As the work loop traverses children, reconcileChildren (lines 97-108) constructs the new child list and invokes the diff algorithm. This comparison produces a deterministic set of actions—INSERT, UPDATE, MOVE, or REMOVE—which are stored on each child fiber. The diff algorithm optimizes for minimal DOM manipulation by leveraging key-based reconciliation similar to other modern virtual DOM implementations.

Commit Phase

When a fiber completes processing (the bubble phase), side effects from hooks such as useEffect execute. Finally, the commit function applies the queued DOM actions to the real browser document, ensuring the UI reflects the virtual tree's current state. Once the work queue empties, flush stops scheduling frames and the application enters an idle state until the next render() call or state update.

Practical Usage Examples

Basic Component Mounting

import { h, render } from 'fre';

function App() {
  return h('div', null,
    h('h1', null, 'Hello, Fre!'),
    h('p', null, 'Fast, lightweight UI.')
  );
}

const container = document.getElementById('root');
render(h(App, null), container);

Concurrent Features with Suspense

import { h, render, useState, Suspense, lazy } from 'fre';

const LazyComponent = lazy(() => import('./Heavy'));

function Counter() {
  const [count, setCount] = useState(0);
  return h('button', { 
    onClick: () => setCount(c => c + 1) 
  }, `Clicked ${count}`);
}

function App() {
  return h(Suspense, { fallback: h('div', null, 'Loading…') },
    h(LazyComponent, null),
    h(Counter, null)
  );
}

render(h(App, null), document.getElementById('root'));

Server-Side Hydration

import { h, render } from 'fre';

// Container contains HTML from server rendering
const container = document.getElementById('root');

// Fre automatically hydrates using existing DOM nodes
render(h(App, null), container);

Summary

  • render() in src/reconcile.ts acts as the thin public wrapper that initializes the Fiber architecture and triggers the scheduler.
  • The function stores node.firstChild as currentDom to enable SSR hydration and efficient DOM reuse.
  • Root fiber creation packages the virtual tree into a work unit compatible with the reconciliation loop.
  • Cooperative scheduling via src/schedule.ts ensures time-sliced rendering that yields to the browser.
  • The capture phase distinguishes components (processed by updateHook) from host elements (processed by updateHost).
  • Diff and commit phases translate virtual changes into minimal, batched DOM updates.

Frequently Asked Questions

Where is Fre's render function defined?

Fre's render() function is defined in src/reconcile.ts at lines 21-30. This module also contains the core reconciliation logic including update(), capture(), and the diffing algorithms.

How does Fre's render function support server-side rendering?

The render() function accepts a DOM container that may already contain HTML markup. It stores node.firstChild as currentDom, which updateHost() uses during the reconciliation process to hydrate existing server-rendered elements rather than creating new DOM nodes from scratch.

What happens immediately after calling render()?

After render() constructs the root fiber, it synchronously calls update(rootFiber) to mark the fiber dirty and enqueue the first task. Control then returns to the browser until the scheduler's flush function begins processing the work loop asynchronously.

Why does Fre use a Fiber-based architecture for rendering?

The Fiber architecture enables incremental rendering by breaking work into small units that can be prioritized, paused, and resumed. This supports concurrent features like Suspense and Error Boundaries while maintaining 60fps performance by yielding to the browser's main thread between time slices.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →