This is a Minimal Reproducible Example (MRE) demonstrating an issue with tsx when handling package.json exports fields. tsx transpiles TypeScript source files in node_modules instead of respecting the exports field that points to built JavaScript files.
Note: This MRE includes a test using the real npm package @tastytrade/api which demonstrates the issue. The MRE also includes example local packages to show the structure, but the actual failure occurs with real npm packages.
When a package has both:
- TypeScript source files in
lib/directory - Built JavaScript files in
dist/directory package.jsonexportsfield pointing todist/files
Expected behavior: tsx should use the built files from dist/ as specified in the exports field.
Actual behavior with tsx: tsx finds and transpiles the TypeScript source files from lib/ instead, bypassing the exports field. This can cause module resolution failures when those source files import from other packages.
src/test-tastytrade.ts- Main test using real npm package@tastytrade/api(demonstrates the issue)example-package/- Example package structure (for reference only, shows lib/ vs dist/ pattern)lib/index.ts- TypeScript source (should NOT be used)dist/index.js- Built JavaScript (SHOULD be used per exports field)package.json- Package configuration with exports field
dependency-package/- Example dependency using exports field (for reference only)package.json- Project configuration with tsx and @tastytrade/api dependenciestsconfig.json- TypeScript configuration
-
Install dependencies:
npm install
-
Test with tsx using real npm package (demonstrates the issue):
npm run test:tastytrade # or: tsx src/test-tastytrade.tsExpected output: This will fail with:
SyntaxError: The requested module '@dxfeed/dxlink-api' does not provide an export named 'DXLinkFeed' at /path/to/node_modules/@tastytrade/api/lib/quote-streamer.ts:1This demonstrates that tsx is transpiling the source files from
lib/instead of using the built files fromdist/as specified in theexportsfield. -
Test with tsx using local package:
npm run test:tsx # or: tsx src/index.tsNote: With
file:dependencies, this may work because tsx handles local packages differently. The issue manifests with real npm packages. -
Test with Node.js directly (shows correct behavior):
npm run build npm run test:node # or: node dist/index.jsExpected output: Works correctly because Node.js respects the
package.jsonexportsfield and uses the built files fromdist/.
src/test-tastytrade.tsimports from@tastytrade/api- Node.js/tsx resolves
@tastytrade/apiviapackage.jsonexportsfield exportsfield points todist/tastytrade-api.js- Uses the built file from
dist/
src/test-tastytrade.tsimports from@tastytrade/api- tsx finds TypeScript source files in
lib/directory - tsx transpiles
lib/quote-streamer.tsinstead of using the built file fromdist/ - When that transpiled file imports from
@dxfeed/dxlink-api, module resolution fails
// Uses dist/tastytrade-api.js as specified in exports field
import TastytradeClient from '@tastytrade/api';
// ✅ Works correctly// Transpiles lib/quote-streamer.ts instead of using built file from dist/
import TastytradeClient from '@tastytrade/api';
// ❌ Fails with module resolution errorsThe MRE includes a test using @tastytrade/api which demonstrates the issue:
import TastytradeClient from '@tastytrade/api';
console.log('TastytradeClient imported:', typeof TastytradeClient);npm run test:tastytrade
# or: tsx src/test-tastytrade.tsSyntaxError: The requested module '@dxfeed/dxlink-api' does not provide an export named 'DXLinkFeed'
at /path/to/node_modules/@tastytrade/api/lib/quote-streamer.ts:1
Why this happens:
@tastytrade/apihas TypeScript sources inlib/and built files indist/- The
package.jsonexportsfield points todist/tastytrade-api.js - tsx transpiles
lib/quote-streamer.tsinstead of using the built file - When that transpiled file imports from
@dxfeed/dxlink-api, module resolution fails
Workaround:
# Compile first, then run with Node.js
tsc src/test-tastytrade.ts
node src/test-tastytrade.js # Works correctlyThis issue affects packages that:
- Ship both TypeScript source and compiled JavaScript
- Use the
exportsfield to point consumers to built files - Have dependencies that also use
exportsfields
When tsx transpiles the source files, it bypasses the package's intended module resolution, which can cause:
- Module resolution failures
- Incorrect dependency resolution
- Errors about missing exports
@tastytrade/api- Has TypeScript sources inlib/and built files indist/, withexportspointing todist/. When tsx transpileslib/quote-streamer.ts, it fails to resolve imports from@dxfeed/dxlink-apibecause the transpilation context doesn't properly handle theexportsfield.
- Node.js: Any version (exports field support)
- tsx: 4.21.0 (tested version)
- TypeScript: 5.6.3
Until this is fixed in tsx, you can:
- Compile TypeScript first, then run with Node.js directly (as shown in
test:node) - Use
tscto build, thennodeto run (avoids tsx's transpilation) - Avoid packages that have this structure, or ensure they don't ship TypeScript sources
This demonstrates that tsx does not properly respect the package.json exports field when resolving modules, instead preferring to transpile TypeScript source files it finds in node_modules.
- Issue #442: "Surprising module resolution behavior vs. esbuild and bun" - Discusses how tsx determines module type and resolves conditional exports differently than other tools
- Issue #714: "tsconfig rootDir/outDir are not used when resolving subpath imports" - Related to subpath import resolution
- Issue #696: "In monorepo (pnpm workspace), imported package loads no tsconfig.json" - Related to how tsx handles packages in monorepos
Note: No exact match found for this specific issue (transpiling source files instead of respecting exports field). This may be a new issue to report.