Skip to content
This repository was archived by the owner on Sep 2, 2023. It is now read-only.
This repository was archived by the owner on Sep 2, 2023. It is now read-only.

Proposal: CommonJS named exports defined in package.json #324

@GeoffreyBooth

Description

@GeoffreyBooth

User Story

I’m the publisher of the coffeescript package, which is written using CommonJS and has a Node API that includes exports compile and VERSION. Currently dependents can use my package in CoffeeScript via CommonJS code like:

const { compile } = require('coffeescript');

I would like to be able to provide named exports for dependents that are using ESM syntax, for example:

import { compile } from 'coffeescript';

Currently, the best I can offer my dependents is:

import CoffeeScript from 'coffeescript';
const { compile } = CoffeeScript;

because the --experimental-modules implementation doesn’t currently support named exports from CommonJS.

Alternatively, I could create a proxy ESM file, like this at the root of my package:

// module.mjs at package root
import CoffeeScript from './lib/coffeescript/coffeescript.js'; // CommonJS main

export default CoffeeScript;
const { VERSION, compile } = CoffeeScript;
export { VERSION, compile };

but then users would have to reference it as from 'coffeescript/module.mjs', not from 'coffeescript'. That’s not terrible, but it’s not as intuitive and familiar (and equivalent to CommonJS) as just from 'coffeescript'.

Efforts So Far

We’ve been blocked on a solution for named exports from CommonJS for a long time. I won’t get into the details, but at the moment none of the solutions currently on the table seem like they will likely be viable.

Package authors want to provide equivalent, and equally good, user experiences for both CommonJS and ESM consumers of their packages. This applies whether the packages themselves are originally written in CommonJS or ESM. Personally, I wouldn’t feel the need for a better dual packages solution if we had a way to provide named exports from the package root (the import { compile } from 'coffeescript' example above). And under the dual CommonJS/ESM package approach, the same specifier (e.g. 'coffeescript') would produce two separate instances, causing potentially unwanted results.

What all the CommonJS named exports solutions proposed so far have in common is that they dynamically attempt to figure out what the named exports from a CommonJS package should be. What if, instead, we rely on the package author to define their CommonJS named exports?

Potential Solution: Named Exports in package.json

Rather than a separate entry point for ESM, what if the package.json simply declared what the named exports are for my package? Then import { compile } from 'coffeescript' could work, because the resolver would know that compile was available as a named export for the package root.

This would nicely complement the package path maps feature we’re also working on. As paths are defined in package.json, the CommonJS named exports could be defined along with them.

If we really want to be ambitious, the named exports could be added to the package.json automatically by the package manager during installation or by npm (the company) via a scan through their registry. The named exports could be determined by running Node in a locked-down sandbox environment and using Reflect.ownKeys on the path, e.g. Reflect.ownKeys(require('coffeescript')). The resulting array could be added to package.json as the named exports for the root path of the package.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions