Skip to content

Instantly share code, notes, and snippets.

@dannyh79
Forked from khalidx/node-typescript-esm.md
Created November 24, 2023 02:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dannyh79/fd030156e4d4701538c11bb6a0d9c16c to your computer and use it in GitHub Desktop.
Save dannyh79/fd030156e4d4701538c11bb6a0d9c16c to your computer and use it in GitHub Desktop.
A Node + TypeScript + ts-node + ESM experience that works.

The experience of using Node.JS with TypeScript, ts-node, and ESM is horrible.

There are countless guides of how to integrate them, but none of them seem to work.

Here's what worked for me.

Just add the following files and run npm run dev. You'll be good to go!

package.json

{
  "private": true,
  "type": "module",
  "exports": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "sideEffects": false,
  "files": [
    "./dist/"
  ],
  "engines": {
    "node": "^20.9.0",
    "npm": "^10.1.0"
  },
  "scripts": {
    "dev": "node --no-warnings --enable-source-maps --loader ts-node/esm src/index.ts"
  },
  "dependencies": {},
  "devDependencies": {
    "@sindresorhus/tsconfig": "^5.0.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.2.2"
  }
}

tsconfig.json

{
  "extends": "@sindresorhus/tsconfig",
  "compilerOptions": {
    "outDir": "./dist/",                              /* Specify an output folder for all emitted files. */
    "lib": ["ES2022"],
    "target": "ES2022",
    "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */    
    "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    "esModuleInterop": true
  },
  "include": [
    "./src/**/*.ts"
  ],
  "ts-node": {
    "esm": true,
    "transpileOnly": true,
    "files": true,
    "experimentalResolver": true
  }
}

src/utilities/node.ts

Some utilities for getting similar behavior as __filename, __dirname, and require.main === module in Node.JS CommonJS.

This file is optional.

import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
import { argv } from 'node:process'
import { createRequire } from 'node:module'

/**
 * This is an ESM replacement for `__filename`.
 * 
 * Use it like this: `__filename(import.meta)`.
 */
export const __filename = (meta: ImportMeta): string => fileURLToPath(meta.url)

/**
 * This is an ESM replacement for `__dirname`.
 * 
 * Use it like this: `__dirname(import.meta)`.
 */
export const __dirname = (meta: ImportMeta): string => dirname(__filename(meta))

/**
 * Indicates that the script was run directly.
 * This is an ESM replacement for `require.main === module`.
 * 
 * Use it like this: `isMain(import.meta)`.
 */
export const isMain = (meta: ImportMeta): boolean => {
  if (!meta || !argv[1]) return false
  const require = createRequire(meta.url)
  const scriptPath = require.resolve(argv[1])
  const modulePath = __filename(meta)
  return scriptPath === modulePath
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment