Analysis and Solutions for 'Cannot use import statement outside a module' Error in TypeScript

Nov 21, 2025 · Programming · 19 views · 7.8

Keywords: TypeScript | ES Modules | CommonJS | Node.js | Module System

Abstract: This article provides an in-depth analysis of the common 'Cannot use import statement outside a module' error in TypeScript projects. Starting from the principles of Node.js module systems, it explains the differences and compatibility issues between ES modules and CommonJS modules. Through comparison of different configuration schemes, it offers comprehensive solutions for TypeScript projects, including tsconfig.json configuration, package.json settings, and file extension strategies. The article also presents practical cases demonstrating how to choose appropriate module strategies in different scenarios to ensure proper execution of TypeScript code in Node.js environments.

Module System Fundamentals and Error Root Causes

The fundamental cause of the 'Cannot use import statement outside a module' error in Node.js environments lies in the module system recognition mechanism. Node.js treats .js files as CommonJS modules by default, while ES modules require specific configuration or file extensions for identification.

When the TypeScript compiler is configured to output CommonJS modules (module: 'commonjs') but the code uses ES module import syntax, this mismatch occurs. The compiled JavaScript files still contain import statements, which Node.js cannot properly parse without explicit identification as ES modules.

TypeScript Configuration Strategies

In tsconfig.json, the module option determines the module format output by the TypeScript compiler. When set to 'commonjs', the compiler transforms import/export statements into require/module.exports forms. However, mixing both module syntaxes in source code leads to problems.

Consider the following configuration example:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

This configuration ensures TypeScript outputs ES module format, consistent with the 'type': 'module' setting in package.json.

package.json Configuration Details

Adding the 'type' field to the package.json file in the project root directory is crucial for informing Node.js about ES module usage:

{
  "type": "module",
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc"
  }
}

This configuration treats all .js files as ES modules, but compatibility with TypeScript compilation output must be considered.

File Extension Strategies

TypeScript 4.5 introduced .mts and .cts extension support, providing finer control over module systems:

// math.mts - Always compiled as ES module
import { add } from './utils.mjs';
export function multiply(a: number, b: number): number {
  return a * b;
}

// utils.cts - Always compiled as CommonJS module
module.exports = {
  add: (a: number, b: number) => a + b
};

The compiler outputs .mjs and .cjs files accordingly, allowing Node.js to correctly identify module types based on extensions.

Mixed Module System Compatibility Solutions

Real-world projects often require simultaneous use of ES and CommonJS modules. Here are several compatibility solutions:

Importing CommonJS modules from ES modules:

// Import CommonJS in ES module
import cjsModule from './legacy.cjs';
import { namedExport } from './mixed.cjs';

Dynamically importing ES modules from CommonJS modules:

// Import ESM in CommonJS
async function loadESM() {
  const esmModule = await import('./modern.mjs');
  return esmModule.default;
}

TypeScript-Specific Tool Configuration

For development environments using ts-node, separate compiler options can be set in tsconfig.json:

{
  "ts-node": {
    "compilerOptions": {
      "module": "commonjs"
    }
  },
  "compilerOptions": {
    "module": "esnext",
    "target": "es2020"
  }
}

This configuration allows running CommonJS modules with ts-node during development while outputting ES modules during build.

Practical Case Analysis

Consider the code scenario from the original problem:

import { Class } from 'abc';
module.exports = {
  execute(a: Class, args: Array<string>) {
    // Implementation logic
  }
};

This mixes ES module import syntax with CommonJS export syntax. Solutions include:

Solution 1: Unified ES module syntax

import { Class } from 'abc';
export function execute(a: Class, args: string[]): void {
  // Implementation logic
}

Solution 2: Unified CommonJS syntax

const { Class } = require('abc');
module.exports = {
  execute(a: Class, args: string[]) {
    // Implementation logic
  }
};

Testing Environment Configuration

In Jest testing environments, ensure the test runner properly handles TypeScript and module systems. Refer to the configuration from the reference article:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  transform: {
    '^.+\\.ts$': 'ts-jest'
  },
  moduleFileExtensions: ['ts', 'js'],
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json'
    }
  }
};

This configuration ensures Jest correctly parses TypeScript files and corresponding module syntax.

Best Practices Summary

Choose appropriate module strategies based on project requirements: new projects recommend ES modules, while legacy projects may opt for gradual migration or maintain CommonJS based on实际情况. Key configuration points include: maintaining consistency between TypeScript configuration and Node.js environment, using explicit file extensions, avoiding mixed module syntax, and ensuring proper testing environment configuration.

Through systematic configuration and code standards, the 'Cannot use import statement outside a module' error can be effectively avoided, ensuring stable operation of TypeScript projects in Node.js environments.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.