Migrating from std/esm to esm support in node V14 - can't find named import - works fine with std/esm

Viewed 1865

First off for a year+ I've written all my code using esm and with the std/esm package I am able to seamlessly use dependent packages with cjs no issues without the need of babel.

In nodejs V14 mixed esm/cjs support is now included without a flag and the std/esm project seems to be winding down so I thought I should attempt to migrate to see what the issues may be. Well I found one.

https://github.com/standard-things/esm

https://nodejs.org/api/esm.html#esm_ecmascript_modules

The issue I am having is that unlike std/esm the esm/cjs support now included with V14 nodejs is breaking the named imports (from I assume commonjs module exports) that worked fine with std/esm.

Take for example https://github.com/sindresorhus/make-dir/blob/978bee9186bf0c41640ed21567921daf8c303225/index.js#L106

as packages is uses cjs. Here is the export

module.exports.sync = (input, options) => {
    checkPath(input);
    options = processOptions(options);

Using "type":"module" in my package.json. I have in my code the import import { sync as mkdir } from 'make-dir' which works fine using std/esm. But using in nodejs 14 it says it can't find the named export sync.**

import { sync as mkdir } from 'make-dir'
         ^^^^
SyntaxError: The requested module 'make-dir' does not provide an export named 'sync'

Am I stuck here? Do I need to stay with std/esm? (but it looks like the project is over now) I can't/shouldn't go through my entire code base accommodating cjs modules with

import mk from 'make-dir'
const mkdir = mk.sync

To make this easy for anyone to recreate I have made a repo one can clone and run to see this difference

https://github.com/dkebler/core-esm-named-import-error

Anyway was under the assumption that esm in v14 was going to be a drop in replacement for using std/esm. Apparently not :(.

1 Answers

EDIT: As of Node.js 14.13.0 (and Node.js 12.19.0 LTS), importing a CJS module parses the file using https://github.com/guybedford/cjs-module-lexer to detect most common named exports, so you can use those from ESM. The original answer is out-of-date, but is still relevant to understand why Node.js doesn't detect some CJS named exports.

The technical reason Node.js only supports default exports for CJS scripts is because ESM import is static (it is parsed prior to module execution) and CJS module.exports is dynamic (you can do something crazy like module.exports[Date.now()] = 0 and get away with it). The only way be know for sure what "names" a CJS module exports is to parse and execute it, that seems to be incompatible with ES6 specs.

Some bundlers (such as std/esm) use a regex on the CJS modules code to find instances of module.exports.<named export> =, and use that to define the named export list for the module. The downside of this method is that you won't find all the exports, and the parsing of the large files can be very memory intensive. Also that means parsing CJS modules twice, and while the performance hit might be OK for bundlers, for a runtime such as Node.js it is not worth it.

So what can be done? Here's what I have been doing when I encounter this issue:

Related