Fix: TypeScript isolatedModules Errors (const enum, type-only imports)
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.
This restriction breaks several TypeScript features that only work when the compiler sees the whole program:
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.
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/**/*.tsxStill 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 codeFor related TypeScript issues, see Fix: TypeScript Cannot Find Module and Fix: TypeScript Path Alias Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: ESLint import/no-unresolved Error (Module Exists but ESLint Can't Find It)
How to fix ESLint's import/no-unresolved errors when modules actually resolve correctly — configure eslint-import-resolver-typescript, fix path alias settings, and handle node_modules that ESLint cannot find.
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Next.js Middleware Not Running (middleware.ts Not Intercepting Requests)
How to fix Next.js middleware not executing — wrong file location, matcher config errors, middleware not intercepting API routes, and how to debug middleware execution in Next.js 13 and 14.
Fix: Vitest Setup Not Working (setupFiles, Mocks, and Global Config Issues)
How to fix Vitest configuration not taking effect — why setupFiles don't run, globals are undefined, mocks don't work, and how to configure Vitest correctly for React, Vue, and Node.js projects.