Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

importing and exporting an interface fails for type module in package.json and ESXXXX module in tsconfig.json #272

Closed
1 task
stefanrusu-loctax opened this issue Jul 19, 2023 · 5 comments
Labels
bug Something isn't working

Comments

@stefanrusu-loctax
Copy link

Bug description

I tried tsx against a codebase where an interface is imported by a module, then re-exported. Basically it fails with:

npm start

> start
> npx tsx index.ts

~/source/tsx-repro/index.ts:1
import { Interf } from './interf.js';
         ^

SyntaxError: The requested module './interf.js' does not provide an export named 'Interf'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)

See linked gist with the minimal context to reproduce this issue.

This doesn't happen with ts-node in ESM mode nor with esbuild. Basically, I piped the esbuild's STDOUT to node's STDIN and the whole codebase runs as expected. So, this behaviour is particular to tsx.

Basically, anything from ES2015 to ESNext compilerOptions module in tsconfig.json and package.json "type": "module" triggers this failure when importing and exporting an interface. It does not happen if it imports and exports a function for example.

Reproduction

https://gist.github.com/stefanrusu-loctax/8e02e07bc18d00a29c33b045d93a15a9

Environment

System:
    OS: macOS 12.6.7
    CPU: (8) arm64 Apple M1 Pro
    Memory: 91.78 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 18.16.0 - ~/.nodenv/versions/18.16.0/bin/node
    npm: 9.5.1 - ~/.nodenv/versions/18.16.0/bin/npm
    pnpm: 7.32.2 - ~/.nodenv/versions/18.16.0/bin/pnpm
  npmPackages:
    tsx: ^3.12.7 => 3.12.7

Can you work on a fix?

  • I’m interested in opening a pull request to address this issue.
@privatenumber
Copy link
Owner

This is expected behavior and is consistent with TypeScript:

  • interf.ts exports a type (an interface), which doesn't have a runtime value so it's stripped by the TypeScript compiler.
  • index.ts imports Interf but the compiler isn't sure if Interf is a type or value, so it keeps it.
  • Loading index.ts fails because Interf is not resolvable

The fix here is declaring that you're importing a type:

import type { Interf } from './interf.js';
export type { Interf }

@stefanrusu-loctax
Copy link
Author

stefanrusu-loctax commented Sep 26, 2023

This is expected behavior and is consistent with TypeScript

Besides ts-node and esbuild, tsc doesn't err on that. swc doesn't either.

bun is the only other tool that returns:

SyntaxError: Indirectly exported binding name 'Interf' is not found.

The de-facto compiler and the only usable TS type checker, aka tsc itself, doesn't return an error.

esbuild strips the declarations as they are immaterial for the JS runtime. This is how it behaves:

❯ npx esbuild --bundle --format=esm --outfile=esbuild.js ./index.ts

  esbuild.js  0b

⚡ Done in 2ms
❯ cat esbuild.js
[empty file]

@privatenumber
Copy link
Owner

Hey, you're coming on a little strong.

When asking a stranger for help online, consider rephrasing your message to sound less accusatory. If you feel entitled to some kind of result, maybe you should at least support the project by contributing or sponsoring first.

Happy to discuss the differences in what you're seeing if you edit your comment to be respectful.

@stefanrusu-loctax
Copy link
Author

Apologies for that. Bear in mind that different people come from different backgrounds and various degrees of diversity. Not everyone speaks English as 1st language. I removed my opinion (and I made sure it was phrased as opinion) and kept the statements of fact.

I am not asking for help, merely reporting a bug that I have encountered whilst trying to use tsx as that behaviour is surprising compared to other tooling from this space (except for bun, but bun is unusable for a lot of projects due to lack of http2).

For me it was very time consuming to detect the root cause and wrap up the minimum amount of code to reproduce this problem which is isolated to tsx. Therefore, I believe classifying this as expected behaviour read a bit too dismissive. I find that rude particularly when spending a lot of time investigating the cause. The pain of miscommunication is shared.

As a project maintainer you are entitled to do whatever you wish. However, people are also entitled to their opinions, particularly when that's supported by the behaviour of most tooling in this space.

Overall, I understand your position. I stopped developing free software particularly due to people demanding that I'd implement things in my own projects based on their desired outcome and having to justify/defend why I made particular choices despite the project goals being clearly stated. I am making no such demands.

@privatenumber
Copy link
Owner

The problem was the accusatory tone of your message that I'm being misleading, all while being misinformed.

You filed an issue on the project to seek support so you're asking for help. If it was merely an FYI, you'd be ok with a closed issue + explanation. Instead, you argued back prompting for more explanation (basically more help).


Anyway, here's why it's expected behavior:

You're comparing a bundlers (tsc, esbuild, swc) to runtimes (tsx, ts-node w/ transpileOnly) which are completely different. Bundlers analyze relevant files when compiling a module. Runtimes don't have this advantage because modules are compiled in isolation. ts-node (without transpileOnly) compiles the entire project with tsc before running it, which is why it's slow.

With isolatedModules enabled, tsc will error:

Further explanation in esbuild: evanw/esbuild#1941 (comment)

You might also be interested to learn the motivation for the import/export type syntax is precisely this use-case: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export

From the PR microsoft/TypeScript#35200:

At the same time, users who transpile their code file by file (as in Babel, ts-loader in transpileOnly mode) sometimes have the opposite problem, where a re-export of a type should be elided, but the compiler can’t tell that the re-export is only a type during single-file transpilation

BTW, your esbuild snippet doesn't demonstrate anything if you don't share what you're compiling. A link to the esbuild REPL would've been more insightful.

Repository owner locked as resolved and limited conversation to collaborators Sep 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants