A monorepo of small, focused, open-source React libraries — independently versioned and published to npm under the
@galalemscope.
This repository is a home for many independent React packages, not a single library. Each package solves one problem well (a component, a hook, a utility), ships on its own version, and can be installed on its own:
npm i @galalem/<package-name>They live together in one repo so they can share tooling, testing, and a release pipeline — but they are published separately and have no forced coupling. You can install one without dragging in the others.
These principles are the contract. Every package in this repo should honor them, and any change — human or automated — should be evaluated against them.
- Small and single-purpose. A package does one thing. If it grows two responsibilities, it becomes two packages. Prefer many small packages over one kitchen-sink library.
- The app owns React, not us.
react(andreact-domwhen needed) are peer dependencies, never regular dependencies. We bind to the single React instance the consuming app already has. A package must never bundle or pin React. - Typed by default. Everything is written in TypeScript and ships
.d.tstype definitions. The public API is the exported types. - ESM-first, dual-format. Every package ships both ESM and CJS builds so it works in
modern bundlers and older toolchains, and is tree-shakeable (
sideEffects: falseunless a package genuinely has side effects, e.g. imported CSS). - Publish the build, not the source. Only compiled output (
dist/) is published. Source, tests, and config stay in the repo. - Tested before shipped. A package without tests is not ready to publish. Behavior is verified with Vitest.
- Compatible and honest. The supported React range in
peerDependenciesis a promise we verify in CI against every version we claim — not an aspiration. - Independent versioning. Packages version and release on their own cadence via Changesets. One package's release never forces a bump on another.
Packages live under a scope-named folder so the on-disk layout mirrors the npm scope:
react/ ← this repo (private root, never published)
├── @galalem/ ← workspace packages; folder name mirrors the npm scope
│ └── <package-name>/ ← one directory per package → publishes as @galalem/<package-name>
│ ├── src/
│ │ └── index.ts ← the package's public entry point (barrel of exports)
│ ├── package.json ← name, exports, peerDependencies, build script
│ ├── tsconfig.json ← extends ../../tsconfig.base.json
│ ├── README.md ← per-package docs (install, usage, API)
│ └── CHANGELOG.md ← generated by Changesets — do not edit by hand
├── .changeset/ ← pending release notes + Changesets config
├── .github/workflows/ ← CI (test matrix) and Release (publish) pipelines
├── pnpm-workspace.yaml ← declares packages live under @galalem/*
├── tsconfig.base.json ← shared TypeScript settings all packages extend
├── package.json ← private root: shared devDeps + workspace scripts
└── README.md ← you are here
Note on the folder name: the directory is literally named
@galalemso the repo path visually matches the published name. npm only cares about thenamefield inside eachpackage.json— the folder name is cosmetic. A package at@galalem/foowith"name": "@galalem/foo"publishes as@galalem/foo.
| Concern | Tool |
|---|---|
| Package manager | pnpm workspaces |
| Language | TypeScript |
| Build (lib bundling) | tsup — ESM + CJS + .d.ts |
| Tests | Vitest + React Testing Library |
| Versioning/publish | Changesets |
| CI/CD | GitHub Actions |
Prerequisites: Node >=20 and pnpm (corepack enable will provide it).
git clone https://github.com/galalem/react.git
cd react
pnpm install # installs deps for the whole workspace
pnpm build # builds every package
pnpm test # runs every package's test suiteRoot scripts fan out across all packages via pnpm -r:
| Script | What it does |
|---|---|
pnpm build |
Build every package (src/ → dist/). |
pnpm test |
Run every package's tests. |
pnpm changeset |
Record an intended release (run after a change). |
pnpm version |
Apply pending changesets: bump versions + changelogs. |
pnpm release |
Build, then publish changed packages to npm. |
Every package in @galalem/* must provide the following. This is the checklist a new
package is measured against.
package.json with at minimum:
Also required:
src/index.ts— the single public entry point. Everything consumers can use is exported from here; anything not exported here is private.tsconfig.jsonextending../../tsconfig.base.json.- At least one test file (
*.test.ts/*.test.tsx) covering the public behavior. - A
README.mdwith install, a usage example, and the public API. - React used only via
peerDependencies(+devDependenciesfor local build/test). Never independencies.
- Create
@galalem/<package-name>/following the structure and contract above. - Write
src/index.tsand its tests. - From the repo root,
pnpm install(links the new package into the workspace). pnpm --filter @galalem/<package-name> build && pnpm --filter @galalem/<package-name> test.- Add a changeset:
pnpm changeset(see Releasing below).
- Build a single package:
pnpm --filter @galalem/<package-name> build - Test in watch mode:
pnpm --filter @galalem/<package-name> exec vitest - Consume locally from another repo (verifies the real published tarball):
Prefer
cd @galalem/<package-name> && pnpm pack # → @galalem-<package-name>-x.y.z.tgz cd ~/other-project && pnpm add /abs/path/to/that.tgz
pnpm packovernpm link— packing tests exactly what users download and avoids the "two copies of React" hazard that linking can cause.
Releases are driven by Changesets and gated by a human-approved PR:
- Make your change in a package.
- Run
pnpm changeset, pick the affected package(s) and a semver bump (patch/minor/major), and describe the change. This writes a small markdown file under.changeset/. Commit it with your change. - On merge to
main, the Release workflow opens (or updates) a "Version Packages" PR that applies the bumps and updates changelogs. Nothing publishes yet. - Merging that PR triggers the workflow to publish only the changed packages to npm.
Semver, briefly: patch = bug fix, no API change · minor = backward-compatible addition · major = breaking change. Pre-1.0 packages may break on minors; note it in the changeset.
- The React versions a package supports are declared as a range in
peerDependencies(e.g.">=18"), never a single pinned version. - That range is a promise: CI runs each package's tests against every major React version in the range (via a test matrix). We only widen the range after CI is green on the new version.
- Avoid React internals and undocumented APIs so packages survive framework majors untouched.
- One focused change per PR; include a changeset for any user-facing change.
- Keep the public surface minimal — everything exported from
src/index.tsis API you have to support. - Tests and types are part of the change, not a follow-up.
This section is the assignment. If you are an agent working in this repo:
- Your job is to build, maintain, and release small React packages under the
@galalemscope that satisfy The package contract above. Read the Philosophy — those principles override convenience. - Adding a package: follow Creating a new package. Do not deviate from the required
package.jsonshape (peer-dep React, dualexports,files: ["dist"], public access). - Never add
react/react-domto a package'sdependencies. Peer + dev only. - Never publish manually or edit
CHANGELOG.mdby hand. Ship a changeset and let the Release pipeline publish. The root isprivateand must stay unpublishable. - Definition of done for any package change:
pnpm --filter <pkg> buildandpnpm --filter <pkg> testboth pass, types are exported fromsrc/index.ts, the packageREADME.mdreflects the change, and a changeset is included. - When unsure about scope, API surface, or a breaking change, prefer the smaller, more conservative option and surface the question rather than guessing.
MIT © Galalem
{ "name": "@galalem/<package-name>", "version": "0.0.0", "description": "<one sentence>", "license": "MIT", "type": "module", "sideEffects": false, "files": ["dist"], // only ship the build "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } }, "scripts": { "build": "tsup src/index.ts --format esm,cjs --dts --external react", "test": "vitest run" }, "peerDependencies": { "react": ">=18" // widen only to versions CI verifies }, "publishConfig": { "access": "public" // scoped packages are private by default } }