diff --git a/src/MiniReact.ts b/src/MiniReact.ts
index e4863dd..63a5b19 100644
--- a/src/MiniReact.ts
+++ b/src/MiniReact.ts
@@ -10,34 +10,28 @@ export type {
FunctionalComponent,
ElementType,
MiniReactElement,
-} from "./core/types";
-
-// Hooks
-export {
- useState,
- useEffect,
- useReducer,
- useRef,
- useMemo,
- useCallback,
-} from "./hooks";
-export type {
- UseStateHook,
- UseEffectHook,
- UseReducerHook,
- UseRefHook,
EffectCallback,
DependencyList,
Reducer,
MutableRefObject,
-} from "./hooks/types";
+} from "./core/types";
+
+// Fiber-based hooks
+export {
+ useStateFiber as useState,
+ useEffectFiber as useEffect,
+ useReducerFiber as useReducer,
+ useRefFiber as useRef,
+ useMemoFiber as useMemo,
+ useCallbackFiber as useCallback,
+} from "./fiber";
// Context
export { createContext, useContext } from "./context";
export type { MiniReactContext } from "./context/types";
// Fragments
-export { Fragment } from "./fragments";
+export { FRAGMENT as Fragment } from "./core/types";
// Portals
export { createPortal } from "./portals";
@@ -57,18 +51,17 @@ export { jsx, jsxs, jsxDEV } from "./jsx-runtime";
export type { SyntheticEvent } from "./events/types";
import { createContext, useContext } from "./context";
-// Higher-order components
import { createElement, render } from "./core";
-import type { FunctionalComponent, VDOMInstance } from "./core/types";
-import { Fragment } from "./fragments";
+import { FRAGMENT as Fragment } from "./core/types";
+import type { FunctionalComponent } from "./core/types";
import {
- useCallback,
- useEffect,
- useMemo,
- useReducer,
- useRef,
- useState,
-} from "./hooks";
+ useCallbackFiber as useCallback,
+ useEffectFiber as useEffect,
+ useMemoFiber as useMemo,
+ useReducerFiber as useReducer,
+ useRefFiber as useRef,
+ useStateFiber as useState,
+} from "./fiber";
import { jsx, jsxDEV, jsxs } from "./jsx-runtime";
import {
getPerformanceMetrics,
@@ -90,7 +83,7 @@ export function memo
>(
};
// Store the comparison function and original component for reconciliation
- (MemoizedComponent as unknown as Record).__memo = {
+ (MemoizedComponent as unknown as Record)["__memo"] = {
Component,
areEqual: areEqual || shallowEqual,
};
@@ -122,30 +115,7 @@ function shallowEqual>(
return true;
}
-import {
- popContextValues,
- pushContextValues,
- setContextRenderInstance,
-} from "./context";
-// Set up cross-module dependencies
-import { scheduleEffect, setHookContext } from "./hooks";
-import {
- setContextHooks,
- setHookContext as setReconcilerHookContext,
- setScheduleEffect,
-} from "./reconciler";
-
-// Initialize cross-module dependencies
-setScheduleEffect(scheduleEffect);
-setContextHooks(pushContextValues, popContextValues);
-// Connect context render instance to hooks render instance
-setReconcilerHookContext((instance: VDOMInstance | null) => {
- setHookContext(instance);
- setContextRenderInstance(instance);
-});
-
-// Export internal utilities for advanced usage
-export { setHookContext } from "./hooks";
+// Export event system for advanced usage
export { eventSystem } from "./events";
// Constants
diff --git a/src/core/index.ts b/src/core/index.ts
index 7c8aafc..f9b825a 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -2,22 +2,21 @@
/* Core Functionality */
/* ****************** */
-import { eventSystem } from "../events";
-import { trackRenderEnd, trackRenderStart } from "../performance";
-import { reconcile } from "../reconciler";
+import {
+ type FiberRoot,
+ createRoot as createFiberRoot,
+ flushSync,
+ updateContainer,
+} from "../fiber";
import {
type AnyMiniReactElement,
type ElementType,
type JSXElementType,
- PORTAL,
TEXT_ELEMENT,
- type VDOMInstance,
} from "./types";
-// Store root instances for each container
-const rootInstances = new Map();
-// Store original root elements for re-rendering
-const rootElements = new Map();
+// Store fiber roots for each container
+const fiberRoots = new Map();
/**
* Creates a MiniReact element (virtual DOM node)
@@ -59,7 +58,7 @@ export function createElement(
}
/**
- * Renders a MiniReact element into a container using the reconciler
+ * Renders a MiniReact element into a container using the fiber reconciler
* @param element The element to render (can be null to clear)
* @param containerNode The container DOM node
*/
@@ -67,120 +66,41 @@ export function render(
element: AnyMiniReactElement | null | undefined,
containerNode: HTMLElement,
): void {
- // Initialize event system with the container
- eventSystem.initialize(containerNode);
-
const newElement = element || null;
- // Get the old instance BEFORE potentially deleting it
- const oldInstance = rootInstances.get(containerNode) || null;
-
- if (newElement === null) {
- // When unmounting, remove original element from map to prevent memory leaks
- rootElements.delete(containerNode);
- } else {
- // Store the original element for re-renders
- rootElements.set(containerNode, newElement);
+ // Get or create fiber root for this container
+ let root = fiberRoots.get(containerNode);
+ if (!root) {
+ root = createFiberRoot(containerNode);
+ fiberRoots.set(containerNode, root);
}
- // Track render performance
- trackRenderStart();
- const newInstance = reconcile(containerNode, newElement, oldInstance);
- trackRenderEnd();
+ // Render tracking is handled inside performSyncWorkOnRoot
+ updateContainer(newElement, root);
+ flushSync();
if (newElement === null) {
- // Ensure container is completely cleared when rendering null
- containerNode.innerHTML = "";
- // Clean up rootInstances after reconciliation
- rootInstances.delete(containerNode);
- } else {
- rootInstances.set(containerNode, newInstance);
+ // Clean up fiber root when unmounting
+ fiberRoots.delete(containerNode);
}
}
/**
- * Finds the root container for a given VDOM instance
- * @param instance The VDOM instance
- * @returns The root container element or null
+ * Gets the fiber root for a container
+ * @param container The container element
+ * @returns The fiber root or undefined
*/
-export function findRootContainer(instance: VDOMInstance): HTMLElement | null {
- // Strategy 1: Walk up the parent chain and validate rootContainer references
- let current: VDOMInstance | undefined = instance;
- let depth = 0;
- while (current) {
- if (current.rootContainer) {
- // Verify this rootContainer is actually a real root by checking our rootInstances map
- for (const [container, rootInstance] of rootInstances) {
- if (container === current.rootContainer && rootInstance) {
- return container;
- }
- }
- }
- current = current.parent;
- depth++;
- if (depth > 10) {
- console.warn(
- "Parent chain depth exceeded 10, breaking to avoid infinite loop",
- );
- break;
- }
- }
-
- // Strategy 2: Search through all root instances to find the one containing this instance
- for (const [container, rootInstance] of rootInstances) {
- if (rootInstance && isInstanceInTree(instance, rootInstance)) {
- return container;
- }
- }
-
- // Strategy 3: If not found in main trees, check if this instance is part of a portal tree
- current = instance;
- while (current) {
- if (
- current.element &&
- typeof current.element === "object" &&
- "type" in current.element &&
- current.element.type === PORTAL
- ) {
- // Found a portal parent - now find which root tree contains this portal
- for (const [container, rootInstance] of rootInstances) {
- if (rootInstance && isInstanceInTree(current, rootInstance)) {
- return container;
- }
- }
- }
- current = current.parent;
- }
-
- return null;
+export function getFiberRoot(container: HTMLElement): FiberRoot | undefined {
+ return fiberRoots.get(container);
}
-/**
- * Checks if a given instance is part of a VDOM tree
- * @param targetInstance The instance to find
- * @param rootInstance The root of the tree to search
- * @returns True if the instance is in the tree
- */
-function isInstanceInTree(
- targetInstance: VDOMInstance,
- rootInstance: VDOMInstance,
-): boolean {
- if (targetInstance === rootInstance) {
- return true;
- }
+// Re-export fiber-based hydration for SSR
+export { hydrateRoot, type HydrateRootOptions } from "../fiber/hydration";
- return rootInstance.childInstances.some((child) =>
- isInstanceInTree(targetInstance, child),
- );
-}
-
-/**
- * Gets the root element for re-rendering
- * @param container The container element
- * @returns The root element or null
- */
-export function getRootElement(
- container: HTMLElement,
-): AnyMiniReactElement | null {
- return rootElements.get(container) || null;
-}
+// Re-export fiber-based resumability for state persistence
+export {
+ serializeFiberTree,
+ dehydrateRoot,
+ parseSerializedRoot,
+ type SerializedFiberRoot,
+} from "../fiber/resumability";
diff --git a/src/dom-renderer/index.ts b/src/dom-renderer/index.ts
deleted file mode 100644
index a7aeea4..0000000
--- a/src/dom-renderer/index.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { type JSXElementType, TEXT_ELEMENT } from "../core/types";
-
-/* ******************** */
-/* DOM Renderer Utility */
-/* ******************** */
-
-/**
- * Checks if a prop name is an event handler
- * @param propName The prop name to check
- * @returns True if it's an event handler, false otherwise
- */
-function isEventHandler(propName: string): boolean {
- return propName.startsWith("on") && propName.length > 2;
-}
-
-/**
- * Sets a single attribute on a DOM element, handling special cases
- * @param element The DOM element
- * @param key The attribute name
- * @param value The attribute value
- */
-function setAttribute(element: Element, key: string, value: unknown): void {
- if (key === "className") {
- element.setAttribute("class", String(value));
- } else if (key === "style" && typeof value === "string") {
- element.setAttribute("style", value);
- } else if (typeof value === "boolean") {
- // For boolean attributes, only set if true, remove if false
- if (value) {
- element.setAttribute(key, ""); // Set to empty string for boolean attributes
- } else {
- element.removeAttribute(key);
- }
- } else if (value !== undefined && value !== null) {
- element.setAttribute(key, String(value));
- }
-}
-
-/**
- * Creates a DOM node from a VDOM element
- * @param element The VDOM element to convert
- * @returns The created DOM node
- */
-export function createDomNode(element: JSXElementType): Element | Text {
- // Handle text elements (TEXT_ELEMENT)
- if (element.type === TEXT_ELEMENT) {
- return document.createTextNode(String(element.props.nodeValue));
- }
-
- // Create host element
- const domElement = document.createElement(element.type as string);
-
- // Set attributes (props), but skip event handlers, children, and key
- if (element.props) {
- for (const [key, value] of Object.entries(element.props)) {
- if (key !== "children" && key !== "key" && !isEventHandler(key)) {
- setAttribute(domElement, key, value);
- }
- }
- }
-
- return domElement;
-}
-
-/**
- * Removes a DOM node from its parent
- *
- * @param domNode The DOM node to remove
- */
-export function removeDomNode(domNode: Node): void {
- if (domNode.parentNode) {
- domNode.parentNode.removeChild(domNode);
- }
-}
-
-/**
- * Replaces an old DOM node with a new one
- *
- * @param oldDom The old DOM node
- * @param newDom The new DOM node
- */
-export function replaceDomNode(oldDom: Node, newDom: Node): void {
- if (oldDom.parentNode) {
- oldDom.parentNode.replaceChild(newDom, oldDom);
- }
-}
diff --git a/src/fragments/index.ts b/src/fragments/index.ts
deleted file mode 100644
index f090e9f..0000000
--- a/src/fragments/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* ************* */
-/* Fragments */
-/* ************* */
-
-import { FRAGMENT } from "../core/types";
-
-/**
- * Fragment component - renders children without creating a wrapper DOM node
- */
-export const Fragment: typeof FRAGMENT = FRAGMENT;
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
deleted file mode 100644
index 7d96064..0000000
--- a/src/hooks/index.ts
+++ /dev/null
@@ -1,465 +0,0 @@
-/* *********** */
-/* Hooks API */
-/* *********** */
-
-import { findRootContainer, getRootElement, render } from "../core";
-import type { VDOMInstance } from "../core/types";
-import type {
- CallbackHook,
- DependencyList,
- EffectCallback,
- EffectHook,
- MemoHook,
- MutableRefObject,
- Reducer,
- ReducerHook,
- RefHook,
- StateHook,
- StateOrEffectHook,
- UseStateHook,
-} from "./types";
-
-// Hook state management
-let currentRenderInstance: VDOMInstance | null = null;
-
-// Effect queue management
-const effectQueue: (() => void)[] = [];
-let isFlushingEffects = false;
-
-/**
- * Helper function to trigger re-render for a hook instance
- * @param hookInstance The VDOM instance that contains the hook
- */
-function triggerRerender(hookInstance: VDOMInstance): void {
- // Find the root container for this instance and trigger re-render
- const container = findRootContainer(hookInstance);
- if (container) {
- // Use the original root element for re-render instead of stale element from instance
- const rootElement = getRootElement(container);
- if (rootElement) {
- render(rootElement, container);
- } else {
- console.warn("No root element found for container, skipping re-render");
- }
- } else {
- console.warn(
- "No root container found for hook instance, skipping re-render",
- );
- }
-}
-
-/**
- * Sets the current render instance for hooks
- * @param instance The current VDOM instance
- */
-export function setHookContext(instance: VDOMInstance | null): void {
- currentRenderInstance = instance;
- // Reset hookCursor to 0 at the beginning of each component's render
- if (instance) {
- instance.hookCursor = 0;
- }
-}
-
-/**
- * Schedules an effect to be run after the current render
- */
-export function scheduleEffect(effectFn: () => void): void {
- effectQueue.push(effectFn);
-
- if (!isFlushingEffects) {
- queueMicrotask(flushEffects);
- }
-}
-
-/**
- * Flushes all queued effects
- */
-function flushEffects(): void {
- if (isFlushingEffects) return;
-
- isFlushingEffects = true;
-
- try {
- while (effectQueue.length > 0) {
- const effect = effectQueue.shift();
- if (effect) {
- effect();
- }
- }
- } finally {
- isFlushingEffects = false;
- }
-}
-
-/**
- * Gets the effect queue for external access
- */
-export function getEffectQueue(): (() => void)[] {
- return effectQueue;
-}
-
-/**
- * useState hook implementation
- * @param initialState The initial state value or function that returns initial state
- * @returns A tuple with current state and setState function
- */
-export function useState(initialState: T | (() => T)): UseStateHook {
- if (!currentRenderInstance) {
- throw new Error("useState must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- const initialStateValue =
- typeof initialState === "function"
- ? (initialState as () => T)()
- : initialState;
-
- const stateHook: StateHook = {
- type: "state",
- state: initialStateValue,
- setState: () => {}, // Will be set below
- };
-
- (hooks as StateOrEffectHook[]).push(stateHook);
- }
-
- const hook = hooks[currentHookIndex] as StateHook;
-
- // Create setState function with closure over hook and container
- const setState = (newState: T | ((prevState: T) => T)) => {
- const nextState =
- typeof newState === "function"
- ? (newState as (prevState: T) => T)(hook.state as T)
- : newState;
-
- // Only update if state actually changed
- if (nextState !== hook.state) {
- hook.state = nextState;
- triggerRerender(hookInstance);
- }
- };
-
- // Update the setState function reference
- hook.setState = setState;
-
- return [hook.state as T, setState];
-}
-
-/**
- * useEffect hook implementation
- * @param callback Effect callback function that may return a cleanup function
- * @param dependencies Optional dependency array
- */
-export function useEffect(
- callback: EffectCallback,
- dependencies?: DependencyList,
-): void {
- if (!currentRenderInstance) {
- throw new Error("useEffect must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- const effectHook: EffectHook = {
- type: "effect",
- callback,
- dependencies,
- hasRun: false,
- };
- hooks.push(effectHook);
- }
-
- const hook = hooks[currentHookIndex] as EffectHook;
- const prevDependencies = hook.dependencies;
-
- // Check if dependencies have changed
- const dependenciesChanged =
- dependencies === undefined ||
- prevDependencies === undefined ||
- dependencies.length !== prevDependencies.length ||
- dependencies.some((dep, index) => !Object.is(dep, prevDependencies[index]));
-
- // Update hook data
- hook.callback = callback;
- hook.dependencies = dependencies;
-
- // Schedule effect if dependencies changed or it's the first run
- if (!hook.hasRun || dependenciesChanged) {
- scheduleEffect(() => {
- // Run cleanup from previous effect if it exists
- if (hook.cleanup) {
- try {
- hook.cleanup();
- } catch (error) {
- console.error("Error in useEffect cleanup:", error);
- }
- hook.cleanup = undefined;
- }
-
- // Run the effect
- try {
- const cleanupFunction = hook.callback();
- if (typeof cleanupFunction === "function") {
- hook.cleanup = cleanupFunction;
- }
- } catch (error) {
- console.error("Error in useEffect callback:", error);
- }
-
- hook.hasRun = true;
- });
- }
-}
-
-/**
- * useReducer hook implementation
- * @param reducer The reducer function that takes state and action and returns new state
- * @param initialArg The initial state or argument for lazy initialization
- * @param init Optional lazy initialization function
- * @returns A tuple with current state and dispatch function
- */
-export function useReducer(
- reducer: Reducer,
- initialArg: State,
-): [State, (action: Action) => void];
-export function useReducer(
- reducer: Reducer,
- initialArg: Init,
- init: (arg: Init) => State,
-): [State, (action: Action) => void];
-export function useReducer(
- reducer: Reducer,
- initialArg: State | Init,
- init?: (arg: Init) => State,
-): [State, (action: Action) => void] {
- if (!currentRenderInstance) {
- throw new Error("useReducer must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- // Calculate initial state based on whether init function is provided
- const initialState = init
- ? init(initialArg as Init)
- : (initialArg as State);
-
- const reducerHook: ReducerHook = {
- type: "reducer",
- state: initialState,
- reducer,
- dispatch: () => {}, // Will be set below
- };
-
- hooks.push(reducerHook as StateOrEffectHook);
- }
-
- const hook = hooks[currentHookIndex] as ReducerHook;
-
- // Update reducer in case it changed between renders
- hook.reducer = reducer;
-
- // Create dispatch function with closure over hook and container
- const dispatch = (action: Action) => {
- const nextState = hook.reducer(hook.state, action);
-
- // Only update if state actually changed
- if (nextState !== hook.state) {
- hook.state = nextState;
- triggerRerender(hookInstance);
- }
- };
-
- // Update the dispatch function reference
- hook.dispatch = dispatch;
-
- return [hook.state, dispatch];
-}
-
-/**
- * useRef hook implementation
- * @param initialValue The initial value to set on the ref object
- * @returns A mutable ref object with a current property
- */
-export function useRef(initialValue: T): MutableRefObject {
- if (!currentRenderInstance) {
- throw new Error("useRef must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- const refHook: RefHook = {
- type: "ref",
- current: initialValue,
- };
-
- hooks.push(refHook as StateOrEffectHook);
- }
-
- const hook = hooks[currentHookIndex] as RefHook;
-
- // Return a reference to the hook itself as the mutable ref object
- // This ensures mutations to .current directly modify the hook's state
- return hook as MutableRefObject;
-}
-
-/**
- * useMemo hook implementation
- * @param factory Function that returns the memoized value
- * @param dependencies Optional dependency array
- * @returns The memoized value
- */
-export function useMemo(factory: () => T, dependencies?: DependencyList): T {
- if (!currentRenderInstance) {
- throw new Error("useMemo must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- const memoHook: MemoHook = {
- type: "memo",
- value: factory(),
- dependencies,
- hasComputed: true,
- };
-
- hooks.push(memoHook as StateOrEffectHook);
- return memoHook.value;
- }
-
- const hook = hooks[currentHookIndex] as MemoHook;
- const prevDependencies = hook.dependencies;
-
- // Check if dependencies have changed
- const dependenciesChanged =
- dependencies === undefined ||
- prevDependencies === undefined ||
- dependencies.length !== prevDependencies.length ||
- dependencies.some((dep, index) => !Object.is(dep, prevDependencies[index]));
-
- // Recompute value if dependencies changed or it's the first computation
- if (!hook.hasComputed || dependenciesChanged) {
- hook.value = factory();
- hook.dependencies = dependencies;
- hook.hasComputed = true;
- }
-
- return hook.value;
-}
-
-/**
- * useCallback hook implementation
- * @param callback The callback function to memoize
- * @param dependencies Optional dependency array
- * @returns The memoized callback function
- */
-export function useCallback unknown>(
- callback: T,
- dependencies?: DependencyList,
-): T {
- if (!currentRenderInstance) {
- throw new Error("useCallback must be called inside a functional component");
- }
-
- // Capture the current instance at hook creation time
- const hookInstance = currentRenderInstance;
-
- // Ensure hooks array exists
- if (!hookInstance.hooks) {
- hookInstance.hooks = [];
- }
-
- const hooks = hookInstance.hooks;
- const currentHookIndex = hookInstance.hookCursor ?? 0;
- hookInstance.hookCursor = currentHookIndex + 1;
-
- // Initialize hook if it doesn't exist
- if (hooks.length <= currentHookIndex) {
- const callbackHook: CallbackHook = {
- type: "callback",
- callback,
- dependencies,
- };
-
- hooks.push(callbackHook as StateOrEffectHook);
- return callbackHook.callback;
- }
-
- const hook = hooks[currentHookIndex] as CallbackHook;
- const prevDependencies = hook.dependencies;
-
- // Check if dependencies have changed
- const dependenciesChanged =
- dependencies === undefined ||
- prevDependencies === undefined ||
- dependencies.length !== prevDependencies.length ||
- dependencies.some((dep, index) => !Object.is(dep, prevDependencies[index]));
-
- // Update callback if dependencies changed
- if (dependenciesChanged) {
- hook.callback = callback;
- hook.dependencies = dependencies;
- }
-
- return hook.callback;
-}
diff --git a/src/hooks/types.ts b/src/hooks/types.ts
deleted file mode 100644
index fb46005..0000000
--- a/src/hooks/types.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/* ********** */
-/* Hook Types */
-/* ********** */
-
-import type { MiniReactContext } from "../context/types";
-
-export interface StateHook {
- type: "state";
- state: T;
- setState: (newState: T | ((prevState: T) => T)) => void;
-}
-
-export interface EffectHook {
- type: "effect";
- callback: EffectCallback;
- cleanup?: () => void;
- dependencies?: DependencyList;
- hasRun: boolean;
-}
-
-export interface ContextHook {
- type: "context";
- context: MiniReactContext;
- value: T;
-}
-
-export interface ReducerHook {
- type: "reducer";
- state: State;
- reducer: (state: State, action: Action) => State;
- dispatch: (action: Action) => void;
-}
-
-export interface RefHook {
- type: "ref";
- current: T;
-}
-
-export interface MemoHook {
- type: "memo";
- value: T;
- dependencies?: DependencyList;
- hasComputed: boolean;
-}
-
-export interface CallbackHook<
- T extends (...args: unknown[]) => unknown = (...args: unknown[]) => unknown,
-> {
- type: "callback";
- callback: T;
- dependencies?: DependencyList;
-}
-
-/**
- * Union type for hooks stored in component instances.
- * Note: The generic parameter T only applies to StateHook and ContextHook, EffectHook ignores it.
- */
-export type StateOrEffectHook =
- | StateHook
- | EffectHook
- | ContextHook
- | ReducerHook
- | RefHook
- | MemoHook
- | CallbackHook<(...args: unknown[]) => unknown>;
-
-export type UseStateHook = [
- T,
- (newState: T | ((prevState: T) => T)) => void,
-];
-
-// Effect types
-export type EffectCallback = (() => void) | (() => () => void);
-export type DependencyList = readonly unknown[];
-
-export type UseEffectHook = (
- callback: EffectCallback,
- dependencies?: DependencyList,
-) => void;
-
-// Reducer types
-export type Reducer = (state: State, action: Action) => State;
-export type ReducerStateWithoutAction = R extends Reducer
- ? S
- : never;
-export type ReducerActionWithoutState = R extends Reducer
- ? A
- : never;
-
-export type UseReducerHook = {
- , I>(
- reducer: R,
- initializerArg: I,
- initializer: (arg: I) => ReducerStateWithoutAction,
- ): [
- ReducerStateWithoutAction,
- (action: ReducerActionWithoutState) => void,
- ];
- >(
- reducer: R,
- initialState: ReducerStateWithoutAction,
- ): [
- ReducerStateWithoutAction,
- (action: ReducerActionWithoutState) => void,
- ];
-};
-
-// Ref types
-export type MutableRefObject = {
- current: T;
-};
-
-export type UseRefHook = (initialValue: T) => MutableRefObject;
diff --git a/src/jsx-dev-runtime.ts b/src/jsx-dev-runtime.ts
index 0d9a5af..4ce6577 100644
--- a/src/jsx-dev-runtime.ts
+++ b/src/jsx-dev-runtime.ts
@@ -4,4 +4,4 @@
// Export development JSX runtime functions
export { jsxDEV as jsx, jsxDEV as jsxs } from "./jsx-runtime/index";
-export { Fragment } from "./fragments";
+export { FRAGMENT as Fragment } from "./core/types";
diff --git a/src/jsx-runtime.ts b/src/jsx-runtime.ts
index 5a3975a..287a1cf 100644
--- a/src/jsx-runtime.ts
+++ b/src/jsx-runtime.ts
@@ -4,4 +4,4 @@
// Re-export JSX runtime functions for build tool integration
export { jsx, jsxs, jsxDEV } from "./jsx-runtime/index";
-export { Fragment } from "./fragments";
+export { FRAGMENT as Fragment } from "./core/types";
diff --git a/src/jsx-runtime/index.ts b/src/jsx-runtime/index.ts
index 6616916..adab32b 100644
--- a/src/jsx-runtime/index.ts
+++ b/src/jsx-runtime/index.ts
@@ -19,7 +19,7 @@ export function jsx(
const finalProps = { ...restProps };
if (key !== undefined) {
- finalProps.key = key;
+ finalProps["key"] = key;
}
// Handle children
@@ -69,7 +69,7 @@ export function jsxDEV(
// Store development metadata if needed (for future dev tools support)
if (source && typeof element === "object" && element !== null) {
- (element as unknown as Record).__source = source;
+ (element as unknown as Record)["__source"] = source;
}
return element;
diff --git a/src/reconciler/index.ts b/src/reconciler/index.ts
deleted file mode 100644
index 61b4f2c..0000000
--- a/src/reconciler/index.ts
+++ /dev/null
@@ -1,1090 +0,0 @@
-import type {
- AnyMiniReactElement,
- FunctionalComponent,
- VDOMInstance,
-} from "../core/types";
-import { FRAGMENT, PORTAL, TEXT_ELEMENT } from "../core/types";
-import { createDomNode, removeDomNode, replaceDomNode } from "../dom-renderer";
-import { eventSystem } from "../events";
-import type { PortalElement } from "../portals/types";
-
-// Import scheduleEffect to properly schedule cleanup
-let scheduleEffectFunction: ((effectFn: () => void) => void) | null = null;
-// Context management functions
-let pushContextFunction:
- | ((contextValues: Map) => void)
- | null = null;
-let popContextFunction: (() => void) | null = null;
-
-export function setScheduleEffect(
- scheduleEffect: (effectFn: () => void) => void,
-): void {
- scheduleEffectFunction = scheduleEffect;
-}
-
-export function setContextHooks(
- pushContext: (contextValues: Map) => void,
- popContext: () => void,
-): void {
- pushContextFunction = pushContext;
- popContextFunction = popContext;
-}
-
-/* ********** */
-/* Reconciler */
-/* ********** */
-
-/**
- * Main reconciliation function that handles creation, updates, and removal of VDOM instances
- *
- * @param parentDom The parent DOM node (can be null during cleanup)
- * @param newElement The new element to reconcile
- * @param oldInstance The existing VDOM instance to reconcile against
- * @returns The resulting VDOM instance (null if removed)
- */
-export function reconcile(
- parentDom: Node | null,
- newElement: AnyMiniReactElement | null,
- oldInstance: VDOMInstance | null,
-): VDOMInstance | null {
- // Case 1: Element removal - newElement is null but oldInstance exists
- if (newElement === null && oldInstance !== null) {
- // Handle removal based on element type
- if (oldInstance.element.type === PORTAL) {
- // For portals, clean up children from their target container
- const portalElement = oldInstance.element as PortalElement;
- const targetContainer = portalElement.props.targetContainer;
-
- // Clean up all portal children from target container
- for (const childInstance of oldInstance.childInstances) {
- if (childInstance?.dom && targetContainer.contains(childInstance.dom)) {
- reconcile(null, null, childInstance);
- }
- }
- } else if (oldInstance.element.type === FRAGMENT) {
- // For fragments, recursively clean up all children
- for (const childInstance of oldInstance.childInstances) {
- if (childInstance) {
- reconcile(null, null, childInstance);
- }
- }
- } else {
- // For regular elements, clean up hooks and remove from DOM
- if (oldInstance.hooks && scheduleEffectFunction) {
- for (const hook of oldInstance.hooks) {
- if (hook.type === "effect" && hook.cleanup) {
- const cleanup = hook.cleanup;
- scheduleEffectFunction(() => {
- try {
- cleanup();
- } catch (error) {
- console.error("Error in useEffect cleanup:", error);
- }
- });
- }
- }
- }
-
- if (oldInstance.dom) {
- eventSystem.unregisterInstance(oldInstance);
- removeDomNode(oldInstance.dom);
- }
- }
-
- // Clean up child instances
- for (const childInstance of oldInstance.childInstances) {
- if (childInstance) {
- reconcile(null, null, childInstance);
- }
- }
-
- return null;
- }
-
- // Case 2: Element creation - newElement exists but oldInstance is null
- if (newElement !== null && oldInstance === null) {
- if (!parentDom) {
- throw new Error("Parent DOM node is required for element creation");
- }
- return createVDOMInstance(parentDom, newElement);
- }
-
- // Both should exist at this point - but handle edge cases gracefully
- if (!newElement || !oldInstance) {
- // Add detailed logging for debugging
- console.warn("Reconcile called with suspicious null values:", {
- newElement: newElement,
- oldInstance: oldInstance,
- parentDom: parentDom,
- stack: new Error().stack,
- });
-
- // If we have newElement but no oldInstance, treat as creation
- if (newElement && !oldInstance) {
- if (!parentDom) {
- throw new Error("Parent DOM node is required for element creation");
- }
- return createVDOMInstance(parentDom, newElement);
- }
- // If we have oldInstance but no newElement, treat as removal
- if (!newElement && oldInstance) {
- return reconcile(parentDom, null, oldInstance);
- }
- // If both are null, check if this is a valid cleanup scenario
- if (!newElement && !oldInstance) {
- // This could be a valid case during cleanup - just return null
- console.warn("Both newElement and oldInstance are null - returning null");
- return null;
- }
- // If we reach here, something unexpected happened
- throw new Error(
- `Unexpected reconciliation state: newElement=${newElement}, oldInstance=${oldInstance}`,
- );
- }
-
- // Case 3: Type change - recreate everything
- if (!isSameElementType(oldInstance.element, newElement)) {
- if (!parentDom) {
- throw new Error(
- "Parent DOM node is required for type change reconciliation",
- );
- }
- const newInstance = createVDOMInstance(parentDom, newElement);
-
- // Clean up old instance hooks
- if (oldInstance.hooks && scheduleEffectFunction) {
- for (const hook of oldInstance.hooks) {
- if (hook.type === "effect" && hook.cleanup) {
- const cleanup = hook.cleanup;
- scheduleEffectFunction(() => {
- try {
- cleanup();
- } catch (error) {
- console.error(
- "Error in useEffect cleanup during type change:",
- error,
- );
- }
- });
- }
- }
- }
-
- // Handle DOM cleanup - fragments and portals need special handling
- if (oldInstance.element.type === FRAGMENT) {
- // For fragments, recursively clean up all children
- for (const childInstance of oldInstance.childInstances) {
- if (childInstance) {
- reconcile(null, null, childInstance);
- }
- }
- } else if (oldInstance.element.type === PORTAL) {
- // For portals, clean up children from their target container
- const oldPortalElement = oldInstance.element as PortalElement;
- const oldTargetContainer = oldPortalElement.props.targetContainer;
-
- for (const childInstance of oldInstance.childInstances) {
- if (
- childInstance?.dom &&
- oldTargetContainer.contains(childInstance.dom)
- ) {
- reconcile(null, null, childInstance);
- }
- }
- } else if (oldInstance.dom && newInstance.dom) {
- // For regular elements, replace the DOM node
- eventSystem.unregisterInstance(oldInstance);
- replaceDomNode(oldInstance.dom, newInstance.dom);
- } else if (oldInstance.dom) {
- // If old instance had DOM but new doesn't, just remove old
- eventSystem.unregisterInstance(oldInstance);
- removeDomNode(oldInstance.dom);
- }
-
- return newInstance;
- }
-
- // Case 4: Same type - update existing instance
- return updateVDOMInstance(oldInstance, newElement);
-}
-
-/**
- * Creates a new VDOM instance for the given element and attaches it to the parent DOM
- *
- * @param parentDom The parent DOM node
- * @param element The element to create an instance for
- * @returns The created VDOM instance
- */
-function createVDOMInstance(
- parentDom: Node,
- element: AnyMiniReactElement,
-): VDOMInstance {
- // Handle primitives by converting them to text elements
- if (
- typeof element === "string" ||
- typeof element === "number" ||
- typeof element === "boolean"
- ) {
- const textElement = {
- type: TEXT_ELEMENT,
- props: {
- nodeValue: element,
- children: [],
- },
- };
- return createVDOMInstance(parentDom, textElement);
- }
-
- // Handle null/undefined elements
- if (element === null || element === undefined) {
- throw new Error(
- "Cannot create VDOM instance for null or undefined element",
- );
- }
-
- // Type guard to ensure we have an element object, not a primitive
- if (
- !element ||
- typeof element !== "object" ||
- !("type" in element) ||
- !("props" in element)
- ) {
- throw new Error("createVDOMInstance expects an element object");
- }
-
- const { type, props } = element;
-
- // Handle portals
- if (type === PORTAL) {
- const portalElement = element as PortalElement;
- const targetContainer = portalElement.props.targetContainer;
-
- // Add event delegation to portal target so events bubble through React tree
- eventSystem.addEventDelegation(targetContainer);
-
- const instance: VDOMInstance = {
- element,
- dom: null, // Portals don't have their own DOM node in the parent tree
- childInstances: [],
- rootContainer:
- parentDom.nodeType === Node.ELEMENT_NODE
- ? (parentDom as HTMLElement)
- : undefined,
- };
-
- // Render children directly to the target container
- // Portal children should inherit context from their logical parent, not DOM parent
- instance.childInstances = reconcileChildren(
- targetContainer,
- [],
- portalElement.props.children,
- instance,
- );
-
- // Ensure all portal children have the correct rootContainer for state management
- function propagateRootContainer(
- instances: VDOMInstance[],
- rootContainer: HTMLElement | undefined,
- ) {
- for (const childInstance of instances) {
- if (!childInstance.rootContainer && rootContainer) {
- childInstance.rootContainer = rootContainer;
- }
- propagateRootContainer(childInstance.childInstances, rootContainer);
- }
- }
- propagateRootContainer(instance.childInstances, instance.rootContainer);
-
- return instance;
- }
-
- // Handle fragments
- if (type === FRAGMENT) {
- const instance: VDOMInstance = {
- element,
- dom: null, // Fragments don't have their own DOM node
- childInstances: [],
- rootContainer:
- parentDom.nodeType === Node.ELEMENT_NODE
- ? (parentDom as HTMLElement)
- : undefined,
- };
-
- // Use reconcileChildren to handle fragment children properly
- // This will ensure correct ordering through reorderDomNodes
- instance.childInstances = reconcileChildren(
- parentDom,
- [],
- props.children,
- instance,
- );
-
- return instance;
- }
-
- // Handle functional components
- if (typeof type === "function") {
- // Determine rootContainer - prefer the actual root container over immediate DOM parent
- let rootContainer: HTMLElement | undefined;
- if (parentDom.nodeType === Node.ELEMENT_NODE) {
- const parentElement = parentDom as HTMLElement;
- // If parentDom is the main app container, use it
- if (parentElement === document.body) {
- rootContainer = parentElement;
- } else {
- // Otherwise, try to find the root container by looking for a container with an ID or walking up
- let current: HTMLElement | null = parentElement;
- while (current && current !== document.body) {
- if (current.id) {
- rootContainer = current;
- break;
- }
- current = current.parentElement;
- }
- // Fallback to the immediate parent if we can't find a better root
- if (!rootContainer) {
- rootContainer = parentElement;
- }
- }
- }
-
- // Create the instance
- const instance: VDOMInstance = {
- element,
- dom: null,
- childInstances: [],
- hooks: [], // Initialize hooks array
- rootContainer,
- };
-
- // Set hook context before calling component
- setCurrentRenderInstance(instance);
-
- let childElement: AnyMiniReactElement | null;
- try {
- childElement = (type as FunctionalComponent)(props);
- } finally {
- setCurrentRenderInstance(null); // always reset
- }
- // Check if this component is a context provider (has contextValues)
- // and push context BEFORE reconciling children
- let contextWasPushed = false;
- if (instance.contextValues && pushContextFunction) {
- pushContextFunction(instance.contextValues);
- contextWasPushed = true;
- }
-
- let childInstance: VDOMInstance | null = null;
- try {
- childInstance = childElement
- ? createVDOMInstance(parentDom, childElement)
- : null;
- } finally {
- // Pop context AFTER reconciling children - ensure this happens even on exceptions
- if (contextWasPushed && popContextFunction) {
- popContextFunction();
- }
- }
-
- if (childInstance) {
- childInstance.parent = instance; // Set parent reference for functional component child
- // Ensure child inherits the same rootContainer
- if (!childInstance.rootContainer && instance.rootContainer) {
- childInstance.rootContainer = instance.rootContainer;
- }
- }
-
- instance.dom = childInstance?.dom || null;
- instance.childInstances = childInstance ? [childInstance] : [];
-
- // Don't register functional components with event system
- // They don't have their own DOM nodes and shouldn't be in the event path
-
- return instance;
- }
-
- // Handle host elements (including text elements)
- const domNode = createDomNode(element);
- const childInstances: VDOMInstance[] = [];
-
- // Create the instance
- const instance: VDOMInstance = {
- element,
- dom: domNode,
- childInstances: [],
- rootContainer:
- parentDom.nodeType === Node.ELEMENT_NODE
- ? (parentDom as HTMLElement)
- : undefined,
- };
-
- // Register with event system for host elements
- eventSystem.registerInstance(instance, domNode);
-
- // Check if this element has event handlers that need delegation
- eventSystem.hasEventHandlers(props as Record);
-
- // Process children
- for (const child of props.children) {
- const childInstance = createVDOMInstance(domNode, child);
- childInstance.parent = instance; // Set parent reference
- childInstances.push(childInstance);
-
- // Handle DOM insertion based on child type
- if (childInstance.element.type === FRAGMENT) {
- // For fragments, append all their DOM children
- for (const fragChild of childInstance.childInstances) {
- if (fragChild.dom) {
- domNode.appendChild(fragChild.dom);
- }
- }
- } else if (childInstance.dom) {
- // For regular elements, append the DOM node
- domNode.appendChild(childInstance.dom);
- }
- }
-
- // Update instance with children
- instance.childInstances = childInstances;
-
- // Append to parent
- parentDom.appendChild(domNode);
-
- return instance;
-}
-
-// Hook context function - will be set by MiniReact module
-let setCurrentRenderInstance: (instance: VDOMInstance | null) => void =
- () => {};
-
-/**
- * Sets the hook context function from MiniReact module
- * @param fn The setCurrentRenderInstance function
- */
-export function setHookContext(
- fn: (instance: VDOMInstance | null) => void,
-): void {
- setCurrentRenderInstance = fn;
-}
-
-/**
- * Checks if two elements have the same type for reconciliation purposes
- *
- * @param oldElement The old element
- * @param newElement The new element
- * @returns True if the elements have the same type, false otherwise
- */
-function isSameElementType(
- oldElement: AnyMiniReactElement,
- newElement: AnyMiniReactElement,
-): boolean {
- // Type guards to ensure we have element objects
- if (
- !oldElement ||
- typeof oldElement !== "object" ||
- !("type" in oldElement) ||
- !newElement ||
- typeof newElement !== "object" ||
- !("type" in newElement)
- ) {
- return false;
- }
- return oldElement.type === newElement.type;
-}
-
-/**
- * Efficiently diffs props and applies only the necessary DOM updates
- *
- * @param domElement The DOM element to update
- * @param oldProps The previous props
- * @param newProps The new props
- * @param instance The VDOM instance (for event system registration)
- */
-function diffProps(
- domElement: Element,
- oldProps: Record,
- newProps: Record,
-): void {
- // Helper function to check if a prop is an event handler
- const isEventHandler = (key: string): boolean =>
- key.startsWith("on") && key.length > 2;
-
- // Create sets of old and new prop keys (excluding children, key, and event handlers)
- const oldKeys = new Set(
- Object.keys(oldProps).filter(
- (key) => key !== "children" && key !== "key" && !isEventHandler(key),
- ),
- );
- const newKeys = new Set(
- Object.keys(newProps).filter(
- (key) => key !== "children" && key !== "key" && !isEventHandler(key),
- ),
- );
-
- // Remove props that are no longer present
- for (const key of oldKeys) {
- if (!newKeys.has(key)) {
- domElement.removeAttribute(key === "className" ? "class" : key);
- }
- }
-
- // Update props that have changed or are new
- for (const key of newKeys) {
- const oldValue = oldProps[key];
- const newValue = newProps[key];
-
- if (oldValue !== newValue) {
- setAttribute(domElement, key, newValue);
- }
- }
-}
-
-/**
- * Sets an attribute on a DOM element with proper handling for special cases
- *
- * @param domElement The DOM element
- * @param key The attribute key
- * @param value The attribute value
- */
-function setAttribute(domElement: Element, key: string, value: unknown): void {
- if (key === "key") {
- // Keys are used internally for reconciliation and should not be set as DOM attributes
- return;
- }
- if (key === "className") {
- domElement.setAttribute("class", String(value));
- } else if (key.startsWith("on") && typeof value === "function") {
- // Event handling is now managed by the event system
- // No need to attach individual listeners here
- } else if (typeof value === "boolean") {
- // Handle boolean attributes specially
- if (value) {
- domElement.setAttribute(key, ""); // Set to empty string for boolean attributes
- } else {
- // Remove the attribute when boolean value is false
- domElement.removeAttribute(key);
- }
- } else if (value !== undefined && value !== null) {
- domElement.setAttribute(key, String(value));
- } else if (value === null || value === undefined) {
- // Remove the attribute when value is null/undefined
- domElement.removeAttribute(key);
- }
-}
-
-/**
- * Efficiently reconciles children using key-based diffing and DOM node reuse
- *
- * @param parentDom The parent DOM node
- * @param oldChildInstances The existing child VDOM instances
- * @param newChildElements The new child elements to render
- * @param parentInstance The parent VDOM instance (optional)
- * @returns The updated child instances
- */
-function reconcileChildren(
- parentDom: Node,
- oldChildInstances: VDOMInstance[],
- newChildElements: AnyMiniReactElement[],
- parentInstance?: VDOMInstance,
-): VDOMInstance[] {
- // Filter out null/undefined elements early and handle them separately
- const filteredNewChildElements = newChildElements.filter(
- (child) => child !== null && child !== undefined,
- );
-
- // Separate keyed and unkeyed children
- const oldKeyed = new Map();
- const oldUnkeyed: VDOMInstance[] = [];
- const newKeyed = new Map();
- const newUnkeyed: AnyMiniReactElement[] = [];
-
- // Categorize old children by key
- for (const oldChild of oldChildInstances) {
- if (oldChild) {
- // Add null check for safety
- const key = getElementKey(oldChild.element);
- if (key !== null) {
- oldKeyed.set(key, oldChild);
- } else {
- oldUnkeyed.push(oldChild);
- }
- }
- }
-
- // Categorize new children by key - only process filtered elements
- for (const newChild of filteredNewChildElements) {
- const key = getElementKey(newChild);
- if (key !== null) {
- newKeyed.set(key, newChild);
- } else {
- newUnkeyed.push(newChild);
- }
- }
-
- const newChildInstances: VDOMInstance[] = [];
- let unkeyedIndex = 0;
-
- // Process new children in order - only process filtered elements
- for (let i = 0; i < filteredNewChildElements.length; i++) {
- const newChild = filteredNewChildElements[i];
-
- // Skip null/undefined elements (already filtered but add extra safety)
- if (newChild === null || newChild === undefined) {
- continue;
- }
-
- const key = getElementKey(newChild);
- let newChildInstance: VDOMInstance | null = null;
-
- if (key !== null) {
- // Handle keyed child
- const oldChildInstance = oldKeyed.get(key) || null;
- newChildInstance = reconcile(parentDom, newChild, oldChildInstance);
-
- // Remove from oldKeyed so we know it's been processed
- if (oldChildInstance) {
- oldKeyed.delete(key);
- }
- } else {
- // Handle unkeyed child - match with next available unkeyed old child
- const oldChildInstance = oldUnkeyed[unkeyedIndex] || null;
- newChildInstance = reconcile(parentDom, newChild, oldChildInstance);
- unkeyedIndex++;
- }
-
- if (newChildInstance) {
- // Set parent reference for the new child instance
- newChildInstance.parent = parentInstance;
- // Inherit rootContainer if child doesn't have one
- if (!newChildInstance.rootContainer && parentInstance?.rootContainer) {
- newChildInstance.rootContainer = parentInstance.rootContainer;
- }
- // Special case: if parent is a portal, ensure children inherit the portal's root container
- if (
- parentInstance?.element.type === PORTAL &&
- parentInstance.rootContainer
- ) {
- newChildInstance.rootContainer = parentInstance.rootContainer;
- }
- newChildInstances.push(newChildInstance);
- }
- }
-
- // Remove any remaining old keyed children that weren't reused
- for (const [, oldChild] of oldKeyed) {
- // Clean up based on element type
- if (oldChild.element.type === PORTAL) {
- // For portals, clean up from their target container
- const portalElement = oldChild.element as PortalElement;
- const targetContainer = portalElement.props.targetContainer;
-
- for (const childInstance of oldChild.childInstances) {
- if (childInstance?.dom && targetContainer.contains(childInstance.dom)) {
- reconcile(null, null, childInstance);
- }
- }
- } else if (oldChild) {
- // For regular elements and fragments - add null check
- reconcile(null, null, oldChild);
- }
- }
-
- // Remove any remaining old unkeyed children that weren't reused
- for (let i = unkeyedIndex; i < oldUnkeyed.length; i++) {
- const oldChild = oldUnkeyed[i];
- // Clean up based on element type
- if (oldChild?.element.type === PORTAL) {
- // For portals, clean up from their target container
- const portalElement = oldChild.element as PortalElement;
- const targetContainer = portalElement.props.targetContainer;
-
- for (const childInstance of oldChild.childInstances) {
- if (childInstance?.dom && targetContainer.contains(childInstance.dom)) {
- reconcile(null, null, childInstance);
- }
- }
- } else if (oldChild) {
- // For regular elements and fragments - add null check
- reconcile(null, null, oldChild);
- }
- }
-
- // Ensure DOM nodes are in correct order (only for non-portal parent containers)
- if (parentInstance?.element.type !== PORTAL) {
- reorderDomNodes(parentDom, newChildInstances);
- }
-
- return newChildInstances;
-}
-
-/**
- * Extracts the key from an element, returning null if no key is present
- *
- * @param element The element to get the key from
- * @returns The key string or null
- */
-function getElementKey(element: AnyMiniReactElement): string | null {
- // Handle null/undefined elements
- if (element === null || element === undefined) {
- return null;
- }
-
- // Handle primitive values (string, number, boolean)
- if (
- typeof element === "string" ||
- typeof element === "number" ||
- typeof element === "boolean"
- ) {
- return null; // Primitives don't have keys
- }
-
- // Type guard to ensure we have an element object
- if (!element || typeof element !== "object" || !("props" in element)) {
- return null;
- }
-
- const key = (element.props as Record).key;
- return key !== undefined && key !== null ? String(key) : null;
-}
-
-/**
- * Reorders DOM nodes to match the order of VDOM instances
- *
- * @param parentDom The parent DOM node
- * @param childInstances The child instances in the desired order
- */
-function reorderDomNodes(
- parentDom: Node,
- childInstances: VDOMInstance[],
-): void {
- // Collect all DOM nodes that should be in this parent, in order
- const targetDomNodes: Node[] = [];
-
- function collectDomNodes(instances: VDOMInstance[]): void {
- for (const instance of instances) {
- if (instance.element.type === PORTAL) {
- // Skip portal instances - their children render to different containers
- } else if (instance.dom && instance.dom.parentNode === parentDom) {
- targetDomNodes.push(instance.dom);
- } else if (instance.element.type === FRAGMENT) {
- // For fragments, collect their children's DOM nodes
- collectDomNodes(instance.childInstances);
- }
- }
- }
-
- collectDomNodes(childInstances);
-
- // Now reorder the DOM nodes to match the target order
- let currentDomChild = parentDom.firstChild;
- for (const targetNode of targetDomNodes) {
- if (currentDomChild !== targetNode) {
- parentDom.insertBefore(targetNode, currentDomChild);
- }
- currentDomChild = targetNode.nextSibling;
- }
-}
-
-/**
- * Updates an existing VDOM instance with a new element (same type)
- *
- * @param instance The existing VDOM instance
- * @param newElement The new element to update the VDOM instance with
- * @returns The updated VDOM instance
- */
-function updateVDOMInstance(
- instance: VDOMInstance,
- newElement: AnyMiniReactElement,
-): VDOMInstance {
- // Type guard to ensure we have an element object
- if (
- !newElement ||
- typeof newElement !== "object" ||
- !("type" in newElement) ||
- !("props" in newElement)
- ) {
- throw new Error("updateVDOMInstance expects an element object");
- }
-
- const { type, props } = newElement;
-
- // Handle portals
- if (type === PORTAL) {
- const portalElement = newElement as PortalElement;
- const targetContainer = portalElement.props.targetContainer;
-
- // For portal updates, we need to check if the target container changed
- const oldPortalElement = instance.element as PortalElement;
- const oldTargetContainer = oldPortalElement.props.targetContainer;
-
- // If target container changed, clean up old container first
- if (oldTargetContainer !== targetContainer) {
- // Clean up old portal children from old container
- for (const childInstance of instance.childInstances) {
- if (childInstance) {
- reconcile(null, null, childInstance);
- }
- }
- instance.childInstances = [];
- }
-
- // Update portal children in the (possibly new) target container
- instance.element = newElement;
- instance.childInstances = reconcileChildren(
- targetContainer,
- instance.childInstances,
- portalElement.props.children,
- instance,
- );
-
- return instance;
- }
-
- // Handle fragments
- if (type === FRAGMENT) {
- // Find parent DOM node for fragments
- let parentNode: Node | null = null;
-
- // Strategy 1: Check if any current child has a DOM parent
- for (const childInstance of instance.childInstances) {
- if (childInstance.dom?.parentNode) {
- parentNode = childInstance.dom.parentNode;
- break;
- }
- }
-
- // Strategy 2: Walk up parent tree to find DOM node
- if (!parentNode) {
- let currentParent = instance.parent;
- while (currentParent && !parentNode) {
- if (currentParent.dom) {
- parentNode = currentParent.dom;
- break;
- }
- // For fragments, check if any child has a DOM node we can use
- if (currentParent.element.type === FRAGMENT) {
- for (const childInstance of currentParent.childInstances) {
- if (childInstance.dom?.parentNode) {
- parentNode = childInstance.dom.parentNode;
- break;
- }
- }
- if (parentNode) break;
- }
- currentParent = currentParent.parent;
- }
- }
-
- if (!parentNode) {
- throw new Error("Unable to find parent node for fragment reconciliation");
- }
-
- // Reconcile fragment children directly with parent DOM
- instance.childInstances = reconcileChildren(
- parentNode,
- instance.childInstances,
- props.children,
- instance,
- );
-
- return instance;
- }
-
- // Handle functional components - re-execute and reconcile output
- if (typeof type === "function") {
- // Check for memo optimization
- const memoInfo = (type as unknown as Record).__memo as
- | {
- Component: FunctionalComponent;
- areEqual: (
- prevProps: Record,
- nextProps: Record,
- ) => boolean;
- }
- | undefined;
-
- // If this is a memoized component, check if props have changed
- if (memoInfo) {
- const oldProps = instance.element.props as Record;
- const newProps = props as Record;
-
- // If props haven't changed according to the comparison function, skip re-render
- if (memoInfo.areEqual(oldProps, newProps)) {
- // Update the element but keep the same child instances
- instance.element = newElement;
- return instance;
- }
- }
-
- // Set hook context before calling component
- setCurrentRenderInstance(instance);
-
- const childElement = (type as FunctionalComponent)(props);
-
- setCurrentRenderInstance(null); // Clear context after call
-
- const oldChildInstance = instance.childInstances[0] || null;
-
- // Find the correct parent node to reconcile in
- let parentNode: Node | null = null;
-
- // Strategy 1: Check if old child has a parent DOM node
- if (oldChildInstance?.dom?.parentNode) {
- parentNode = oldChildInstance.dom.parentNode;
- }
- // Strategy 2: Check if this instance has a DOM parent
- else if (instance.dom?.parentNode) {
- parentNode = instance.dom.parentNode;
- }
- // Strategy 3: Walk up the parent tree to find a DOM node
- else {
- let currentParent = instance.parent;
- while (currentParent && !parentNode) {
- if (currentParent.dom) {
- parentNode = currentParent.dom;
- break;
- }
- // For fragments, check if any child has a DOM node we can use
- if (currentParent.element.type === FRAGMENT) {
- for (const childInstance of currentParent.childInstances) {
- if (childInstance.dom?.parentNode) {
- parentNode = childInstance.dom.parentNode;
- break;
- }
- }
- if (parentNode) break;
- }
- currentParent = currentParent.parent;
- }
- }
-
- // Strategy 4: Check siblings for DOM parents
- if (!parentNode && instance.parent) {
- for (const sibling of instance.parent.childInstances) {
- if (sibling !== instance && sibling.dom?.parentNode) {
- parentNode = sibling.dom.parentNode;
- break;
- }
- }
- }
-
- // Strategy 5: If still no parent, try to find root container
- if (!parentNode) {
- // Walk up to find the root container by checking if this instance is a root
- let currentInstance: VDOMInstance | undefined = instance;
- while (currentInstance?.parent) {
- currentInstance = currentInstance.parent;
- }
- // Check if any child of root has a DOM node
- if (currentInstance?.childInstances) {
- for (const child of currentInstance.childInstances) {
- if (child.dom?.parentNode) {
- parentNode = child.dom.parentNode;
- break;
- }
- }
- }
- // If we found a root instance, check if it has a rootContainer
- if (!parentNode && currentInstance?.rootContainer) {
- parentNode = currentInstance.rootContainer;
- }
- }
-
- // Strategy 6: Use instance's own rootContainer as last resort
- if (!parentNode && instance.rootContainer) {
- parentNode = instance.rootContainer;
- }
-
- if (!parentNode) {
- throw new Error(
- "Unable to find parent node for functional component reconciliation",
- );
- }
-
- // Special case: if component was rendering something but now returns null,
- // we need to clean up the functional component's effects
- if (oldChildInstance && childElement === null) {
- // Schedule cleanup for the functional component's hooks when it stops rendering
- if (instance.hooks && scheduleEffectFunction) {
- for (const hook of instance.hooks) {
- if (hook.type === "effect" && hook.cleanup) {
- const cleanup = hook.cleanup;
- scheduleEffectFunction(() => {
- try {
- cleanup();
- } catch (error) {
- console.error(
- "Error in useEffect cleanup during null return:",
- error,
- );
- }
- });
- hook.cleanup = undefined;
- hook.hasRun = false; // Reset for potential future re-renders
- }
- }
- }
- }
-
- // Check if this component is a context provider (has contextValues)
- // and push context BEFORE reconciling children
- let contextWasPushed = false;
- if (instance.contextValues && pushContextFunction) {
- pushContextFunction(instance.contextValues);
- contextWasPushed = true;
- }
-
- let childInstance: VDOMInstance | null = null;
- try {
- childInstance = reconcile(parentNode, childElement, oldChildInstance);
- if (childInstance) {
- childInstance.parent = instance;
- }
- } finally {
- // Pop context AFTER reconciling children - ensure this happens even on exceptions
- if (contextWasPushed && popContextFunction) {
- popContextFunction();
- }
- }
-
- // Update the existing instance in-place instead of creating a new one
- // This preserves the event system mappings and other references
- instance.element = newElement;
- instance.dom = childInstance?.dom || null;
- instance.childInstances = childInstance ? [childInstance] : [];
-
- // hooks are already preserved on the instance
-
- return instance;
- }
-
- // Handle host elements - use efficient prop diffing
- const oldProps = instance.element.props as Record;
- instance.element = newElement;
-
- // For host elements, update only changed DOM attributes using diffProps
- if (instance.dom && instance.dom.nodeType === Node.ELEMENT_NODE) {
- const domElement = instance.dom as Element;
- const newProps = props as Record;
-
- // Use efficient prop diffing instead of naive clear-and-set approach
- diffProps(domElement, oldProps, newProps);
- }
-
- // For now, we'll just update the DOM node if it's a text element
- if (instance.dom && instance.dom.nodeType === Node.TEXT_NODE) {
- instance.dom.nodeValue = String(props.nodeValue);
- }
-
- // Efficient children reconciliation with key-based diffing
- if (instance.dom) {
- instance.childInstances = reconcileChildren(
- instance.dom,
- instance.childInstances,
- props.children,
- instance,
- );
- }
-
- return instance;
-}
diff --git a/tsconfig.json b/tsconfig.json
index 2ed5981..a92a5c1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,10 +20,15 @@
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
- // Some stricter flags (disabled by default)
- "noUnusedLocals": false,
- "noUnusedParameters": false,
- "noPropertyAccessFromIndexSignature": false,
+ // Strict flags
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "noImplicitOverride": true,
// Path aliases
"paths": {