---
title: "Bundling"
description: "Keep forges small in a browser or server bundle, and learn how it behaves in a browser."
canonical_url: "https://forgeslink-3mwmle35v-poetry.vercel.app/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

A bundler leaves out the providers that you don't import. The package declares `sideEffects: false`, so this works the same whether you import from the root entry or from a provider's own subpath, and the two bundles end up within a kilobyte of each other once gzipped:

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

The subpath makes a difference when your build copies the library instead of bundling it, as described in [Server builds](#server-builds).

## Links without a client

If you only need to read and build web URLs, or to read and write references such as `acme/widgets#42`, import the forges from `forges/web` instead. Each factory there takes the same `baseUrl` and `instance` as its provider and returns the web links of that forge, with no API client:

```ts
import { createForges, github, gitlab } from 'forges/web'

const forges = createForges([github(), gitlab({ baseUrl: 'https://gitlab.example.com' })])

forges.parseUrl('https://gitlab.example.com/acme/widgets/-/merge_requests/3')
forges.parseReferences('Fixes #12', { from: { forge: 'github', instance: 'github.com', owner: 'acme', name: 'widgets' } })
```

The forges you create this way have `parseUrl()`, `urlFor()`, `referenceFor()` and `parseReferences()`, and they read URLs exactly as the providers do. The GitHub links are about 3.4 kB gzipped. `createForges()` from `forges/web` also accepts providers, so you can mix the two.

## Webhook deliveries

A browser doesn't receive webhook deliveries, yet the code that translates their payloads is a sizeable part of every provider. Each provider loads it with a dynamic `import()` the first time `webhooks.ingest()` needs it. Your bundler turns it into a separate chunk, which the browser only downloads when your code ingests a delivery. The signature check and the list of event kinds in `webhooks.events` stay in the provider.

## 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 entry you import decides what gets copied:

- `forges` copies every provider, because the root entry imports all of them.
- `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.
- `forges/web` copies the web links of every forge and none of the API clients.

Import each provider from its subpath to keep the other forges out of your server output. If its size still 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 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>
    </th>
    
    <th>
      Existing client
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      GitHub
    </td>
    
    <td>
      139.3 kB, 42.3 kB gzip
    </td>
    
    <td>
      <code>
        @octokit/rest
      </code>
      
       22: 105.7 kB, 21.1 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      GitLab
    </td>
    
    <td>
      100.5 kB, 31.3 kB gzip
    </td>
    
    <td>
      <code>
        @gitbeaker/rest
      </code>
      
       43: 233.7 kB, 55.3 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      Gitea / Forgejo
    </td>
    
    <td>
      97.4 kB, 30.1 kB gzip
    </td>
    
    <td>
      <code>
        gitea-js
      </code>
      
       1.23: 54.9 kB, 6.8 kB gzip
    </td>
  </tr>
  
  <tr>
    <td>
      Bitbucket
    </td>
    
    <td>
      76.0 kB, 24.3 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://forgeslink-3mwmle35v-poetry.vercel.app/sitemap.md) for all pages.
