---
title: "Bundling"
description: "Keep forges small in a browser or server bundle, and learn how it behaves in a browser."
canonical_url: "https://forges.link/guides/bundling"
---
# Bundling

> Keep forges small in a browser or server bundle, and learn how it behaves in a browser.

When you ship `forges` to a browser, every kilobyte that you include is a kilobyte your visitors have to download. This guide shows how to keep the library small in a browser bundle, and describes the few ways in which it behaves differently in a browser than on a server. It also covers server builds that copy the library instead of bundling it.

## Import one forge

Each provider has its own subpath export. If you import from that subpath, your bundle only contains the forge that you actually use:

```ts
import { githubLite } from 'forges/github'
```

The root `forges` entry re-exports every provider. The package declares `sideEffects: false`, so a bundler can tree-shake the root entry and drop the providers you don't use. Even so, importing from a subpath is the more dependable choice, because it states which forge you depend on instead of leaving it to the bundler to find out.

## Use the Lite factory

A browser doesn't receive webhook deliveries, yet the code that verifies their signatures and translates their payloads is a sizeable part of every provider. To let you leave it out, every provider except Pushin.eu and Cloudflare Artifacts exports two factories, `<kind>()` and `<kind>Lite()`. Both build the same provider, but only `<kind>()` references the webhook handlers. A bundle that imports only `githubLite` therefore contains neither the signature verification nor the payload translation.

The only difference you will notice in use is that a Lite provider can't receive webhooks. Its `capabilities.sources.webhook` is `false`, and `webhooks.verify()` and `webhooks.ingest()` reject with `UnsupportedOperationError`. Everything else works as usual, including managing webhooks with `webhooks.create()` and `webhooks.list()`.

Pushin.eu and Cloudflare Artifacts don't have a Lite factory, because `pushin()` and `cloudflareArtifacts()` contain no webhook code to begin with.

## GraphQL on GitHub

The GitHub provider loads its GraphQL documents lazily, with a dynamic `import()` the first time it needs them. Your bundler turns them into a separate chunk, which the browser only downloads when your code uses a GraphQL operation. That includes discussions and their reactions, `threads.getMany()`, review threads, subscriptions, transfers and duplicate marking. `threads.list()` uses GraphQL as well, to read the checks of each pull request in a page. If your code only calls operations that use the REST API, or sends its own queries with `graphql()`, the chunk is never loaded.

An anonymous GitHub provider never uses GraphQL, because GitHub requires a credential for its GraphQL API.

## Redirects

On a server, the provider follows redirects itself, and it is strict about it. It refuses an API redirect to another origin, and when a download redirects to another origin, it drops every header from the request.

A browser works differently, because it doesn't tell `fetch` where a redirect leads. When a GET or HEAD request is redirected, for example because a GitHub repository was renamed, the provider sends the request again and lets the browser follow the redirect. From then on, the browser applies its own rules for headers: it drops `Authorization` when the redirect goes to another origin, and it keeps the other headers. The provider still rejects an API response that came from another origin, as well as a download that came over HTTP. A redirected POST, PATCH, PUT or DELETE rejects too, because sending it again could repeat the write.

If a download redirects to a CDN, as `ci.log()` does on GitHub, it only works in a browser when the CDN allows the origin of your page with CORS.

## Test modules

`forges/fake` and `forges/testing` are meant for tests, so keep them out of your production bundles.

Apart from that, `forges` is safe to bundle for a browser. The only place that uses a Node.js built-in module is `loadFixtures()` in `forges/testing`, which imports `node:fs` when you call it. No other entry imports one.

## Server builds

Many server builds leave dependencies out of the bundle. Instead, they trace the files that your server code imports and copy them as published into the `node_modules` of their output. Tree shaking doesn't apply to those files, so the advice above saves less:

- `forges/github` copies the GitHub provider, the core that every provider shares and the GraphQL documents, none of them minified. The other forges stay out.
- `githubLite()` saves nothing, because both factories and the webhook code are in the same file.

If the size of your server output matters, configure your build to bundle `forges` with your own code instead. The bundler then minifies it and leaves out what you don't use, as it does for a browser.

## Sizes

To give you an idea of the cost, 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:

<table>
<thead>
  <tr>
    <th>
      Forge
    </th>
    
    <th>
      <code>
        forges
      </code>
      
       (Lite)
    </th>
    
    <th>
      Existing client
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      GitHub
    </td>
    
    <td>
      116.8 kB, 36.2 kB gzip
    </td>
    
    <td>
      <code>
        @octokit/rest
      </code>
      
       22: 105.7 kB, 21.1 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      GitLab
    </td>
    
    <td>
      90.0 kB, 28.1 kB gzip
    </td>
    
    <td>
      <code>
        @gitbeaker/rest
      </code>
      
       43: 233.7 kB, 55.3 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      Gitea / Forgejo
    </td>
    
    <td>
      84.7 kB, 26.2 kB gzip
    </td>
    
    <td>
      <code>
        gitea-js
      </code>
      
       1.23: 54.9 kB, 6.8 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      Bitbucket
    </td>
    
    <td>
      66.9 kB, 21.4 kB gzip
    </td>
    
    <td>
      <code>
        bitbucket
      </code>
      
       2.12: 110.4 kB, 15.0 kB gzip
    </td>
  </tr>
</tbody>
</table>

The numbers aren't a like-for-like comparison, because the two sides do different things. The existing clients cover every endpoint and return each forge's raw responses. `forges` covers fewer operations, but it returns the shared data model, with capabilities, pagination and warnings. Keep in mind that a provider is bundled as a whole. Importing one forge leaves out the other forges, but it keeps the operations of that forge that you never call.


## Sitemap

See the full [sitemap](https://forges.link/sitemap.md) for all pages.
