Question presented to candidate: "An old CommonJS codebase needs to use a dependency that only ships ESM. What are the options in a current Node version?"
What a strong answer should cover:
- Modern Node (from Node 22, with the capability broadening in later releases) lets
require()load an ES Module synchronously and directly, as long as that module does not itself use top-levelawait. - This closed a long-standing, genuinely painful gap: for years the only way for CommonJS to consume an ESM-only package was an async
import(), which cannot be used at the top of a synchronousrequirechain without restructuring the caller. - The one hard limitation is top-level await.
require()is synchronous by contract, and a module suspended on an unresolved promise cannot return a value synchronously β Node throws a specific, named error rather than silently hanging or coercing. - The precise error is
ERR_REQUIRE_ASYNC_MODULE, with a message pointing you atimport()instead β a good thing to be able to name exactly, since it identifies a version-aware, non-obvious failure mode. - This does not turn a
.mjsfile into CommonJS. It is still an ES Module, evaluated as one;require()is simply now able to synchronously wait for that evaluation to finish, when nothing async blocks it. - The direction of travel matters for the interview: dynamic
import()from CommonJS still works and is still the answer for anything using top-level await β this feature narrows the gap, it does not remove every case where you need it.
Clarifying questions expected:
- "Does the target module use top-level await?" β that is the one thing that decides whether
require()works at all. - "Which Node version is this running on?" β the capability rolled out and broadened across releases.
Code / implementation expected: Optional. Showing the successful case and the thrown error side by side is the clearest demonstration.