working node typescript boilerplate in 2024?

103 views Asked by At

Updated - some workarounds and incantations using the very latest node at time of writing.

  • use node 20.6 or above
  • don't use nodemon, ts-node esm/loader etc directly, instead use below
  • avoid chalk and other ESM only packages

tsconfig

{
    "include": [
        "./src/**/*"
    ],
    "exclude": [
        "**/*.test.*",
        "node_modules"
    ],
    "ts-node": {
        // Tell ts-node CLI to install the --loader automatically
        "esm": true
    },
    "compilerOptions": {
        "target": "ESNext",
        "module": "commonjs",
        "sourceMap": true,
        "outDir": "dist",
        "strict": true,
        "lib": [
            "esnext"
        ],
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
        "skipLibCheck": true /* Skip type checking all .d.ts files. */
    }
}

package.json do NOT use type: module

  "engines": {
    "node": ">=20.6.0"
  },
  "scripts": {
    "dev": "node --watch -r ts-node/register src/server.ts",
    

---- for reference to my future self ----

All my old node/ts projects seem to throw compiler errors as I update node.

Can someone recommend a reference node+typescript boilerplate that works in 2024?

  • it should compile (!!)
  • it should hot reload in dev mode
  • there should be a test lib that works (jest/ava/anything)
  • able to import other packages

Seems reasonable?

I started with this prisma template (they know TS pretty well) https://github.com/prisma/prisma-examples/tree/latest/typescript/rest-express

but it breaks as soon as I add some packages.

for development

ts-node-esm src/index.ts
> ReferenceError: exports is not defined in ES module scope

so remove "type": "module" from the package.json

Error [ERR_REQUIRE_ESM]: require() ...

ts-node src/index.ts

> TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/dc/dev/hacks/kbxt/api/src/index.ts

maybe the node version broke everything?

$ nvm use --lts

Now using node v20.11.1 (npm v10.2.4)

$ ts-node src/index.ts
TypeError: Unknown file extension ".ts" for ...

npm i -g ts-node@latest

changed 20 packages in 391ms
➜  api git:(dc/vidgraph) ✗ ts-node -v
v10.9.1

$ node -v
v20.11.1


$ npx tsc
node_modules/@types/node/globals.d.ts:6:76 - error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?

/// dozens of similar errors

$ npm run build

> [email protected] build
> tsc

node_modules/@types/node/globals.d.ts:6:76 - error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?

ok let's add that to the tsconfig

"moduleResolution": "node",

bingo, ok now we can at least compile.

 npm run dev

> [email protected] dev
> ts-node src/index.ts

TypeError: Unknown file extension ".ts" for /Users/dc/dev/hacks/kbxt/api/src/index.ts

I recall some voodoo way to use a loader, let's try that

$ nodemon --exec 'ts-node-esm' src/index.ts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /Users/dc/dev/hacks/kbxt/api/src/index.ts

google it

says to remove type: module but that doesn't work.

read this page

confused.

npm run build

> [email protected] build
> tsc

$ node dist/index.js
(node:57981) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/dc/dev/hacks/kbxt/api/dist/index.js:1
import express from "express";
^^^^^^

OK so put type: module back.

 node dist/index.js
node:internal/modules/esm/resolve:264
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/dc/dev/hacks/kbxt/api/dist/utils/Clog' imported from /Users/dc/dev/hacks/kbxt/api/dist/index.js

OK so I can't even run the dist compiled output now.

try adding some

  "moduleDirectories": [
    "node_modules",
    "src"
  ]

nope.

read this

$ node --loader ts-node/esm dist/index.js
(node:58752) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)

node:internal/process/esm_loader:34
      internalBinding('errors').triggerUncaughtException(

read this

OH so maybe chalk is one of those militant ESM packages that will only work with JS and screws up typescript projects? Like cuid and a few others.

npm uninstall chalk
npm i [email protected]

OK got it.

So we're at a point in the JS ecosystem where certain packages completely break typescript, and the maintainers are more focused on pure JS and ESM future?

I guess I'll leave this here as a warning.

Is there a better way to figure this out in future? Does the TS team need to basically fork major packages to maintain versions that work with typescript?

OK so ...

$ npm run build

> [email protected] build
> tsc

$ node dist/index.js
(node:59510) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/dc/dev/hacks/kbxt/api/dist/index.js:1
import express from "express";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:77:18)
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.11.1

so add type: module back and try again.

$ npm run build

> [email protected] build
> tsc

$ node dist/index.js
node:internal/modules/esm/resolve:264
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/dc/dev/hacks/kbxt/api/dist/utils/Clog' imported from /Users/dc/dev/hacks/kbxt/api/dist/index.js
    at finalizeResolution (node:internal/modules/esm/resolve:264:11)
    at moduleResolve (node:internal/modules/esm/resolve:917:10)
    at defaultResolve (node:internal/modules/esm/resolve:1130:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:396:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:365:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:240:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
    at link (node:internal/modules/esm/module_job:84:36) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///Users/dc/dev/hacks/kbxt/api/dist/utils/Clog'
}

Node.js v20.11.1

seems like we're closer but the mirage of working TS is still on the horizon in 2024

python and fast api beckons...

at point of pausing I had

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext", //defines what sort of code ts generates
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": [
      "esnext",
    ],
    "esModuleInterop": true,
    "moduleResolution": "node",
  }
}

package.json

{
  "name": "ts-test",
  "version": "1.0.0",
  "license": "MIT",
  "type": "module",
  "scripts": {
    "dev": "ts-node src/index.ts",
    "build": "tsc"
  },
  "dependencies": {
    "chalk": "^4.1.2",
    "express": "4.18.3"
  },
  "devDependencies": {
    "@types/express": "4.17.21",
    "@types/node": "20.11.30",
    "nodemon": "^3.1.0",
    "ts-node": "^10.9.2",
    "typescript": "5.4.3"
  },
  "moduleDirectories": [
    "node_modules",
    "src"
  ]
}
1

There are 1 answers

0
dcsan On

Updated - some workarounds and incantations using the very latest node at time of writing.

use node 20.6 or above don't use nodemon, ts-node esm/loader etc directly, instead use below avoid chalk and other ESM only packages tsconfig

{
    "include": [
        "./src/**/*"
    ],
    "exclude": [
        "**/*.test.*",
        "node_modules"
    ],
    "ts-node": {
        // Tell ts-node CLI to install the --loader automatically
        "esm": true
    },
    "compilerOptions": {
        "target": "ESNext",
        "module": "commonjs",
        "sourceMap": true,
        "outDir": "dist",
        "strict": true,
        "lib": [
            "esnext"
        ],
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
        "skipLibCheck": true /* Skip type checking all .d.ts files. */
    }
}