WebReflection / uhtml

A micro HTML/SVG render

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Incompatible with TypeScript when moduleResolution === nodenext

SirPepe opened this issue · comments

TypeScript refuses to use the type definitions for uhtml when moduleResolution is set to nodenext in tsconfig.json.

Minimal reproduction

package.json:

{
  "name": "uhtml-bug-ts-nodenext",
  "version": "0.0.0",
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^5.2.2",
    "uhtml": "^3.2.1"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "nodenext",
    "strict": true
  },
  "include": [
    "**/*"
  ]
}

src/test.ts:

import { html, render } from "uhtml";

// Could not find a declaration file for module 'uhtml'. '/home/peter/uhtml-bug-ts-nodenext/node_modules/uhtml/esm/index.js' implicitly has an 'any' type.
// There are types at '/home/peter/uhtml-bug-ts-nodenext/node_modules/uhtml/index.d.ts', but this result could not be resolved when respecting package.json "exports". The 'uhtml' library may need to update its package.json or typings.ts(7016)

As far as I understand it, the exports entries in package.json need their own type fields next to import and default to fix this: https://www.typescriptlang.org/docs/handbook/esm-node.html#packagejson-exports-imports-and-self-referencing

I think this module is not TS at all and there is not even JSDoc TS in it so that I cannot really add types, this is a JS project.

Is TS really incapable of using JS projects these days? That's a really bad move for the ecosystem, if that's the case.

@SirPepe from Xwitter I got this answer:
https://twitter.com/shvr93/status/1699341735246143819?s=20

For or JS only modules ambient declarations should “fix” everything:

declare module "uhtml";

As far as I understand it, this is what is going on: uhtml has types in package.json, but this field nowadays has the same relevance as main, browser and the like. What really matters is what's in exports, and today's TypeScript expects there to be a types field for everything in exports if some configuration options for TS (like moduleResolution: nodenext) are set.

So the proper way to get rid of this problem is to add "types": "./index.d.ts", to everything in exports:

{
  "name": "uhtml",
  "version": "3.2.1",
  "description": "A micro HTML/SVG render",
  "main": "cjs/index.js",
  "types": "index.d.ts", // keep this
  "module": "./esm/index.js",
  "type": "module",
  // more...
  "exports": {
    ".": {
      "types": "./index.d.ts", // add this
      "import": "./esm/index.js",
      "default": "./cjs/index.js"
    },
    "./async": {
      "types": "./index.d.ts", // add this
      "import": "./esm/async.js",
      "default": "./cjs/async.js"
    },
    "./init": {
      "types": "./index.d.ts", // add this
      "import": "./esm/init.js",
      "default": "./cjs/init.js"
    },
    "./json": {
      "types": "./index.d.ts", // add this
      "import": "./esm/json.js",
      "default": "./cjs/json.js"
    },
    "./jsx": {
      "types": "./index.d.ts", // add this
      "import": "./esm/x.js",
      "default": "./cjs/x.js"
    },
    "./package.json": "./package.json"
  },
  // more...
}

Basically, keep the top-level types field for backwards compatibility but also add an extra types for each export. In my mind it makes sense to have it set up like this because it is not entirely inconceivable to have different types for different exports. Making it your job to deal with the breaking changes that an entirely unrelated project introduces is the dick move of the century, but apart from that minor fact, it kinda makes sense.

I'd be OK with that PR, thanks!

it's up and running 🥳