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: Allow CommonJS modules to declare ESM named exports #448

Closed
@coreyfarrell

Description

@coreyfarrell

Goal

Allow module authors to support const {named1, named2} = require('pkg1') and import {named1, named2} from 'pkg1' in the same version of pkg1.

Proposed implementation

When CommonJS is loaded by the ESM loader it would use const esmExports = module.esmExports || {default: module.exports}; to determine the intended export structure. The result of Object.entries(esmExports) would be iterated to set exports on the ES module.

Usage Examples

Named exports only

module.exports.named1 = 'value1';
module.exports.named2 = 'value2';
module.esmExports = module.exports;

Default function with named exports

function defaultFn(...args) {}
defaultFn.namedExport = 'const value';

module.exports = defaultFn;
module.esmExports = {
  default: (...args) => defaultFn(...args),
  ...module.exports
);

With API compatibility

module.esmExports = {
  ...module.exports,
  default: module.exports
);

This compatibility code would ensure import pkg from 'pkg' will provide the same result in 13.2.0 as it will in a future version supporting esmExports, so named ESM exports can be added to a package without a semver-major release.

@babel/plugin-transform-modules-commonjs

// ./dist/index.js is generated by babel
module.exports = module.esmExports = require('./dist/index.js');

This shows how to produce correct ESM exports using output of ESM code translated by babel. This does not address API compatibility so it would potentially be a semver-major release. This example does not show fix-ups to the CJS default export, so CJS users would still have to use require('pkg').default to access the default export.

Why not interpret __esModule

The meaning of import cjsDefault from 'cjs-module' is already established. Supporting the babel __esModule attribute now would result in a breaking change to existing modules, authors would be unable to control for this. If this is implemented I think we should ask @babel/plugin-transform-modules-commonjs if they're willing to add an option to have the transformed code set module.esmExports in addition to module.exports.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions