Skip to content

Instantly share code, notes, and snippets.

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 cmaas/db2e0fba1e2a3fd8991a9d7bbf4aa1ad to your computer and use it in GitHub Desktop.
Save cmaas/db2e0fba1e2a3fd8991a9d7bbf4aa1ad to your computer and use it in GitHub Desktop.
I write stuff in TypeScript and used Tape as a test runner. After converting my project to use ECMAScript modules (ESM), tape stopped working, causing quite a bit of headache.

2023-09-27 How to use Tape with TypeScript and ESM

I write stuff in TypeScript and used Tape as a test runner. After converting my project to use ECMAScript modules (ESM), tape stopped working, causing quite a bit of headache.

Goals

  • Write tests in TypeScript. Tests live next to source files.
  • Do not compile tests to the dist directory.
  • Use ts-node to transpile tests on the fly and use tape as the test runner.

What doesn't work anymore

  • Use ts-node tape to run stuff. Results in various errors, because tape isn't a true ESM package (I think).

Structure

I converted my project to ESM based on Sindre Sorhus' guide.

This is an excerpt of my package.json:

{
    //...
    "type": "module",
    "scripts": {
        "test": "node --loader ts-node/esm node_modules/tape/bin/tape 'src/**/*.spec.ts' | tap-diff",
    }
    // ...
}

ts-config.json:

{
    "compilerOptions": {
        "moduleResolution": "node16",
        "module": "node16",
        "target": "es2017",
        "declaration": true,
        "outDir": "./dist",
        "lib": ["dom", "es2017"],
        "baseUrl": "./src",
    },
    "include": ["./src/**/*.ts"],
    "exclude": ["node_modules", "dist", "src/**/test"],

    "ts-node": {
        "transpileOnly": true,
        "files": true,
        "experimentalResolver": true
    }
}

Unfortunately, it doesn't work right out of the box:

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1153:20)
    at Module._compile (node:internal/modules/cjs/loader:1205:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1295:10)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Module._load (node:internal/modules/cjs/loader:938:12)
    at Module.require (node:internal/modules/cjs/loader:1115:19)
    at require (node:internal/modules/helpers:130:18)
    at importOrRequire (./node_modules/tape/bin/import-or-require.js:14:2)

The culprit is tape. It doesn't like the .ts-extension. I had to patch tape this way:

Edit node_modules/tape/bin/import-or-require.js line 11 (in tape 5.7.0) and tell tape to also load .ts-files. Because it's just the filename. It'll be transpiled on the fly via the ts-node loader.

if (ext === '.mjs' || (ext === '.js' && getPackageType.sync(file) === 'module') || (ext === '.ts' && getPackageType.sync(file) === 'module')) {

After that, it runs flawlessly. Don't like editing files in node_modules? I didn't either, but since I learned about the extremely simple patch-package, I use it quite often to reliably change small errors. Read more about it here and here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment