Skip to content

Latest commit

 

History

History
273 lines (193 loc) · 15.1 KB

CONTRIBUTING.md

File metadata and controls

273 lines (193 loc) · 15.1 KB

Contributing

This document is intended for developers interest in making contributions to Preact and document our internal processes like releasing a new version.

Getting Started

This steps will help you to set up your development environment. That includes all dependencies we use to build Preact and developer tooling like git commit hooks.

  1. Clone the git repository: git clone [email protected]:preactjs/preact.git
  2. Go into the cloned folder: cd preact/
  3. Install all dependencies: npm install

The Repo Structure

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 lifcycles.
  # 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 application. 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

What does 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 it's own distribution files we need to ensure that the variable names stay consistent across bundles.

What does 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.

Important Branches

We merge every PR into the main branch which is the one that we'll use to publish code to npm. For the previous Preact release line we have a branch called 8 which is in maintenance mode. As a new contributor you won't have to deal with that ;)

Creating your first Pull-Request

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 branch 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:

  1. Make changes and submit a PR
  2. Modify change according to feedback (if there is any)
  3. PR will be merged into main
  4. A new release will be cut (every 2-3 weeks).

Commonly used scripts for contributions

Scripts can be executed via npm run [script] or yarn [script] respectively.

  • build - compiles all packages ready for publishing to npm
  • build:core - builds just Preact itself
  • build:debug - builds the debug addon only
  • build:hooks - builds the hook addon only
  • build:test-utils - builds the test-utils addon only
  • test:ts - Run all tests for TypeScript definitions
  • test:karma - Run all unit/integration tests.
  • test:karma:watch - Same as above, but it will automatically re-run the test suite if a code change was detected.

But to be fair, the only ones we use ourselves are build and test:karma:watch. The other ones are mainly used on our CI pipeline and we rarely use them.

Note: Both test:karma and test:karma: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: The test suite is based on karma and mocha. Individual tests can be executed by appending .only:

it.only('should test something', () => {
	expect(1).to.equal(1);
});

Common terminology and variable names

  • vnode -> shorthand for virtual-node which is an object that specifies how a Component or DOM-node looks like
  • commit -> A commit is the moment in time when you flush all changes to the DOM
  • c -> 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 tree

Tips for getting to know the code base

  • Check the JSDoc block right above the function definition to understand what it does. It contains a short description of each function argument and what it does.
  • Check the callsites of a function to understand how it's used. Modern editors/IDEs allow you to quickly find those, or use the plain old search feature instead.

Benchmarks

We 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 --recursive after switching branches, or run git checkout --recurse-submodules when checking out a new branch.

FAQ

Why does the JSDoc use TypeScript syntax to specify types?

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 definitons 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.

Why does the code base often use let instead of const?

There is no real reason for that other a historical one. Back before auto-formatting via prettier was a thing and minifiers weren't as advanced as they are today we used a pretty terse code-style. The code-style deliberately was aimed at making code look as concise and short as possible. The let keyword is a bit shorter than const to write, so we only used that. This was done only for stylistic reasons.

This helped our minds to not lose sight of focusing on size, but made it difficult for newcomers to start contributing to Preact. For that reason alone we switched to prettier and loosened our rule regarding usage of let or const. Today we use both, but you can still find many existing places where let is still in use.

In the end there is no effect on size regardless if you use const, let or use both. Our code is downtranspiled to ES5 for npm so both will be replaced with var anyways. Therefore it doesn't really matter at all which one is used in our codebase.

This will only become important once shipping modern JavaScript code on npm becomes a thing and bundlers follow suit.

How to create a good bug report

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 🎉

I have more questions on how to contribute to Preact. How can I reach you?

We closely watch our issues and have a pretty active Slack workspace. Nearly all our communication happens via these two forms of communication.

Releasing Preact (Maintainers only)

This guide is intended for core team members that have the necessary rights to publish new releases on npm.

  1. Make a PR where only the version number is incremented in package.json and everywhere else. A simple search and replace works. (note: We follow SemVer conventions)
  2. Wait until the PR is approved and merged.
  3. Switch back to the main branch and pull the merged PR
  4. Create and push a tag for the new version you want to publish:
    1. git tag 10.0.0
    2. git push --tags
  5. Wait for the Release workflow to complete
    • It'll create a draft release and upload the built npm package as an asset to the release
  6. Fill in the release notes in GitHub and publish them
  7. Run the publish script with the tag you created
    1. node ./scripts/release/publish.mjs 10.0.0
    2. Make sure you have 2FA enabled in npm, otherwise the above command will fail.
    3. If you're doing a pre-release add --npm-tag next to the publish.mjs command to publish it under a different tag (default is latest)
  8. Tweet it out

Legacy Releases (8.x)

ATTENTION: Make sure that you've cleared the project correctly when switching from a 10.x branch.

  1. Run rm -rf dist node_modules && npm i to make sure to have the correct dependencies.
  2. Write the release notes and keep them as a draft in GitHub
    1. I'd recommend writing them in an offline editor because each edit to a draft will change the URL in GitHub.
  3. Make a PR where only the version number is incremented in package.json (note: We follow SemVer conventions)
  4. Wait until the PR is approved and merged.
  5. Switch back to the main branch and pull the merged PR
  6. Run npm run build && npm publish
    1. Make sure you have 2FA enabled in npm, otherwise the above command will fail.
    2. If you're doing a pre-release add --tag next to the npm publish command to publish it under a different tag (default is latest)
  7. Publish the release notes and create the correct git tag.
  8. Tweet it out

Writing release notes

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 😉

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:

  • Be nice, use a positive tone. Avoid negative words
  • Show, don't just tell.
  • Be honest.
  • Don't write too much, keep it simple and short.
  • Avoid making promises and don't overpromise. That leads to unhappy users
  • Avoid framework comparisons if possible
  • Highlight awesome community contributions (or great issue reports)
  • If in doubt, praise the users.

After this section we typically follow with a changelog part that's divided into 4 groups in order of importance for the user:

  • Features
  • Bug Fixes
  • Typings
  • Maintenance

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.