Cannot access ambient const enums when run by `tsx` but no problem by `ts-node`
Problem
I declare enum in typings like it:
// src/typings/main/index.d.ts
declare const enum Test {
hello = 'world',
}
and tsconfig.json like it:
{
"compilerOptions": {
"typeRoots": ["src/typings"]
}
}
and then I try to use enums like it:
// src/main.ts
console.log(Test.hello);
then it will be ReferenceError: Test is not defined.
Expected behavior
print world just like if I run it by ts-node.
I run it by ts-node without problem, so I think may it is a bug for tsx ?
ts-node behavior: https://codesandbox.io/p/sandbox/tsx-bug-in-ts-node-r9h985
tsx behavior: https://codesandbox.io/p/sandbox/tsx-bug-rl55dz
because
StackBlitzdoes not supportts-node, I useCodeSandbox.
Minimal reproduction URL
https://codesandbox.io/p/sandbox/tsx-bug-rl55dz
Version
v3.12.6
Node.js version
v16.17.0
Package manager
pnpm
Operating system
Windows
Contributions
- [ ] I plan to open a pull request for this issue
- [ ] I plan to make a financial contribution to this project
I think it might be because of this: https://github.com/esbuild-kit/tsx#does-it-have-any-limitations
esbuild does not support typeRoots so i cannot use it with tsx, but is there any way to slove it?
Same issue. Importing a const enum from a private node module. ts-node works with the declare const enum output but not tsx.
Same, issue, @marcghorayeb any workaround to this ?
Seems like this is still a problem, I just encountered this.
I have library that have .d.ts with const enum to map string to Enum, just like OP:
// some-lib/index.d.ts
export const enum TestEnum {
Foo = 'foo',
Bar = 'bar'
}
export function functionRequiresEnum(param: TestEnum);
export default functionRequiresEnum;
When I use this const enum with tsc compiler, this is the result:
Input (TypeScript):
import functionRequiresEnum, { TestEnum } from '../some-lib/index.js'; // This uses .d.ts mentioned above
functionRequiresEnum(TestEnum.Foo);
Output (JavaScript):
import functionRequiresEnum from '../some-lib/index.js';
functionRequiresEnum("foo" /* TestEnum.Foo */);
tsc correctly converts Enum to string, because it is const enum and TestEnum does not actually exists in JavaScript file in first place.
But on other hand, tsx does bad job on it:
import functionRequiresEnum, { TestEnum } from '../some-lib/index.js';
^
SyntaxError: The requested module '../some-lib/index.js' does not provide an export named 'TestEnum'
at ModuleJob._instantiate (node:internal/modules/esm/module_job:134:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:217:5)
at async ModuleLoader.import (node:internal/modules/esm/loader:323:24)
at async loadESM (node:internal/process/esm_loader:28:7)
at async handleMainPromise (node:internal/modules/run_main:120:12)
Node.js v21.7.2
TestEnum is a const enum and should be expanded before evaluation, which in this case did not.
This is same for namespace imports:
import * as someLib from '../some-lib/index.js';
someLib.functionRequiresEnum(someLib.TestEnum.Foo);
^
TypeError: Cannot read properties of undefined (reading 'Foo')
at <anonymous> (/tmp/mre-tsx/src/index.ts:3:47)
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:323:24)
at async loadESM (node:internal/process/esm_loader:28:7)
at async handleMainPromise (node:internal/modules/run_main:120:12)
Node.js v21.7.2
My best guess is that tsx doesn't currently handles "inline" definitions like const enum does.
I have to use slow tsc compiler for now...
Arch Linux rolling
Linux DESKTOP-CMM2KTE-ARCH 6.8.2-arch2-1 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
Intel Core i9-9900 (8C16T x86_64)
As per the Contribution guide, I've hid the comments that aren't productive towards landing a fix.
The ts-node reproduction doesn't work. Can you fix it? More importantly, I'm interested in a TypeScript (tsc) reproduction as that's what tsx aims have parity with.
> demo@ start /workspace
> ts-node --esm src/index.ts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /workspace/src/index.ts
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
at defaultLoad (node:internal/modules/esm/load:143:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async Hooks.load (node:internal/modules/esm/hooks:448:20)
at async MessagePort.handleMessage (node:internal/modules/esm/worker:196:18) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
ELIFECYCLE Command failed with exit code 1.
Hi @privatenumber, I just published repo that contains minimal reproducible example of this bug: https://github.com/MeemeeLab/tsx-mre-issue-321
Just install dependencies by npm i and use scripts:
- TypeScript
tsccompiler:npm run start-tsc - node-ts compiler:
npm run start-node-ts - TSX compiler:
npm run start-tsc
Expected results and actual results are documented in my previous post, which apparently you marked it as off-topic.
Thanks for the reproduction. Being able to play with the result (instead of reading a description) is extremely helpful.
The limitation is simply because tsx doesn't read .d.ts files. But tsx should support them in these cases, and I'd be happy to accept a PR to look up the adjacent .d.ts file for .js files. I think typeRoots could also be supported too but as a follow up (and with lower priority).
I don't want to get into this as it's off-topic, but I hid your previous comment because the interpretation of the problem was confusing to follow without being able to see the problem. And as per the Contribution guide, discussing workarounds (tsc) isn't exactly productive towards landing a fix within tsx.