Webpack Node.js Addon Loader
node-loader is a Webpack loader designed to facilitate the bundling and usage of Node.js native add-ons (files with the `.node` extension) within Webpack-powered applications. It is particularly useful for projects targeting Node.js environments, including Electron's main and renderer processes. The current stable version is `2.1.0`, released in November 2024. The project generally follows an irregular release cadence, focusing on compatibility with newer Webpack and Node.js versions, as well as addressing specific issues like platform compatibility (e.g., macOS dlopen). Its primary differentiator is its specific focus on handling native Node.js modules, a niche that requires careful configuration within Webpack due to the binaries involved. It handles copying the native module to the output directory and ensures it can be dynamically loaded at runtime.
Common errors
-
Module parse failed: Unexpected character '�' (1:0)
cause Attempting to import a `.node` file without `node-loader` configured or the loader failing to process the file.fixEnsure you have a `module.rules` entry for `test: /\.node$/` with `loader: 'node-loader'` in your `webpack.config.js`. Also, verify `target: 'node'` and `node: { __dirname: false }` are set. -
Error: 'node-loader' can only be used with target 'node', 'async-node', 'electron-main', 'electron-renderer', or 'electron-preload'.
cause The Webpack `target` option is set to a non-Node.js compatible value (e.g., 'web', 'browserslist').fixChange `target` in `webpack.config.js` to `node`, `async-node`, `electron-main`, `electron-renderer`, or `electron-preload`. -
ReferenceError: __dirname is not defined in ES module scope
cause Webpack's default behavior for `__dirname` in Node.js environments (especially ESM) can interfere with native modules expecting standard Node.js global behavior.fixAdd `node: { __dirname: false, __filename: false }` to your `webpack.config.js` to prevent Webpack from polyfilling these globals. -
TypeError: The 'flags' option must be a number. Received type string.
cause The `flags` option for `node-loader` was provided with a non-numeric value, or a string that could not be parsed as a number.fixEnsure the `flags` option in `node-loader` configuration is a number, typically from `os.constants.dlopen` (e.g., `os.constants.dlopen.RTLD_NOW`). Remember to `require('os')` if using these constants.
Warnings
- breaking Version 2.0.0 introduced a breaking change by requiring Webpack 5 or higher. Projects on Webpack 4 or earlier must upgrade Webpack or remain on `node-loader` v1.x.
- breaking Version 1.0.0 raised the minimum supported Node.js version to 10.13 and the minimum supported Webpack version to 4. Projects using older Node.js runtimes or Webpack 3 will need to upgrade.
- gotcha `node-loader` is strictly designed for Node.js environments. It will only work correctly when Webpack's `target` option is set to `node`, `async-node`, `electron-main`, `electron-renderer`, or `electron-preload`. Using it with browser targets will lead to runtime errors.
- gotcha When using `node-loader`, it is critical to disable Webpack's polyfill for `__dirname` and `__filename` global variables to ensure native modules can correctly locate their dependencies. Failing to do so will result in `ReferenceError` or incorrect module loading paths.
Install
-
npm install node-loader -
yarn add node-loader -
pnpm add node-loader
Imports
- NodeLoader Rule
import NodeLoader from 'node-loader';
module: { rules: [ { test: /\.node$/, loader: 'node-loader', }, ], } - NodeLoader with Options
loader: 'node-loader?name=[path][name].[ext]'
module: { rules: [ { test: /\.node$/, loader: 'node-loader', options: { name: '[path][name].[ext]', flags: 2 // os.constants.dlopen.RTLD_NOW }, }, ], } - Inline Loader Application
import nativeModule from './path/to/my-native-module.node';
import nativeModule from 'node-loader!./path/to/my-native-module.node';
Quickstart
const path = require('path');
const fs = require('fs');
// Create a dummy .node file for the quickstart
const dummyNodeModulePath = path.resolve(__dirname, 'dummy.node');
fs.writeFileSync(dummyNodeModulePath, '// This is a placeholder for a native Node.js module');
// webpack.config.js
module.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
extensions: ['...', '.node'],
},
target: 'node', // Crucial for node-loader
node: {
__dirname: false, // Crucial for node-loader
},
module: {
rules: [
{
test: /\.node$/,
loader: 'node-loader',
options: {
name: '[name].[ext]'
}
},
],
},
};
// index.js (application entry point)
// For demonstration, we'll pretend 'dummy.node' is a real native module
// In a real scenario, this would be a compiled C++ addon.
const nativeModule = require('./dummy.node');
console.log('Native module loaded:', nativeModule);
// Expected output: Native module loaded: {}
// (Since dummy.node is empty, but the loader successfully processed it)
// To run:
// 1. npm install webpack webpack-cli node-loader
// 2. Save the webpack config above as `webpack.config.js`
// 3. Save the index.js content above as `index.js`
// 4. Create a dummy.node file (as shown in the config comments)
// 5. Run `npx webpack`
// 6. Then `node dist/bundle.js`