The Evolution and Solutions for ES6 Module Imports in Node.js: From SyntaxError to Stable Support

Oct 31, 2025 · Programming · 19 views · 7.8

Keywords: Node.js | ES6 Modules | SyntaxError | import Syntax | Module System

Abstract: This article provides an in-depth exploration of the development history of ES6 module import syntax in Node.js, analyzing the causes and solutions for the SyntaxError: Unexpected token import error across different versions. It details the evolution from experimental features to stable support in Node.js, comparing the differences between require and import, explaining the roles of .mjs extensions and package.json configurations, and offering comprehensive migration guidance from Node v5.6.0 to modern versions. The article also examines compatibility issues and resolution strategies in global installations, TypeScript environments, and various deployment scenarios through practical case studies.

The Development History of ES6 Module Imports in Node.js

In the JavaScript ecosystem, the introduction of ES6 module system represents a significant advancement in modular programming. However, the adoption of ES6 modules in Node.js environment has undergone a gradual process. Early Node.js versions were primarily based on the CommonJS module system, using the require function for module imports. When developers attempted to use ES6 import syntax in Node.js, they frequently encountered the SyntaxError: Unexpected token import error, indicating insufficient runtime support for the new syntax.

Root Cause Analysis

In early versions like Node.js v5.6.0, the JavaScript engine lacked native support for ES6 module syntax. When the parser encountered the import keyword, it threw a syntax error because this construct was outside the supported grammar at that time. The error stack trace shows failures in vm.js and module.js, indicating issues during the module compilation phase.

Consider this typical error scenario code example:

// Causes SyntaxError in early Node.js versions
import express from 'express';

In contrast, traditional CommonJS syntax executes normally:

// Syntax compatible with all Node.js versions
const express = require('express');

Module Support Evolution Across Node.js Versions

Node.js 9.x Series

Node.js 9.6.0 first introduced experimental support for ES modules, but required specific conditions. Developers had to use .mjs as the file extension for modules and enable the feature through the --experimental-modules flag.

// Save file as app.mjs
import express from 'express';

// Execution command
node --experimental-modules app.mjs

Node.js 12.x Series

Node.js 12 further improved ES module support, providing two activation methods. In addition to continuing support for .mjs extensions, developers could configure "type": "module" in package.json. However, this version still required the --experimental-modules flag.

// package.json configuration
{
  "type": "module",
  "name": "my-project",
  "version": "1.0.0"
}

// Or use .mjs extension
// Execution command
node --experimental-modules index.js

Node.js 13+ Versions

Starting from Node.js 13.2.0, ES module support reached stability. The --experimental-modules flag was no longer required, and developers could freely choose between using .mjs extensions or setting "type": "module" in package.json. This change marked the formal maturation of ES modules in the Node.js ecosystem.

// Method 1: Use .mjs extension
// app.mjs
import express from 'express';

// Method 2: package.json configuration
// package.json
{
  "type": "module"
}
// app.js
import express from 'express';

// Normal execution
node app.js

Compatibility Issues in Practical Applications

Global Installation Scenarios

When using ES modules in globally installed packages, environment configuration issues may arise. As shown in Reference Article 1, when installing packages via yarn global add or yarn create, Node.js might fail to correctly identify module types, resulting in SyntaxError: Unexpected token import errors.

Solutions include ensuring proper configuration of global packages:

// Ensure global package's package.json contains correct configuration
{
  "type": "module",
  "bin": {
    "my-cli": "./src/index.js"
  }
}

TypeScript Environments

Module resolution becomes more complex when using ts-node in TypeScript projects. It's essential to align TypeScript configuration with Node.js's module system:

// tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "node",
    "target": "ES2020"
  }
}

Import Assertion Problems

As described in Reference Article 2, when using import assertions, SyntaxError: Unexpected token 'with' errors may occur. This is caused by compatibility issues between specific versions of the SWC compiler and Node.js's ES module loader.

Alternative approach using dynamic imports:

// Problematic syntax
import bcd from "@mdn/browser-compat-data" assert { type: "json" };

// Viable alternative
const { default: bcd } = await import("@mdn/browser-compat-data", {
  assert: { type: "json" }
});

Migration Strategies and Best Practices

Version Upgrade Path

For projects using older Node.js versions, the recommended upgrade path is:

  1. First upgrade to Node.js 12.x with experimental module support
  2. Then migrate to Node.js 14.x or 16.x for more stable ES module support
  3. Finally upgrade to Node.js 18+ versions for complete ES module ecosystem

Mixed Module Environments

During transition periods, projects may contain both CommonJS and ES modules. Node.js provides interoperability support:

// Import CommonJS modules in ES modules
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const legacyModule = require('./legacy-module.cjs');

// Use ES modules in CommonJS modules (requires dynamic import)
const dynamicImport = async () => {
  const esModule = await import('./modern-module.mjs');
  return esModule;
};

Deployment Environment Considerations

As shown in Reference Article 3, different deployment environments (like Glitch) may have specific limitations on Node.js versions and module systems. Before deployment, you should:

Toolchain Configuration

Babel Transpilation Solution

For projects requiring backward compatibility, Babel can transpile ES6+ code to versions compatible with older Node.js:

// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }]
  ]
}

// Run with babel-node
npx babel-node script.js

Build Tool Integration

Modern build tools like Webpack, Rollup, and Vite provide excellent support for ES modules, handling module transformation and bundling during the build process.

Future Outlook

With the continuous evolution of ECMAScript standards and ongoing development of Node.js, ES modules are becoming the standard for JavaScript module systems. The Node.js team continues to improve module loading performance, enhance alignment with web standards, and simplify developer experience. For new projects, directly adopting the ES module system is recommended, while for existing projects, developing a reasonable migration plan is crucial.

By understanding the development history and technical details of ES module support in Node.js, developers can better plan project architecture, avoid common compatibility issues, and fully leverage the advantages of the modern JavaScript ecosystem.

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.