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.
Goal
Allow module authors to support
const {named1, named2} = require('pkg1')andimport {named1, named2} from 'pkg1'in the same version ofpkg1.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 ofObject.entries(esmExports)would be iterated to set exports on the ES module.Usage Examples
Named exports only
Default function with named exports
With API compatibility
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 supportingesmExports, so named ESM exports can be added to a package without a semver-major release.@babel/plugin-transform-modules-commonjs
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').defaultto access the default export.Why not interpret
__esModuleThe meaning of
import cjsDefault from 'cjs-module'is already established. Supporting the babel__esModuleattribute 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-commonjsif they're willing to add an option to have the transformed code setmodule.esmExportsin addition tomodule.exports.