tsx
tsx copied to clipboard
ERR_MODULE_NOT_FOUND importing mts from mjs file from mts file
Bug description
tsx index.mts
to run
index.mts
imports file.mjs
by import x from "./file.mjs"
file.mjs
imports rootfile.mts
by import x from "./rootfile.mjs"
I expect this to resolve rootfile.mts
and work.
❯ tsx index.mts
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/projects/rootfile.mjs' imported from /home/projects/node-war8wy/file.mjs
at get (https://node-war8wy.w-corp.staticblitz.com/blitz.a0ea75dd36396805bd36e49d0b54d86602583e15.js:6:292488)
at U@file:///home/projects/node-war8wy/node_modules/ (esbuild-kit/esm-loader/dist/index.js:19:1634)
at <anonymous> (<anonymous>) {
code: 'ERR_MODULE_NOT_FOUND'
}
Reproduction
https://stackblitz.com/edit/node-war8wy has the reproduction
Environment
System:
OS: Linux 5.0 undefined
CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 0 Bytes / 0 Bytes
Shell: 1.0 - /bin/jsh
Binaries:
Node: 16.14.2 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 7.17.0 - /usr/local/bin/npm
npmPackages:
tsx: ^3.11.0 => 3.11.0
Can you contribute a fix?
- [ ] I’m interested in opening a pull request for this issue.
Thanks for the reproduction.
The .mjs
-> .mts
resolution isn't happening because it's not coming from a TypeScript file.
Do you know if TypeScript supports that?
BTW In ./file.mjs
, it should be importing from ./rootfile.mjs
instead of ../rootfile.mjs
.
BTW In
./file.mjs
, it should be importing from./rootfile.mjs
instead of../rootfile.mjs
.
Doh, missed that in my simplification.
Do you know if TypeScript supports that?
ts-node
does, and tsc
does this:
tsc --noEmit --allowJs file.mjs index.mts rootfile.mts
(no output)
but if I rename rootfile.mts
to rootfile.bad
,
❯ tsc --noEmit --allowJs file.mjs index.mts rootfile.mts
error TS6053: File 'rootfile.mts' not found.
The file is in the program because:
Root file specified for compilation
Found 1 error.
Would love to see an update on this. This project is essentially unusable in any project that isn't 100% typescript
Given this is a personal project, I mainly work on issues that I'm impacted by.
If you'd like me to prioritize this issue, you can incentivize it by sponsoring me.
To anyone stumbling on this issue, you can bypass it somehow by writing a custom node loader leveraging tsx resolve/load implementation.
Place that somewhere like ./node/loader.js
:
import { load, resolve as resolveTsx } from 'tsx';
export { load };
export async function resolve(specifier, ctx, defaultResolve) {
// JS imports can point to JS or TS files, so we need to check both.
if (specifier.endsWith('.js')) {
const specifierTs = specifier.replace(/\.js$/, '') + '.ts';
try {
return await resolveTsx(specifierTs, ctx, defaultResolve);
} catch (err) {
return resolveTsx(specifier, ctx, defaultResolve);
}
}
// Fallback for other files (like module imports, json, etc.)
return resolveTsx(specifier, ctx, defaultResolve);
}
And you'll also need to register this loader using node registration protocol, so place the following in ./node/register.js
for example:
import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
register('./node/loader.js', pathToFileURL('./'));
Then instead of calling tsx mystuff.js
directly, use node --import ./node/register.js mystuff.js
.
If using node<20, use node --loader ./node/loader.js mystuff.js
, no need for a registration. This works on node20+ too but with a warning that they may drop support for --loader in the future.
Happy to accept a PR to address this. Basically, tsx should read the allowJs
config, and if it's enabled, treat JS files as TypeScript files during resolution.
Locking thread to direct further dialogue in the form of PRs.
:tada: This issue has been resolved in v4.7.3
If you appreciate this project, please consider supporting this project by sponsoring :heart: :pray: