Reading the repo
Reading the repo
Assembly instructions
⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
Saved setup from 4 h ago
Will it work on my device?
CONTRIBUTING.mdThis document is intended for developers interested in making contributions to Preact and documents our internal processes like releasing a new version.
These steps will help you set up your development environment. That includes all dependencies we use to build Preact and developer tooling like git commit hooks.
git clone git@github.com:preactjs/preact.gitcd preact/npm install -g pnpm@11.3.0) using the Node version in package.json.pnpm installThe pnpm workspace contains the root library and demo only. Entry-point
package.json files are build and publishing metadata, not separate workspace
projects. The benchmark submodule keeps its own pnpm 8 workspace and lockfile.
Dependency updates must pass the 24-hour release-age and publishing-trust checks.
Review new install scripts explicitly in allowBuilds; keep any required trust
exceptions version-specific and document why they are needed. CI uses frozen
installs; commit pnpm-lock.yaml with dependency changes.
This repository contains Preact itself, as well as several addons like the debugging package for example. This is reflected in the directory structure of this repository. Each package has a src/ folder where the source code can be found, a test folder for all sorts of tests that check if the code in src/ is correct, and a dist/ folder where you can find the bundled artifacts. Note that the dist/ folder may not be present initially. It will be created as soon as you run any of the build scripts inside package.json. More on that later ;)
A quick overview of our repository:
# The repo root (folder where you cloned the repo into)
/
src/ # Source code of our core
test/ # Unit tests for core
dist/ # Build artifacts for publishing on npm (may not be present)
# Sub-package, can be imported via `preact/compat` by users.
# Compat stands for react-compatibility layer which tries to mirror the
# react API as close as possible (mostly legacy APIs)
compat/
src/ # Source code of the compat addon
test/ # Tests related to the compat addon
dist/ # Build artifacts for publishing on npm (may not be present)
# Sub-package, can be imported via `preact/hooks` by users.
# The hooks API is an effect based API to deal with component lifecycles.
# It's similar to hooks in React
hooks/
src/ # Source code of the hooks addon
test/ # Tests related to the hooks addon
dist/ # Build artifacts for publishing on npm (may not be present)
# Sub-package, can be imported via `preact/debug` by users.
# Includes debugging warnings and error messages for common mistakes found
# in Preact applications. Also hosts the devtools bridge
debug/
src/ # Source code of the debug addon
test/ # Tests related to the debug addon
dist/ # Build artifacts for publishing on npm (may not be present)
# Sub-package, can be imported via `preact/test-utils` by users.
# Provides helpers to make testing Preact applications easier
test-utils/
src/ # Source code of the test-utils addon
test/ # Tests related to the test-utils addon
dist/ # Build artifacts for publishing on npm (may not be present)
# A demo application that we use to debug tricky errors and play with new
# features.
demo/
# Contains build scripts and dependencies for development
package.json
Note: The code for rendering Preact on the server lives in another repo and is a completely separate npm package. It can be found here: https://github.com/preactjs/preact-render-to-string
mangle.json do?It's a special file that can be used to specify how terser (previously known as uglify) will minify variable names. Because each sub-package has its own distribution files we need to ensure that the variable names stay consistent across bundles.
options.js do?Unique to Preact we do support several ways to hook into our renderer. All our addons use that to inject code at different stages of a render process. They are documented in our typings in internal.d.ts. The core itself doesn't make use of them, which is why the file only contains an empty object.
We have a couple of important branches to be aware of:
main - This is the main development branch and represents the upcoming v11 release line.v10.x - This branch represents the current stable release line, v10.As we have yet to release v11, contributors are welcome to use either branch to build upon. We will try to port changes between the branches when possible, to keep them in sync, but if you're feeling generous, we'd love if you'd submit PRs to both branches!
We try to make it as easy as possible to contribute to Preact and make heavy use of GitHub's "Draft PR" feature which tags Pull-Requests (short = PR) as work in progress. PRs tend to be published as soon as there is an idea that the developer deems worthwhile to include into Preact and has written some rough code. The PR doesn't have to be perfect or anything really ;)
Once a PR or a Draft PR has been created our community typically joins the discussion about the proposed change. Sometimes that includes ideas for test cases or even different ways to go about implementing a feature. Often this also includes ideas on how to make the code smaller. We usually refer to the latter as "code-golfing" or just "golfing".
When everything is good to go someone will approve the PR and the changes will be merged into the main or v10.x branches and we usually cut a release a few days/ a week later.
The big takeaway for you here is, that we will guide you along the way. We're here to help to make a PR ready for approval!
The short summary is:
main or v10.xScripts can be executed via pnpm run [script].
build - compiles all packages ready for publishing to npm. Pass package
directories as arguments to build only selected packages, for example
pnpm run build . hooks compattest - Run all tests (linting, TypeScript definitions, unit/integration tests)test:ts - Run all tests for TypeScript definitionstest:vitest - Run all unit/integration tests.test:vitest:watch - Same as above, but it will automatically re-run the test suite if a code change was detected.But to be fair, the ones we mostly use locally are build and test:vitest:watch. The other ones are mainly used on our CI pipeline.
Note: Both test:vitest and test:vitest:watch listen to the environment variable COVERAGE=true. Disabling code coverage can significantly speed up the time it takes to complete the test suite.
Note2: Individual tests can be executed by appending .only:
it.only('should test something', () => {
expect(1).to.equal(1);
});
vnode -> shorthand for virtual-node which is an object that specifies how a Component or DOM-node looks likecommit -> A commit is the moment in time when you flush all changes to the DOMc -> The variable c always refers to a component instance throughout our code base.diff/diffing -> Diffing describes the process of comparing two "things". In our case we compare the previous vnode tree with the new one and apply the delta to the DOM.root -> The topmost node of a vnode treeWe have a benchmark suite that we use to measure the performance of Preact. Our benchmark suite lives in our preactjs/benchmarks repository, but is included here as Git submodule. To run the benchmarks, first ensure PNPM is installed on your system and initialize and setup the submodule (it uses pnpm as a package manager):
pnpm -v # Make sure pnpm is installed
git submodule update --init --recursive
cd benchmarks
pnpm i
Then you can run the benchmarks:
# In the benchmarks folder
pnpm run bench
Checkout the README in the benchmarks folder for more information on running benchmarks.
Note: When switching branches, git submodules are not automatically updated to the commit of the new branch - it stays at the commit of the previous branch. This can be a feature! It allows you to work in different branches with the latest versions of the benchmarks - especially if you have made changes to the benchmarks.
However if you want to switch branches and also update the benchmarks to the latest commit of the new branch, you can run
git submodule update --recursiveafter switching branches, or rungit checkout --recurse-submoduleswhen checking out a new branch.
Several members of the team are very fond of TypeScript and we wanted to leverage as many of its advantages, like improved autocompletion, for Preact. We even attempted to port Preact to TypeScript a few times, but we ran into many issues with the DOM typings. Those would force us to fill our codebase with many any castings, making our code very noisy.
Luckily TypeScript has a mode where it can somewhat reliably typecheck JavaScript code by reusing the types defined in JSDoc blocks. It's not perfect and it often has trouble inferring the correct types the further one strays away from the function arguments, but it's good enough that it helps us a lot with autocompletion. Another plus is that we can make sure that our TypeScript definitions are correct at the same time.
Check out the official TypeScript documentation for more information.
Note that we have separate tests for our TypeScript definition files. We only use ts-check for local development and don't check it anywhere else like on the CI.
To be able to fix issues we need to see them on our machine. This is only possible when we can reproduce the error. The easiest way to do that is narrow down the problem to specific components or combination of them. This can be done by removing as much unrelated code as possible.
The perfect way to do that is to make a codesandbox. That way you can easily share the problematic code and ensure that others can see the same issue you are seeing.
For us a codesandbox says more than a 1000 words :tada:
We closely watch our issues and have a pretty active Slack workspace. Nearly all our communication happens via these two forms of communication.
This guide is intended for core team members that have the necessary rights to publish new releases on npm.
Before using the automated npm publishing flow, make sure npm trusted publishing is configured for the preactjs/preact repository, the release.yml workflow, and the npm environment. The GitHub npm environment should require reviewer approval, and repository rules should protect 11.* tags.
package.json and everywhere else. A simple search and replace works. (note: We follow SemVer conventions)main branch and pull the merged PRgit tag 11.0.0git push origin 11.0.0publish job to request approval for the npm environment.
npm environment.publish job validates that the package name and version match the tag, selects the npm dist-tag, and submits the tarball with pnpm stage publish. A successful job means the package is staged; it is not public yet.preact package.
latest npm dist-tag; prereleases use the approved prerelease dist-tag (alpha, beta, rc, or next).pnpm view preact@11.0.0 version dist-tags --json.ATTENTION: Make sure that you've cleared the project correctly when switching from a 10.x branch.
rm -rf dist node_modules demo/node_modules && pnpm install to make sure to have the correct dependencies.package.json (note: We follow SemVer conventions)main branch and pull the merged PRpnpm run build && pnpm publish
--tag next to the pnpm publish command to publish it under a different tag (default is latest)The release notes have become a sort of tiny blog post about what's happening in preact-land. The title usually has this format:
Version Name
Example:
10.0.0-beta.1 Los Compresseros
The name is optional, we just have fun finding creative names :wink:
To keep them interesting we try to be as concise as possible and to just reflect where we are. There are some rules we follow while writing them:
After this section we typically follow with a changelog part that's divided into 4 groups in order of importance for the user:
We generate it via this handy cli program: changelogged. It will collect and format
the descriptions of all PRs that have been merged between two tags.
The usual command is changelogged 10.0.0-rc.2..HEAD similar to how
you'd diff two points in time with git. This will get you 90% there,
but you still need to divide it into groups. It's also a good idea
to unify the formatting of the descriptions, so that they're easier
to read and don't look like a mess.
Needs Git: xcode-select --install. The repo uses git submodules, so the clone fetches them too.
git clone --recurse-submodules https://github.com/preactjs/preact.git cd preact
Node.js 22.22.2
# needs nvm: brew install nvm (or https://github.com/nvm-sh/nvm) nvm install 22
corepack enable pnpm install
pnpm dev
pnpm test
💁 Note: You don't need ES2015 to use Preact... but give it a try!
With Preact, you create user interfaces by assembling trees of components and elements. Components are functions or classes that return a description of what their tree should output. These descriptions are typically written in JSX (shown underneath), or HTM which leverages standard JavaScript Tagged Templates. Both syntaxes can express trees of elements with "props" (similar to HTML attributes) and children.
To get started using Preact, first look at the render() function. This function accepts a tree description and creates the structure described. Next, it appends this structure to a parent DOM element provided as the second argument. Future calls to render() will reuse the existing tree and update it in-place in the DOM. Internally, render() will calculate the difference from previous outputted structures in an attempt to perform as few DOM operations as possible.
import { h, render } from 'preact';
// Tells babel to use h for JSX. It's better to configure this globally.
// See https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#usage
// In tsconfig you can specify this with the jsxFactory
/** @jsx h */
// create our tree and append it to document.body:
render(
<main>
<h1>Hello</h1>
</main>,
document.body
);
// update the tree in-place:
render(
<main>
<h1>Hello World!</h1>
</main>,
document.body
);
// ^ this second invocation of render(...) will use a single DOM call to update the text of the <h1>
Hooray! render() has taken our structure and output a User Interface! This approach demonstrates a simple case, but would be difficult to use as an application grows in complexity. Each change would be forced to calculate the difference between the current and updated structure for the entire application. Components can help here – by dividing the User Interface into nested Components each can calculate their difference from their mounted point. Here's an example:
import { render, h } from 'preact';
import { useState } from 'preact/hooks';
/** @jsx h */
const App = () => {
const [input, setInput] = useState('');
return (
<div>
<p>Do you agree to the statement: "Preact is awesome"?</p>
<input value={input} onInput={e => setInput(e.target.value)} />
</div>
);
};
render(<App />, document.body);
No reports for macOS yet.
Claude reads the 11 key files and writes a step-by-step guide for macOS. Once written, it's saved for everyone.