mrkeyoor.com_
Sat 07 Nov 23:37 UTC
npmCLI & Toolingupdated 07 Nov 2026

cjs-module-lexer review

cjs-module-lexer detects likely named exports and reexport specifiers in CommonJS source. If your scanner finds exports.debug inside if (false), that name can still appear in its result. The package reads syntax without scope analysis, so its output is a candidate list rather than a description of the object a running module will return. Version 2.3.0 suits tooling that needs these specific CommonJS detection rules, especially when deciding how to expose CommonJS names to ESM consumers. It does not turn the source into an AST or prove that a named import will work in your deployed runtime.

Verdict

Use cjs-module-lexer 2.3.0 when your tool can act on likely CommonJS names and preserve the uncertainty. Start with the dead-branch and alias examples below before deciding whether its output is precise enough for your caller.

Answers from our run

cjs-module-lexer or es-module-lexer: which should you use?

es-module-lexer: Choose es-module-lexer for ES module import/export metadata. It addresses native ESM inspection rather than this guide's CommonJS detection rules. Use cjs-module-lexer 2.3.0 when your tool can act on likely CommonJS names and preserve the uncertainty.

When should you not use cjs-module-lexer?

You need an exact inventory of the properties a module exposes after execution. Dead branches and shadowed exports identifiers can overclassify names.

API stability4/5The 2.3.0 documentation explicitly freezes export detection patterns and describes the same parse result as exports and reexports. That gives tools a defined target. The score rewards that narrow contract, but the release's initialization change is a reminder to pin the package and check entry behavior. It is not a tested promise about every Node release.
Docs4/5The version-matched README explains both entry styles, gives the token grammar, and shows overclassification, renamed identifiers, getter exclusions and the last reexport assignment rule. Those examples help a caller interpret misleading results. The score stops below 5 because the reader still has to design dependency resolution and choose how to present uncertain names.
Maintenance3/5The 2.3.0 release notes identify concrete work on Wasm initialization, source copying, parser scans and stack bounds. The README also limits accepted work to parser bugs, performance and syntax support while freezing detection patterns. That supports a maintenance assessment for an intentionally narrow component, without establishing future release pace or support response time.
Ecosystem3/5The pinned README states that Node.js core uses this project for CommonJS names imported into ESM, and the package metadata provides separate import and default entry paths. Those are useful integration signals for this task. This score gives no credit for unknown adoption or untested browser and runtime compatibility, and the package remains specialized rather than a general parser.

Use it if

  • You are writing CommonJS inspection tooling and want candidate named exports without evaluating the module.
  • You need to explain why one emitted CommonJS assignment is recognized while an aliased assignment is missed.
  • You can keep reexport specifiers separate from resolved dependencies and accept the documented last-assignment rule.
  • Your build tooling can target the frozen detection patterns instead of requiring new export shapes to be recognized.
Skip it if

Setup reality

Install the version this guide describes with npm install cjs-module-lexer@2.3.0. In a .cjs script, require('cjs-module-lexer') gives you parse without an initialization step. The package exports map routes ESM imports to dist/lexer.mjs, whose implementation uses Wasm. Await init() before parsing in browsers: the 2.3.0 docs warn that browser main threads can restrict synchronous Wasm compilation. This guide's snippets are documentation-based illustrations. We did not execute them, install the package, run upstream tests, or measure its speed or memory use.

The harder setup work is deciding what your caller should believe. parse(source) returns exports and reexports arrays, but a name in exports is only a detected candidate. Dead branches and a function parameter named exports can produce matches. Rename that parameter to e and the same assignment can disappear from detection. Keep that distinction in your report labels, and retain a fixture for each CommonJS shape your tool accepts. An empty list does not prove that a module exports nothing.

Reexports need their own handling. A reexports entry is a module specifier, not the dependency's exported names or a resolved file path. The documented assignment rule returns only the last module.exports = require(...) match, even if that assignment is in a dead branch. Resolve and inspect dependencies separately if your application needs their candidates, with your own policy for cycles and missing files. This guide does not provide a dependency walker. Do not use the lexer as a syntax validator: its docs say invalid JavaScript may parse without an error.

