Skip to content

TypeScript erroneously type checks original .ts source files of libraries that have tsconfig.json#compilerOptions.declarationMap #44522

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

Closed
brillout opened this issue Jun 9, 2021 · 11 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@brillout
Copy link

brillout commented Jun 9, 2021

Bug Report

πŸ”Ž Search Terms

Library
declarationMap
Declaration Map

πŸ•— Version & Regression Information

This happens with [email protected] but I believe it to happen with prior versions as well.

⏯ Playground Link

npm init vite-plugin-ssr
# select `react-ts` or `vue-ts`
cd vite-ssr-project/
npm install
npx tsc
# See how TypeScript fails to type check the original source files of `vite-plugin-ssr`

πŸ™ Actual behavior

TypeScript type checks the original .ts files of vite-plugin-ssr.

πŸ™‚ Expected behavior

TypeScript should only type check the declaration .d.ts files.

Note that vite-plugin-ssr doesn't define a package.json#types but setting package.json#types doesn't solve the problem.

Overall goal here is to enable vite-plugin-ssr users to browse the orginal .ts source files of vite-plugin-ssr. It's currenlty not a viable option because TypeScript type checks the original .ts source files of vite-plugin-ssr which leads to errors when the user's tsconfig.json mismatches vite-pluin-ssr's tsconfig.json. For example, vite-plugin-ssr needs tsconfig.json#compilerOptions.resolveJsonModule set to true and building the user's TypeScript project fails if the user didn't set that flag.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 10, 2021
@orta orta added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Needs Investigation This issue needs a team member to investigate its status. labels Jun 15, 2021
@orta
Copy link
Contributor

orta commented Jun 15, 2021

TypeScript is checking the .ts files because they are included in your project, the node resolver roughly resolves as *.d.ts, *.ts, *.js. The files which fail are ones which do not have corresponding .d.ts files, I'd recommend generating .d.ts files for those files in order to not have TypeScript type check those .ts files, and then they are treated as lib files.

You don't have .d.ts files so TypeScript assumes you have .ts code that you want it to understand and use. Treating this as a bug and fixing that would mean that folks would not be able to ship npm packages which are TypeScript and use something like ts-node, ts-babel, Hermes or Deno to run.

@orta orta removed this from the TypeScript 4.4.1 (RC) milestone Jun 15, 2021
@brillout
Copy link
Author

The files which fail are ones which do not have corresponding .d.ts files

vite-plugin-ssr does ship .d.ts files, see node_modules/vite-plugin-ssr/dist/esm/ and node_modules/vite-plugin-ssr/dist/cjs/.

For example:

node_modules/vite-plugin-ssr/utils/assert.ts:1:25 - error TS2732: Cannot find module '../package.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.

1 import { version } from '../package.json'
                          ~~~~~~~~~~~~~~~~~

The .d.ts file of the failing node_modules/vite-plugin-ssr/utils/assert.ts is located at node_modules/vite-plugin-ssr/dist/esm/utils/assert.d.ts and node_modules/vite-plugin-ssr/dist/cjs/utils/assert.d.ts.

So it seems like TypeScript doesn't know how to map the .d.ts files with the .ts files. Is there a way to teach TypeScript such mapping?

@orta
Copy link
Contributor

orta commented Jun 15, 2021

Following the resolution strategy TS looks at your package.json then sees no types nor main and looks for an index.ts which it finds no dts and thinks its a TS project and starts to resolve through the file tree.

You can look at either using types in the package json, add .d.ts files to your root .ts files or because you are already shipping the ts files in dist don't include those TS files in the package root

@brillout
Copy link
Author

I just tried your suggestions but without success.

You can look at either using types in the package json

When I set node_modules/vite-plugin-ssr/package.json#types to dist/esm/index.d.ts or dist/esm/, TS still type checks the .ts files.

looks for an index.ts which it finds no dts and thinks its a TS project and starts to resolve through the file tree.

I created an empty file index.d.ts at node_modules/vite-plugin-ssr/index.d.ts but TS still type checks the .ts files.

add .d.ts files to your root .ts files or because you are already shipping the ts files in dist don't include those TS files in the package root

I'm not sure this is actionable: there are actually no .ts files in dist/ and I'm not sure I can change that since the d.ts.map files assume the .ts files to live at the package root.

@orta orta added Needs Investigation This issue needs a team member to investigate its status. and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Jun 15, 2021
@orta
Copy link
Contributor

orta commented Jun 15, 2021

You can use --traceResolution on tsc to see why and how a particular file was resolved. So, for example, here is your repo when I cloned it, the 2nd entry is for vite-plugin-ssr/plugin which resolves to a TS file and not a .d.ts file:

======== Resolving module 'vite-plugin-ssr/plugin' from '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/vite.config.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'vite-plugin-ssr/plugin' from 'node_modules' folder, target file type 'TypeScript'.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/package.json' does not exist.
Found 'package.json' at '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/package.json'.
'package.json' does not have a 'typesVersions' field.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.ts' does not exist.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.tsx' does not exist.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' does not have a 'types' field.
'package.json' has 'main' field './dist/cjs/index.js' that references '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/cjs/index.js'.
Loading module as file / folder, candidate module location '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/cjs/index.js', target file type 'TypeScript'.
File name '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/cjs/index.js' has a '.js' extension - stripping it.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts' exist - use it as a name resolution result.
Resolving real path for '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts', result '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts'.
======== Module name 'vite-plugin-ssr/plugin' was successfully resolved to '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts' with Package ID 'vite-plugin-ssr/plugin/[email protected]'. ========

