Core Components of the Fre Library: Architecture and Functions Explained
The Fre library comprises seven tightly-focused modules—JSX element factory, virtual node helpers, reconciliation engine, hook system, cooperative scheduler, DOM operations, and TypeScript definitions—that together provide a React-like API in under 7 KB.
Fre is a lightweight, React-compatible UI library written in TypeScript, hosted in the frejs/fre repository. Understanding the core components of the Fre library reveals how it achieves component-based rendering with hooks, context, and time-sliced updates while maintaining a minimal footprint. Each component is isolated to a specific source file, creating a clear separation between virtual DOM creation, reconciliation, and platform-specific operations.
JSX Element Factory and Virtual Node Creation
The element factory resides in src/h.ts and exports the h function (also available as createElement from src/index.ts). This function transforms JSX or manual function calls into virtual nodes (v-nodes) that serve as the immutable description of your UI.
According to the source code at lines 5-20, h performs four critical operations: it normalizes props to ensure an object exists even when omitted, flattens nested children arrays while filtering booleans and null values, extracts key and ref properties for reconciliation, and returns a plain vnode object via createVnode. The createVnode helper (lines 37-42) constructs the final object with properties for type, props, key, and ref.
Additional utilities in this module support advanced patterns:
createText(lines 44-46) generates text-node fibers with the type#textFragment(lines 47-49) returns children unchanged to enable JSX fragments without wrapper elementsmemo(lines 51-55) marks function components for shallow comparison to prevent unnecessary re-renderslazy(lines 59-78) enables code-splitting by returning a component that throws a Promise during load, triggering the Suspense mechanism
Reconciliation Engine and Diff Algorithm
The reconciler in src/reconcile.ts implements Fre's virtual DOM diffing and time-sliced rendering. This module contains the core work loop that determines what changes must be committed to the DOM.
The public entry point render (lines 21-30) creates a root fiber and initiates the work loop. When state changes occur, update (lines 32-36) marks fibers as dirty and schedules reconciliation. The reconcile function (lines 39-44) serves as the work loop itself, repeatedly calling capture until the scheduler yields control back to the browser.
Key internal functions include:
capture(lines 86-101): The heart of the diff algorithm that distinguishes between component and host fibers, executes component functions, or updates DOM nodesdiff(lines 40-108): An O(N) list differencing algorithm that produces action arrays (INSERT,UPDATE,REMOVE,MOVE,REPLACE) to transform child lists efficientlycommit(lines 16-18 and called frombubble): The phase where DOM mutations actually occur viaupdateElementandremoveElementsuspenseRenderanderrorBoundaryRender(lines 61-84): Handle asynchronous boundaries and error catching by rendering fallback UI when components throw Promises or exceptions
The reconciler operates in time-sliced mode, checking shouldYield() from the scheduler after each work chunk to maintain responsive UIs during heavy renders.
Hook System and Context API
Fre provides a complete React-compatible hook API implemented in src/hook.ts. The system relies on a cursor that walks the hooks.list array during each component render, enforcing the rules of hooks through call order rather than runtime validation.
Core hook implementations include:
useState(lines 25-27): A wrapper arounduseReducerthat manages a single value and its setter functionuseReducer(lines 29-51): Maintains a state tuple[value, dispatch]and schedules updates when dispatch is calleduseEffectanduseLayout(lines 53-58): Register side effects inhooks.effectorhooks.layoutarrays that execute after the render phase completesuseMemo(lines 74-84): Caches expensive computations and recomputes only when dependency arrays changeuseCallback(lines 86-91): A convenience wrapper arounduseMemothat returns memoized function referencesuseRef(lines 93-95): Returns a mutable ref object that persists across renders without triggering updates
For cross-component communication, createContext (lines 14-26) builds a provider component that tracks values and notifies subscribers, while useContext (lines 29-40) subscribes to the nearest context boundary and forces re-renders when the context value changes.
Cooperative Task Scheduler
The scheduler in src/schedule.ts implements a cooperative task queue that works in both browser and Node environments. This component ensures that rendering work never blocks the main thread for more than 5 milliseconds.
Key exports include:
schedule(cb)(lines 12-15): Enqueues a callback and initiates a transitionstartTransition(cb)(lines 8-11): Explicitly marks low-priority updates that can be interrupted by higher-priority workflush()(lines 32-46): Processes the task queue until the deadline (threshold of 5 ms) is reached, then yields controlshouldYield()(lines 49-51): Comparesperformance.now()against the deadline to determine if the browser needs controlgetTime()(line 53): A thin wrapper aroundperformance.now()for cross-platform timing
This architecture allows Fre to split large component trees into incremental work units, maintaining smooth frame rates even during complex updates.
DOM Operations and Property Diffing
Platform-specific code is isolated in src/dom.ts, which handles the creation and mutation of actual DOM nodes. This separation allows the reconciler to remain agnostic about the rendering target.
The module exports two primary functions:
createElement(fiber)(lines 48-56): Instantiates real DOM nodes—whether text nodes, SVG elements, or HTML elements—and applies initial propertiesupdateElement(dom, aProps, bProps)(lines 19-45): Performs granular diffing of attributes, styles, and event listeners between renders
The updateElement function uses a joint iterator to compare old and new props simultaneously, handling special cases such as style object diffing and on* event listener attachment and removal.
Type Definitions and Public API
All TypeScript contracts are centralized in src/type.ts, defining interfaces for Fiber, FC (function components), Hook types, Action types for the diff algorithm, and enumeration values for TAG and MODE. These definitions provide compile-time safety for both internal development and library consumers.
The public API surface is curated through src/index.ts, which re-exports the essential functions from each module: h, render, useState, useEffect, memo, lazy, Suspense, createContext, and schedule. This centralized export pattern ensures that consumers interact with a consistent interface while the internal architecture remains modular.
Practical Implementation Examples
The following examples demonstrate how the core components work together in real applications.
State Management and Side Effects
import { h, render, useState, useEffect } from 'fre'
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000)
return () => clearInterval(id)
}, [])
return <button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
}
render(<Counter />, document.getElementById('root')!)
This example utilizes the h factory for JSX transformation, render to bootstrap the reconciler, and hooks from src/hook.ts for state and effects.
Memoization and Code Splitting
import { h, memo, lazy, Suspense } from 'fre'
const Heavy = lazy(() => import('./Heavy'))
const Expensive = memo(({ value }) => {
console.log('render Expensive')
return <div>{value}</div>
})
function App() {
return (
<Suspense fallback={<span>Loading…</span>}>
<Heavy />
<Expensive value={42} />
</Suspense>
)
}
The memo helper prevents unnecessary re-renders via shallow comparison (lines 51-55 in src/h.ts), while lazy throws a Promise that triggers the suspenseRender pathway (lines 61-84 in src/reconcile.ts).
Context API Usage
import { h, createContext, useContext } from 'fre'
const ThemeContext = createContext('light')
function ThemedButton() {
const theme = useContext(ThemeContext)
return <button className={theme}>I am {theme}</button>
}
function App() {
return (
<ThemeContext value="dark">
<ThemedButton />
</ThemeContext>
)
}
createContext (lines 14-26 in src/hook.ts) constructs the provider mechanism, while useContext (lines 29-40) handles subscription and update propagation.
Scheduling Prioritized Updates
import { h, render, startTransition } from 'fre'
function List({ items }) {
return (
<ul>
{items.map(i => <li key={i}>{i}</li>)}
</ul>
)
}
startTransition(() => {
render(<List items={heavyArray} />, document.body)
})
Calling startTransition (lines 8-11 in src/schedule.ts) pushes heavy rendering work into the cooperative scheduler, ensuring that high-priority user interactions remain responsive.
Summary
- JSX Factory (
src/h.ts): Transforms JSX into virtual nodes viah, supporting fragments, memoization, lazy loading, and error boundaries. - Reconciler (
src/reconcile.ts): Implements time-sliced virtual DOM diffing withcapture,diff, andcommitphases, plus Suspense and error boundary handling. - Hooks (
src/hook.ts): Provides React-compatible state and effect management throughuseState,useReducer,useEffect,useMemo,useRef, and context APIs using a cursor-based architecture. - Scheduler (
src/schedule.ts): Cooperative task queue withschedule,startTransition, andshouldYieldto prevent UI jank via 5ms time slicing. - DOM Operations (
src/dom.ts): Abstracts browser APIs throughcreateElementandupdateElementfor node creation and property diffing. - Type System (
src/type.ts): Centralizes TypeScript definitions for fibers, hooks, and component types. - Public API (
src/index.ts): Curates exports to provide a clean interface to the underlying core components.
Frequently Asked Questions
How does Fre's reconciler differ from React's Fiber architecture?
While both use a fiber-based tree structure, Fre's reconciler in src/reconcile.ts implements a simpler cooperative scheduling model. The reconcile function (lines 39-44) explicitly checks shouldYield() from the scheduler after each work unit, whereas React uses a more complex priority-based lane system. Fre's approach prioritizes bundle size over advanced concurrent features, offering time-slicing through a straightforward 5ms deadline check in the flush loop.
What prevents long-running renders from blocking the browser in Fre?
The cooperative scheduler in src/schedule.ts prevents blocking via time-slicing. When render or update is called, work is pushed to the schedule queue (lines 12-15). The flush function (lines 32-46) processes tasks until the 5ms threshold is reached, at which point shouldYield() (lines 49-51) returns true and the loop breaks, yielding control to the browser via setTimeout or MessageChannel before resuming with startTransition.
How does Fre implement hooks without React's internal dispatcher?
Fre implements hooks using a cursor-based approach in src/hook.ts. Instead of a global dispatcher, each fiber maintains a hooks.list array. The resetCursor function (called at the start of component renders in reconcile.ts) initializes a cursor index to zero. Each hook call (e.g., useState at lines 25-27) reads from or writes to hooks.list[cursor++], enforcing call-order consistency by array index rather than linked list traversal. This achieves React-compatible behavior with minimal code overhead.
What is the role of the h function in Fre's component lifecycle?
The h function in src/h.ts serves as the entry point for all component descriptions. During the render phase, component functions return values created by h (or JSX transpiled to h calls). These virtual nodes carry type, props, key, and ref information that the reconciler's capture function (lines 86-101 in src/reconcile.ts) uses to either invoke child components or generate DOM instructions. Without h and its associated helpers like createVnode (lines 37-42), the fiber tree could not be constructed.
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 →