WittCode💻

Don't Exclude Users from Your npm Package!

By

There are a few different ways to import npm packages into Node projects. This can lead to compatability issues. Learn how to fix these issues to avoid excluding users from your npm package.

Table of Contents 📖

My Express Middleware

Recently, I built an Express middleware with TypeScript called console-log-middleware. This middleware works along side my Chrome extension ConsoleLog to allow users to view server logs in the browser. When I initially published this package to npm, I pushed up the folder below:

[object Object]

This JavaScript code was generated using the TypeScript compiler with the module set to ESNext. This means that the outputted code will have import and export throughout. An issue with this is compatibility. When you try to require an ESModule package in CommonJS, you will get an error like the following:

const wittcodeDefault = require('@wittcode/ecmascript-module');
                        ^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/wittcode/Desktop/npm/commonjs-module/node_modules/@wittcode/ecmascript-module/dist/index.js from /Users/wittcode/Desktop/npm/commonjs-module/src/index.js not supported.
Instead change the require of /Users/wittcode/Desktop/npm/commonjs-module/node_modules/@wittcode/ecmascript-module/dist/index.js in /Users/wittcode/Desktop/npm/commonjs-module/src/index.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/Users/wittcode/Desktop/npm/commonjs-module/src/index.js:1:25) {
  code: 'ERR_REQUIRE_ESM'
}

INFO: Node modules have different formatting systems, or module formatting systems. This means that there are different syntaxes used to work with Node modules. One of these systems is CommonJS which uses require and module.exports, another is ECMAScript which uses import and export.

Fixing Compatability

To add CommonJS support to this middleware, I used the exports key inside package.json.

"exports": {
  "require": "./dist/cjs/index.js",
  "import": "./dist/esm/index.js",
  "types": "./dist/types/index.d.ts"
},

Now, when someone uses require they will be provided the default export inside dist/cjs/index.js and when they use import they will be provided the default export inside dist/esm/index.js. These files were generated using two different TypeScript compiler files and one npm run build command:

{
  "compilerOptions": {
    "module": "CommonJS",
    "outDir": "./dist/cjs",
    "declaration": true,
    "declarationDir": "./dist/types",
    "target": "ES6",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}
{
  "compilerOptions": {
    "module": "ESNext",
    "outDir": "./dist/esm",
    "declaration": true,
    "declarationDir": "./dist/types",
    "target": "ES6",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}
"scripts": {
  "build:cjs": "tsc --project tsconfig.cjs.json",
  "build:esm": "tsc --project tsconfig.esm.json",
  "build": "npm run build:cjs && npm run build:esm"
},

The main difference here is the module key. Setting module to ESNext will generate an ECMAScript files and setting it to CommonJS will generate CommonJS files.

Fixing Compatability

Now this middleware can be used easily with both require and import. Of course, there are module bundlers out there that can handle this functionality. However, not everyone will use bundlers so it's a good idea to make sure anyone can use this package.