Skip to content

Module resolution: NodeNext breaks typechecking #60561

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

Open
damianobarbati opened this issue Nov 22, 2024 · 10 comments
Open

Module resolution: NodeNext breaks typechecking #60561

damianobarbati opened this issue Nov 22, 2024 · 10 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@damianobarbati
Copy link

damianobarbati commented Nov 22, 2024

Demo Repo

https://github.com/damianobarbati/ts-repro

Which of the following problems are you reporting?

The module specifier resolves to the right file, but something about the types are wrong.
I'm using NodeNext to leverage the new node flag --transform-types.

Demonstrate the defect described above with a code sample.

To reproduce:

git clone [email protected]:damianobarbati/ts-repro.git
cd ts-repro
git checkout typescript-constructable-issue
pnpm i
pnpm tsc

Here the line where this can be seen:
https://github.com/damianobarbati/ts-repro/blob/typescript-constructable-issue/services/api/src/index.ts#L2

Run tsc --showConfig and paste its output here

{
    "compilerOptions": {
        "moduleResolution": "nodenext",
        "module": "nodenext",
        "target": "esnext",
        "lib": [
            "dom",
            "dom.iterable",
            "esnext",
            "webworker"
        ],
        "allowJs": true,
        "allowImportingTsExtensions": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "noUnusedLocals": false,
        "esModuleInterop": true,
        "resolveJsonModule": true,
        "strictPropertyInitialization": false,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "baseUrl": "./",
        "tsBuildInfoFile": "../../../../tmp/tsbuildinfo",
        "moduleDetection": "force",
        "allowSyntheticDefaultImports": true,
        "resolvePackageJsonExports": true,
        "resolvePackageJsonImports": true,
        "preserveConstEnums": true,
        "useDefineForClassFields": true,
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictBuiltinIteratorReturn": true,
        "alwaysStrict": true,
        "useUnknownInCatchVariables": true
    },
    "files": [
        "./services/api/src/Foe.spec.ts",
        "./services/api/src/Foe.ts",
        "./services/api/src/index.ts",
        "./services/api/src/helper/fn.spec.ts",
        "./services/api/src/helper/fn.ts",
        "./services/types/src/User.spec.ts",
        "./services/types/src/User.ts"
    ]
}

Run tsc --traceResolution and paste its output here

File '/Users/damians/Desktop/ts-repro/services/api/src/package.json' does not exist.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/services/api/package.json'.
======== Resolving module 'ioredis' from '/Users/damians/Desktop/ts-repro/services/api/src/index.ts'. ========
Explicitly specified module resolution kind: 'NodeNext'.
Resolving in ESM mode with conditions 'import', 'types', 'node'.
'baseUrl' option is set to '/Users/damians/Desktop/ts-repro', using this value to resolve non-relative module name 'ioredis'.
Resolving module name 'ioredis' relative to base url '/Users/damians/Desktop/ts-repro' - '/Users/damians/Desktop/ts-repro/ioredis'.
Loading module as file / folder, candidate module location '/Users/damians/Desktop/ts-repro/ioredis', target file types: TypeScript, JavaScript, Declaration, JSON.
Directory '/Users/damians/Desktop/ts-repro/ioredis' does not exist, skipping all lookups in it.
File '/Users/damians/Desktop/ts-repro/services/api/src/package.json' does not exist according to earlier cached lookups.
File '/Users/damians/Desktop/ts-repro/services/api/package.json' exists according to earlier cached lookups.
Loading module 'ioredis' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Directory '/Users/damians/Desktop/ts-repro/services/api/src/node_modules' does not exist, skipping all lookups in it.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/package.json'.
'package.json' does not have a 'typesVersions' field.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field './built/index.d.ts' that references '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts'.
File '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts', result '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/index.d.ts'.
======== Module name 'ioredis' was successfully resolved to '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/index.d.ts' with Package ID 'ioredis/built/[email protected]'. ========
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/package.json' does not exist.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/package.json'.
======== Resolving module './Redis' from '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/index.d.ts'. ========
Explicitly specified module resolution kind: 'NodeNext'.
Resolving in CJS mode with conditions 'require', 'types', 'node'.
Loading module as file / folder, candidate module location '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/Redis', target file types: TypeScript, JavaScript, Declaration, JSON.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/Redis.ts' does not exist.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/Redis.tsx' does not exist.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/Redis.d.ts' exists - use it as a name resolution result.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/package.json' exists according to earlier cached lookups.
'package.json' does not have a 'peerDependencies' field.

