How ego-lite's Build System Produces Both Per-File dist and Rollup Bundles

ego-lite uses a two-stage Node.js build script that first transpiles individual TypeScript files with esbuild, then bundles the CLI entry point with Rollup to generate both granular modules and a single executable artifact.

The ego-browser package in the citrolabs/ego-lite repository implements a hybrid build pipeline designed for flexibility. Developers get fine-grained per-file outputs for testing and library consumption, while end users receive a compact, single-file CLI bundle. This dual-output strategy is orchestrated entirely within scripts/build.mjs.

The Two-Stage Build Architecture

The build system in ego-browser follows a deliberate sequence: non-bundled transpilation first, bundled artifact second. This ensures source maps remain accurate for individual files while the final CLI remains lean and self-contained.

Stage 1: Per-File esbuild Transpilation

The script begins by invoking esbuild.build() with bundle: false (lines 58-64 of scripts/build.mjs). This configuration is critical—it instructs esbuild to treat each .ts file as an independent compilation unit rather than resolving imports into a single output.

Key configuration parameters include:

  • outdir: "dist" — sets the output root directory
  • outbase: "." — preserves the original directory structure relative to the project root
  • bundle: false — disables bundling to maintain file-to-file correspondence

The result is a dist/ directory that mirrors your source layout exactly. Files from scripts/ and src/ appear as individual .js files, enabling:


# Import specific modules for unit testing

import { browserRuntime } from './dist/src/browser-runtime.js'

This per-file ego-lite build output supports incremental compilation and simplifies debugging since stack traces map directly to original source positions.

Stage 2: Rollup Bundle Generation

After esbuild completes, the script constructs a Rollup configuration (lines 68-82) targeting the main CLI entry point at src/index.ts. This stage produces the distributed executable.

The Rollup configuration treats Node.js built-ins as external (line 70) to avoid bundling core modules unnecessarily. Two plugins handle resolution and compilation:

  • @rollup/plugin-node-resolve (line 72) — resolves node_modules dependencies
  • @rollup/plugin-typescript (lines 73-80) — compiles TypeScript with project-specific settings

The bundle writes to dist/out/index.js with ESM format (format: "esm"), producing a single-file CLI artifact. This path is referenced directly in package.json (line 7) via the bin field:

{
  "bin": {
    "ego-browser": "./dist/out/index.js"
  }
}

Embedding Runtime Documentation

Following bundle generation, the script executes embedHelpDocs() (lines 96-119). This utility:

  1. Parses JSDoc comments from the Rollup output
  2. Extracts help text for CLI commands
  3. Injects the documentation into help-runtime.js

The embedded approach eliminates runtime file system reads for help text, keeping the CLI fully self-contained.

Resulting Build Artifacts

Artifact Tool Location Purpose
Per-file modules esbuild dist/**/*.js Library imports, testing, debugging
CLI bundle Rollup dist/out/index.js Executable command-line tool

Running the Build Locally

Execute the complete pipeline as defined in package.json:

npm run build   # triggers node scripts/build.mjs

The resulting structure:

dist/
├── src/
│   ├── helpers.js
│   ├── browser-runtime.js
│   └── index.js          # Also available standalone

├── scripts/
│   └── *.js              # Build utilities transpiled

└── out/
    └── index.js          # Bundled CLI for distribution

Using the Built CLI

The Rollup bundle executes directly without additional dependencies:

./dist/out/index.js <<'JS'
await ego.navigate('https://example.com')
await ego.click('button#submit')
JS

Key Implementation Files

File Role
scripts/build.mjs Orchestrates esbuild and Rollup stages
package.json Declares CLI entry point and npm scripts
tsconfig.json Shared TypeScript configuration
src/index.ts Rollup entry point for bundle generation
src/helpers.ts Helper context injected into user scripts

Summary

  • esbuild with bundle: false generates per-file dist/ outputs preserving source structure for modular consumption
  • Rollup with @rollup/plugin-typescript creates a single dist/out/index.js bundle optimized for CLI distribution
  • embedHelpDocs() inlines help documentation to keep the executable self-contained
  • The package.json bin field points exclusively to the Rollup bundle, while the esbuild outputs remain available for programmatic imports

Frequently Asked Questions

Why does ego-lite use two different bundlers instead of one?

esbuild handles fast, incremental transpilation of dozens of files efficiently, while Rollup produces superior tree-shaking and cleaner output for Node.js CLI distribution. The combination leverages each tool's strengths: esbuild for speed and granularity, Rollup for bundle optimization.

Can I import individual modules from the per-file dist output?

Yes. The esbuild-generated files in dist/src/ and dist/scripts/ are fully resolvable ESM modules. Import them directly for testing or extending functionality without pulling in the entire CLI bundle.

Is the Rollup bundle strictly required for the CLI to function?

Yes. The package.json bin entry points exclusively to dist/out/index.js. While you could theoretically execute dist/src/index.js, it would require manual dependency resolution since esbuild does not bundle node_modules. The Rollup step ensures all third-party imports are resolved and inlined.

What TypeScript configuration does ego-lite's build system use?

Both esbuild and Rollup reference tsconfig.json in the ego-browser package root. The @rollup/plugin-typescript plugin explicitly passes these settings through its configuration object (lines 73-80 of build.mjs), ensuring consistent compiler behavior across both build stages.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →