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.
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.jsModule 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.jsRecommendations
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
