-
-
Notifications
You must be signed in to change notification settings - Fork 166
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
Surprising module resolution behavior vs. esbuild and bun #442
Comments
Thank you for your detailed issue report, @andrewbranch. Your insight and feedback are highly valued. Regarding tsx's perceived functionalityOriginally, tsx was marketed as an esbuild powered runtime. However, I've shifted away from this description to prevent misunderstandings. Many people filed bugs expecting tsx to mimic esbuild's behavior exactly. In reality, tsx only leverages esbuild for transforming TypeScript (and ESM syntax within CommonJS environments). I haven't made explicit comparisons to bun, but I acknowledge the need to refine our branding & messaging. Clarifying what users can expect from tsx is a priority I hope to address in early 2024. Regarding the first issue with
|
Thank for the pointer to #239, I’ll follow that!
Honestly, I don’t have a strong mental model around what’s really possible with the loader APIs, so I wasn’t implying an implementation suggestion here. Just changing the
TypeScript files can import and export not just values, but types too. CommonJS constructs use expression-level syntax that wouldn’t have made sense for types: interface Foo {}
module.exports = Foo; // AssignmentExpression, Foo must be a value
const SomeType = require("a"); // VariableDeclaration, SomeType declares a value Using alternative syntax made it more clear that (a) these constructs would be transformed before emit, and (b) they can declare meanings besides values. So |
Acknowledgements
Minimal reproduction URL
https://github.com/andrewbranch/tsx-module-resolution
Version
v4.6.2
Node.js version
v21.1.0
Package manager
npm
Operating system
macOS
Problem & Expected behavior
I uploaded a repo instead of using StackBlitz because it looks like StackBlitz doesn’t support Bun yet, and I wanted to use it as a point of comparison.
This is probably not a bug per se but I wanted to raise this as a design issue that I think should be considered.
Module resolution of node_modules packages does not work how I expected, and differs from what both esbuild and Bun do. It looks like tsx is choosing whether to transform the input to CommonJS based on the file extension / package.json
"type"
, and then performing module resolution from the transformed syntax. So, when you runnpm run tsx-ts
in the provided repro, the import declaration gets invisibly transformed to arequire
, and therefore loads the"require"
conditional export ofpkg
. But on the same code, esbuild and Bun both load the"import"
conditional export.Also unlike esbuild and Bun, the behavior changes based on the file extension or package.json
"type"
of the importing file. When we try the same code from a.mts
file (npm run tsx-mts
), the explicitrequire
call fails because it’s not processed by tsx; it’s passed on to Node.js in an ESM file.npm run tsx-ts
{ import: 'import.mjs', require: 'require.js', dynamicImport: 'import.mjs' }
{ import: 'require.js', require: 'require.js', dynamicImport: 'import.mjs' }
npm run tsx-mts
{ import: 'import.mjs', require: 'require.js', dynamicImport: 'import.mjs' }
All this behavior just falls out of Node.js’s behavior, but it’s not clear to me why tsx can’t or won’t override Node.js here and hide more of the implementation details. I think Bun implemented the intuitive behavior here, and I’ve heard many people suggest tsx as a way to bring Bun-style interop to Node.js, but it seems like the two are fairly far apart on how interop should work.
Moreover, there is no tsconfig.json that can represent what tsx is doing with module resolution. I had previously thought
--module esnext --moduleResolution bundler
(and soon,--module preserve
) were accurate settings, but this difference in conditional"exports"
resolution proved me wrong. I’ll have to update the TypeScript docs but it seems there’s no great suggestion I can make for how to type check code written for tsx—--module nodenext
is stricter than necessary, but--moduleResolution bundler
is inaccurate.Contributions
The text was updated successfully, but these errors were encountered: