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.
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.
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.
- You need an exact inventory of the properties a module exposes after execution. Dead branches and shadowed exports identifiers can overclassify names.
- You need an AST or reliable rejection of invalid JavaScript. This lexer can accept invalid source without errors.
- Your inputs are native ESM and you need import/export metadata. es-module-lexer is the more relevant package.
- You need arbitrary dynamic assignments or renamed exports aliases to be understood. The detection patterns are frozen.
- You only want to consume a CommonJS package in an application. The scanner adds a separate analysis task that ordinary module loading does not require.
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
| Package | Registry | Pick it when |
|---|---|---|
| es-module-lexer | npm | Choose es-module-lexer for ES module import/export metadata. It addresses native ESM inspection rather than this guide's CommonJS detection rules. |
| acorn | npm | Choose 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.