The 2.3.0 release notes list automatic Wasm initialization before parsing and a parser stack bounds fix. Those changes do not expand the export vocabulary. The project explicitly freezes detection patterns and accepts parser fixes, performance work and new syntax support. If your emitted CommonJS uses an unsupported export shape, a later release is not a sound migration plan.

Ratings below are editorial judgments about this documented task, on a 1 to 5 scale: 1 means weak support, 3 means usable with material limits, and 5 means unusually complete support for the stated task. Intermediate scores sit between those anchors. They do not rate measured performance, installed compatibility, adoption, security, or response times from maintainers.

Patterns

Collect direct named assignments read-direct-assignments

const { parse } = require('cjs-module-lexer');
const result = parse("exports.ready = true; module.exports.label = 'ok';");
console.log(result.exports); // ['ready', 'label']

Use this in a .cjs file. The CommonJS entry does not require init(), and the names describe syntax matches rather than runtime values.

Initialize the ESM Wasm entry explicitly initialize-esm

import { init, parse } from 'cjs-module-lexer';
await init();
const result = parse('exports.ready = true;');
console.log(result.exports); // ['ready']

Use this in an ESM context. Version 2.3.0 can initialize synchronously on first parse, but the docs recommend awaiting init() in browsers because synchronous Wasm compilation can be restricted.

Read a literal bracket property read-literal-properties

const { parse } = require('cjs-module-lexer');
console.log(parse("exports['feature-name'] = true;").exports);
// ['feature-name']

The documented computed assignment rule takes a string literal. This example does not establish detection for a variable used as the property key.

Detect a value descriptor inspect-value-descriptors

const { parse } = require('cjs-module-lexer');
const source = "Object.defineProperty(exports, 'ready', { value: true });";
console.log(parse(source).exports); // ['ready']

Object.defineProperty has specific supported shapes. Object.defineProperties is not supported by the documented detection rules.

Detect a supported getter shape inspect-simple-getters

const { parse } = require('cjs-module-lexer');
const source = `Object.defineProperty(exports, 'ready', {
  enumerable: true,
  get () { return state.ready; }
});`;
console.log(parse(source).exports); // ['ready']

This getter returns a member expression. The docs exclude arrow getters and getters that return a function call, so do not generalize this to arbitrary accessor bodies.

Inspect a simple exports object inspect-object-exports

const { parse } = require('cjs-module-lexer');
const source = "module.exports = { ready, 'label': text };";
console.log(parse(source).exports); // ['ready', 'label']

The source is a string being inspected, so ready and text need no bindings in the calling script. Arbitrary expressions in the object can stop detection before later properties.

Keep reexport specifiers separate separate-reexport-specifiers

const { parse } = require('cjs-module-lexer');
const result = parse("module.exports = require('./adapter.cjs');");
console.log(result.reexports); // ['./adapter.cjs']

This gives the specifier, not adapter.cjs export names. The lexer API does not resolve or recursively inspect that dependency for you.

Notice the last assignment rule notice-last-reexport

const { parse } = require('cjs-module-lexer');
const source = `module.exports = require('./live.cjs');
if (false) module.exports = require('./dead.cjs');`;
console.log(parse(source).reexports); // ['./dead.cjs']

The last matching module.exports require assignment wins in this example. The lexer does not decide which branch executes.

Keep shadowed assignments as candidates recognize-scope-false-positives

const { parse } = require('cjs-module-lexer');
const source = `(function (exports) {
  exports.localOnly = true;
})({});`;
console.log(parse(source).exports); // ['localOnly']

There is no scope analysis. The parameter shadows the module exports object, but the assignment still matches the named export rule.

Show why an alias can hide a name recognize-alias-false-negatives

const { parse } = require('cjs-module-lexer');
const source = `(function (e) { e.ready = true; })(exports);`;
console.log(parse(source).exports); // []

Renaming exports to e defeats this detection rule. Treat an empty array as no recognized names, not proof that a running module has no named properties.

Alternatives

PackageRegistryPick it when
es-module-lexernpmChoose es-module-lexer for ES module import/export metadata. It addresses native ESM inspection rather than this guide's CommonJS detection rules.
acornnpmChoose Acorn when the job calls for an ECMAScript parser. Budget separate work for interpreting CommonJS assignments rather than expecting this lexer's ready-made candidate list.

More cli & tooling guides

commander · chalk · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.