TypeScript 6.0 `--isolatedModules` Is Now the Default: What Every Build Pipeline Must Change
TypeScript 6.0's new isolatedModules default breaks const enums, namespaces, and type-only imports. Here's how to fix your build pipeline before deployment fails.
Most TypeScript build failures in 2026 stem from a single default change: --isolatedModules is now on by default in TypeScript 6.0. Teams running Babel, esbuild, or swc discovered this when builds that passed in 5.x started throwing hard errors on const enums, namespaces, and ambiguous re-exports. The failure mode is subtle but expensive—code that type-checks perfectly will crash at runtime because the transpiler cannot safely emit JavaScript without cross-file type information.
The compiler now enforces per-file compilation constraints that align with how modern transpilers actually work. This is not a regression. This is TypeScript admitting that the mental model developers used for years—"tsc validates everything, other tools just strip types"—was always incomplete. Transpilers operate on isolated modules. They see one file at a time. TypeScript's old defaults let you write code those tools cannot handle.
%% alt: Problem flowchart showing const enum usage leading to runtime crashes
flowchart LR
A("const enum definition") --> B("cross-file import")
B --> C("transpiler runs")
C --> D("inlined value missing")
D --> E("runtime crash")
style E stroke:#ef4444,fill:#450a0a,color:#fca5a5
The fix is straightforward: enable isolatedModules in your tsconfig.json and refactor the three problem patterns. The compiler will catch every violation at build time. Production deployments stop failing. Developer feedback loops tighten because the type checker now reports errors that previously surfaced only when swc or Babel processed the bundle.
%% alt: Solution flowchart showing const object usage leading to safe compilation
flowchart LR
A("const object definition") --> B("cross-file import")
B --> C("transpiler runs")
C --> D("value preserved")
D --> E("safe compilation")
style E stroke:#34d399,fill:#0b3b2e,color:#d1fae5
The implication here is that TypeScript 6.0 is forcing an architectural alignment. If your build pipeline uses anything other than tsc --noEmit for validation plus tsc for emit, you were already in isolatedModules mode—you just did not know it. Now the defaults match reality.
Key Takeaways
- TypeScript 6.0 enables
--isolatedModulesby default, breaking const enums, namespaces, and ambiguous type-only imports that were valid in 5.x. - Transpilers like Babel, esbuild, and swc operate per-file; they cannot inline const enums or resolve namespace merges without cross-file type information.
- The migration path is mechanical: replace const enums with const objects, convert namespaces to ES modules, and add explicit
typekeywords to disambiguate imports. - Build performance improves 30-50% in multi-file projects because the compiler can now parallelize module analysis without dependency graph traversal.
- Teams using
tscfor both validation and emit can safely disable isolatedModules if no transpiler sits in the pipeline, but this is increasingly rare in modern toolchains.
What isolatedModules Actually Enforces
The isolatedModules flag enforces a single constraint: every TypeScript file must be transformable to JavaScript without type information from other files. This constraint mirrors how fast transpilers work. They parse one file, strip type annotations, and emit JavaScript. They do not load imports. They do not build a type graph. They see one module in isolation.
%% alt: Compilation flow showing isolated module constraint
flowchart TD
A("source file") --> B("parse syntax")
B --> C("strip type annotations")
C --> D{"cross-file type needed?"}
D -->|"yes"| E("compilation error")
D -->|"no"| F("emit JavaScript")
style E stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
style F stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style C stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
The flag rejects three patterns. First: const enums. The compiler inlines const enum values at compile time. A transpiler cannot do this—it does not know the enum's numeric values without loading the definition file. Second: namespaces that merge declarations across files. The transpiler cannot resolve which export belongs to which namespace without cross-file analysis. Third: re-exports that mix types and values when the import source is ambiguous.
This matters because production builds that ignore these constraints ship broken JavaScript. The const enum becomes undefined at runtime. The namespace export throws a ReferenceError. The type-only import survives in the emitted code as a runtime dependency that does not exist. The type checker never caught these because it had full cross-file context. The transpiler does not.
The distinction is critical. TypeScript's type checker operates in two modes: full program analysis (what tsc does) and per-file validation (what --isolatedModules enforces). Most build pipelines split these responsibilities. The type checker validates correctness. A faster tool emits JavaScript. The flag ensures both tools see the same contract.
Breaking Changes: const enum, namespace, and Type-Only Imports
The first pattern that breaks: const enums. Developers use const enums for zero-runtime-cost constants. The compiler replaces every reference with the literal value. This optimization requires cross-file knowledge. A transpiler processing import { Color } from './constants' in isolation cannot determine that Color is a const enum, let alone what Color.Red evaluates to.
// constants.ts (old pattern - breaks with isolatedModules)
export const enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff
}
// app.ts
import { Color } from './constants';
const primary = Color.Red; // transpiler emits: const primary = Color.Red;
// Runtime crash: Color is not definedThe correct pattern: replace const enums with const objects or plain enums. The transpiler can emit the object literal without cross-file context. The runtime cost is negligible—modern JavaScript engines optimize frozen object access nearly as well as literals.
// constants.ts (new pattern - works with isolatedModules)
export const Color = {
Red: 0xff0000,
Green: 0x00ff00,
Blue: 0x0000ff
} as const;
// app.ts
import { Color } from './constants';
const primary = Color.Red; // transpiler emits: const primary = Color.Red;
// Runtime: works, Color is a real objectThe second breaking pattern: namespaces. TypeScript namespaces allow declaration merging across files. A transpiler cannot resolve these merges without building a module graph. The failure mode is subtle—exports appear to work in development but crash in production when the bundler tree-shakes the namespace object.
// models.ts (old pattern - breaks with isolatedModules)
export namespace User {
export interface Profile {
name: string;
}
}
// auth.ts
export namespace User {
export interface Credentials {
token: string;
}
}
// Transpiler cannot merge these without cross-file analysisThe correct pattern: use ES modules. Export interfaces and types directly. Let the module system handle namespacing through import paths.
// models/user-profile.ts (new pattern)
export interface UserProfile {
name: string;
}
// models/user-credentials.ts
export interface UserCredentials {
token: string;
}
// app.ts
import type { UserProfile } from './models/user-profile';
import type { UserCredentials } from './models/user-credentials';The third breaking pattern: ambiguous type-only imports. When you write import { Type } from './module', the transpiler cannot determine if Type is a type or a value without loading module.ts. If it is a type, the import must be stripped. If it is a value, it must remain. The ambiguity causes incorrect emit.
// Ambiguous - transpiler cannot decide
import { User } from './types';
// Explicit - transpiler knows to strip this
import type { User } from './types';
// Also explicit - preserves runtime import
import { createUser, type User } from './factory';Add the type keyword. The transpiler now knows the import is type-only and removes it during emit. No cross-file analysis required.
How This Affects Build Tools: Babel, esbuild, swc, and TSC
Each major TypeScript build tool handles isolatedModules differently because each tool has different compilation strategies. The constraint's impact depends on whether the tool operates per-file or builds a full program graph.
%% alt: Comparison of build tool compilation strategies
flowchart LR
subgraph Babel["Babel/swc/esbuild"]
B1("per-file transpilation")
B2("no type checking")
B3("fast parallel builds")
end
subgraph TSC["tsc"]
T1("full program analysis")
T2("complete type checking")
T3("slower sequential builds")
end
B1 --> B2
B2 --> B3
T1 --> T2
T2 --> T3
style B3 stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style T3 stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
Babel with @babel/preset-typescript operates in pure isolated mode. It strips type annotations without understanding them. Const enums fail silently—Babel emits Color.Red as-is and the code crashes at runtime. Enabling isolatedModules in TypeScript catches these violations before Babel runs. The build fails fast with a clear error instead of shipping broken JavaScript.
The esbuild TypeScript loader follows the same model. It parses TypeScript syntax and removes types. It does not type-check. It does not inline const enums. The isolatedModules flag protects esbuild users by moving const enum errors from runtime to compile time. Teams often run tsc --noEmit in CI to catch type errors while using esbuild for fast development builds. The flag ensures both tools agree on what code is valid.
swc operates identically. It transforms TypeScript to JavaScript per-file. Cross-file type features break. The isolatedModules constraint prevents developers from writing code swc cannot handle. This alignment matters because swc is now the default transpiler in Next.js, Turbopack, and many Rust-based build tools. When those tools adopt TypeScript 6.0's defaults, projects with const enums fail immediately instead of after deployment.
The outlier is tsc. TypeScript's own compiler builds a full program graph. It can inline const enums because it loads every file and resolves every import. A team using only tsc for both validation and emit could disable isolatedModules. The code would work. But this configuration is increasingly rare. Most pipelines split type-checking and transpilation. The moment you add Babel, esbuild, or swc, you are in isolated mode whether the flag is on or not.
The performance implication is significant. With isolatedModules enabled, the compiler can skip dependency resolution for emit. Each file transforms independently. This enables parallel compilation. On large codebases, build times drop 30-50% because the compiler no longer waits for the full type graph before emitting JavaScript. The constraint that seemed like a limitation is actually an optimization.
Migration Checklist: Fixing Your Build Pipeline
The migration process is mechanical. TypeScript 6.0 will report every violation once isolatedModules is enabled. The errors are clear. The fixes are deterministic.
%% alt: Migration workflow from detection to validation
flowchart LR
A("enable isolatedModules") --> B("run tsc")
B --> C("fix const enums")
C --> D("convert namespaces")
D --> E("add type keywords")
E --> F("validate in CI")
style F stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style C stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
Step one: add "isolatedModules": true to your tsconfig.json. Run tsc. The compiler will emit errors for every const enum, namespace, and ambiguous import. Do not fix anything yet. Collect the full list of violations. This gives you scope. A codebase with five const enums migrates in an hour. A codebase with fifty requires coordination across teams.
Step two: replace const enums with const objects. The pattern is identical for every occurrence. Change const enum to const and add as const. The type safety is identical. The runtime cost is one object allocation per module. Modern JavaScript engines inline frozen object property access. The performance difference is unmeasurable in production.
Step three: convert namespaces to ES modules. Create one file per namespace. Export each member directly. Update imports to use the new module paths. This step is more invasive than const enum replacement but the benefit is immediate: tree-shaking works correctly. Bundlers can now remove unused exports because they operate on module boundaries, not namespace properties.
Step four: add explicit type keywords to imports. The compiler will flag every ambiguous import. The fix is one word: import type { ... } or import { type Foo, bar }. This change has zero runtime impact but clarifies intent. Code reviewers can now see which imports are type-only without reading the import source.
Step five: validate the migration in CI. Add tsc --noEmit to your continuous integration pipeline if it is not already there. This catches isolatedModules violations before they reach production. The type checker runs on every commit. Developers get feedback in seconds. The build never ships code that will crash in transpilation.
The common failure mode during migration: partial fixes. A developer changes const enums in one module but misses imports in another. The build passes locally because their transpiler does not enforce the constraint. CI catches it. The fix is trivial but the delay is expensive. Enable isolatedModules in your local tsconfig.json immediately. Let the compiler catch violations in your editor, not in CI.
Performance Wins and When to Disable It
The performance benefit of isolatedModules comes from eliminating dependency analysis during emit. When the compiler knows each file transforms independently, it can parallelize the work. A 500-file codebase that took 12 seconds to compile now takes 7 seconds. The 40% improvement comes from CPU saturation—all cores emit JavaScript simultaneously instead of waiting for the type graph.
%% alt: Performance comparison showing parallel vs sequential compilation
flowchart LR
A("source files") --> B{"isolatedModules?"}
B -->|"enabled"| C("parallel emit")
B -->|"disabled"| D("sequential emit")
C --> E("7 second build")
D --> F("12 second build")
style C stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
style E stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style F stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
The constraint also improves incremental builds. When one file changes, the compiler only re-emits that file. It does not recompute the type graph. It does not re-emit imports. This optimization is critical for watch mode during development. File save to visible change drops from 500ms to 100ms. The feedback loop tightens. Developers iterate faster.
The tradeoff is clear: you lose cross-file type features in exchange for compilation speed. For most codebases, this is a net win. Const enums were always a micro-optimization. Namespaces were a legacy pattern from before ES modules. Type-only imports should have been explicit from the start. The features you lose are features you should not have been using.
The scenario where disabling isolatedModules makes sense: a pure TypeScript codebase using tsc for both validation and emit with no transpiler in the pipeline. This configuration is rare. It exists in Node.js backends that run TypeScript directly via ts-node or tsx in production. In this case, tsc has full program context. It can safely inline const enums. The code never touches a transpiler.
Even in this scenario, consider leaving isolatedModules enabled. The constraint protects future migrations. When you eventually add a bundler or switch to a faster transpiler, the code is already compliant. The performance cost of full program analysis is real. A 100-file backend with isolatedModules disabled might compile in 3 seconds. With it enabled, 2 seconds. The 33% improvement compounds over hundreds of daily builds.
The hard rule: if any part of your build pipeline uses Babel, esbuild, swc, or any tool that transforms TypeScript per-file, enable isolatedModules. Your code is already subject to the constraint. The flag just makes violations visible at compile time instead of runtime.
Future-Proofing Your TypeScript Configuration
TypeScript 6.0's isolatedModules default is part of a broader trend: aligning TypeScript's compilation model with how modern JavaScript tooling actually works. The ecosystem moved to per-file transpilation years ago. TypeScript's defaults are finally catching up. This alignment reduces surprises. The type checker and the transpiler now agree on what code is valid.
The pattern here extends beyond isolatedModules. TypeScript 6.0 also defaults to stricter module resolution. It enforces explicit file extensions in imports when using moduleResolution: bundler. It deprecates legacy module formats. The theme is consistent: TypeScript is removing ambiguities that caused production failures. The compiler is becoming more opinionated. The opinions match industry best practices.
For teams maintaining large TypeScript codebases, the strategy is clear: adopt the strict defaults early. Enable isolatedModules, strict, and exactOptionalPropertyTypes. Let the compiler catch violations now instead of during the next major version upgrade. The migration cost is lower when you control the timing. Waiting until TypeScript 7.0 forces another breaking change doubles the work.
The investment in isolatedModules compliance pays dividends immediately. Build times drop. CI pipelines run faster. Developers get quicker feedback. The code becomes more portable—any transpiler can handle it. The architecture becomes clearer because implicit cross-file dependencies are now explicit imports. The pattern that seemed like a constraint is actually a forcing function for better design.
That covers the essential patterns for migrating to TypeScript 6.0's isolatedModules default. Apply these changes to your build pipeline and the difference will be immediate: faster builds, clearer errors, and production deployments that do not fail because a transpiler could not inline a const enum it never saw.
Frequently Asked Questions
Can I keep using const enums if I only use tsc for compilation?
Yes, but only if zero transpilers sit in your pipeline and you never plan to add one. The moment Babel, esbuild, or swc enters the build chain, const enums will break at runtime because those tools cannot inline values without cross-file type information.
Does isolatedModules affect type checking speed?
No, the flag only changes emit constraints and enables parallel JavaScript generation. Type checking speed depends on program structure and the --incremental flag, not on whether modules compile in isolation.
How do I find all const enums in a large codebase?
Run grep -r "const enum" --include="*.ts" . in your project root or use your IDE's search. TypeScript will also report every violation once you enable isolatedModules and run tsc.
Will enabling isolatedModules break third-party library imports?
No, the flag only affects your own code. Libraries compiled to JavaScript are already past the transpilation stage. Libraries that ship TypeScript source must also comply with isolatedModules if they target modern transpilers.
What happens to declaration files (.d.ts) with isolatedModules enabled?
Declaration emit is unaffected because .d.ts files describe types, not runtime behavior. The compiler can generate declarations regardless of isolatedModules settings since declarations never contain const enum values or namespace implementations.