Diagnosing TypeScript isolatedModules Errors: const enum, export =, and import type Pitfalls
A diagnostic guide for TypeScript builds that succeed locally but fail in CI due to isolatedModules errors (TS1208, TS2669, TS1192, TS2589). Covers recognizable compiler messages, a cause table, ordered config and code checks, targeted fixes, and escalation criteria.
29 Nov 2025, 04:42 UTC

Recognizable Condition
A TypeScript build passes on a developer machine but fails in CI or after a stricter tsconfig.json is introduced. The compiler emits one of the following messages:
- TS1208:
cannot be used as a value because it was compiled with 'isolatedModules' - TS2669:
'export =' is not supported by 'isolatedModules' - TS1192:
Module '...' has no exported member '...'. Did you mean to use 'import type'? - TS2589:
Type instantiation is excessively deep and possibly infinite(often a side‑effect of missinglibtargets whenisolatedModulesis on)
These errors typically surface after adding a transpiler (esbuild, swc, Babel) or enabling project references, because those tools require isolatedModules: true to guarantee single‑file compilation.
Cause Diagnostic Table
| Error Code | Root Cause | Typical File Pattern |
|---|---|---|
| TS1208 | const enum used as a runtime value (e.g., in a switch, object key, or passed to a function) | const enum Status { Ok = 1 } referenced in non‑type position |
| TS2669 | Legacy export = / import = syntax (CommonJS‑style) which cannot be emitted per‑file | export = MyClass; or import MyClass = require('./MyClass'); |
| TS1192 | import type used but the imported symbol is later used as a value | import type { Foo } from './foo'; const x = Foo; |
| TS2589 | Missing or mismatched lib entries (e.g., ES2022 without DOM) combined with isolatedModules | tsconfig.json with "lib": ["ES2022"] in a DOM project |
Ordered Checks
- Diff the effective configs – Run
tsc --showConfig -p tsconfig.jsonlocally and in CI (or in the stricter config). CompareisolatedModules,composite,module,moduleResolution, andtargetvalues.# Local npx tsc --showConfig -p tsconfig.json > local-config.json # CI (if you can run the same command in the pipeline) npx tsc --showConfig -p tsconfig.ci.json > ci-config.json diff -u local-config.json ci-config.jsonRun from the repository root; requires read access to
tsconfig*.jsonand Node/npm installed. - Search for offending patterns – Use ripgrep or grep to locate the constructs that trigger the errors.
# const enum used as value rg -n "const enum" --ts # export = rg -n "export =" --ts # import type used as value rg -n "import type" --ts | rg -v "type"Run in the source directory (e.g.,
src/). No special permissions beyond file read. - Verify moduleResolution and target compatibility across project references – If you use
referencesintsconfig.json, ensure each referenced project has the samemoduleResolution(usuallynodeorbundler) and a compatibletarget. Mismatched settings cause isolatedModules to be implicitly enabled for the referenced project.# Example check cat tsconfig.json | jq '.references[] | .path' | xargs -I{} sh -c 'echo "=== {} ==="; cat {}/tsconfig.json | jq ".moduleResolution, .target"'Requires
jqand a Unix‑like shell.
Fixes Tied to Findings
TS1208 – Replace const enum
Option A: Change to a regular enum (emits a runtime object).
// Before
export const enum Status { Ok = 1, Fail = 2 }
// After
export enum Status { Ok = 1, Fail = 2 }
Option B: Inline a plain const object when you only need the values and want zero runtime overhead.
export const Status = { Ok: 1, Fail: 2 } as const;
// Usage stays the same: Status.Ok
Choose based on bundle‑size impact; regular enums add a small IIFE, while as const objects are fully erased by most bundlers.
TS2669 – Convert export = to ES‑module syntax
// Before (CommonJS style)
export = MyClass;
// After (ESM default export)
export default MyClass;
// Consumers change from:
import MyClass = require('./MyClass');
// to:
import MyClass from './MyClass';
If the codebase must support both CJS and ESM, add a dual‑export shim in a separate entry file rather than disabling isolatedModules.
TS1192 – Use a value import when the symbol is needed at runtime
// Before
import type { Foo } from './foo';
const x = Foo; // error
// After
import { Foo } from './foo';
const x = Foo;
If the import is only used for types, keep import type and remove the value usage.
TS2589 – Align lib and target
Add the missing library entries to the project that fails.
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"isolatedModules": true
}
}
Run tsc --noEmit after the change to confirm the error disappears.
Escalation Criteria
Escalate to a broader architectural review when:
- All config flags are aligned but the same error persists – indicates a custom transformer or a third‑party
.d.tsthat still emitsconst enumvalues. - Monorepo hoisting (npm/yarn workspaces, pnpm) causes a shared package to be compiled with a different
tsconfigthan the consumer. - Fixes would require changing public API signatures of a widely‑used library; consider versioning the change or providing a migration guide.
Verification Checklist
- Run
npx tsc --noEmit -p tsconfig.ci.json(or the exact CI config) locally. The error list must be empty. - Pick one file that previously triggered TS1208, apply the chosen fix, and re‑run the build. Confirm that specific error clears before rolling out the change to the whole codebase.
- Compare the effective config again with
tsc --showConfigto ensure no drift after the fix. - Run the full test suite (unit + integration) to catch runtime behavior changes from enum replacement.
Limitations
- Error codes and exact messages vary across TypeScript major versions (e.g., TS5.0 vs TS5.5). The table reflects the most common codes as of TS 5.x.
- Replacing
const enumwith a regular enum changes emitted JavaScript; verify bundle size if the enum is in a hot path. - Disabling
isolatedModulesglobally to silence errors is discouraged – downstream transpilers (esbuild, swc) rely on the guarantee that each file can be compiled independently.
Practical Result Check
After applying fixes, the definitive proof is a clean tsc --noEmit run against the exact CI configuration. Capture the exit code (should be 0) and ensure no new diagnostics appear. This single command validates that the isolatedModules constraints are satisfied across the whole project.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.