Course contentsShow
← Node.js
Lesson 141 of 1,9802. Module Systems and Environment ConfigurationFree lesson

Handling Native Addons in ESM

Manage the complexities of loading C++ native bindings within modern ECMAScript module projects.

In this lesson, you will learn how to bridge the gap between high-performance C++ native addons and the modern ECMAScript Module (ESM) environment.

Why Native Addons and ESM Conflict

Native addons are compiled C++ code (usually .node files) that Node.js loads to perform heavy computation. In the older CommonJS system, loading these files was simple because the require() function was synchronous and handled file resolution automatically.

In an ESM environment, things change. ESM uses import statements, which are designed to be asynchronous and static. Because native addons rely on the older, synchronous dlopen mechanism to load binary code into memory, they don’t always "speak the same language" as the modern ESM loader.

The Solution: Using createRequire

To load a native addon in your ESM project without running into resolution errors, you should use the createRequire utility from the node:module built-in module. This allows you to "bring back" the familiar require function specifically for the purpose of loading your binary files.

Here is how you implement it:

import { createRequire } from 'node:module';

// Create a local require function scoped to the current file
const require = createRequire(import.meta.url);

// Now you can load your native addon as you did in CommonJS
const myNativeAddon = require('./build/Release/my-addon.node');

// Use your high-performance methods
myNativeAddon.performHeavyComputation();

Why this approach works

By using createRequire(import.meta.url), you are creating a bridge. You keep your project's architecture clean using ESM import statements for your JavaScript logic, but you delegate the responsibility of loading the binary .node file to the underlying CommonJS loader.

This avoids "module not found" errors that often occur when the ESM loader tries to parse a binary file as if it were a text-based JavaScript module. It ensures that your high-performance C++ logic remains accessible while your codebase follows modern standard-compliant syntax.

Key Takeaway: Use createRequire from the node:module package to safely load legacy native .node binaries within your ESM-based application.