Skip to content

@presencelearning/angular-builders

Angular CLI builders that wrap @angular/build (esbuild) with automatic process.env replacement from .env files and shell environment variables.

Angular’s esbuild builder (@angular/build:application) supports a define option for compile-time replacements, but requires you to manually specify every process.env.KEY value. This builder automates that by:

  1. Scanning your environment TypeScript file for process.env.KEY and process.env['KEY'] references
  2. Resolving values from shell env vars → .env.local.env (in priority order)
  3. Merging with any static define entries in angular.json
  4. Falling back to undefined for unset variables so runtime defaults work
Terminal window
npm install --save-dev @presencelearning/angular-builders

Replace the default Angular builder in angular.json:

{
"projects": {
"my-app": {
"architect": {
"build": {
"builder": "@presencelearning/angular-builders:application",
"options": {
...
}
},
"serve": {
"builder": "@presencelearning/angular-builders:dev-server",
"options": {
...
}
}
}
}
}
}

The builder automatically detects which environment file is active via fileReplacements:

src/environments/environment.staging.ts
export const environment = {
apiUrl: process.env['API_URL'] || 'https://default-api.example.com',
featureFlag: process.env.FEATURE_FLAG === 'true',
};

Values are resolved with this precedence (highest first):

  1. Shell environment variablesAPI_URL=https://api.example.com ng build
  2. .env.local — Local overrides (git-ignored)
  3. .env — Shared defaults
  4. define in angular.json — Static build configuration
  5. undefined — Fallback (allows || defaults in your environment file)

You can still use the define option in angular.json for values that don’t come from env:

{
"configurations": {
"staging": {
"define": {
"process.env.FEATURE_FLAGS_STRATEGY": "\"local\""
}
}
}
}

Shell/.env values always take precedence over static defines.

@presencelearning/angular-builders:application

Section titled “@presencelearning/angular-builders:application”

Drop-in replacement for @angular/build:application. Accepts all the same options — the option schemas are generated at package build time from the installed @angular/build schemas, so they stay in step with the stock builders.

@presencelearning/angular-builders:dev-server

Section titled “@presencelearning/angular-builders:dev-server”

Drop-in replacement for @angular/build:dev-server. Injects env defines into the build target options before serving.

Requires 0.2.2 or later. Earlier versions shipped empty option schemas, so Architect never applied the stock defaults (watch, liveReload) and the dev server never rebuilt.

The builder picks the first fileReplacements entry whose replace path ends with environment.ts, falling back to src/environments/environment.ts. Two consequences worth knowing:

  • Only that one file is scanned. A key referenced only in the base environment.ts while a replacement is active does not get a define.
  • A replacement whose replace path does not end in environment.ts is ignored silently.

The env-scanning logic is also exported from the ./env subpath, for scripts and custom builders:

import {
buildDefines,
mergeDefines,
parseDotenv,
extractProcessEnvKeys,
} from '@presencelearning/angular-builders/env';

| Export | Description | | -------------------------------------------- | --------------------------------------------------------------------------------------- | | buildDefines(root, envTsFiles) | Returns the Record<string, string> of esbuild defines for the given environment files | | mergeDefines(dotenvDefines, staticDefines) | Merges dotenv-derived defines over static ones | | parseDotenv(filePath) | Parses a .env file into a plain object | | extractProcessEnvKeys(filePath) | Extracts process.env.KEY / process.env['KEY'] references from a TypeScript file |

Define keys are always emitted in dot notation (process.env.KEY) because esbuild rejects bracket-notation define keys — esbuild still matches both forms in source. Keys may be mixed-case; the pattern is [A-Za-z_][A-Za-z0-9_]*.

Supports Angular 18, 19, and 20. Requires Node >=18.19.1 <20.0.0 || >=20.19.0.

| Angular Version | @angular/build | @angular-devkit/architect | | --------------- | ---------------- | --------------------------- | | 18.x | 18.x | 0.18xx.x | | 19.x | 19.x | 0.19xx.x | | 20.x | 20.x | 0.20xx.x |

The consuming Angular application must have @angular/build and @angular-devkit/architect installed (they come with any standard Angular CLI project). The builders resolve these from the host app’s node_modules at runtime.

Note: These are not declared as peerDependencies because GitHub Packages’ registry API does not support peerDependenciesMeta, causing npm to install the latest major version and create conflicts with the host app’s Angular version.


Source: packages/angular-builders