-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
post: publish How to publish an npm package for ESM and CommonJS with…
… TypeScript (#164)
- Loading branch information
Showing
3 changed files
with
152 additions
and
2 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
--- | ||
layout: post | ||
title: How to publish an npm package for ESM and CommonJS with TypeScript | ||
description: | ||
A step by step tutorial on how to publish an npm package for ECMAScript Modules (ESM) and CommonJS | ||
(CJS) with TypeScript | ||
slug: publish-npm-package-esm-and-cjs | ||
date: 2023-09-08 | ||
language: en | ||
cover: ./cover.jpg | ||
tags: ['JavaScript'] | ||
--- | ||
|
||
import Info from '../../../../src/components/MDX/Info' | ||
|
||
I faced a problem recently. I have a reference data package which is an npm package that can be | ||
consumed by two different projects: | ||
|
||
- The frontend: a React application bundled with Vite.js, an ESM builder (with no CJS support). | ||
- The backend: a Nestjs application that only supports CommonJS. | ||
|
||
Also, this package contains some big files inside. Bundling everything in one file is a big no-no. | ||
|
||
<Info title="💡 ESM? CJS? What’s that?"> | ||
<p>CommonJS (CJS) is the older and traditional way of dealing with JS dependencies.</p> | ||
<p>ESM (ECMAScript Modules) is the official standard format to package JavaScript.</p> | ||
</Info> | ||
|
||
```tsx | ||
// CommonJS (CJS) | ||
const path = require('path') | ||
module.exports = path.extname('index.html') | ||
|
||
// ESM (ECMAScript Modules) | ||
import path from 'path' | ||
export default path.extname('index.html') | ||
``` | ||
|
||
## Folder structure | ||
|
||
Let's start with a classic file architecture: | ||
|
||
```bash | ||
├── dist | ||
├── src | ||
│ ├── function-a.ts | ||
│ ├── function-b.ts | ||
│ └── index.ts | ||
├── package.json | ||
├── tsconfig.esm.json | ||
├── tsconfig.cjs.json | ||
└── README.md | ||
``` | ||
|
||
Please note that everything under the `src/` folder import/export dependencies with the ES7 `import` | ||
/ `export` keyword. | ||
|
||
## Library's package.json | ||
|
||
The solution I found is to have 2 folders under the `dist` folder: | ||
|
||
```json | ||
{ | ||
"name": "@maxpou/my-cool-package", | ||
"main": "dist/cjs/index.js", | ||
"exports": { | ||
"require": "./dist/cjs/index.js", | ||
"import": "./dist/esm/index.js" | ||
}, | ||
"files": ["README.md", "dist"], | ||
"scripts": { | ||
"build": "npm run build:cjs && npm run build:esm", | ||
"build:cjs": "tsc -p tsconfig.cjs.json", | ||
"build:esm": "tsc -p tsconfig.esm.json" | ||
} | ||
} | ||
``` | ||
|
||
You can see that I use the `exports` token. This allows the package owner to define multiple entry | ||
points. A CommonJS system will use the index located in the `./dist/cjs/index.js` folder while an | ||
ESM system will use the other index located in the _esm_ folder. | ||
|
||
By the way, you can do | ||
[much more with the conditional exports](https://nodejs.org/api/packages.html#conditional-exports)! | ||
|
||
## tsconfig.json files | ||
|
||
I don't have one file but two. | ||
|
||
```jsx{5-6} | ||
// tsconfig.esm.json | ||
{ | ||
"compilerOptions": { | ||
"target": "esnext", | ||
"module": "esnext", | ||
"outDir": "./dist/esm", | ||
// ... | ||
}, | ||
"include": ["src/index.ts"], | ||
"exclude": ["node_modules"] | ||
} | ||
``` | ||
|
||
```jsx{5-6} | ||
// tsconfig.cjs.json | ||
{ | ||
"compilerOptions": { | ||
"target": "es2017", | ||
"module": "commonjs", | ||
"outDir": "./dist/cjs", | ||
// ... | ||
}, | ||
"include": ["src/index.ts"], | ||
"exclude": ["node_modules"] | ||
} | ||
``` | ||
|
||
If your IDE or other tools need one named `tsconfig.json`, you can create a master one. Then the | ||
specific tsconfig files can extend from it. | ||
|
||
Then you can `npm publish` and call it a day 🥳 | ||
|
||
## Usage | ||
|
||
Your package will be available in both ESM and CommonJS environments. Your users can use it without | ||
knowing there's a separation. | ||
|
||
```ts | ||
// ESM | ||
import { myFunction } from 'my-cool-package' | ||
|
||
// CommonJS | ||
const { myFunction } = require('my-cool-package') | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters