# Preact Admin & Architecture Guide

This guide is the deep technical reference for Preact — its architecture, internals, build pipeline, module map, security posture, code conventions, and contribution workflow. It is written for library maintainers, framework authors, integrators embedding Preact, and engineers who need to understand precisely how Preact's renderer works. Everything here is derived directly from the Preact source at the documented commit.

> [!IMPORTANT]
> This guide covers **Preact v11.0.0-beta.1** (the `main` branch — the in-development next major). Internals such as the `options` hook names, vnode flags, and the diff algorithm differ from stable v10. For production integrations, target the `v10.x` branch unless you are specifically building against v11.

For the end-user/application-developer API (components, hooks, JSX, compat usage), see the companion **User Guide**.

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture Overview](#2-architecture-overview)
3. [Repository Structure](#3-repository-structure)
4. [The Rendering Pipeline (Deep Dive)](#4-the-rendering-pipeline-deep-dive)
5. [The `options` Hook System](#5-the-options-hook-system)
6. [Module Reference](#6-module-reference)
7. [The Compat Layer Internals](#7-the-compat-layer-internals)
8. [Build System & Distribution](#8-build-system--distribution)
9. [Testing Strategy](#9-testing-strategy)
10. [Code Quality & Conventions](#10-code-quality--conventions)
11. [Security Analysis](#11-security-analysis)
12. [Integration & Deployment Concerns](#12-integration--deployment-concerns)
13. [Known Issues & Roadmap](#13-known-issues--roadmap)
14. [Potential Improvements](#14-potential-improvements)
15. [Contributing](#15-contributing)
16. [Quick Reference](#16-quick-reference)

---

## 1. Executive Summary

Preact is a **~4 kB Virtual DOM library** that implements React's modern component API in a fraction of the size. It is not a fork of React; it is an independent, from-scratch implementation whose public surface deliberately mirrors React so that knowledge and (via `preact/compat`) code transfer directly.

| Attribute | Value |
|---|---|
| Package | `preact` |
| Version (this guide) | `11.0.0-beta.1` (pre-release `main`) |
| License | MIT |
| Runtime dependencies | **None** (core) |
| Language | JavaScript (ES modules), shipped with hand-authored TypeScript definitions |
| Module formats | CJS, ESM, UMD (per sub-package) |
| Build tool | microbundle |
| Test runner | Vitest (browser mode via Playwright/Chromium) + `tsc` type tests |
| Source size | ~877 KB across 282 files; core engine ~158 KB |
| Homepage | https://preactjs.com |
| Repository | https://github.com/preactjs/preact |

**Design philosophy.** Three properties drive nearly every implementation decision:

1. **Minimal size.** The codebase aggressively golfs bytes — bitfield flags instead of booleans, shared `EMPTY_OBJ`/`EMPTY_ARR` singletons, `null`/`undefined` constants reused by name, and `Array.prototype.some` used as a short-circuitable `forEach`.
2. **Extensibility via `options`.** All add-ons (`hooks`, `compat`, `debug`, `devtools`) are built by patching a small set of renderer hook points on a shared `options` object, rather than by forking the core. This is the single most important architectural idea in the project.
3. **React API fidelity.** The public API matches React's; `preact/compat` closes the remaining gaps (DOM prop normalization, synthetic-event shims, `defaultProps`, portals, Suspense, etc.).

---

## 2. Architecture Overview

```
                       Application code (JSX)
                               │  compiled to h()/jsx()
                               ▼
   ┌───────────────────────────────────────────────────────────┐
   │                        preact (core)                        │
   │                                                             │
   │   create-element.js ──► VNode objects                       │
   │            │                                                │
   │   render.js / hydrate ──► diff/index.js (diff)              │
   │            │                    │                           │
   │            │                    ├─ diff/children.js (keyed) │
   │            │                    ├─ diff/props.js (DOM attrs)│
   │            │                    └─ diff/catch-error.js      │
   │            ▼                                                │
   │   component.js (BaseComponent, setState, enqueueRender,     │
   │                 microtask render queue, depth sort)         │
   │            │                                                │
   │   options.js  ◄── the extension seam (hook registry)       │
   └───────────────────────────────────────────────────────────┘
        ▲            ▲             ▲              ▲
        │            │             │              │   (each patches options.*)
   preact/hooks  preact/compat  preact/debug  preact/devtools
   (state +      (React API +   (dev warnings (DevTools
    effects)      DOM norm +     + validation)  bridge)
                  Suspense)
                               ▲
                               │
                   preact/jsx-runtime  (Babel automatic transform)
                   preact/compat/server (SSR via preact-render-to-string)
                   preact/test-utils    (act/rerender/teardown)
```

The **core** owns VNode creation, the diff/commit cycle, the asynchronous render scheduler, and the `options` registry. Every other package is a *consumer of `options`* — it imports `options` from `preact` and wraps the existing hook functions, chaining to the previous implementation. This is how hooks, devtools, compat, and debug all coexist and compose without modifying core.

---

## 3. Repository Structure

The repository is a single npm package that publishes **multiple entry points**, each with its own `src/`, `dist/`, and `package.json` (used only for the per-entry module fields), wired together by the root `package.json` `exports` map.

```
preact/
├── src/                  Core engine (h, render, diff, Component, options, context)
│   └── diff/             Diffing: index.js, children.js, props.js, catch-error.js
├── hooks/src/            preact/hooks — useState, useEffect, …
├── compat/src/           preact/compat — React compatibility layer
│   ├── suspense.js       Suspense + lazy
│   ├── portals.js        createPortal
│   ├── memo.js           memo
│   ├── forwardRef.js     forwardRef
│   ├── PureComponent.js  PureComponent
│   ├── Children.js       Children utilities
│   ├── render.js         DOM prop normalization + event shims + render proxy
│   └── hooks.js          useTransition/useDeferredValue/useSyncExternalStore shims
├── compat/               server.js, server.browser.js, scheduler.js, client.* (SSR + shims)
├── debug/src/            preact/debug — dev validation + component stacks
├── devtools/src/         preact/devtools — DevTools bridge
├── jsx-runtime/src/      preact/jsx-runtime — automatic JSX transform
├── test-utils/src/       preact/test-utils — act/rerender/teardown
├── test/                 Browser, node, ts, and shared test suites
├── demo/                 Local development demos (not published)
├── benchmarks/           Performance benchmarks (not published)
├── config/               compat-entries.js (postbuild entry generation)
├── package.json          Root manifest + exports map
├── babel.config.js       JSX transform config for tests
├── biome.json            Formatter config
├── vitest.config.mjs     Test runner config
├── mangle.json           Property-mangling rules (size optimization)
└── jsconfig*.json        Type-check configs
```

> [!NOTE]
> `src/`, `hooks/src/`, `compat/src/`, etc. are the **product**. `demo/`, `benchmarks/`, and `test/` dominate the file count but are development-only and excluded from the published package's `files` allowlist. The published artifacts are the `dist/` bundles plus the `src/` sources and `.d.ts` files.

### Published entry points (`exports` map)

| Specifier | Source | Notes |
|---|---|---|
| `preact` | `src/index.js` | Core. Types: `src/index.d.ts`. |
| `preact/hooks` | `hooks/src/index.js` | |
| `preact/compat` | `compat/src/index.js` | Aliasable as `react`/`react-dom`. |
| `preact/debug` | `debug/src/index.js` | Dev only. |
| `preact/devtools` | `devtools/src/index.js` | |
| `preact/test-utils` & `preact/compat/test-utils` | `test-utils/src/index.js` | |
| `preact/jsx-runtime` & `preact/jsx-dev-runtime` | `jsx-runtime/src/index.js` | |
| `preact/compat/jsx-runtime`, `…/jsx-dev-runtime` | `compat/jsx-runtime.*` | React-source JSX wiring. |
| `preact/compat/server`, `…/server.browser` | `compat/server.*` | SSR (delegates to `preact-render-to-string`). |
| `preact/compat/scheduler` | `compat/scheduler.*` | React `scheduler` shim. |
| `preact/compat/client` | `compat/client.*` | `react-dom/client` shim. |

Each `exports` entry resolves `types`, `module`/`import` (ESM `.mjs`), `umd`, and `require` (CJS `.js`) conditions.

---

## 4. The Rendering Pipeline (Deep Dive)

### VNode shape

`createElement`/`h` extract `key` and `ref` from props, then call `createVNode`, which always allocates an object of the **same shape** (so V8 can use a single hidden class):

```js
const vnode = {
	type, props, key, ref,
	_children: null, _parent: null, _depth: 0,
	_dom: null, _component: null,
	constructor: undefined,       // set to undefined to prevent JSON injection
	_original: ++vnodeId, _index: -1, _flags: 0
};
```

`constructor: undefined` is a security measure: `isValidElement` checks `vnode.constructor === undefined`, so a JSON payload (whose `constructor` is `Object`) can never masquerade as a VNode. See [Security Analysis](#11-security-analysis).

### `render(vnode, parent)` flow

1. If `parent === document`, retarget to `document.documentElement` (issue #3794).
2. Fire `options._root(vnode, parent)` if registered.
3. Determine hydration mode from the vnode's `MODE_HYDRATE` flag.
4. Retrieve the previous tree from `parent._children` (Preact stores the last-rendered tree on the container's `_children` property — this is how repeated `render()` calls update in place).
5. Wrap the new vnode in a `Fragment` and store it on `parent._children`.
6. Call `diff(...)` with a fresh `commitQueue` and `refQueue`.
7. Call `commitRoot(commitQueue, parent._children, refQueue)` to apply refs and run lifecycle/effect callbacks.

`hydrate(vnode, parent)` simply sets the `MODE_HYDRATE` flag on the vnode and delegates to `render`.

### The `diff()` function (`src/diff/index.js`)

`diff()` is the heart of the renderer. For a given new/old vnode pair it:

- Bails out early when `newVNode.constructor !== undefined` (injection guard).
- Resumes suspended hydration when the old vnode carries `MODE_SUSPENDED | MODE_HYDRATE`.
- **Function/class components:** instantiates or reuses the component, runs `getDerivedStateFromProps`, the pre-render lifecycle methods, `shouldComponentUpdate` (bail-out path), then calls `render()`. Function components are backed by a `BaseComponent` whose `render` is a thunk (`doRender`) calling the function. Handles `setState` called during render via a loop capped at 25 iterations.
- **DOM/text nodes:** delegates to `diffElementNodes`, which creates/locates the element, diffs props (`setProperty` in `diff/props.js`), tracks SVG/MathML namespaces, handles `dangerouslySetInnerHTML`, and recurses into `diffChildren`.
- Fires `options._diff`, `options._render`, and `options.diffed` hooks at the appropriate phases.
- Routes thrown errors (including thrown promises for Suspense) to `options._catchError`.

### Keyed children diffing (`src/diff/children.js`)

`diffChildren` matches old and new children by `key` (and type), using vnode `_flags` (`INSERT_VNODE`, `MATCHED`) to mark insertions and matches, minimizing DOM moves on reorder. `getDomSibling` (in `component.js`) locates the correct DOM insertion point by walking the vnode tree.

### Component state & the render scheduler (`src/component.js`)

`BaseComponent` provides `setState` and `forceUpdate`:

- `setState(update, callback?)` clones `state` into `_nextState` (once), merges the update (calling it as a function with a cloned state if it's a function — to play nice with Immer's frozen state, issue #2716), pushes any callback to `_stateCallbacks`, and calls `enqueueRender(this)`.
- `forceUpdate(callback?)` sets the `COMPONENT_FORCE` bit (so `shouldComponentUpdate` is skipped) and enqueues.

`enqueueRender` adds the component to a module-level `rerenderQueue` (guarded by the `COMPONENT_DIRTY` bit to avoid duplicates) and schedules a flush via `options.debounceRendering` or `queueMicrotask`. The flush (`process`) **sorts the queue by vnode depth** (`depthSort`) so parents render before children, then re-renders each still-dirty component. New components added mid-flush (e.g. via a provider update) are kept in depth order for single-pass handling.

> [!NOTE]
> This asynchronous, depth-sorted, microtask-batched scheduler is what makes `setState` calls coalesce into a single re-render per tick and guarantees top-down render order.

### Commit & refs

`commitRoot` applies queued refs (`applyRef`), fires `options._commit`, then drains each component's `_renderCallbacks` (lifecycle callbacks like `componentDidMount`/`componentDidUpdate` and effect runners installed by hooks). `applyRef` supports both function refs (storing a returned cleanup on the function itself) and object refs (`ref.current = value`). `unmount` tears down a subtree, invoking `componentWillUnmount`, detaching listeners, and clearing pointers.

---

## 5. The `options` Hook System

`options` (from `src/options.js`) is a single shared object — initially just `{ _catchError }`. The core calls optional hook functions on it at well-defined points; add-ons register by overwriting a hook and chaining to the previous value.

### Public option hooks (`Options` interface)

| Hook | Fired when |
|---|---|
| `vnode(vnode)` | Every time a VNode is created. |
| `unmount(vnode)` | Immediately before a vnode unmounts. |
| `diffed(vnode)` | After a vnode has rendered/diffed. |
| `event(e)` | When a DOM event is dispatched (compat shims synthetic-event methods here). |
| `requestAnimationFrame(cb)` | Scheduler override for after-paint effects. |
| `debounceRendering(cb)` | Override the render-queue flush scheduler. |
| `useDebugValue(value)` | Forward custom-hook debug labels to DevTools. |
| `attr(name, value)` | Customize attribute serialization for the precompiled JSX transform. |

### Internal (underscore-prefixed) hooks

These are private but are the extension points the first-party add-ons use: `_catchError`, `_diff` (before diff), `_render` (before component render), `_commit`, `_root`, `_hook` (on each hook invocation), `_hydrationMismatch`, `_addHookName`, `__suspenseDidResolve`. They are mangled in production builds (see `mangle.json`) and are **not** a stable public API.

> [!CAUTION]
> Do not depend on the internal underscore-prefixed `options` hooks or vnode fields from application code. They are mangled, unstable across versions, and exist solely so that `hooks`/`compat`/`debug`/`devtools` can extend core. Build on the public `Options` interface only.

**Example of the pattern** (`preact/hooks` installing its effect machinery):

```js
import { options as _options } from 'preact';
const options = _options;

let oldBeforeRender = options._render;
options._render = vnode => {
	if (oldBeforeRender) oldBeforeRender(vnode);   // chain
	// …hooks-specific logic…
};
```

Every add-on follows this "save old, install new, call old" discipline so multiple packages can layer hooks on the same point.

---

## 6. Module Reference

### Core (`src/`)

| File | Responsibility |
|---|---|
| `index.js` | Public barrel — re-exports `render`, `hydrate`, `h`/`createElement`, `Fragment`, `createRef`, `isValidElement`, `Component`, `cloneElement`, `createContext`, `toChildArray`, `options`. |
| `create-element.js` | `createElement`/`h`, `createVNode`, `createRef`, `Fragment`, `isValidElement`. |
| `component.js` | `BaseComponent` (`setState`/`forceUpdate`), `enqueueRender`, render queue + `process`, `getDomSibling`, `renderComponent`. |
| `render.js` | `render`/`hydrate` entry points. |
| `clone-element.js` | `cloneElement`. |
| `create-context.js` | `createContext` (provider/consumer via subscriber `Set`, `shouldComponentUpdate`-driven propagation). |
| `options.js` | The shared `options` registry (seeded with `_catchError`). |
| `constants.js` | Bitfield flags (`MODE_HYDRATE`, `MODE_SUSPENDED`, `INSERT_VNODE`, `MATCHED`, `FORCE_PROPS_REVALIDATE`, `COMPONENT_*`), namespace URIs (SVG/XHTML/MathML), shared `NULL`/`UNDEFINED`/`EMPTY_OBJ`/`EMPTY_ARR` singletons. |
| `util.js` | `isArray`, `slice`, `assign` (= `Object.assign`), `removeNode`. |
| `diff/index.js` | `diff`, `commitRoot`, `diffElementNodes`, `applyRef`, `unmount`. |
| `diff/children.js` | `diffChildren` (keyed reconciliation), `toChildArray`. |
| `diff/props.js` | `setProperty` — applies DOM attributes, events, styles. |
| `diff/catch-error.js` | `_catchError` — walks up to the nearest error boundary / `getDerivedStateFromError` / `componentDidCatch`. |
| `index.d.ts`, `dom.d.ts`, `jsx.d.ts`, `internal.d.ts` | Public and internal type definitions. `dom.d.ts` carries the exhaustive HTML/SVG attribute and event-handler type surface, including `Signalish<T>` for `@preact/signals` interop. |

### Hooks (`hooks/src/`)

`index.js` installs `options._diff`, `_render`, `diffed`, `_commit`, `unmount`, `_root` hooks to drive a per-component hook list (`__hooks._list`) and effect queue (`_pendingEffects`). Hook types are numbered (`getHookState(index, type)`): 1 `useState`, 2 `useReducer`, 3 `useEffect`, 4 `useLayoutEffect`, 5 `useRef`, 6 `useImperativeHandle`, 7 `useMemo`, 8 `useCallback`, 9 `useContext`, 10 `useErrorBoundary`, 11 `useId`. Effects run after paint via `afterNextFrame` (rAF + 35 ms `setTimeout` fallback for background tabs).

### Diff sub-modules, compat, debug, devtools, jsx-runtime, test-utils

Covered in their dedicated sections ([Compat Internals](#7-the-compat-layer-internals)) and the User Guide. `devtools/src` is a thin bridge that initializes the DevTools connection and exposes `addHookName`. `jsx-runtime/src` implements `jsx`/`jsxs`/`jsxDEV` (all aliased to one optimized `createVNode`) plus the precompiled-transform helpers `jsxTemplate`/`jsxAttr`/`jsxEscape`.

---

## 7. The Compat Layer Internals

`preact/compat` (`compat/src/`) is where React fidelity lives. It is the most behaviorally dense add-on.

### `compat/src/render.js` — normalization seam

Installs several `options` hooks:

- **`options.vnode`** — sets `vnode.$$typeof = Symbol.for('react.element')` on every vnode (so `react-is` checks pass), applies `defaultProps`, moves `ref` from props to the vnode field for class components, and calls `handleDomVNode` for string-typed nodes.
- **`handleDomVNode`** — the prop-normalization table: `className` ⇄ `class` (with a non-enumerable `className` getter), `onChange`→`oninput` for text `input`/`textarea` (except file/checkbox/radio), `onDoubleClick`→`ondblclick`, `onFocus`/`onBlur`→`onfocusin`/`onfocusout`, camelCase SVG props → dashed (`CAMEL_PROPS` regex), numeric `style` values → `px` (unless matched by `IS_NON_DIMENSIONAL`), `defaultValue`/`defaultChecked` fallbacks, `download={true}`→`""`, `translate="no"`→`false`, and array `value` for `<select multiple>`.
- **`options.event`** — adds React synthetic-event shims: `persist()` (no-op), `nativeEvent`, `isPropagationStopped()`, `isDefaultPrevented()`.
- **`options._render`/`diffed`** — track `currentComponent` (for `react-relay`) and sync `<textarea>` value.
- **`Component.prototype.isReactComponent = true`** and `UNSAFE_*` lifecycle aliasing via `Object.defineProperty` getters/setters.
- **`render`/`hydrate` proxies** — accept an optional `callback`, return the root component reference, and clear existing container DOM (`textContent = ''`) on first render (React parity, issues #1727/#1828).
- Exposes `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED` with a `ReactCurrentDispatcher` for libraries that reach into React internals.

### Suspense & lazy (`compat/src/suspense.js`)

`Suspense` is a hand-rolled `Component` subclass (custom prototype assignment to save bytes). It patches `options._catchError` to intercept **thrown promises**: it walks up to the nearest component with `_childDidSuspend`, parks the suspended subtree (cloning it into a detached `<div>` so its effects/cleanups pause), renders the `fallback`, and restores + `forceUpdate`s the real subtree when the promise resolves. During hydration it keeps the SSR markup on screen instead of swapping in the fallback. `lazy(loader)` returns a component that throws the load promise until it resolves, then renders `exports.default || exports`; it caches the promise/result/error across renders.

### Other compat modules

| Module | Behavior |
|---|---|
| `memo.js` | Wraps a component with a `shouldComponentUpdate` using `shallowDiffers` (or a custom comparer); sets `displayName = 'Memo(...)'` and `_forwarded`/`isReactComponent`. |
| `PureComponent.js` | `Component` subclass with `shallowDiffers`-based `shouldComponentUpdate`; sets `isPureReactComponent`. |
| `forwardRef.js` | Wraps a render fn, passing `props.ref` as the second arg; tags it with `Symbol.for('react.forward_ref')` and a `render` property (mobx-react relies on this). |
| `portals.js` | `createPortal` renders into a "fake" DOM parent that proxies `insertBefore` to the real container, wrapped in a `ContextProvider` so context flows across the portal. |
| `Children.js` | `map`/`forEach`/`count`/`only`/`toArray` over the (flattened) children — mostly passthrough to `toChildArray`. |
| `hooks.js` | React 18 concurrency shims: `startTransition` runs synchronously, `useDeferredValue` returns its argument, `useTransition` returns `[false, startTransition]`, `useInsertionEffect = useLayoutEffect`, and a real `useSyncExternalStore` implementation. |

---

## 8. Build System & Distribution

### Toolchain

| Concern | Tool |
|---|---|
| Bundler | **microbundle** (per-package, `--raw --no-generateTypes -f cjs,esm,umd`) |
| Formatter | **Biome** (`biome format`) |
| Linter | **oxlint** + `tsc` type-checking (`jsconfig-lint.json`) |
| Tests | **Vitest** (browser mode via Playwright Chromium) + `tsc` type tests |
| JSX (tests) | Babel (`@babel/plugin-transform-react-jsx`) |
| Size tracking | gzip/brotli badges via badgesize/unpkg (README) |
| Node version | pinned to `20.19.1` via Volta |

### npm scripts

| Script | Action |
|---|---|
| `build` | Runs all `build:*` in parallel (core, debug, devtools, hooks, test-utils, compat, jsx), then `postbuild`. |
| `build:compat` | microbundle with `--globals 'preact/hooks=preactHooks'` (UMD global mapping). |
| `postbuild` | `node ./config/compat-entries.js` generates the compat sub-entry files. |
| `dev`, `dev:hooks`, `dev:compat` | microbundle watch mode. |
| `test` | `build` → `lint` → `test:unit`. |
| `test:unit` | Vitest (minified run) + TS type tests in parallel. |
| `test:vitest` / `:min` | Vitest run (with `COVERAGE`/`MINIFY` env flags). |
| `test:ts:core` / `:compat` | `tsc -p` against the `test/ts` and `compat/test/ts` projects. |
| `lint` | `oxlint` + `tsc`. |
| `format` / `format:check` | Biome formatting. |

### Property mangling (`mangle.json`)

To hit the 4 kB target, internal property names (the underscore-prefixed vnode/component/options fields) are renamed at build time via `babel-plugin-transform-rename-properties`. Each sub-package has its own `mangle.json` so internal names stay consistent across the core and add-ons that share the renamed shapes.

> [!IMPORTANT]
> Because internal fields are mangled, source-level field names (`_children`, `_component`, `_diff`, …) shown in this guide are the *unmangled* development names. Never reference them from outside the library — in a production bundle they will have short, unstable names.

### Distribution artifacts

The published `files` allowlist ships, per entry point: `dist/*.{js,mjs,umd.js}`, the `src/` sources, the `.d.ts` types, and the per-entry `package.json`. `main`/`module`/`umd:main`/`source`/`types` plus the `exports` map route consumers to the right artifact for their module system. `publishConfig.provenance: true` enables npm build provenance attestation.

---

## 9. Testing Strategy

| Suite | Location | Tool | Purpose |
|---|---|---|---|
| Browser behavior | `test/browser/` | Vitest + Playwright Chromium | The bulk of coverage — rendering, lifecycles, keys, focus, hydration, SVG/MathML, events, refs, fragments. |
| Lifecycle matrix | `test/browser/lifecycles/` | Vitest | Each lifecycle method individually. |
| Node/build artifact | `test/node/` | Vitest + chai | Asserts the built bundle exports the expected public API (`createElement`, `h`, `Component`, `render`, `hydrate`). |
| Shared | `test/shared/` | Vitest | `createElement`/`createContext`/`isValidElement` semantics shared across environments. |
| TypeScript types | `test/ts/`, `compat/test/ts/` | `tsc` | Compile-time type assertions (`*.test-d.tsx`) for the `.d.ts` surface, including JSX namespace, DOM attributes, refs, HOCs. |
| Per-package | `hooks/test/`, `compat/test/`, `debug/test/`, `devtools/test/`, `jsx-runtime/test/`, `test-utils/test/` | Vitest | Each add-on's own suite. |

Tests use `preact/test-utils`' `act`/`setupRerender`/`teardown` to flush the async scheduler deterministically. Coverage is collected with `@vitest/coverage-v8` and reported to Coveralls.

> [!TIP]
> Tests run against the **minified** build by default (`test:vitest:min`, `MINIFY=true`). This catches mangling/minification regressions that a source-only test run would miss — a deliberate choice for a size-critical library.

---

## 10. Code Quality & Conventions

**Byte-golfing idioms** (intentional, pervasive):

- Bitfield flags on `vnode._flags` and `component._bits` instead of multiple booleans (`constants.js`).
- `Array.prototype.some` used as a short-circuitable iterator (returning truthy to stop).
- Shared singletons `EMPTY_OBJ`, `EMPTY_ARR`, and named `NULL`/`UNDEFINED` constants (mangler renames them to short tokens; reusing names improves compression).
- Same-shape VNode allocation from a single call site so V8 keeps one hidden class.
- Custom prototype assignment (`Suspense.prototype = new Component()`) instead of `class extends` to shave bytes.

**Conventions:**

- Pure ES modules, no transpiled `class` in core (`@ts-ignore` notes "we don't use TS to downtranspile").
- Type safety via JSDoc `@type`/`@param` annotations checked by `tsc` (`jsconfig-lint.json`) — the source is `.js`, not `.ts`, but fully type-checked.
- Formatting enforced by Biome; linting by oxlint; pre-commit via `nano-staged` + Husky.
- Hand-authored `.d.ts` files (not generated) give precise public types, including an exhaustive DOM attribute/event surface in `dom.d.ts` and `@preact/signals` interop via the `Signalish<T>` type.

**Complexity hotspots:** `src/diff/index.js` (the `diff` function, ~330 lines, deeply branched between component and element paths, hydration, and Suspense bailouts) and `src/diff/children.js` (keyed reconciliation) are the highest-complexity, most safety-critical files. They are also the most thoroughly tested.

---

## 11. Security Analysis

Preact is a client-side rendering library with **no network, filesystem, auth, or secret-handling surface** — its threat model is XSS/DOM-injection within the rendering path. The relevant, source-grounded items:

| Area | Finding |
|---|---|
| **JSON / prototype injection** | `createVNode` sets `constructor: undefined`, and `isValidElement`/`diff` reject any object whose `constructor !== undefined`. This prevents arbitrary JSON (whose `constructor` is `Object`) from being treated as a renderable VNode — a deliberate injection guard. |
| **`dangerouslySetInnerHTML`** | Honored verbatim in `diffElementNodes` (sets `dom.innerHTML`). This is the primary XSS vector in any VDOM library: **never** pass untrusted/unsanitized HTML. Sanitize upstream. |
| **Text escaping** | Normal children are set via `dom.data`/text nodes (not `innerHTML`), so interpolated strings are inherently escaped. The SSR JSX-attr serializer (`jsxAttr`/`encodeEntities` in `jsx-runtime`) HTML-escapes attribute values. |
| **Trusted Types** | Conditional `dangerouslySetInnerHTML` can trip a Trusted Types policy (community issue #4934). When deploying under a strict Trusted-Types CSP, route HTML through an approved policy. |
| **SES / lockdown** | Running under SES `lockdown()` has surfaced a `TypeError` (issue #5109). Test Preact explicitly if you ship into a hardened/locked-down realm. |
| **No `eval`/dynamic code** | The core does not use `eval`, `Function()`, or dynamic `import` of remote code. `lazy()` imports modules your own code references. |
| **Supply chain** | **Zero runtime dependencies** in core — a minimal supply-chain surface. SSR adds `preact-render-to-string`. npm provenance attestation is enabled (`publishConfig.provenance`). |

> [!CAUTION]
> The single highest-risk API is `dangerouslySetInnerHTML`. Treat any value flowing into it as untrusted by default and sanitize with a vetted sanitizer (e.g. DOMPurify) before render. The `dangerously` prefix is intentional.

> [!NOTE]
> `preact/debug`'s `_catchError` re-throws handled errors on a `setTimeout` to emit a `window` error event (so dev overlays like Next.js work). This is development-only behavior and must not ship to production.

---

## 12. Integration & Deployment Concerns

### React drop-in via aliasing

Map `react` and `react-dom` to `preact/compat` (and `react/jsx-runtime` to `preact/jsx-runtime`) in your bundler. Most React libraries then work unchanged. Edge cases arise with libraries that depend on React-internal behavior or precise event timing — known community reports include `floating-ui` event processing differences (#4373), `@radix-ui` slider/tooltip behaviors (#3666, #3805), and `jsx-email` rendering (#5106). Validate ecosystem libraries against your app.

### Bundler configuration

- Ensure tree-shaking is enabled so unused sub-packages drop out.
- Use the `import`/`module` (ESM) conditions for the smallest output; the `umd` build is only for script-tag usage.
- For TypeScript drop-in, set `paths` for `react`/`react-dom` **and** the JSX `jsxImportSource`.

### SSR deployment

Use `preact/compat/server`'s `renderToString` (sync) or the streaming functions for SSR; require `preact-render-to-string >= 6.5.0` for streaming. Hydrate on the client with `hydrate()`. Keep server and client renders deterministic to avoid hydration mismatches (use `useId()` for IDs).

### Production hardening checklist

- [ ] `preact/debug` is excluded from production bundles.
- [ ] All `dangerouslySetInnerHTML` inputs are sanitized.
- [ ] A Content-Security-Policy (and Trusted Types policy, if used) is configured and tested with Preact.
- [ ] SSR output is deterministic (no `Date.now()`/`Math.random()` in first render).
- [ ] `preact/devtools` is gated to the environments where you want DevTools.
- [ ] React aliases (if using compat) resolve correctly in the production build, not just dev.

---

## 13. Known Issues & Roadmap

From the community issue tracker at the documented commit (representative, not exhaustive):

**Confirmed bugs**

| Issue | Summary |
|---|---|
| #5109 | `TypeError` when SES `lockdown` is enabled. |
| #4737 | `<select>` value update fails when set to a newly added `<option>` simultaneously. |
| #3242 | Focus lost during list re-order. |

**React-compat parity requests**

| Issue | Summary |
|---|---|
| #4977 | Support React 19 `<Activity />`. |
| #4756 | Support `use(promise)` in `preact/compat`. |
| #4972 | `useSyncExternalStore` third argument (`getServerSnapshot`). |
| #4927 | `useEffectEvent`. |
| #4994 | JSX types reflect core + compat even when compat is unused. |
| #4299 | Run unmount hooks during commit phase for React consistency. |

**Forward-looking**

| Issue | Summary |
|---|---|
| #2621 | "The road to Preact 11" — the umbrella tracking issue for the major this guide documents. |
| #5034 | RFC: Streaming SSR hydration coordination for Preact core. |
| #4934 | Conditional `dangerouslySetInnerHTML` + Trusted Types. |

> [!IMPORTANT]
> Because this is the **v11 pre-release branch**, expect API churn and incomplete features relative to stable v10. Track the "road to Preact 11" issue (#2621) and the release notes before adopting in production.

---

## 14. Potential Improvements

Observations for maintainers and integrators (derived from the source, not prescriptive):

- **Core render callback parity.** v11 core `render`/`hydrate` dropped the third `callback` argument (it now lives only in compat). Migration guidance / a codemod would smooth the v10→v11 transition for apps that used `render(vnode, parent, cb)`.
- **JSX type split (#4994).** The published types merge core and compat surfaces; splitting them would prevent compat-only APIs from type-checking in plain-Preact apps.
- **Trusted Types support.** First-class Trusted-Types-aware HTML handling (beyond raw `innerHTML`) would help strict-CSP deployments (#4934).
- **SES compatibility.** Hardening against `lockdown()` realms (#5109) would broaden where Preact can run.
- **Streaming hydration coordination (#5034).** Bringing streaming-SSR hydration into core (rather than relying solely on `preact-render-to-string`) is an active RFC.

---

## 15. Contributing

| Step | Command |
|---|---|
| Install (pins Node via Volta, installs Playwright Chromium) | `npm install` |
| Run the demo app | `npm run dev` (microbundle watch) |
| Build all packages | `npm run build` |
| Lint + typecheck | `npm run lint` |
| Format | `npm run format` |
| Full test (build + lint + unit) | `npm test` |
| Unit tests only | `npm run test:unit` |
| Type tests | `npm run test:ts` |

Conventions to follow when contributing:

- Author in ES modules with JSDoc types; the source is type-checked by `tsc` even though it's `.js`.
- Keep bundle size in mind — size is a first-class review gate (gzip/brotli badges track it).
- Add tests under the matching suite (`test/browser`, per-package `*/test`, and `*.test-d.tsx` for types).
- Run Biome format and oxlint before committing (enforced via Husky + nano-staged pre-commit).
- New renderer behavior almost always means a new or modified `options` hook interaction — preserve the "save old, install new, chain" pattern.

See `CONTRIBUTING.md` in the repository for the full process.

---

## 16. Quick Reference

### Vnode flags (`constants.js`)

| Flag | Bit | Meaning |
|---|---|---|
| `MODE_HYDRATE` | `1<<5` | Attaching to existing DOM without diffing. |
| `MODE_SUSPENDED` | `1<<7` | Suspended on the previous render. |
| `INSERT_VNODE` | `1<<2` | Needs insertion during child patching. |
| `MATCHED` | `1<<1` | Matched with another vnode during diff. |
| `FORCE_PROPS_REVALIDATE` | `1<<0` | Re-validate props (post-suspense). |

### Component bits

| Bit | Value | Meaning |
|---|---|---|
| `COMPONENT_PROCESSING_EXCEPTION` | `1<<0` | Processing an exception. |
| `COMPONENT_PENDING_ERROR` | `1<<1` | Has a pending error. |
| `COMPONENT_FORCE` | `1<<2` | Force update (skip `shouldComponentUpdate`). |
| `COMPONENT_DIRTY` | `1<<3` | Queued for update. |

### Namespaces

| Constant | URI |
|---|---|
| `SVG_NAMESPACE` | `http://www.w3.org/2000/svg` |
| `XHTML_NAMESPACE` | `http://www.w3.org/1999/xhtml` |
| `MATH_NAMESPACE` | `http://www.w3.org/1998/Math/MathML` |

### Entry-point cheat sheet

| Need | Import |
|---|---|
| Render UI | `preact` |
| State/effects in function components | `preact/hooks` |
| React API / ecosystem libs | `preact/compat` |
| Dev warnings + DevTools | `preact/debug` |
| DevTools in production | `preact/devtools` |
| Automatic JSX transform | `preact/jsx-runtime` |
| SSR | `preact/compat/server` (+ `preact-render-to-string`) |
| Tests | `preact/test-utils` |

---

_Generated June 2026 from preactjs/preact at 850bbdb. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