....OMISSIS....

======== Module name '@typescript/lib-webworker' was not resolved. ========
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/typescript/lib/package.json' does not exist according to earlier cached lookups.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/typescript/package.json' exists according to earlier cached lookups.
services/api/src/index.ts:2:19 - error TS2351: This expression is not constructable.
  Type 'typeof import("/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/index")' has no construct signatures.

2 const redis = new Redis();
                    ~~~~~


Found 1 error in services/api/src/index.ts:2

 ELIFECYCLE  Command failed with exit code 1.

Paste the package.json of the importing module, if it exists

{
  "name": "ioredis",
  "version": "5.6.0",
  "description": "A robust, performance-focused and full-featured Redis client for Node.js.",
  "main": "./built/index.js",
  "types": "./built/index.d.ts",
  "files": [
    "built/"
  ],
  "scripts": {
    "test:tsd": "npm run build && tsd",
    "test:js": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/helpers/*.ts\" \"test/unit/**/*.ts\" \"test/functional/**/*.ts\"",
    "test:cov": "nyc npm run test:js",
    "test:js:cluster": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/cluster/**/*.ts\"",
    "test": "npm run test:js && npm run test:tsd",
    "lint": "eslint --ext .js,.ts ./lib",
    "docs": "npx typedoc --logLevel Error --excludeExternals --excludeProtected --excludePrivate --readme none lib/index.ts",
    "format": "prettier --write \"{,!(node_modules)/**/}*.{js,ts}\"",
    "format-check": "prettier --check \"{,!(node_modules)/**/}*.{js,ts}\"",
    "build": "rm -rf built && tsc",
    "prepublishOnly": "npm run build",
    "semantic-release": "semantic-release"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/luin/ioredis.git"
  },
  "keywords": [
    "redis",
    "cluster",
    "sentinel",
    "pipelining"
  ],
  "tsd": {
    "directory": "test/typing"
  },
  "author": "Zihua Li <[email protected]> (http://zihua.li)",
  "license": "MIT",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/ioredis"
  },
  "dependencies": {
    "@ioredis/commands": "^1.1.1",
    "cluster-key-slot": "^1.1.0",
    "debug": "^4.3.4",
    "denque": "^2.1.0",
    "lodash.defaults": "^4.2.0",
    "lodash.isarguments": "^3.1.0",
    "redis-errors": "^1.2.0",
    "redis-parser": "^3.0.0",
    "standard-as-callback": "^2.1.0"
  },
  "devDependencies": {
    "@ioredis/interface-generator": "^1.3.0",
    "@semantic-release/changelog": "^6.0.1",
    "@semantic-release/commit-analyzer": "^9.0.2",
    "@semantic-release/git": "^10.0.1",
    "@types/chai": "^4.3.0",
    "@types/chai-as-promised": "^7.1.5",
    "@types/debug": "^4.1.5",
    "@types/lodash.defaults": "^4.2.7",
    "@types/lodash.isarguments": "^3.1.7",
    "@types/mocha": "^9.1.0",
    "@types/node": "^14.18.12",
    "@types/redis-errors": "^1.2.1",
    "@types/sinon": "^10.0.11",
    "@typescript-eslint/eslint-plugin": "^5.48.1",
    "@typescript-eslint/parser": "^5.48.1",
    "chai": "^4.3.6",
    "chai-as-promised": "^7.1.1",
    "eslint": "^8.31.0",
    "eslint-config-prettier": "^8.6.0",
    "mocha": "^9.2.1",
    "nyc": "^15.1.0",
    "prettier": "^2.6.1",
    "semantic-release": "^19.0.2",
    "server-destroy": "^1.0.1",
    "sinon": "^13.0.1",
    "ts-node": "^10.4.0",
    "tsd": "^0.19.1",
    "typedoc": "^0.22.18",
    "typescript": "^4.6.3",
    "uuid": "^9.0.0"
  },
  "nyc": {
    "reporter": [
      "lcov"
    ]
  },
  "engines": {
    "node": ">=12.22.0"
  },
  "mocha": {
    "exit": true,
    "timeout": 8000,
    "recursive": true,
    "require": "ts-node/register"
  }
}

