Fundamentals 20 min read

Fixing JS Module Errors: CommonJS vs ES6 and Circular Dependency Solutions

This article examines why JavaScript applications encounter runtime errors when using CommonJS or ES6 modules, explains the underlying module loading mechanisms, highlights pitfalls of circular dependencies, and provides practical solutions—including webpack plugins and code adjustments—to reliably resolve such issues.

ELab Team
ELab Team
ELab Team
Fixing JS Module Errors: CommonJS vs ES6 and Circular Dependency Solutions

Background

The online classroom platform provides a classroom SDK that encapsulates core capabilities. Business teams build user‑facing classroom apps on top of this SDK. During a major SDK change, a perplexing runtime error appeared in the app, consuming three days of debugging before a temporary fix was found.

The app compiled with TypeScript but threw errors at runtime. Two error scenarios were observed: one after installing the SDK normally (Figure 1) and another after linking the SDK with yarn link (Figure 2).

CommonJS vs ES6 Modules

CommonJS and ES6 (ECMAScript 6) modules differ in several ways. The following points summarize the key distinctions:

CommonJS modules are implemented by the JavaScript runtime, while ES6 modules rely on the JavaScript engine; ES6 modules are a language‑level feature.

CommonJS loads and executes modules synchronously; ES6 performs a pre‑loading (linking) phase before execution, with both phases using depth‑first traversal. Execution order differs: CommonJS runs parent → child → parent, whereas ES6 runs child → parent.

Improper circular references usually do not cause errors in CommonJS, but they often do in ES6.

The position of import/export statements affects execution results in CommonJS but not in ES6.

CommonJS Modules

In Node.js, CommonJS modules are loaded by cjs/loader.js, which uses a module wrapper. Browsers emulate this behavior via package‑manager runtimes.

Module Usage Errors

When a CommonJS module is misused, cjs/loader.js throws an error, for example:

// Node.js
internal/modules/cjs/loader.js:905
  throw err;

  ^

Error: Cannot find module './none_existed.js'
Require stack:
- /Users/wuliang/Documents/code/demo_module/index.js

Module Execution Order

CommonJS executes modules sequentially: encountering a require loads and runs the required module before returning to the caller.

Figure 3 illustrates a module A that depends on modules B and C, split into three sections A1, A2, A3.

Figure 4 shows the execution sequence: A1 → B → A2 → C → A3.

Module Circular References

During loading, Node creates module objects and caches them, allowing circular references to be resolved without immediate errors. Figure 5 shows modules A and B requiring each other, split into A1, A2, B1, B2.

Figure 6 displays the execution order: A1 → B1 → B2 → A2.

Improper Use Issues

If B2 accesses a variable exported by A2, the variable is undefined. While undefined does not usually cause a JS error, the location of require statements can affect execution results.

ES6 Modules

ES6 modules are implemented by the JavaScript engine, with an adaptation layer in the runtime. The engine handles linking and evaluation phases.

Module Usage Errors

Misusing an ES6 module triggers engine‑level errors, such as:

// Node.js error
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException

              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module

// Browser error
Uncaught SyntaxError: The requested module './child.js' does not provide an export named 'b'

Module Execution Order

ES6 modules have five states: unlinked, linking, linked, evaluating, evaluated. Linking creates module environments and binds imports, while evaluation runs the module code according to the ECMAScript specification.

Figure 7 shows the linking process for the earlier module graph, where child modules are initialized before parents.

Figure 8 illustrates the evaluation phase, also using depth‑first traversal, causing child modules to execute before parents.

Because imports are bound before evaluation, the position of import/export statements does not affect execution results. Example:

console.log(a) // prints a's value
import { a } from './child.js'

Module Circular References

During linking, sub‑modules are pre‑processed and executed first. Figure 9 shows the processing order for the earlier circular graph: preprocess B → preprocess A → execute B → execute A.

Improper Use Issues

Since child modules run before parents, accessing a parent’s exported variable from a child can cause a ReferenceError. Example code demonstrates this failure and a working asynchronous alternative.

// parent.js
import {} from './child.js';
export const parent = 'parent';

// child.js
import { parent } from './parent.js';
console.log(parent); // ReferenceError
// Asynchronous fix
import {} from './child.js';
export const parent = 'parent';

import { parent } from './parent.js';
setTimeout(() => { console.log(parent); }, 0); // prints 'parent'

Correcting Tutorial Claims

The book "ECMAScript 6 入门教程" lists three major differences between CommonJS and ES6 modules, which are partially inaccurate:

Both systems export references, not copies.

CommonJS loads at runtime; ES6 loads during a pre‑processing phase, but both may load files during execution.

CommonJS require is synchronous; ES6 import performs an early linking step, not true asynchronous loading.

Analysis of the Real Issue

With a deeper understanding of module mechanics, the original error is revisited.

Problem 1

In the first scenario (Figure 1), the app is bundled with webpack, producing a CommonJS bundle. Although CommonJS circular references usually do not throw, the undefined value from a circular import was passed to _inherits (Babel helper) and caused a runtime error, as did Object.create(undefined).

Problem 2

In the second scenario (Figure 2), the SDK is linked via yarn link. The SDK is built with Rollup, which places child module code before the parent. Because Babel ignores the symlinked path, the app consumes the raw ES6+ SDK, leading to a JS‑engine error when a child module accesses an uninitialized parent export.

Solution

The root cause is circular dependencies. To locate them in large codebases, the circular-dependency-plugin for webpack can be used.

Webpack Plugin

The plugin scans modules during the optimizeModules hook, recursively following dependencies and reporting cycles when a module’s debugId matches an ancestor.

Detecting and Cutting Circular References

After adding the plugin, the build reports 112 circular chains. By matching stack traces to reported chains and incrementally breaking the offending cycles, the errors were resolved after removing two specific cycles. An example detection output:

Circular dependency detected:

node_modules/@byted-classroom/room/lib/service/assist/stream-validator.js ->
node_modules/@byted-classroom/room/lib/service/rtc/engine.js ->
node_modules/@byted-classroom/room/lib/service/rtc/definitions.js ->
node_modules/@byted-classroom/room/lib/service/rtc/base.js ->
node_modules/@byted-classroom/room/lib/service/monitor/index.js ->
node_modules/@byted-classroom/room/lib/service/monitor/monitors.js ->
node_modules/@byted-classroom/room/lib/service/monitor/room.js ->
node_modules/@byted-classroom/room/lib/service/npy-courseware/student-courseware.js ->
node_modules/@byted-classroom/room/lib/service/index.js ->
node_modules/@byted-classroom/room/lib/service/audio-mixing/index.js ->
node_modules/@byted-classroom/room/lib/service/audio-mixing/mixing-player.js ->
node_modules/@byted-classroom/room/lib/index.js ->
node_modules/@byted-classroom/room/lib/room/base.js ->
node_modules/@byted-classroom/room/lib/service/rtc/manager.js ->
node_modules/@byted-classroom/room/lib/service/assist/stream-validator.js

Recommendations

Circular imports are common in TypeScript projects. Integrate detection tools such as circular-dependency-plugin and ESLint’s import/no-cycle rule to catch and refactor problematic dependencies early.

Conclusion

The article started from a concrete runtime error, delved into the intricacies of CommonJS and ES6 module systems, corrected misconceptions from existing tutorials, and offered concrete tooling and workflow advice to identify and eliminate circular module dependencies.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

webpackCommonJScircular-dependencyModuleses6
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.