ai/dual-publish

Publish JS project as dual ES modules and CommonJS package to npm

JavaScript

186

285 commits

updated Feb 25, 2026

See the code

README

Dual Publish

Publish JS project as dual ES modules and CommonJS package to npm.

  • Tested on projects with 16M downloads per month.
  • CI tests with it works with Node.js, browsers, React Native, bundlers (webpack, Parcel, Rollup, or esbuild) and CDNs (like jspm).
  • Does not change line numbers in stacktrace and keep sources readable.
  • No build step. No need for separated src/ and dist/ dirs in repository. You will be able to test branch by installing version from GitHub like npm i example@you/example#fix.
  • Multiple files support. Your user will be able to import separated files
    like import { nanoid } from 'nanoid/async'.
  • Cleans npm package from development configs before publishing.
  • Supports process.env.NODE_ENV for development checks, which you want to remove in production JS bundle.

You write CommonJS in your npm library sources:

// index.js
module.exports = { lib }

npx dual-publish compiles your library during publishing to npm:

// index.js
export { lib }

// index.cjs
module.exports = { lib }

// package.json
{
  …
  "type": "module",
  "module": "index.js",
  "main": "index.cjs",
  "exports": {
    "require": "./index.cjs",
    "import": "./index.js"
  }
}

Now your library can be imported natively as ESM or CommonJS:

// CommonJS
let { lib } = require('lib')

// ESM in Node.js, webpack, Parcel, and Rollup
import { lib } from 'lib'

// ESM in browser
import { lib } from 'https://cdn.jsdelivr.net/npm/lib/index.js'
Sponsored by Evil Martians

Limits

  • We recommend to avoid default export because of bug in webpack.
  • Every JS file should be in own dir. lib/index.js instead of lib.js. We need it to put package.json with module.

Usage

  1. Take a normal CommonJS project with require() and module.exports =.

  2. Because of bug in webpack we recommend to use only named exports:

    const NAME = 'a'
    function change {
      …
    }
    
    module.exports = { NAME, change }
    
  3. Move all files into separated dirs. Rename lib.js to lib/index.js. Old require('./lib') will work.

  4. Add dual-publish to the project:

    npm i --save-dev dual-publish
    
  5. Test the result by calling npx dual-publish --check. It will create a folder in your project with converted files. Review them manually.

  6. Publish your project with npx dual-publish instead of npm publish.

    npx dual-publish
    

Contributors

ai

256 commits

dependabot[bot]

10 commits

AleksandrSl

5 commits

vankop

5 commits

ai/dual-publish

Publish JS project as dual ES modules and CommonJS package to npm

JavaScript

186

285 commits

updated Feb 25, 2026

See the code

README

Dual Publish

Publish JS project as dual ES modules and CommonJS package to npm.

  • Tested on projects with 16M downloads per month.
  • CI tests with it works with Node.js, browsers, React Native, bundlers (webpack, Parcel, Rollup, or esbuild) and CDNs (like jspm).
  • Does not change line numbers in stacktrace and keep sources readable.
  • No build step. No need for separated src/ and dist/ dirs in repository. You will be able to test branch by installing version from GitHub like npm i example@you/example#fix.
  • Multiple files support. Your user will be able to import separated files
    like import { nanoid } from 'nanoid/async'.
  • Cleans npm package from development configs before publishing.
  • Supports process.env.NODE_ENV for development checks, which you want to remove in production JS bundle.

You write CommonJS in your npm library sources:

// index.js
module.exports = { lib }

npx dual-publish compiles your library during publishing to npm:

// index.js
export { lib }

// index.cjs
module.exports = { lib }

// package.json
{
  …
  "type": "module",
  "module": "index.js",
  "main": "index.cjs",
  "exports": {
    "require": "./index.cjs",
    "import": "./index.js"
  }
}

Now your library can be imported natively as ESM or CommonJS:

// CommonJS
let { lib } = require('lib')

// ESM in Node.js, webpack, Parcel, and Rollup
import { lib } from 'lib'

// ESM in browser
import { lib } from 'https://cdn.jsdelivr.net/npm/lib/index.js'
Sponsored by Evil Martians

Limits

  • We recommend to avoid default export because of bug in webpack.
  • Every JS file should be in own dir. lib/index.js instead of lib.js. We need it to put package.json with module.

Usage

  1. Take a normal CommonJS project with require() and module.exports =.

  2. Because of bug in webpack we recommend to use only named exports:

    const NAME = 'a'
    function change {
      …
    }
    
    module.exports = { NAME, change }
    
  3. Move all files into separated dirs. Rename lib.js to lib/index.js. Old require('./lib') will work.

  4. Add dual-publish to the project:

    npm i --save-dev dual-publish
    
  5. Test the result by calling npx dual-publish --check. It will create a folder in your project with converted files. Review them manually.

  6. Publish your project with npx dual-publish instead of npm publish.

    npx dual-publish
    

Contributors

ai

256 commits

dependabot[bot]

10 commits

AleksandrSl

5 commits

vankop

5 commits

Languages

JavaScript

100.0%