You want to transform your TypeScript project into an ECMAScript module (ESM)? Look no further! This tool (ts2esm
) converts your import and export declarations into ESM-compatible ones. It's the ideal tool for converting a CommonJS project to ESM. It also works with plain JavaScript projects! 🪄
Simply run this command to install ts2esm
globally on your machine:
npm i -g ts2esm
You can also run it locally (without being globally installed):
npx ts2esm
Convert your CommonJS projects (TypeScript or JavaScript) into ECMAScript modules with a single command. Just launch the program inside the directory of your project (it will ask you for your tsconfig.json
path):
ts2esm
You can also provide a list of tsconfigs (no prompt):
ts2esm packages/foo/tsconfig.json packages/bar/tsconfig.json
Note: The path can be specified absolutely (i.e. /home/user/cornerstone3D/tsconfig.json
) or relative (i.e. ../../cornerstone3D/tsconfig.json
).
There is also a debug mode with verbose logging:
ts2esm --debug
[!WARNING]
Make sure you have a backup (in Git or similar) of your code as "ts2esm" will modify your source code.
[!IMPORTANT]
Use TypeScript 5.2 or later as there have been breaking changes to the Node.js settings, which you don't want to miss.
[!IMPORTANT]
Since TypeScript 5.3 import assertions are replaced with import attributes.
This workflow migrates a CommonJS project and checks its types:
# Build your project
npx tsc
# Check your types
npx @arethetypeswrong/cli --pack .
# Convert to ESM
npx ts2esm tsconfig.json
# Rebuild your project
npx tsc
# Check your types again
npx @arethetypeswrong/cli --pack . --ignore-rules cjs-resolves-to-esm
Watch this 5-minute video and learn how to migrate from CommonJS to ESM:
Here you can see the transformations that ts2esm
applies.
Before:
const fs = require('node:fs');
const path = require('path');
After:
import fs from 'node:fs';
import path from 'path';
Before:
const Benny = 1;
const Code = 2;
module.exports = Benny;
module.exports.Code = Code;
After:
const Benny = 1;
const Code = 2;
export default Benny;
export {Code};
Before:
import {AccountAPI} from '../account';
import {RESTClient} from './client/RESTClient';
import {removeSuffix} from '@helpers/removeSuffix';
After:
import {AccountAPI} from '../account/index.js';
import {RESTClient} from './client/RESTClient.js';
import {removeSuffix} from '@helpers/removeSuffix.js';
Before:
export * from './account';
export * from './UserAPI';
After:
export * from './account/index.js';
export * from './UserAPI.js';
Before:
import listAccounts from '../test/fixtures/listAccounts.json';
After:
import listAccounts from '../test/fixtures/listAccounts.json' with {type: 'json'};
Before:
import styles from './MyComponent.module.css';
After:
import styles from './MyComponent.module.css' with {type: 'css'};
The ts2esm
program adjusts your relative imports, adding extensions like index.js
or .js
to make them ESM-compatible. Say goodbye to import errors such as TS2305, TS2307, TS2834, and TS2835!
Errors that get automatically fixed (🛠️):
TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module needs an import assertion of type "json"
error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Consider adding an extension to the import path.
error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'.
With ESM, you can no longer use Node.js objects like __filename
or __dirname
. Here is a simple snippet to replicate their behavior using the import.meta property:
import path from 'node:path';
import url from 'node:url';
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
This program was born from an inspiring conversation I had with Basarat Ali Syed. I recommend checking out Basarat's coding tutorials. 👍
- ts2esm got highlighted in Deno's article on How to convert CommonJS to ESM
- ts2esm helped migrating cornerstonejs/cornerstone3D from CommonJS to ESM
Ideally, the extension change would be available as a codefix in TypeScript itself. Then all conversions could be applied using ts-fix.