Skip to content

module: expose CJS named-exports detection (cjs-module-lexer equivalent) #63123

@SimenB

Description

@SimenB

What is the problem this feature will solve?

When loading a CJS module from ESM (import {foo} from './bar.cjs'), the named exports have to be derived statically before the CJS body runs - ESM bindings need to exist at link time. Node already does this internally for require(esm) and friends, using cjs-module-lexer to lex the CJS source.

Frameworks doing the same job - Jest, and likely others wrapping CJS as ESM in custom contexts - currently take a direct dependency on the cjs-module-lexer npm package and call it themselves. Works fine, but it's redundant: every framework re-vendors a parser that Node already runs, and parsing happens twice for the same source whenever Node and the framework both touch it.

What is the feature you are proposing to solve the problem?

Some way to ask Node to do the lex for us. Two shapes that would both work:

  • module.cjsNamedExports(source: string | Buffer): string[] - pure helper, takes source, returns names.
  • SyntheticModule.fromCjsExports(exports: object, options): SyntheticModule - higher-level convenience that does both the lex and the synthetic construction.

Either shape lets frameworks delete the explicit cjs-module-lexer dependency and lean on whatever Node ships. The first is more flexible (callers may want to combine the lexed names with runtime Object.keys, which is what Jest does today to handle Object.assign patterns the lexer can miss); the second is more ergonomic if the only use case is "wrap this CJS as ESM.". Both might make sense as well, but I understand the second one might feel out of scope 🙂

What alternatives have you considered?

The current approach is the alternative: depending directly on cjs-module-lexer. There's a real tradeoff worth flagging here, though.

The npm package versions independently of Node. Bug fixes ship to users regardless of which Node version they're running. If the lexer moves into Node's surface, users get whatever lexer ships with their Node version - older Node releases would lock users to older lexer behavior, including known bugs, until they upgrade Node itself.

So I think the question isn't strictly "should Node expose this" but "is exposing it a net win given the versioning tradeoff." Possible answers:

  • Leave it as-is. Frameworks keep their npm dep; bug fixes stay decoupled from Node
  • Expose it and document the tradeoff. Frameworks that prefer always-current can keep the npm dep; frameworks that prefer not-revendoring can use the Node API and accept the version coupling

I lean toward the second - the documented choice - but happy for that to be wrong.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues that request new features to be added to Node.js.

    Type

    No type

    Projects

    Status

    Awaiting Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions