Write the package in CommonJS or transpile ES module sources into CommonJS, and create an ES module wrapper file that defines the named exports. Using [Conditional exports][], the ES module wrapper is used for import and the CommonJS entry point for require.

    1. // ./node_modules/pkg/package.json
    2. {
    3. "type": "module",
    4. "main": "./index.cjs",
    5. "exports": {
    6. "import": "./wrapper.mjs",
    7. "require": "./index.cjs"
    8. }
    9. }

    The preceding example uses explicit extensions .mjs and .cjs. If your files use the .js extension, "type": "module" will cause such files to be treated as ES modules, just as "type": "commonjs" would cause them to be treated as CommonJS. See Enabling.

    1. // ./node_modules/pkg/index.cjs
    2. exports.name = 'value';
    1. // ./node_modules/pkg/wrapper.mjs
    2. import cjsModule from './index.cjs';
    3. export const name = cjsModule.name;

    In this example, the name from import { name } from 'pkg' is the same singleton as the name from const { name } = require('pkg'). Therefore === returns true when comparing the two names and the divergent specifier hazard is avoided.

    If the module is not simply a list of named exports, but rather contains a unique function or object export like module.exports = function () { ... }, or if support in the wrapper for the import pkg from 'pkg' pattern is desired, then the wrapper would instead be written to export the default optionally along with any named exports as well:

    1. import cjsModule from './index.cjs';
    2. export const name = cjsModule.name;
    3. export default cjsModule;

    This approach is appropriate for any of the following use cases:

    • The package is currently written in CommonJS and the author would prefer not to refactor it into ES module syntax, but wishes to provide named exports for ES module consumers.
    • The package has other packages that depend on it, and the end user might install both this package and those other packages. For example a utilities package is used directly in an application, and a utilities-plus package adds a few more functions to utilities. Because the wrapper exports underlying CommonJS files, it doesn’t matter if utilities-plus is written in CommonJS or ES module syntax; it will work either way.
    • The package stores internal state, and the package author would prefer not to refactor the package to isolate its state management. See the next section.

    A variant of this approach not requiring conditional exports for consumers could be to add an export, e.g. "./module", to point to an all-ES module-syntax version of the package. This could be used via import 'pkg/module' by users who are certain that the CommonJS version will not be loaded anywhere in the application, such as by dependencies; or if the CommonJS version can be loaded but doesn’t affect the ES module version (for example, because the package is stateless):

    1. // ./node_modules/pkg/package.json
    2. {
    3. "type": "module",
    4. "main": "./index.cjs",
    5. "exports": {
    6. ".": "./index.cjs",
    7. "./module": "./wrapper.mjs"
    8. }
    9. }