tape-testing / tape

tap-producing test harness for node and browsers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Getting tape working with TypeScript and/or ESM

EvHaus opened this issue · comments

I'm trying to add tape to https://github.com/EvHaus/test-runner-benchmarks as a follow up to @ljharb's Twitter post here, but I'm having a really hard time figuring out how to get it to work with a Typescript codebase.

My test file looks like this:

// Alert.test.tsx

import describe from 'tape-describe';
import Alert from '.';
import React from 'react';
import {render} from '@testing-library/react';

describe('<Alert />', (test) => {
	test('should render the given message', (t) => {
		const {getByText} = render(<Alert>Hello World</Alert>);
		t.ok(getByText('Hello World'));
	});
});

And my package.json has "type": "module" set.

1st Attempt (tape)

If I run:

tape tests/Alert.test.tsx

I get:

import describe from 'tape-describe';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1175:20)
    at Module._compile (node:internal/modules/cjs/loader:1219:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1309:10)
    at Module.load (node:internal/modules/cjs/loader:1113:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1137:19)
    at require (node:internal/modules/helpers:121:18)
    at importOrRequire (/Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/import-or-require.js:14:2)
    at /Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/tape:96:8

2nd Attempt (ts-node)

If I switch to ts-node and use:

ts-node ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

import describe from 'tape-describe';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1175:20)
    at Module._compile (node:internal/modules/cjs/loader:1219:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1309:10)
    at Object.require.extensions.<computed> [as .js] (/Users/evhaus/Git/jest-vs-jasmine/node_modules/ts-node/src/index.ts:1608:43)
    at Module.load (node:internal/modules/cjs/loader:1113:32)
    at Function.Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1137:19)
    at require (node:internal/modules/helpers:121:18)
    at importOrRequire (/Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/import-or-require.js:14:2)

3rd Attempt (ts-node --esm)

If I try with ts-node --esm, ala:

ts-node --esm ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /Users/evhaus/Git/jest-vs-jasmine/node_modules/tape/bin/tape
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at nextLoad (node:internal/modules/esm/hooks:735:28)
    at load (/Users/evhaus/Git/jest-vs-jasmine/node_modules/ts-node/dist/child/child-loader.js:19:122)
    at nextLoad (node:internal/modules/esm/hooks:735:28)
    at Hooks.load (node:internal/modules/esm/hooks:380:26)
    at handleMessage (node:internal/modules/esm/worker:165:24)
    at checkForMessages (node:internal/modules/esm/worker:114:28) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

4th Attempt (tape-es)

If I try with tape-es:

tape-es ./node_modules/tape/bin/tape tests/Alert.test.tsx

I get:

node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for /Users/evhaus/Git/jest-vs-jasmine/benchmarks/tape/tests/original/Alert/Alert.test.tsx
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at DefaultModuleLoader.load (node:internal/modules/esm/loader:317:26)
    at DefaultModuleLoader.moduleProvider (node:internal/modules/esm/loader:195:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:219:17)
    at DefaultModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:172:34)
    at DefaultModuleLoader.getModuleJob (node:internal/modules/esm/loader:157:17) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

5th Attempt (ts-node & tape-es)

If I combine ts-node and tape-es, ala:

ts-node --esm ./node_modules/.bin/tape-es tests/Alert.test.tsx

I get:

node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for /Users/evhaus/Git/jest-vs-jasmine/benchmarks/tape/tests/original/Alert/Alert.test.tsx
    at new NodeError (node:internal/errors:399:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:139:38)
    at defaultLoad (node:internal/modules/esm/load:83:20)
    at DefaultModuleLoader.load (node:internal/modules/esm/loader:317:26)
    at DefaultModuleLoader.moduleProvider (node:internal/modules/esm/loader:195:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at #createModuleJob (node:internal/modules/esm/loader:219:17)
    at DefaultModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:172:34)
    at DefaultModuleLoader.getModuleJob (node:internal/modules/esm/loader:157:17) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

I know how much you hate the ESM drama, but I'm out of ideas and I'd really love to add tape to the benchmarks (so it can also serve as possible documentation for those who want to try it). Any pointers?

tape just runs node, so the only extensions it understands by default are js, cjs, or mjs - so if you want jsx/tsx/ts to work directly, you'll have to --require or --import something that can handle those, like babel-node or ts-node.

@EvHaus have you had any progress on using a loader here, or transpiling beforehand?

I haven't spent any more time with it. Would you recommend the transpiling route or the ts-node route? I'm guessing ts-node would be a more realistic workflow from a DX perspective.

For performance, I'd recommend pre-transpiling; for DX, ts-node.

It might be good to do both, because the delta wouldn't be due to tape.

I just turned my tests into TypeScript and in my case it was as easy as:

  1. include index.test.ts in tsconfig.json
  2. running tsc && tape index.test.js

Not great because it requires running TypeScript first, but in my case I was already doing it: fregante/text-field-edit#29

Got it working. The trick was to:

  • Use latest ts-node version 10.9.2
  • Use ts-node --esm
  • Pass -P tsconfig.json to ts-node
  • Remove "type": "module" from package.json

Glad you got it working! type module just makes things worse anyways :-)

Got it working.
Remove "type": "module" from package.json

You got it working by not doing what the issue suggested. Now it's not ESM anymore. It'd be good to keep the issue open. Likewise in my case it worked by not having TS anymore.

Removing type module means that only .mjs is ESM (as it should be).

@fregante maybe i'm confused, help me understand why this should be open and what we need to fix?

what we need to fix?

Documenting how to deal with ESM/TS given that all the tries with ts-node above failed

@fregante if you know the answer to that, a PR to the docs would be most helpful :-)

I prefer to avoid native ESM or native TS, so I'm not the expert here.

tape just runs node, so the only extensions it understands by default are js, cjs, or mjs - so if you want jsx/tsx/ts to work directly, you'll have to --require or --import something that can handle those, like babel-node or ts-node.

@ljharb Would you be able to provide a (non working) example of running tape using node and passing nodes --import flag? I tried running node_modules/tape/bin/tape --help and node_modules/.bin/tape --help but there is no help output and I have no idea in what order I am meant to pass arguments or flags.

The documentation mentions the --require flag and since you mention import, I tried --import but I'm not sure I am even running tape correctly because running tape as above with an empty --require or --import flag doesn't return an error.

Tape: 5.7.5
Nodejs: 22
OS: Linux

@darcyrush that sounds like a great idea to add to https://github.com/tape-testing/tape/tree/HEAD/example !

Specifically, it's something like NODE_OPTIONS='--import=ts-node' tape '**/*.ts', for example.

@ljharb Thank you for the example. I tried to get ts-node working with a work-around I stumbled across in a ts-node issue, but I still wasn't successful.

I tried the following;

NODE_OPTIONS='--import ./ts-node.register.mjs' node_modules/tape/bin/tape 'test/**/*.test.ts'`

With ts-node.register.mjs being

import { pathToFileURL } from "node:url";
import { register } from "node:module";

register("ts-node/esm", pathToFileURL("./"));

I thought that would transpile to JS before reaching tape, but I'm still not really sure about how the 'piping' works.