tape-testing / tape

tap-producing test harness for node and browsers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Tape cannot resolve paths with file extensions

acolytec3 opened this issue · comments

First off, our monorepo has very successfully used tape as our test runner for many years so thanks for your continued work on it!

My question is related to how tape resolves internal imports. Our monorepo is Typescript bases and runs our tape tests via ts-node using commands like tape -r ts-node/register -- './path/to/my/test.spec.ts' and we are currently in the process of updating our code to output both CJS and ESM builds. In an attempt to ensure our code can run natively in browser without requiring a bundler, we're adding file extensions (i.e. .js) to all of the internal imports within our codebase. In doing so, tape suddenly is no longer able to resolve any of these paths when we run the tests. Oddly enough, when we run the web version of our tests using karma-typescript to first bundle up the code, tape runs them just fine (presumably because karma bundles everything up first).

Our default build process is still using CJS so there is no "type": "module" in our source code (this gets added to the ESM output from Typescript in a post build step) so I'm assuming the import-or-require module is somehow unsure of how to resolve an import in CJS that looks like import * from '../src/index.js' but I'm not sure why this is the case when import * from '../src' works just fine.

Do you have any instincts on what is going wrong here?

tape only resolves paths when passed on the command line; node is doing the execution and resolution of require and import.

(type module is bad anyways; ESM files should be .mjs, not .js, and unless you have zero deps, your code will require a bundler anyways, so it's probably not a good idea to optimize for that)

Are you saying that tape -r ts-node/register -- './path/to/my/test.spec.ts' used to work, and now doesn't? Can you elaborate on how it fails?

The best way to recreate it would be to clone our monorepo and then navigate to [repo root directory]/packages/common and run npm run test (which uses tape -r ts-node/register to run all the tests for one of our packages) on the master branch and then on the add-file-extensions branch.

It produces output like below on the branch:

Error: Cannot find module './eips/index.js'
Require stack:
- /home/jim/development/ethjs/packages/common/src/common.ts
- /home/jim/development/ethjs/packages/common/test/chains.spec.ts
- /home/jim/development/ethjs/node_modules/tape/bin/import-or-require.js
- /home/jim/development/ethjs/node_modules/tape/bin/tape
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:956:15)
    at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue [as _resolveFilename] (/home/jim/development/ethjs/node_modules/@cspotcode/source-map-support/source-map-support.js:811:30)
    at Function.Module._load (node:internal/modules/cjs/loader:804:27)
    at Module.require (node:internal/modules/cjs/loader:1022:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/home/jim/development/ethjs/packages/common/src/common.ts:17:1)
    at Module._compile (node:internal/modules/cjs/loader:1120:14)
    at Module.m._compile (/home/jim/development/ethjs/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
    at Object.require.extensions.<computed> [as .ts] (/home/jim/development/ethjs/node_modules/ts-node/src/index.ts:1621:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/home/jim/development/ethjs/packages/common/src/common.ts',
    '/home/jim/development/ethjs/packages/common/test/chains.spec.ts',
    '/home/jim/development/ethjs/node_modules/tape/bin/import-or-require.js',
    '/home/jim/development/ethjs/node_modules/tape/bin/tape'
  ]
}

That error is coming from /home/jim/development/ethjs/packages/common/src/common.ts, though, not from tape.

I think the issue is that in order for ts-node/register to work, it has to be evaluated before any file that consumes .ts files runs - however, tape's binary does its -r requires, and its file evaluations, in the same file. However, bin/tape does things quite intentionally in an unidiomatic order specifically to account for this use case.

Thanks for the repro tips, I'll see what I can do.

On both branches, i get Error: Cannot find module 'ts-node/register' from '$PWD/ethereumjs-monorepo/packages/common'. Should ts-node be a dev dep in that directory's package.json?

(when i npm install --no-save ts-node, then i get a TS type error on both branches - is @types/tape installed as a dev dep?)

Ah, sorry, you do npm i from the monorepo root and that installs ts-node as a general dev dependency. If you then go to packages/common, you'll see the error on that branch. You get similar errors with some of the other packages. But, if you run npm run test:browser from packages/common, the tests pass just fine.

ok, so the error I get is:

Error: Cannot find module './eips/index.js'
Require stack:
- $PWD/packages/common/src/common.ts
- $PWD/packages/common/test/chains.spec.ts
…

and $PWD/packages/common/src/eips/index.ts is the file that exists, not .js. Note that I get the same failure sooner if i update $PWD/packages/common/test/chains/index.ts so that import { Common } from '../src/common' becomes import { Common } from '../src/common.js'.

Given that ts-node/register is the package that's supposed to be resolving those, I suspect it's related to the rapidly evolving handling for native ESM in ts-node itself combined with TS.

Have you tried filing an issue on ts-node?

Yep, that's the same as I get. Thanks for looking into it. I hadn't considered ts-node as a culprit but that makes sense given above. Will close here for now.

type module is bad anyways

@ljharb Can you elaborate your thoughts on this? I am on the outside, looking in on the JS ecosystem's painful move to ESM, and its hard to get an understanding of what is really going on.

I assumed "type" within package.json was to ease the migration of CJS projects to ESM by eliminating another pain point (renaming file extensions).

That doesn’t eliminate a pain point, it adds a new one - because instead of renaming file extensions, all your tools have to change their parse goal for “.js”.