Types

What feels off to me is that using "types" to set the type roots doesn't to "types": "./dist/esm/" doesn't seem to quite follow along with node's routing:

> require.resolve("vite-plugin-ssr/plugin")
'/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/dist/cjs/plugin/index.js'
======== Resolving module 'vite-plugin-ssr/plugin' from '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/vite.config.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'vite-plugin-ssr/plugin' from 'node_modules' folder, target file type 'TypeScript'.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/package.json' does not exist.
Found 'package.json' at '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/package.json'.
'package.json' does not have a 'typesVersions' field.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.ts' does not exist.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.tsx' does not exist.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field './dist/esm/' that references '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/esm/'.
Loading module as file / folder, candidate module location '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/esm/', target file type 'TypeScript'.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts' exist - use it as a name resolution result.
Resolving real path for '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts', result '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts'.

Because it looks for

/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/esm/

and not

/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/dist/esm/plugin/

which seems unintuitive to me, and maybe wrong - but it does feel strange that this behavior has always existed the TS node resolution maybe we're not resolving the /subpaths relative to main on purpose but it doesn't feel right to me

@brillout
Copy link
Author

Hm, that does seem strange indeed.

I wouldn't know why it would make sense to consider vite-plugin-ssr/plugin/dist/esm/ the root given a "types": "dist/esm/". I also tried to set "types": "./dist/esm/" but TS still behaves the same.

Also what I find strange is that:

Loading module as file / folder, candidate module location '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/dist/esm/', target file type 'TypeScript'.
File '/Users/ortatherox/dev/typescript/repros/vite-ssr-project/node_modules/vite-plugin-ssr/plugin/index.ts' exist - use it as a name resolution result.

It says that vite-plugin-ssr/plugin/dist/esm/ is now the root directory, and in the next line it says vite-plugin-ssr/plugin/index.ts' exist - use it as a name resolution result. But vite-plugin-ssr/plugin/index.ts is outside the root vite-plugin-ssr/plugin/dist/esm/.

So I guess there is a TS bug going on here?

@orta
Copy link
Contributor

orta commented Jun 15, 2021

Yeah, I think so, perhaps we've not encountered a case where both main and the private accessor in a module identifers are being used, for example this repro fails on main today:

// @module: commonjs
// @moduleResolution: node

// @filename: node_modules/a/package.json
{ "main": "./cjs/index.js" }

// @filename: node_modules/a/cjs/index.d.ts
var x = 1;

// @filename: node_modules/a/cjs/plugin.d.ts
export const abc = "123"

// @filename: app.ts
import plugin = require("a/plugin");

@orta
Copy link
Contributor

orta commented Jun 15, 2021

Actually, TypeScript's behavior matches node's, this isn't a bug with TypeScript's node resolver:

❯ mkdir 44522
❯ cd 44522
❯ mkdir -p node_modules/a/dist
❯ touch node_modules/a/dist/plugins.js
❯ touch node_modules/a/package.json
❯ cat node_modules/a/package.json
{
    "main":"dist"
}
❯ node

> require.resolve("a/plugins")
Uncaught Error: Cannot find module 'a/plugins'
Require stack:
- <repl>
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:924:15)
    at Function.resolve (node:internal/modules/cjs/helpers:98:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '<repl>' ]
}

The private access in TS' type resolution doesn't respect the main field, which acts the same as node. Perhaps bundlers use a different algorithm and that's why you've not had runtime issues?

Which means TypeScript resolving to the root .ts files is the right behavior here, I think the only way you can keep them there for the dts source maps and not get picked up as trying to read TS files in apps using /plugins is duplicating you .d.ts files in the root of the repo which take precedence

@brillout
Copy link
Author

It uses package.json#exports:

$ cat node_modules/vite-plugin-ssr/package.json 
{
  "name": "vite-plugin-ssr",
  "version": "0.1.1",
  "types": "./dist/esm/",
  "main": "./dist/cjs/index.js",
  "module": "./dist/esm/index.js",
  "exports": {
    ".": {
      "node": "./dist/cjs/index.js",
      "import": "./dist/esm/index.js"
    },
    "./client": {
      "browser": "./dist/esm/client/index.js"
    },
    "./client/router": {
      "browser": "./dist/esm/client/router/index.js",
      "node": "./dist/cjs/client/router/index.node.js"
    },
    "./cli": {
      "node": "./dist/cjs/cli/index.js"
    },
    "./plugin": {
      "node": "./dist/cjs/plugin/index.js"
    }
  }
}

Maybe TS doesn't take in consideration package.json#exports?

@orta
Copy link
Contributor

orta commented Jun 15, 2021

TypeScript does not, TypeScript still needs to support older node's and this sort of behavior will need to be opt in.Maybe 4.4 will get it, little bit hard to tell yet as there's a lot to cover with node's new ESM resolvers #44442

@brillout
Copy link
Author

Ok neat. Thanks for the conversation. I'm closing this since the issue is about ESM resolving.

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

3 participants