Guides

Browser bundles

Keep forges small in a browser bundle, and see how CI checks the sizes.

This page explains how to keep forges small in a browser bundle and how CI checks the bundle sizes.

Import one forge

Each provider has its own subpath export. Import from the subpath to bundle only the forges you use:

import { githubLite } from 'forges/github'

The root forges entry re-exports every provider. The package declares sideEffects: false, so bundlers can tree-shake the root entry, but a subpath import makes the dependency explicit.

Use the Lite factory

Every provider except pushin.eu exports two factories, <kind>() and <kind>Lite(). Both build the same provider. Only <kind>() references the webhook handlers. A browser doesn't receive webhook deliveries, so a bundle that imports only githubLite leaves out the signature verification and payload translation code.

On a Lite provider, capabilities.sources.webhook is false, and webhooks.verify() and webhooks.ingest() reject with UnsupportedOperationError. All other operations work the same way, including webhook management with webhooks.create() and webhooks.list().

pushin.eu has no Lite factory because pushin() contains no webhook code.

GraphQL on GitHub

forges/github loads its GraphQL documents with a dynamic import() the first time it needs them, so your bundler emits them as a separate chunk. Discussions and their reactions, threads.getMany(), review threads, subscriptions, transfers and duplicate marking use GraphQL. threads.list() also uses it to read the checks of each pull request in a page. Code that calls only REST operations doesn't load the chunk.

An anonymous GitHub provider doesn't use GraphQL, because GitHub requires a credential for its GraphQL API.

Test modules

forges/fake and forges/testing are for tests. Leave them out of production bundles.

loadFixtures() in forges/testing imports node:fs when you call it. No other entry imports a Node.js built-in module.

Sizes

The following table compares the Lite factory of each provider with an existing client for the same forge. Each row is a one-line import, bundled for the browser with rolldown and minified:

Forgeforges (Lite)Existing client
GitHub79.8 kB, 24.3 kB gzip@octokit/rest 22: 105.7 kB, 21.1 kB gzip
GitLab63.1 kB, 19.7 kB gzip@gitbeaker/rest 43: 233.7 kB, 55.3 kB gzip
Gitea / Forgejo55.6 kB, 17.2 kB gzipgitea-js 1.23: 54.9 kB, 6.8 kB gzip
Bitbucket50.5 kB, 15.9 kB gzipbitbucket 2.12: 110.4 kB, 15.0 kB gzip

The existing clients cover every endpoint and return each forge's raw responses. forges covers fewer operations and returns the shared model, with capabilities, pagination and warnings. A provider is bundled whole: importing one forge leaves out the other forges, but it keeps the operations you don't call.

Size budgets

pnpm test:size runs scripts/bundle-size.ts. For each entry with a budget, the script bundles a one-line browser consumer with rolldown, the bundler that Vite uses. It measures the entry chunk minified and gzipped, and reports lazy chunks separately. Lazy chunks don't count toward the budget.

The script fails in two cases:

  • An entry is over its budget.
  • A Lite bundle contains a string that only the webhook code uses, such as x-hub-signature-256.

The script measures dist, so build first:

pnpm build
pnpm test:size

To print the sizes without failing, pass --report:

node scripts/bundle-size.ts --report
Copyright © 2026