Fix: TypeScript isolatedModules Errors (const enum, type-only imports)
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix TypeScript isolatedModules errors — why const enum fails with Babel and Vite, how to replace const enum, fix re-exported types, and configure isolatedModules correctly for your build tool.
The Error
Building or running TypeScript with Babel, Vite, esbuild, or SWC throws:
error TS1205: Re-exporting a type when 'isolatedModules' is enabled requires using 'export type'.Or:
error TS1294: This syntax is not allowed when 'isolatedModules' is enabled.Or at runtime with Babel/esbuild:
SyntaxError: The requested module './constants' does not provide an export named 'Direction'Or when using const enum:
error TS2748: Cannot access ambient const enums when 'isolatedModules' is enabled.Why This Happens
isolatedModules: true in tsconfig.json tells TypeScript that each file will be transpiled independently — without cross-file type information. This matches how Babel, esbuild, SWC, and Vite actually work: they strip types file-by-file without running the full TypeScript compiler. The flag does not change emit; it only adds compile-time diagnostics that flag patterns those single-file transpilers cannot handle correctly. Catching the error in tsc is much cheaper than discovering it as a broken bundle in production.
The single-file constraint breaks several TypeScript features that previously relied on the compiler seeing the whole program at once:
const enum— the compiler normally inlines const enum values across files. With isolatedModules, each file is transpiled independently, so the inliner cannot look up values from other files.- Re-exporting types without
export type—export { MyType }looks like a value export to a single-file transpiler. IfMyTypeis a type, the transpiler would try to emit a broken runtime export. Useexport type { MyType }to mark it as type-only. - Namespace declarations —
declare namespaceand ambient modules behave differently when files are isolated. - Non-module files — files without any
importorexportare treated as scripts, not modules, causing issues with global declarations.
The reason the error surfaces only with certain build tools is that tsc itself does whole-program emit and can fix up these patterns, but Babel, SWC, esbuild, and Vite cannot. So a project that compiles fine with tsc --build can fail loudly once you switch to a faster transpiler. That tradeoff — speed in exchange for stricter source rules — is the whole point of the flag. Treat the diagnostics as guardrails for staying compatible with any of the modern toolchains.
Fix 1: Replace const enum with Regular enum or Object
const enum inlines values at compile time — it does not exist at runtime. Babel and esbuild cannot inline values from other files:
Broken — const enum across files:
// constants.ts
export const enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
// component.ts
import { Direction } from './constants';
const dir = Direction.Up; // Babel sees: Direction.Up — but Direction is not a runtime valueFix A — use regular enum (simplest):
// constants.ts
export enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
// Regular enum exists at runtime — no inlining neededFix B — use a const object (preferred, zero runtime overhead):
// constants.ts
export const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const;
export type Direction = typeof Direction[keyof typeof Direction];
// Direction type = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
// Usage — identical to enum
import { Direction } from './constants';
const dir: Direction = Direction.Up; // 'UP'Fix C — use declare const enum only in .d.ts files (for library authors):
Ambient const enum in declaration files is allowed and does not affect runtime. But it is complex and generally not recommended for application code.
Fix 2: Fix Re-exported Type Errors
When you re-export a type without the type keyword, the transpiler cannot determine at build time whether the export is a value or a type:
Broken:
// types.ts
export interface User { id: number; name: string; }
export type UserId = number;
// index.ts
export { User, UserId } from './types'; // Error: use 'export type'Fixed — use export type:
// index.ts
export type { User, UserId } from './types'; // ✓ Explicitly type-only
// Or mixed — some values, some types
export { createUser } from './users'; // Value — no 'type' keyword
export type { User, UserId } from './types'; // Types — requires 'type' keywordIn import statements too:
// Broken — ambiguous import
import { User, createUser } from './users';
// Fixed — separate type imports
import { createUser } from './users';
import type { User } from './users';
// Or combined import with inline type annotation (TypeScript 4.5+)
import { createUser, type User } from './users';Pro Tip: Enable
verbatimModuleSyntax: trueintsconfig.json(TypeScript 5.0+) instead ofisolatedModules. It enforces the same rules but gives clearer error messages and is more explicit about what gets emitted.
Fix 3: Configure isolatedModules Correctly
// tsconfig.json
{
"compilerOptions": {
"isolatedModules": true,
// Required when using isolatedModules:
"module": "ESNext", // Or "CommonJS" — but not "None"
"moduleResolution": "Bundler", // Or "Node16" / "NodeNext"
// Recommended alongside isolatedModules:
"verbatimModuleSyntax": true, // TypeScript 5.0+ — stricter, clearer errors
"esModuleInterop": true,
"strict": true
}
}For Vite projects — use the Vite TypeScript template settings:
// tsconfig.json (Vite default)
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true, // Vite handles emit — tsc only type-checks
"strict": true
}
}For Next.js — isolatedModules is enabled automatically when using SWC:
Next.js uses SWC by default (Babel for older projects). You do not need to set isolatedModules manually — Next.js handles it. But your code must still follow isolatedModules rules.
Fix 4: Fix Namespace and Ambient Module Errors
Global namespace declarations in .d.ts files must be explicit:
Broken — namespace in a module file:
// globals.ts — has imports, so it is a module
import { something } from './something';
declare namespace MyApp { // Error with isolatedModules in some configurations
interface Config { ... }
}Fixed — use declare global in module files:
// globals.ts
import { something } from './something';
declare global {
interface Window {
myApp: { version: string };
}
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_KEY: string;
}
}
}
export {}; // Ensure this is treated as a module, not a scriptAmbient module declarations in .d.ts files (no import/export — script context):
// ambient.d.ts — no imports at top level
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}Fix 5: Fix Class Decorator Errors with isolatedModules
TypeScript decorators with metadata require emitDecoratorMetadata, which conflicts with isolatedModules in some setups:
// This combination causes issues with Babel:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true, // Requires full type info — incompatible with isolatedModules
"isolatedModules": true
}
}Fix — use babel-plugin-transform-typescript with metadata support:
npm install --save-dev @babel/plugin-transform-typescript babel-plugin-transform-typescript-metadata// babel.config.js
module.exports = {
plugins: [
'babel-plugin-transform-typescript-metadata',
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-transform-class-properties', { loose: true }],
],
};Or switch from Babel to SWC for decorator support:
npm install --save-dev @swc/core @swc/jest// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
}
}
}Fix 6: Auto-fix with ESLint and TypeScript
Enable the ESLint rule to catch and auto-fix missing type keywords before they reach the build:
npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// Error when type imports are missing 'type' keyword
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'separate-type-imports' }
],
'@typescript-eslint/consistent-type-exports': [
'error',
{ fixMixedExportsWithInlineTypeSpecifier: true }
],
},
};Auto-fix all files:
npx eslint --fix src/**/*.ts src/**/*.tsxIn Production: Incident Lens
An isolatedModules violation is a build-time failure, not a runtime one — which sounds harmless until the failing build is the one promoting a hotfix to production. The blast radius is “no deploys until fixed.” If the violation slipped past local because a developer ran tsc instead of the bundler, the first place it explodes is the CI build step. Every downstream environment is now stuck behind a red pipeline.
Detection. Make the CI build the authoritative gate. Run tsc --noEmit and the bundler build in the same workflow so the diagnostic surfaces before merge. Add a status check that blocks merge on a green build. If you also publish a library, run the build against your exports map so consumers do not get type-only files re-exported as runtime values.
Recovery. The fastest path back to green is correcting the re-export or import to the type-only form: export type { Foo } and import { type Foo } (or a separate import type). For const enum, swap to a const object as object literal or accept the runtime cost of a regular enum. Do not disable isolatedModules to unblock the deploy — the bundler still cannot handle the pattern, and you have just hidden the bug.
Prevention. Two layers work well together: enable verbatimModuleSyntax: true in TypeScript 5.0+ to make every type import explicit at the syntax level, and enforce @typescript-eslint/consistent-type-imports plus consistent-type-exports as errors. With both in place, the wrong-shape import or export is caught at the lint stage with auto-fix, long before the CI bundler trips. Pair that with a pre-commit hook (lint-staged plus eslint --fix) so the failure cannot reach the shared branch in the first place.
Still Not Working?
Check if skipLibCheck: true is suppressing errors. If you have skipLibCheck: true, isolatedModules errors in node_modules type declarations are skipped. But errors in your own code still surface.
Check the TypeScript version. Support for verbatimModuleSyntax, inline type imports (import { type Foo }), and improved isolatedModules diagnostics improved significantly in TypeScript 4.5–5.0. Upgrade if you are on an older version:
npm install --save-dev typescript@latest
npx tsc --versionRun tsc directly to see all errors before the bundler does:
npx tsc --noEmit --isolatedModules
# Shows all isolatedModules violations in your codeCheck for barrel files that re-export everything from index.ts. Barrel files multiply isolatedModules violations because every re-export must be classified as value or type. Convert them to explicit export type { ... } and export { ... } blocks. As a side effect, tree-shaking improves because bundlers can drop the type-only exports entirely.
Check .d.ts files in your own source tree. Ambient declarations in *.d.ts files at the root of your src/ directory are picked up by tsc but ignored by Vite and esbuild. If you rely on ambient types, also include them via tsconfig.json "types" or import them explicitly. Otherwise the same code that type-checks locally fails when the bundler runs.
Check for inline type aliases passed to decorators or class fields. With verbatimModuleSyntax, a type-only import used only in a decorator metadata position is correctly emitted as nothing — meaning Reflect-based DI frameworks (NestJS, TypeORM) lose the metadata. The fix is to import the value, not the type, when the symbol is referenced at runtime by a decorator.
For related TypeScript and tooling issues, see Fix: TypeScript Cannot Find Module, Fix: TypeScript Path Alias Not Working, Fix: esbuild Not Working, and Fix: Next.js Build Failed.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Clack Not Working — Prompts Not Displaying, Spinners Stuck, or Cancel Not Handled
How to fix @clack/prompts issues — interactive CLI prompts, spinners, multi-select, confirm dialogs, grouped tasks, cancellation handling, and building CLI tools with beautiful output.
Fix: Sharp Not Working — Installation Failing, Image Not Processing, or Build Errors on Deploy
How to fix Sharp image processing issues — native binary installation, resize and convert operations, Next.js image optimization, Docker setup, serverless deployment, and common platform errors.
Fix: Shiki Not Working — No Syntax Highlighting, Wrong Theme, or Build Errors
How to fix Shiki syntax highlighter issues — basic setup, theme configuration, custom languages, transformer plugins, Next.js and Astro integration, and bundle size optimization.
Fix: Rspack Not Working — Build Failing, Loaders Not Applying, or Dev Server Not Starting
How to fix Rspack issues — configuration migration from webpack, loader compatibility, CSS extraction, module federation, React Fast Refresh, and build performance tuning.