Skip to content

Latest commit

 

History

History
95 lines (73 loc) · 5.32 KB

File metadata and controls

95 lines (73 loc) · 5.32 KB

@react-hookz/web

A library of general-purpose React hooks, published to npm as ESM-only and consumed by React 16.8+ applications in browsers and during SSR. Every hook is public API: a signature change is a change to thousands of downstream builds, and merging to master publishes automatically. Correctness under SSR and a small, honest public surface matter more than feature count.

Language-level conventions (TypeScript, React, testing) live in .claude/rules/ and load per file type. This file covers what is specific to this repository.

Structure

src/
├── index.ts                # barrel; every hook and public type is re-exported here
├── types.ts                # shared public types
├── <useHook>/
│   ├── index.ts            # implementation -- named export, explicit return type, JSDoc
│   ├── index.dom.test.ts   # DOM test (vitest "DOM" project, jsdom)
│   └── index.ssr.test.ts   # SSR test (vitest "SSR" project, node)
└── util/                   # internals: const.ts (isBrowser), misc.ts, resolve-hook-state.ts
    └── testing/            # test-only helpers, excluded from the build
dist/                       # tsc output, published as-is -- never edit
vite.config.ts              # test, lint and format config, all three

Stack

  • Vite+ is the toolchain: vp fmt, vp lint, vp test. All three configured in vite.config.ts, nowhere else.
  • oxlint with @ver0/oxlint-config presets plus oxlint-tsgolint type-aware rules; oxfmt for formatting.
  • TypeScript 7 native compiler builds dist from tsconfig.build.json. No bundler.
  • Vitest 4, two projects split by filename: *.dom.test.ts runs in jsdom, *.ssr.test.ts in node.
  • yarn 4 via corepack, for dependency work only (yarn up <pkg>, yarn install --immutable).
  • semantic-release publishes from master; GitHub Actions are SHA-pinned via pinact.
  • CI goes through vp only: voidzero-dev/setup-vp provides Node, yarn, the dependency cache and the install, so no corepack enable, no actions/setup-node and no direct yarn call belongs in a workflow.

Conventions

Hooks:

  • One directory per hook, src/<useHook>/index.ts, named export, no default export.
  • Export every custom parameter and return type alongside the hook. No I/T prefixes.
  • Re-export from src/index.ts under its category comment, and add an entry to the README hook list.
  • Import sibling hooks by path with extension: import {useToggle} from '../useToggle/index.js', never from '..'.
  • Guard browser APIs with isBrowser from ../util/const.js. A hook must not throw when rendered on the server.
  • No fallbacks for legacy browser APIs — vendor prefixes, addListener/removeListener, existence guards on standard methods were removed deliberately. Target current browsers.
  • Dev-only warnings go behind process.env.NODE_ENV === 'development'.

Types:

  • isolatedDeclarations is on for the build: every exported symbol needs an explicit type annotation, including export const.
  • @types/node is out of scope (types: []); only process.env.NODE_ENV is declared, in src/util/process-env.d.ts.
  • noUncheckedIndexedAccess is on. An indexed read that cannot be undefined needs a ! plus a comment saying why.

Tests:

  • Every hook needs both files; SSR tests cover at least 'should be defined' and 'should render'.
  • Assertions on hook output go through the helpers in src/util/testing/test-helpers.jsexpectResultValue, expectCallArgs, expectCallResult — which assert presence instead of asserting types away.
  • Aim for full coverage; where a branch is unreachable, leave a comment explaining why.

Lint config:

  • Rule exceptions belong in the repoOverrides object in vite.config.ts, which must stay the last entry of lint.extends: consumer-level rules/overrides merge before extended configs, so anything placed in the lint block's own rules loses to the presets.
  • A test-file override must redeclare plugins: ['vitest'] or its vitest rules are silently ignored.

Commits:

  • Conventional subjects drive releases: feat minor, fix/perf patch, chore/ci/docs/test/style no release.
  • A public API break needs ! in the subject and a BREAKING CHANGE: footer — the footer is what semantic-release reads.

Verification

  • vp fmt --write — oxfmt; vp fmt --check is what CI runs.
  • vp lint — oxlint plus full tsc diagnostics (lint.options.typeCheck), so this also type-checks test files, which tsconfig.build.json excludes.
  • vp test --run — full suite; vp test --run --coverage for coverage.
  • tsc --project ./tsconfig.build.json — the build; isolatedDeclarations violations surface only here.

CI runs Lint, Build and Test (Node LTS) on every PR and master push.

Critical Constraints

  • A feat, fix or breaking commit reaching master is published to npm within minutes. There is no staging step — land release-worthy work through a PR and merge deliberately.
  • Never edit dist/. It is regenerated by every build.
  • Changes to exports, files or a hook's signature alter the published contract. Verify by packing (npm pack) and resolving the specifiers from a scratch consumer, not by reading dist.
  • A hook missing from src/index.ts or from the README list ships invisible — the README list is the documentation.