deno: deno check error resolving package config for npm react jsx-runtime

While importing the npm:@types packages as per #16653 silences errors from the language server when running deno check I see the following error:

% deno check index.tsx
Check file:///Users/lrowe/scratch/react-server-deno/index.tsx
error: Uncaught Error: Error resolving package config for 'npm:react@18.2.0/jsx-runtime': [ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './jsx-runtime' is not defined by "exports" in '/Users/lrowe/Library/Caches/deno/npm/registry.npmjs.org/react/18.2.0/package.json' imported from '/Users/lrowe/Library/Caches/deno/npm/registry.npmjs.org/react/18.2.0/'
    at <anonymous> (ext:deno_tsc/99_main_compiler.js:592:28)

However in react’s package.json an entry for ‘./jsx-runtime’ exists:

  "exports": {
    ".": {
      "react-server": "./react.shared-subset.js",
      "default": "./index.js"
    },
    "./package.json": "./package.json",
    "./jsx-runtime": "./jsx-runtime.js",
    "./jsx-dev-runtime": "./jsx-dev-runtime.js"
  },

Files to reproduce:

index.tsx:

// See: https://github.com/denoland/deno/issues/16653
import type {} from "npm:@types/react@18";
import type {} from "npm:@types/react-dom@18";

import React from "npm:react@18";
import { renderToString } from "npm:react-dom@18/server";

function Hello() {
  return <div>Hello world!</div>;
}

console.log(renderToString(<Hello />));

deno.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "npm:react@18"
  }
}
% deno --version      
deno 1.31.2 (release, aarch64-apple-darwin)
v8 11.0.226.19
typescript 4.9.4

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 6
  • Comments: 16 (9 by maintainers)

Most upvoted comments

This is now possible to specify via jsxImportSourceTypes in Deno 1.43 (pragma or compiler option):

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "npm:react@^18.3",
    "jsxImportSourceTypes": "npm:@types/react@^18.3"
  }
}

https://deno.com/blog/v1.43#jsximportsourcetypes

FWIW, a view from the cheap seats from this relative newcomer:

It seems a bit odd to put this in compilerOptions, since it’s not a TypeScript compiler option, it’s a Deno option. (And there’s always the quite-small chance TypeScript would want to add a types option someday.)

Perhaps an import-types section in deno.json, keyed (as suggested above by @oscarotero) by the module specifier?

{
    "imports": {
        "react": "npm:react@^18.2"
    },
    "import-types": {
        "react": "npm:@types/react@^18.2"
    }
}

The entry in import-types would do two things:

  1. Import the types from the given source.
  2. Associate them with the main module like @deno-types=... does in all places the mapped specifier ("react") or its resolved full specifier ("npm:react@^18.2") is used in an import (or similar, including an implicit one for JSX).

A couple of notes re that example:

  • I’ve used the mapped specifier ("react") as the key in import-types. I don’t know if it would be worth allowing the full specifier as well ("npm:react@^18.2": "npm:@types/react@^18.2"). It adds a third place to maintain the version, which seems silly. The only use case I can immediately think of is if you aren’t using a mapped specifier (you’re actually writing import ... from "npm:react@^18.2"; everywhere) and…why would you do that? 😊
  • I’ve used imports in the above, but conceptually there’s no reason this couldn’t continue to support importMap with a separate map. import-types would remain in deno.json to avoid making the separate map non-standard.

Separately, having to repeat the version number is a maintenance chore (one that Node’s package.json also has), since in the normal case you want the same version as the package. Deno already improves on Node.js and npm in many ways, perhaps another would be to allow opting-in to inferring the version number from the main package? For instance, using @infer:

{
    "imports": {
        "react": "npm:react@^18.2"
    },
    "import-types": {
        "react": "npm:@types/react@infer"
    }
}

There, Deno would infer ^18.2 on npm:@types/react from the react mapping in imports. But if you gave one explicitly ("react": "npm:@types/react@18.1", perhaps because the types are lagging the main package a bit), it would use the version given.

To me, that would be an excellent devex for these packages that don’t directly provide types.

(Using @infer if there’s no version to infer from would be an error.)

Apologies if any of this is unworkable in ways that would be obvious to someone more experienced with the project.

t would be nicer if we could just add the string “npm:@types/react@18.0.2”

That’s a good idea, since npm: specifiers are graced it makes sense to allow that. If npm:@types/foo is detected in that types field, it’s used for untyped npm:foo/<path> specifiers:

{
  "compilerOptions": {
    "types": [
      "./types.d.ts",
      "https://deno.land/x/pkg@1.0.0/types.d.ts",
      // Will be used for any `npm:react@18.x.x/*`
      "npm:@types/react@18"
    ]
  }
}

@dsherret WDYT?

This is just #20455, which is on the roadmap but not currently being worked on.

The following workaround doesn’t work:

// @deno-types="npm:@types/react@18"
import React from "npm:react@18";
// @deno-types="npm:@types/react-dom@18/server"
import { renderToString } from "npm:react-dom@18/server";

function Hello() {
  return <div>Hello world!</div>;
}

console.log(renderToString(<Hello />));

because it doesn’t apply to the "jsxImportSource": "npm:react@18" in your compiler options. There’s no way of tagging that one.

I assume compilerOptions::types entries are put through the import map, so I think you’d naturally be able to do:

{
  "compilerOptions": {
    "types": [
      "./types.d.ts",
      "https://deno.land/x/pkg@1.0.0/types.d.ts",
      // If this resolves to `npm:@types/react@18`, it will be used for any `npm:react@18.x.x/*`
      "@types/react"
    ]
  },
  "imports": {
    "@types/react": "npm:@types/react@18"
  }
}

Though I wouldn’t personally bother if they’re in the same file anyway.

@nayeemrmn I like that too 👍

@KyleJune you can store the import map in a deno.json nowadays (just copy and paste the “imports” and “scopes” properties into the deno.json, delete the “importMap” property in deno.json, then delete the import map file). I don’t think we could support this in an import map file because it would be non-standard.