tsx icon indicating copy to clipboard operation
tsx copied to clipboard

The `esModuleInterop` and module importing is inconsistent (and mandatory?) with tsx

Open anodynos opened this issue 2 years ago • 0 comments
trafficstars

Precheck

  • [X] I searched existing issues before opening this one to avoid duplicates
  • [X] I'm able to reproduce this issue and prove it with a minimal reproduction
  • [X] I understand this is not a place to ask for free debugging support

Problem

I have a file

// @ts-ignore
import deepmergeAsDefault from 'deepmerge'         // < ===  BREAKS IN TypeScript, I have esModuleInterop: false

import * as deepmerge from 'deepmerge'
import * as _ from 'lodash'

console.log(_.merge({ lodashA: 1 }, { lodashB: 2 }))              // < ===  WORKS
console.log(deepmergeAsDefault({ deepmergeAsDefaultA: 1 }, { deepmergeAsDefaultB: 2 })) // < ===  WORKS

console.log(deepmerge({ deepmergeA: 1 }, { deepmergeB: 2 }))        // < ===  BREAKS

I have a // @ts-ignore on top of the default import, since TypeScript complains, rightly so since my esModuleInterop: false (the default):

TS1259: Module
'//wsl.localhost/ubuntara/mnt/projects/devzen-tools/packages/speczen/node_modules/deepmerge/index'
can only be default-imported using the  esModuleInterop  flag
index.d.ts(20, 1): This module is declared with  export = , and can only be used with a default import when using the  esModuleInterop  flag

When I execute I get:

╰─$ tsx temp/import-without-default-import.ts                                                                                                                                                                
{
  detail: undefined,
  id: 'call-import-namespace',
  location: {
    column: 12,
    file: '/mnt/projects/devzen-tools/packages/speczen/temp/import-without-default-import.ts',
    length: 9,
    line: 8,
    lineText: 'console.log(deepmerge({ deepmergeA: 1 }, { deepmergeB: 2 }))',
    namespace: '',
    suggestion: ''
  },
  notes: [
    {
      location: [Object],
      text: 'Consider changing "deepmerge" to a default import instead:'
    },
    {
      location: null,
      text: `Make sure to enable TypeScript's "esModuleInterop" setting so that TypeScript's type checker generates an error when you try to do this. You can read more about this setting here: https://www.typescriptlang.org/tsconfig#esModuleInterop`
    }
  ],
  pluginName: '',
  text: `Calling "deepmerge" will crash at run-time because it's an import namespace object, not a function`
}
{ lodashA: 1, lodashB: 2 }
{ deepmergeAsDefaultA: 1, deepmergeAsDefaultB: 2 }
/mnt/projects/devzen-tools/packages/speczen/temp/import-without-default-import.ts:8
console.log(deepmerge({ deepmergeA: 1 }, { deepmergeB: 2 }))
            ^


TypeError: deepmerge is not a function
    at deepmergeAsDefault (/mnt/projects/devzen-tools/packages/speczen/temp/import-without-default-import.ts:8:13)
    at Object.<anonymous> (/mnt/projects/devzen-tools/packages/speczen/temp/import-without-default-import.ts:8:60)
    at Module._compile (node:internal/modules/cjs/loader:1241:14)
    at Object.j (/home/anodynos/.asdf/installs/nodejs/20.8.0/lib/node_modules/tsx/dist/cjs/index.cjs:1:1197)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Module._load (node:internal/modules/cjs/loader:938:12)
    at cjsLoader (node:internal/modules/esm/translators:284:17)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:234:7)
    at ModuleJob.run (node:internal/modules/esm/module_job:217:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)

Node.js v20.8.0

if you noticed,

{ lodashA: 1, lodashB: 2 }
{ deepmergeAsDefaultA: 1, deepmergeAsDefaultB: 2 }

worked, which is incosistent. I imported lodash normally (for my esModuleInterop: false) and deepmerge in an illegal way for my project, and these are the 2 that worked.

But the legit deepmerge({ deepmergeA: 1 }, { deepmergeB: 2 }) broke.

In ts-node

import * as deepmerge from 'deepmerge'
import * as _ from 'lodash'

work as expected (and the deepmergeAsDefault breaks as should).

All my project is using non-default imports, and I don't want to enable esModuleInterop cause some library was breaking some time ago, and all my project is using the non-esModuleInterop any way and works just fine with all other tooling, including ts-node, jest with ts-jest and as compiled js code.

Is there any flag I'm missing that can accommodate this?

Expected behavior

To work like it does with all other tooling and respecting my ts-config.

Minimal reproduction URL

https://stackblitz.com/edit/node-34fss6?file=index.ts

Version

v3.14.0

Node.js version

20.8.0

Package manager

npm

Operating system

Linux

Contributions

  • [ ] I plan to open a pull request for this issue
  • [ ] I plan to make a financial contribution to this project

anodynos avatar Nov 14 '23 17:11 anodynos