Paste the package.json of the target module, if it exists

{
  "name": "ts-repro",
  "author": "Damiano Barbati <[email protected]> (https://github.com/damianobarbati)",
  "repository": "https://github.com/damianobarbati/strip-types-repro",
  "license": "MIT",
  "version": "1.0.0",
  "description": "Native Typescript support / stripping.",
  "type": "module",
  "engines": {
    "node": ">=23"
  },
  "scripts": {
    "lint": "biome check --write",
    "tsc": "tsc",
    "test": "vitest run"
  },
  "devDependencies": {
    "@biomejs/biome": "^1.9.3",
    "typescript": "^5.8.2",
    "vitest": "^3.1.1"
  },
  "dependencies": {
    "@types/node": "^22.13.16"
  }
}

Any other comments can go here

I'm using NodeNext to leverage the new node flag --transform-types.

@damianobarbati
Copy link
Author

I think we can also assume that as people will switch from tsx-like tools to the native nodejs flag to have typescript, the more we'll see this.

@RyanCavanaugh
Copy link
Member

The repo doesn't appear to be set up correctly?

pnpm i
Scope: all 2 workspace projects
D:\Throwaway\ts-repro\services\api:
 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND  In services\api: "types@workspace:^" is in the dependencies but no package named "types" is present in the workspace

This error happened while installing a direct dependency of D:\Throwaway\ts-repro\services\api

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Nov 27, 2024
@damianobarbati
Copy link
Author

damianobarbati commented Nov 28, 2024

@RyanCavanaugh sorry, I fixed it (a typo leftover from my cleanup to provide you the smallest repro as possible).

@damianobarbati
Copy link
Author

Hey @RyanCavanaugh, have you guys had a chance to look into the repro?

Image

@RyanCavanaugh RyanCavanaugh removed the Needs More Info The issue still hasn't been fully clarified label Apr 1, 2025
@RyanCavanaugh
Copy link
Member

I don't see any file that looks like that in https://github.com/damianobarbati/ts-repro, nor do I see any errors when running tsc in it.

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Apr 3, 2025
@leifmarcus
Copy link

Hi there, I did have the same issue with an import from storyblok-js-client. The exports in the package json of the library looks correct. I saw, that this happens, when having "moduleResolution": "nodenext" inside the compiler options and having "type": "module" inside the package.json.

I also created a little stackblitz to show the issue: https://stackblitz.com/edit/storyblok-js-client-typescript-issue. Investigating a bit, it seems like, that even if the imports are resolved correctly, typescript acts as if the package is a common js package.

@RyanCavanaugh
Copy link
Member

@leifmarcus the problem is in storyblok-js-client, which is misconfigured. See https://arethetypeswrong.github.io/?p=storyblok-js-client%406.10.11

@leifmarcus
Copy link

@RyanCavanaugh thank you very much! I didn’t know about the tool, you sent. appreciate.

@damianobarbati
Copy link
Author

damianobarbati commented May 2, 2025

@RyanCavanaugh did you actually checkout the branch as I described in the issue report?

git clone [email protected]:damianobarbati/ts-repro.git
cd ts-repro
git checkout typescript-constructable-issue
pnpm i
pnpm tsc

I just tested tsc again, and the problem is right there; in the real projects I'm working on, we have this on several libraries and we're just suppressing with @ts-ignore everywhere as we don't know how to properly solve this.

$ pnpm tsc

> [email protected] tsc /Users/damians/Desktop/ts-repro
> tsc

services/api/src/index.ts:2:19 - error TS2351: This expression is not constructable.
  Type 'typeof import("/Users/damians/Desktop/ts-repro/node_modules/.pnpm/[email protected]/node_modules/ioredis/built/index")' has no construct signatures.

2 const redis = new Redis();
                    ~~~~~


Found 1 error in services/api/src/index.ts:2

 ELIFECYCLE  Command failed with exit code 1.

@RyanCavanaugh RyanCavanaugh added Needs Investigation This issue needs a team member to investigate its status. and removed Needs More Info The issue still hasn't been fully clarified labels May 2, 2025
@RyanCavanaugh
Copy link
Member

Yeah, that does look wrong. It looks like the re-exported default chain isn't being correctly preserved as callable/constructable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

4 participants