
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
babel-plugin-node-cjs-interop
Advanced tools
A babel plugin to fix the default import interoperability issue in Node.js
babel-plugin-node-cjs-interop
: fix the default import interoperability issue in Node.jsConsider the following modules:
// a.js
export default function greet() {
console.log("Hello, world!");
}
// b.js
import greet from "a.js";
greet();
They usually work, unless the following conditions are met:
a.js
(the module being imported) is a simulated ESM. That is, the module is transpiled as a CommonJS module (by Babel or TypeScript) before execution. And,b.js
(the importing module) is a native ESM, That is, the module is run on Node.js' native ES Module support.You can reproduce the above condition by placing the following files:
// a.cjs
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = greet;
function greet() {
console.log("Hello, world!");
}
// b.mjs
import greet from "./a.cjs";
greet();
$ node ./b.mjs
./b.mjs:3
greet();
^
TypeError: greet is not a function
at ./b.mjs:3:1
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
The following packages solve the problem:
babel-plugin-node-cjs-interop
(this package) / swc-plugin-node-cjs-interop
: automatically inserts the compatibility wrapper.node-cjs-interop
: allows manually wrapping the exported value.Install the babel plugin:
npm install -D babel-plugin-node-cjs-interop
# or:
yarn add -D babel-plugin-node-cjs-interop
Configure it in your Babel configuration:
// .babelrc.js or babel.config.js
export default {
presets: [
/* ... */
],
plugins: [
// ...
[
"babel-plugin-node-cjs-interop",
{
// List the packages you're experiencing problems
// importing from Node.js' native ESM.
// I.e. list the packages in the simulated ESM format.
packages: ["styled-components", "@babel/helper-plugin-test-runner"],
},
],
],
};
If you're unsure what packages to specify in the configuration, node-cjs-interop-finder
might be useful:
npx node-cjs-interop-finder
export ... from
is not detected yet.
It may negatively affect tree-shaking because the wrapper function makes it difficult to analyze unused imports.
In ES Modules, a module can change its exports' values after it's been loaded, and the importing module can observe the update.
export let counter = 0;
export function countUp() {
counter++;
}
import { counter, countUp } from "./counter.js";
console.log(counter); // => 0
countUp();
console.log(counter); // => 1
Here the semantics is also not preserved when importing a simulated ESM module from a native ESM; the importing module always observes the initial value.
This plugin also restores the intended behavior of updating exported variables. Therefore, in rare cases, you may find your program behaving differently regarding named imports other than default
.
When using native ESM, it is an error to import a non-existent named export:
// Error
import { nonExistent } from "./a.js";
With babel-plugin-node-cjs-interop
, it silently returns undefined
.
This plugin uses the __esModule
flag to detect Babel's ES Modules support. Technically speaking, it may lead to false positives in a rare situation. Consider the following code:
// a.js (simulated ESM)
export const val = 42;
// b.js (native ESM; this plugin is not applied)
export * from "a.js";
export const foo = 100;
// c.js (native ESM)
import { foo } from "b.js";
When this plugin is applied to the last import, the import will return an unintended value.
string[]
[]
List of packages to apply the transformation. If empty, no transformation is applied.
Currently there is no way to apply the transformation to all imports.
You can use node-cjs-interop-finder
to figure out packages that might suit in the option:
npx node-cjs-interop-finder
string[]
[]
Similar to packages
, but applies the "twisted" variant instead. In this mode, the imported default
value
would be the namespace object rather than the proper default export.
This is useful when you have "module"
or "moduleResolution"
set to "nodenext"
or "node16"
in your tsconfig.json
and you need to import default
from a "dual package" in which the
type definitions are recognized in the .cts
mode.
boolean
false
Skips check of the ns.__esModule
export. Note that it still checks
for ns.default.__esModule
.
This is useful if a transpiler or a bundler generates a module which cjs-module-lexer cannot correctly parse.
boolean
false
Imports helpers from the node-cjs-interop
package. You need to add node-cjs-interop
to your package's dependency.
ES Modules and CommonJS Modules use different models for module exports:
For this reason, exports are mapped differently in each direction:
default
is mapped to the single exported value.And it has a big downside: default
exports from ESM don't round-trip when shipped through CJS. This is problematic for transpilers like Babel, which needs to embed the semantics of ESM into CJS. Therefore Babel implements the additional rule to the above:
exports.__esModule
as a truthy value, ESM's importing rule is modified so that the default
import is treated uniformly with other named imports (i.e. mapped to exports.default
rather than exports
).And Babel defines exports.__esModule
when transpiling modules in ESM to CJS format.
However, Node.js didn't implement the rule for __esModule
. It's a result of careful consideration, but is still problematic to gradual migration from CJS to ESM.
node-cjs-interop works around the problem by replacing the namespace object appropriately. This is achieved by the following function:
function interopImportCJSNamespace(ns) {
return ns.__esModule && ns.default && ns.default.__esModule
? // `ns.default` likely comes from Babel's ESM emulation.
// In this case `ns.default` works as the namespace object.
ns.default
: // Original namespace object
ns;
}
babel-node-cjs-interop first transforms named imports:
import f, { a } from "mod";
console.log({ f, a });
to namespace imports:
import * as ns from "mod";
console.log({ f: ns.default, a: ns.a });
and then wraps the namespace object by the aforementioned function:
import * as nsOrig from "mod";
const ns = interopImportCJSNamespace(nsOrig);
console.log({ f: ns.default, a: ns.a });
FAQs
A babel plugin to fix the default import interoperability issue in Node.js
The npm package babel-plugin-node-cjs-interop receives a total of 787 weekly downloads. As such, babel-plugin-node-cjs-interop popularity was classified as not popular.
We found that babel-plugin-node-cjs-interop demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.