jashkenas / underscore

JavaScript's utility _ belt
https://underscorejs.org
MIT License
27.3k stars 5.53k forks source link

ES6 module support broken in 1.13.x #2929

Closed tstibbs closed 3 years ago

tstibbs commented 3 years ago

When importing underscore into an ES6 module, an error is raised by the compiler.

To reproduce

  1. Populate index.mjs like this:

    import underscore from 'underscore'
    console.log(underscore)
  2. npm install underscore@1.13.1

  3. node index.mjs

  4. An error will be logged as follows:

    file:///workspace/node_modules/underscore/underscore-node.mjs:6
    export { VERSION, after, every as all, allKeys, some as any, extendOwn as assign, before, bind, bindAll, chain, chunk, clone, map as collect, compact, compose, constant, contains, countBy, create, debounce, _ as default, defaults, defer, delay, find as detect, difference, rest as drop, each, _escape as escape, every, extend, extendOwn, filter, find, findIndex, findKey, findLastIndex, findWhere, first, flatten, reduce as foldl, reduceRight as foldr, each as forEach, functions, get, groupBy, has, first as head, identity, contains as include, contains as includes, indexBy, indexOf, initial, reduce as inject, intersection, invert, invoke, isArguments, isArray, isArrayBuffer, isBoolean, isDataView, isDate, isElement, isEmpty, isEqual, isError, isFinite, isFunction, isMap, isMatch, isNaN, isNull, isNumber, isObject, isRegExp, isSet, isString, isSymbol, isTypedArray, isUndefined, isWeakMap, isWeakSet, iteratee, keys, last, lastIndexOf, map, mapObject, matcher, matcher as matches, max, memoize, functions as methods, min, mixin, negate, noop, now, object, omit, once, pairs, partial, partition, pick, pluck, property, propertyOf, random, range, reduce, reduceRight, reject, rest, restArguments, result, sample, filter as select, shuffle, size, some, sortBy, sortedIndex, rest as tail, first as take, tap, template, templateSettings, throttle, times, toArray, toPath, unzip as transpose, _unescape as unescape, union, uniq, uniq as unique, uniqueId, unzip, values, where, without, wrap, zip } from './underscore-node-f.cjs';
             ^^^^^^^
    SyntaxError: The requested module './underscore-node-f.cjs' is expected to be of type CommonJS, which does not support named exports. CommonJS modules can be imported by importing the default export.
    For example:
    import pkg from './underscore-node-f.cjs';
    const { VERSION, after, every: all, allKeys, some: any, extendOwn: assign, before, bind, bindAll, chain, chunk, clone, map: collect, compact, compose, constant, contains, countBy, create, debounce, _: default, defaults, defer, delay, find: detect, difference, rest: drop, each, _escape: escape, every, extend, extendOwn, filter, find, findIndex, findKey, findLastIndex, findWhere, first, flatten, reduce: foldl, reduceRight: foldr, each: forEach, functions, get, groupBy, has, first: head, identity, contains: include, contains: includes, indexBy, indexOf, initial, reduce: inject, intersection, invert, invoke, isArguments, isArray, isArrayBuffer, isBoolean, isDataView, isDate, isElement, isEmpty, isEqual, isError, isFinite, isFunction, isMap, isMatch, isNaN, isNull, isNumber, isObject, isRegExp, isSet, isString, isSymbol, isTypedArray, isUndefined, isWeakMap, isWeakSet, iteratee, keys, last, lastIndexOf, map, mapObject, matcher, matcher: matches, max, memoize, functions: methods, min, mixin, negate, noop, now, object, omit, once, pairs, partial, partition, pick, pluck, property, propertyOf, random, range, reduce, reduceRight, reject, rest, restArguments, result, sample, filter: select, shuffle, size, some, sortBy, sortedIndex, rest: tail, first: take, tap, template, templateSettings, throttle, times, toArray, toPath, unzip: transpose, _unescape: unescape, union, uniq, uniq: unique, uniqueId, unzip, values, where, without, wrap, zip } = pkg;
        at ModuleJob._instantiate (internal/modules/esm/module_job.js:98:21)
        at async ModuleJob.run (internal/modules/esm/module_job.js:137:5)
        at async Loader.import (internal/modules/esm/loader.js:165:24)
        at async Object.loadESM (internal/process/esm_loader.js:68:5)

What I've tried

Workaround

Environment

Node version: 14.8.0 npm version: 7.10.0

jgonggrijp commented 3 years ago

Thank you for reaching out, @tstibbs. I'm a bit surprised by the problem you're running into, for two reasons.

Theoretical reason: the Node.js documentation states that

When importing CommonJS modules, the module.exports object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.

I ensured that this static analysis was possible in the underscore-node-f.cjs by listing each named export separately with the exports.name = ... syntax. The error message you're getting from Node 14.8.0 seems like Node.js is contradicting its own documentation.

Empirical reason: I made a CI test that is very similar to yours and which passes without a problem in Node 14.

Of course, I don't doubt your report and of course, I want this to work. Before I invest a lot of effort again into supporting Node.js's exotic "support" for ESM, though: would you mind trying whether upgrading to Node.js 14.17 solves the problem for you?

tstibbs commented 3 years ago

Yes, a node upgrade fixed it... (14.13.0 was the latest available in my package repo but that seemed to be enough). Looking at the changelog it looks like the CJS export detection didn't appear until 14.13 (https://github.com/nodejs/node/pull/35249).

I probably should have tried that first, thanks for the pointer and the well reasoned response, and sorry for the spurious report!

jgonggrijp commented 3 years ago

No problem @tstibbs, I'm glad your problem has been solved!