Pre-Migration tsconfig.json Sanitization

esbuild operates as a transpiler-only architecture and ignores TypeScript compiler directives. Passing tsc-specific flags directly triggers:

error TS5023: Unknown compiler option 'verbatimModuleSyntax' when passed to esbuild

To successfully migrate tsc to esbuild, decouple type-checking from transpilation. Strip verbatimModuleSyntax, isolatedModules, and declaration from your active configuration. Externalize baseUrl and paths resolution to the bundler’s resolver layer. For foundational compiler option decoupling strategies, reference TypeScript Configuration & Build Tooling.

Dual ESM/CJS Output Configuration

Generate parallel format artifacts using native CLI invocations. Executing mixed formats in a single pass produces:

error: Cannot use 'import' with a CommonJS output format

Run separate commands: --format=esm paired with --out-extension:.js=.mjs, and --format=cjs paired with --out-extension:.js=.cjs. Apply --platform=node for backend libraries to preserve Node.js built-ins and prevent polyfill injection. When contrasting esbuild’s AST-based transpilation speed against traditional bundler architectures for dual-format output, review Modern Build Tools: tsup, Rollup, and esbuild.

Path Alias & Monorepo Workspace Resolution

esbuild’s resolver does not parse tsconfig.json paths natively. Unmapped aliases halt compilation with:

error: Could not resolve '@internal/utils' (mark it as external to exclude it from the bundle)

Replicate mappings using --alias:@internal/utils=./packages/utils/src. Configure --external:@internal/* to prevent workspace dependencies from being inlined. Ensure package.json main/module fallbacks align with your output directories. Use --resolve-extensions=.ts,.tsx,.js,.json to bypass explicit extension requirements during resolution.

Strict Type-Checking Pipeline Separation

esbuild strips types and emits zero .d.ts artifacts. Relying on it for validation causes downstream consumers to fail with:

error TS6305: Output file has not been built from source file

Maintain zero-runtime type safety by running tsc --noEmit in CI pre-build hooks. Create a dedicated tsconfig.build.json with skipLibCheck: true to accelerate validation. Parallelize the type-checking and esbuild execution stages in your pipeline to eliminate bottlenecks without sacrificing compile-time guarantees.

package.json Exports & Dual-Package Hazard Mitigation

Consumers relying on conditional exports will fail if the manifest lacks explicit format routing:

ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath './dist/index' is not defined by 'exports'

Define exports with strict import (ESM) and require (CJS) conditions. Point types to the generated .d.ts output directory. Prevent circular resolution by marking peer dependencies as --external during the build phase.

Step-by-Step Execution

  1. Strip incompatible compiler flags from tsconfig.json Remove verbatimModuleSyntax, isolatedModules, and declaration from tsconfig.json. Create a separate tsconfig.build.json with:
{
  "compilerOptions": {
    "noEmit": true,
    "skipLibCheck": true
  }
}
  1. Configure esbuild for parallel ESM/CJS emission Execute distinct CLI commands for each target format:
npx esbuild src/index.ts --bundle --outdir=dist/esm --format=esm --out-extension:.js=.mjs --platform=node && npx esbuild src/index.ts --bundle --outdir=dist/cjs --format=cjs --out-extension:.js=.cjs --platform=node
  1. Generate declaration files via tsc --emitDeclarationOnly Run the type compiler strictly for .d.ts emission:
npx tsc --project tsconfig.build.json --emitDeclarationOnly --declarationDir dist/types
  1. Map conditional exports in package.json Route consumers explicitly to prevent dual-package hazards:
"exports": {
  ".": {
    "import": {
      "types": "./dist/types/index.d.ts",
      "default": "./dist/esm/index.mjs"
    },
    "require": {
      "types": "./dist/types/index.d.ts",
      "default": "./dist/cjs/index.cjs"
    }
  }
}