# @angular-builders/custom-esbuild

> Custom esbuild builders for Angular build facade. Allow to modify Angular build configuration without ejecting it

Latest version **22.0.1** (published 2026-06-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install @angular-builders/custom-esbuild
pnpm add @angular-builders/custom-esbuild
yarn add @angular-builders/custom-esbuild
bun add @angular-builders/custom-esbuild
```

## Health

**Score 65/100 (B)** — status: active.

Positive: has types; no vulnerabilities; has provenance; high maintenance score; high quality score.

Warnings: low downloads; no esm support.

## Facts

| | |
|---|---|
| Version | 22.0.1 |
| Published | 2026-06-23 |
| First published | 2024-01-31 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Node | ^20.19.0 \|\| ^22.12.0 \|\| >=24.0.0 |
| Dependencies | 6 |
| Unpacked size | 97.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 1162 |
| Author | JeB Barabanov |
| Maintainers | justjeb |
| Keywords | cli, angular, builder, angular6, angular-cli, esbuild, custom |

## Links

- npm: https://www.npmjs.com/package/@angular-builders/custom-esbuild
- Repository: https://github.com/just-jeb/angular-builders
- Homepage: https://github.com/just-jeb/angular-builders#readme
- Issues: https://github.com/just-jeb/angular-builders/issues
- npm.io page: https://npm.io/package/@angular-builders/custom-esbuild

## Dependencies (6)

- [@angular/build](https://npm.io/package/@angular/build.md) ^22.0.0
- [@schematics/angular](https://npm.io/package/@schematics/angular.md) ^22.0.0
- [@angular-devkit/core](https://npm.io/package/@angular-devkit/core.md) ^22.0.0
- [@angular-builders/common](https://npm.io/package/@angular-builders/common.md) 6.0.1
- [@angular-devkit/architect](https://npm.io/package/@angular-devkit/architect.md) >=0.2200.0 < 0.2300.0
- [@angular-devkit/schematics](https://npm.io/package/@angular-devkit/schematics.md) ^22.0.0

## Alternatives

- [@salesforce/cli](https://npm.io/package/@salesforce/cli.md) — 389.7K weekly downloads
- [@mintlify/cli](https://npm.io/package/@mintlify/cli.md) — 208.9K weekly downloads
- [@grafana/e2e-selectors](https://npm.io/package/@grafana/e2e-selectors.md) — 128.7K weekly downloads
- [mintlify](https://npm.io/package/mintlify.md) — 112.0K weekly downloads
- [@intlayer/cli](https://npm.io/package/@intlayer/cli.md) — 22.8K weekly downloads

## Recent versions

- 22.0.1 (latest) — 2026-06-23
- 22.0.2-beta.1 (next) — 2026-08-30
- 22.0.2-beta.0 — 2026-08-19
- 22.0.1-beta.1 — 2026-06-22
- 22.0.1-beta.0 — 2026-06-18
- 22.0.0 — 2026-06-10
- 22.0.0-beta.1 — 2026-06-09
- 22.0.0-beta.0 — 2026-06-09
- 21.1.0 — 2026-06-08
- 21.1.0-beta.16 — 2026-06-05
- 21.1.0-beta.15 — 2026-06-04
- 21.1.0-beta.14 — 2026-06-01
- 21.1.0-beta.13 — 2026-06-01
- 21.1.0-beta.12 — 2026-05-09
- 21.1.0-beta.11 — 2026-05-08
- … 47 more at https://npm.io/package/@angular-builders/custom-esbuild/versions

## README

# Custom ESBuild [builders](#builders) for Angular build facade

[![npm version](https://img.shields.io/npm/v/@angular-builders/custom-esbuild.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/custom-esbuild/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/custom-esbuild.svg)](https://www.npmjs.com/package/@angular-builders/custom-esbuild)

Allow customizing ESBuild configuration

# Table of Contents

- [Usage](#usage)
  - [For Example](#for-example)
- [Builders](#builders)
  - [Custom ESBuild `application`](#custom-esbuild-application)
    - [Example](#example)
  - [Custom ESBuild `dev-server`](#custom-esbuild-dev-server)
    - [Example](#example)
  - [Custom ESBuild `unit-test`](#custom-esbuild-unit-test)
    - [Example](#example-1)
- [Index Transform](#index-transform)
  - [Example](#example-2)
- [ES Modules (ESM) Support](#es-modules-esm-support)

# This documentation is for the latest major version only

> ⚠️ **Version alignment:** The major version of `@angular-builders/custom-esbuild` must match the major version of `@angular/core` in your project. For example, Angular 19 requires `@angular-builders/custom-esbuild`@19.x, Angular 20 requires `@angular-builders/custom-esbuild`@20.x, etc. Using a mismatched version is the most common source of issues.

## Previous versions

<details>
  <summary>Click to expand</summary>

- [Version 21](https://github.com/just-jeb/angular-builders/blob/21.x.x/packages/custom-esbuild/README.md)
- [Version 20](https://github.com/just-jeb/angular-builders/blob/20.x.x/packages/custom-esbuild/README.md)
- [Version 19](https://github.com/just-jeb/angular-builders/blob/19.x.x/packages/custom-esbuild/README.md)
- [Version 18](https://github.com/just-jeb/angular-builders/blob/18.x.x/packages/custom-esbuild/README.md)
- [Version 17](https://github.com/just-jeb/angular-builders/blob/17.x.x/packages/custom-esbuild/README.md)

</details>

## Prerequisites:

- [Angular CLI 22](https://www.npmjs.com/package/@angular/cli)

# Usage

1.  `npm i -D @angular-builders/custom-esbuild`
2.  In your `angular.json`:
    ```js
    "projects": {
      ...
      "[project]": {
        ...
        "architect": {
          ...
          "[architect-target]": {
            "builder": "@angular-builders/custom-esbuild:[application|dev-server|unit-test]",
            "options": {
              ...
            }
    ```
    Where:
    - [project] is the name of the project to which you want to add the builder
    - [architect-target] is the name of build target you want to run (build, serve, test etc. or any custom target)
    - [application|dev-server|unit-test] one of the supported builders - [application](#Custom-esbuild-application), [dev-server](#Custom-esbuild-dev-server), or [unit-test](#Custom-esbuild-unit-test)
3.  If `[architect-target]` is not one of the predefined targets (like build, serve, test etc.) then run it like this:  
    `ng run [project]:[architect-target]`  
    If it is one of the predefined targets, you can run it with `ng [architect-target]`

## For Example

- angular.json:
  ```js
  "projects": {
    ...
    "example-app": {
      ...
      "architect": {
        ...
        "build": {
          "builder": "@angular-builders/custom-esbuild:browser",
          "options": {
            ...
          }
  ```
- Run the build: `ng build`

# Builders

- [@angular-builders/custom-esbuild:application](#Custom-esbuild-application)
- [@angular-builders/custom-esbuild:dev-server](#Custom-esbuild-dev-server)
- [@angular-builders/custom-esbuild:unit-test](#Custom-esbuild-unit-test)

## Custom ESBuild `application`

The `@angular-builders/custom-esbuild:application` builder is an extension of the `@angular-devkit/build-angular:application` builder, allowing the specification of additional properties on top of the existing ones. The custom builder runs the original builder at the end, incorporating extra parameters specified in the extended configuration. It will also perform `index.html` transformations if specified.

Builder options:

- All the `@angular-devkit/build-angular:application` options
- `plugins`
- `indexHtmlTransformer`: [see below](#index-transform)

### Example

`angular.json`:

```js
"architect": {
  ...
  "build": {
    "builder": "@angular-builders/custom-esbuild:application",
    "options": {
      "plugins": ["./esbuild/plugins.ts", { "path": "./esbuild/define-env.ts", "options": { "stage": "development" } }],
      "indexHtmlTransformer": "./esbuild/index-html-transformer.js",
      "outputPath": "dist/my-cool-client",
      "index": "src/index.html",
      "browser": "src/main.ts",
      "polyfills": ["zone.js"],
      "tsConfig": "src/tsconfig.app.json"
    },
    "configurations": {
      "production": {
        "plugins": ["./esbuild/plugins.ts", { "path": "./esbuild/define-env.ts", "options": { "stage": "production" } }]
      }
    }
  }
```

In the above example, we specify the list of `plugins` that should implement the ESBuild plugin schema. These plugins are custom user plugins and are added to the original ESBuild Angular configuration. Additionally, the `indexHtmlTransformer` property is used to specify the path to the file that exports the function used to modify the `index.html`.

The plugin file can export either a single plugin, a list of plugins or a factory function that returns a plugin or list of plugins. If a plugin accepts configuration then the config should be provided in `angular.json`:

```ts
// esbuild/plugins.ts
import type { Plugin, PluginBuild } from 'esbuild';

const defineTextPlugin: Plugin = {
  name: 'define-text',
  setup(build: PluginBuild) {
    const options = build.initialOptions;
    options.define.buildText = JSON.stringify('This text is provided during the compilation');
  },
};

export default defineTextPlugin;
```

OR:

```ts
// esbuild/plugins.ts
import type { Plugin, PluginBuild } from 'esbuild';

function defineEnv(pluginOptions: { stage: string }): Plugin {
  return {
    name: 'define-env',
    setup(build: PluginBuild) {
      const buildOptions = build.initialOptions;
      buildOptions.define.stage = JSON.stringify(pluginOptions.stage);
    },
  };
}

export default defineEnv;
```

Or:

```ts
// esbuild/plugins.ts
import type { Plugin, PluginBuild } from 'esbuild';

const defineTextPlugin: Plugin = {
  name: 'define-text',
  setup(build: PluginBuild) {
    const options = build.initialOptions;
    options.define.buildText = JSON.stringify('This text is provided during the compilation');
  },
};

const updateExternalPlugin: Plugin = {
  name: 'update-external',
  setup(build: PluginBuild) {
    const options = build.initialOptions;
    options.external ??= [];
    options.external.push('elysia');
  },
};

export default [defineTextPlugin, updateExternalPlugin];
```

Or:

```ts
// esbuild/plugins.ts
import type { Plugin, PluginBuild } from 'esbuild';
import type { ApplicationBuilderOptions } from '@angular-devkit/build-angular';
import type { Target } from '@angular-devkit/architect';

export default (builderOptions: ApplicationBuilderOptions, target: Target): Plugin => {
  return {
    name: 'define-text',
    setup(build: PluginBuild) {
      const options = build.initialOptions;
      // target.project is the Angular project name (e.g. "my-app")
      options.define.currentProject = JSON.stringify(target.project);
      // target.configuration is the active build configuration (e.g. "production", "staging")
      options.define.currentConfiguration = JSON.stringify(target.configuration ?? 'default');
    },
  };
};
```

## Custom ESBuild `dev-server`

The `@angular-builders/custom-esbuild:dev-server` is an enhanced version of the `@angular-devkit/build-angular:dev-server` builder that allows the specification of `middlewares` (Vite's `Connect` functions). It also obtains `plugins` and `indexHtmlTransformer` from the `:application` configuration to run the Vite server with all the necessary configuration applied.

### Example

`angular.json`:

```js
"architect": {
  ...
  "build": {
    "builder": "@angular-builders/custom-esbuild:application",
    "options": {
      "plugins": ["./esbuild/plugin-1.js"]
      ...
    }
  },
  "serve": {
    "builder": "@angular-builders/custom-esbuild:dev-server",
    "options": {
      "middlewares": ["./esbuild/my-middleware.js"],
      "buildTarget": "my-project:build"
    }
  }
```

## Custom ESBuild `unit-test`

The `@angular-builders/custom-esbuild:unit-test` builder is an enhanced version of the `@angular/build:unit-test` builder that reuses your application ESBuild plugins during test execution. It reads the `plugins` from the referenced `:application` build target and runs the official unit test builder with those plugins applied.
There is no need to specify a `runner` option as the only supported test runner is Vitest.

### Example-1

`angular.json`:

```js
"architect": {
  ...
  "build": {
    "builder": "@angular-builders/custom-esbuild:application",
    "options": {
      "plugins": ["./esbuild/plugin-1.js"]
      ...
    }
  },
  "test": {
    "builder": "@angular-builders/custom-esbuild:unit-test",
    "options": {
      "buildTarget": "my-project:build",
      "tsConfig": "src/tsconfig.spec.json"
    }
  }
}
```

# Index Transform

Since Angular 8, `index.html` is not generated as part of the build. If you want to modify your `index.html`, you should use the `indexHtmlTransformer` option. `indexHtmlTransformer` is a path (relative to the workspace root) to a `.js` or `.ts` file that exports a transformation function for `index.html`. If `indexHtmlTransformer` is written in TypeScript, the application's `tsConfig` file will be used by `tsnode` for its execution:

```typescript
(indexHtmlContent: string, target: Target) => string | Promise<string>;
```

or, in other words, the function receives target options and original `index.html` content (generated by Angular CLI) and returns a new content as `string` or `Promise<string>`.

The `indexHtmlTransformer` function signature is defined [here](https://github.com/angular/angular-cli/blob/7cedcc815c2b4ccdb354a89959fa9f19dc08e9fa/packages/angular/build/src/utils/index-file/index-html-generator.ts#L47).

It is useful when you want to transform your `index.html` according to the build options.

## Example

`angular.json`:

```js
"architect": {
  ...
  "build": {
    "builder": "@angular-builders/custom-esbuild:application",
    "options": {
      "indexHtmlTransformer": "./esbuild/index-html-transformer.js"
      ...
    }
```

`index-html-transformer.js`:

```js
module.exports = (indexHtml, target) => {
  // target.configuration is the active build configuration (e.g. "production", "staging")
  // target.project is the Angular project name
  const i = indexHtml.indexOf('</body>');
  const content = `<p>Dynamically inserted content</p>`;
  return `${indexHtml.slice(0, i)}
            ${content}
            ${indexHtml.slice(i)}`;
};
```

Alternatively, using TypeScript:

```ts
import type { Target } from '@angular-devkit/architect';

export default (indexHtml: string, target: Target) => {
  // target.configuration is the active build configuration (e.g. "production", "staging")
  // target.project is the Angular project name
  const i = indexHtml.indexOf('</body>');
  const content = `<p>Dynamically inserted content</p>`;
  return `${indexHtml.slice(0, i)}
            ${content}
            ${indexHtml.slice(i)}`;
};
```

In the example we add a paragraph with an example content to your `index.html`. It is a very simple example without any asynchronous code but you can also return a `Promise` from this function.

# ES Modules (ESM) Support

Custom ESBuild builder fully supports ESM.

- If your app has `"type": "module"` both `plugin.js` and `index-html-transformer.js` will be treated as ES modules, unless you change their file extension to `.cjs`. In that case they'll be treated as CommonJS Modules. [Example](../../examples/custom-esbuild/sanity-esbuild-app-esm).
- For `"type": "commonjs"` (or unspecified type) both `plugin.js` and `index-html-transformer.js` will be treated as CommonJS modules unless you change their file extension to `.mjs`. In that case they'll be treated as ES Modules. [Example](../../examples/custom-esbuild/sanity-esbuild-app).
- **TypeScript plugins and transformers work in both CommonJS and ESM projects with no extra setup** — just point the builder at your `.ts` file. (Earlier versions required forcing a `ts-node/esm` loader through `NODE_OPTIONS`; that is no longer necessary.) TypeScript path aliases are supported in both module formats.

### Type-checking TypeScript plugins and transformers

`.ts` plugins and `indexHtmlTransformer` files are loaded with [jiti](https://github.com/unjs/jiti) and **transpiled, not type-checked**, at build time. Your editor still type-checks them as you write. To enforce type-checking in CI, add a dedicated tsconfig that includes them and run `tsc`:

`tsconfig.build-config.json`:

```jsonc
{
  "extends": "./tsconfig.json",
  "compilerOptions": { "noEmit": true },
  "include": ["plugins/**/*.ts", "index-html-transformer.ts"],
}
```

```bash
tsc --noEmit -p tsconfig.build-config.json
```

---
_Source: https://npm.io/package/@angular-builders/custom-esbuild · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
