Workaround for ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING

Node supports stripping types from .ts files, but not for files in node_modules. This post discusses a workaround to that limitation.

I much prefer .js files with JSDoc comments for type-checking over .ts files. None of this tooling/config/complexity is needed when you just write .js files. Alas, I found myself in a situation requiring .ts files, so this post is me writing down what I learned about stripping types from .ts files in node_modules.

The Problem

Node.js supports stripping types from .ts files at runtime (no prerequisite build step required).

I have a .ts-based node repo that can run without issue: node server.ts.

I have another repo that has a dependency on the first repo (consuming it as an NPM package). It needs to run the node server: node node_modules/the-first-repo/server.ts.

But when I try to run that I get the error:

Error [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]: Stripping types is currently unsupported for files under node_modules

This is expected behavior. A proposed built-in workaround seems to be going nowhere. The thought is that NPM packages should publish their .js artifacts, not their .ts files.

But in this specific scenario I don't want to deal with transforming the .ts files to .js. This is a private NPM package and I control the consumers, so let me just run this thing without the pre-publish build step, eh?

The Workaround

We'll leverage a node loader to strip the types (nothing novel here), and we'll abstract that detail from consumers by turning the first repo into a bin file that consumers can simply run without knowing about the loader.

Steps

  1. Make the first repo leverage a bin file. In package.json:
{
  "name": "the-first-repo",
  "version": "1.0.0",
  "main": "server.ts",
  "bin": {
    "the-first-repo": "./cli.js"
  }
}
  1. Tell Typescript to rewrite relative import extensions (so when it sees an import from a .ts file it'll change it to a .js file). In tsconfig.json:
{
  "compilerOptions": {
    ...
    "rewriteRelativeImportExtensions": true,
  },
}

I think this step is needed, but I just realized I never tried without it. If you try it without this step and it works, please let me know!

  1. Create the cli.js file with a loader that strips types:
#!/usr/bin/env node --import ts-blank-space/register
import "../server.ts";

NOTE: I like this loader because it just "blanks out" the types (doesn't rewrite the file), so line numbers don't change. Easier to debug, no source maps needed! 💪

  1. Make the cli.js file executable: chmod +x cli.js

  2. In the second repo, instead of running node node_modules/the-first-repo/server.ts, run npx the-first-repo.

💥 The consuming repo doesn't know anything about the loader or the fact that it is running .ts files. It just runs the bin file, which runs the server.

Hopefully you found this post helpful, if you have any questions you can find me on Twitter.

Making a Tooltip (with tether arrow) using Popover & Anchor Positioning
PWA Install Pattern with QR Code and Token