Skip to content

Fix: TypeScript isolatedModules Errors (const enum, type-only imports)

FixDevs ·

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 typeexport { MyType } looks like a value export to a single-file transpiler. If MyType is a type, the transpiler would try to emit a broken runtime export. Use export type { MyType } to mark it as type-only.
  • Namespace declarationsdeclare namespace and ambient modules behave differently when files are isolated.
  • Non-module files — files without any import or export are 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 value

Fix 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 needed

Fix 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' keyword

In 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: true in tsconfig.json (TypeScript 5.0+) instead of isolatedModules. 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 script

Ambient 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/**/*.tsx

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 --version

Run tsc directly to see all errors before the bundler does:

npx tsc --noEmit --isolatedModules
# Shows all isolatedModules violations in your code

For related TypeScript issues, see Fix: TypeScript Cannot Find Module and Fix: TypeScript Path Alias Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles