Skip to content
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

ts-node seems to fail to resolve dependencies in referenced projects #1381

Open
trinitrotoluene opened this issue Jun 29, 2021 · 15 comments
Open

Comments

@trinitrotoluene
Copy link

Expected Behavior

I would expect code that doesn't error when trying to compile it with tsc to compile when run through ts-node.

Actual Behavior

I get a TSError warning me about missing dependencies (the README linked below) when ts-node tries to load a referenced project.

Reproduction

I've created a minimal reproduction repo for my scenario here with the behaviour/stack trace and command to reproduce.

Specifications

  • ts-node version: 10.0.0
  • node version: 16.3.0
  • TypeScript version: 4.3.4
  • tsconfig.json, if you're using one:
    please see the reproduction repo for the tsconfig structure, here is the effective tsconfig:
{
  "compilerOptions": {
    "noEmit": false,
    "paths": {
      "@org/liba": [
        "a/liba"
      ]
    },
    "esModuleInterop": true,
    "target": "es2017",
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "baseUrl": "../..",
    "rootDir": "../..",
    "outDir": "./.ts-node",
    "sourceMap": true,
    "inlineSourceMap": false,
    "inlineSources": true,
    "declaration": false,
    "module": "commonjs"
  },
  "references": [
    {
      "path": "../../a/liba"
    }
  ],
  "ts-node": {
    "cwd": "[SHORTENED]\\ts-node-repro\\b\\btest",
    "projectSearchDir": "[SHORTENED]\\ts-node-repro\\b\\btest",
    "require": [],
    "project": "[SHORTENED]/ts-node-repro/b/btest/tsconfig.json"
  }
}
  • Operating system and version:
    Windows 10 Pro 19043.1052
  • If Windows, are you using WSL or WSL2?: no
@cspotcode
Copy link
Collaborator

The --files option might fix this, I'm not sure.

https://typestrong.org/ts-node/docs/options
https://typestrong.org/ts-node/docs/types

@cspotcode
Copy link
Collaborator

You can also try adding a triple-slash reference directive in the files that rely on cheerio types. Typically TS knows to look at types because you import them. In cases where you do not import them, but still want the compiler to understand they exist, you can /// <reference> them.

@trinitrotoluene
Copy link
Author

I apologise, that was a mistake in my reproduction script- the error persists with import cheerio from 'cheerio'; in index.ts, I've pushed those changes to make that clear.

I've also tried the --files flag but it doesn't seem to make a difference, I get exactly the same error.

Additionally, I thought I'd add (just to rule out that it's a problem with mocha) that the reproduction command uses mocha but you can get the same error by just running ts-node directly (this is with the explicit import and --files specified):

PS C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest> npx ts-node -r tsconfig-paths/register .\b.spec.ts

C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:587
    return new TSError(diagnosticText, diagnosticCodes);
           ^
TSError: ⨯ Unable to compile TypeScript:
../../a/liba/index.ts:3:28 - error TS2503: Cannot find namespace 'cheerio'.

3 export const repro = (foo: cheerio.Root) => {
                             ~~~~~~~

    at createTSError (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:587:12)
    at reportTSError (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:591:19)
    at getOutput (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:921:36)
    at Object.compile (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:1189:32)
    at Module.m._compile (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:1295:42)
    at Module._extensions..js (node:internal/modules/cjs/loader:1138:10)
    at Object.require.extensions.<computed> [as .ts] (C:\Users\trinitrotoluene\WebstormProjects\ts-node-repro\b\btest\node_modules\ts-node\src\index.ts:1298:12)
    at Module.load (node:internal/modules/cjs/loader:989:32)
    at Function.Module._load (node:internal/modules/cjs/loader:829:14)
    at Module.require (node:internal/modules/cjs/loader:1013:19)

Also thank you for the quick responses, I really appreciate it!

@trinitrotoluene trinitrotoluene changed the title ts-node seems to fail to resolve dependencies in referenced projects when running tests ts-node seems to fail to resolve dependencies in referenced projects Jul 2, 2021
@blakeembrey
Copy link
Member

I believe this is the same issue as #1421, which is due to how TypeScript is unfortunately repeated invoked on files. It doesn't release this is outside the scope of your type checking project and is trying to also type check it.

@cspotcode I think one option might be to default scope to true in a future major release, and have the scope automatically set to the root of tsconfig.json.

@cspotcode
Copy link
Collaborator

Are we sure that cheerio is a namespace? Based on the contents of [email protected], it looks like cheerio is a value of type CheerioAPI, not a namespace nor a type. cheerio is the default import, right?

https://unpkg.com/browse/[email protected]/package.json#L28
https://unpkg.com/browse/[email protected]/lib/index.d.ts#L21
https://unpkg.com/browse/[email protected]/lib/load.d.ts#L15

Perhaps it would be helpful to locate where the type cheerio.Root is declared in cheerio's source code.
https://github.com/cheeriojs/cheerio/

@fourpastmidnight
Copy link

I've run into the same issue. My project successfully compiles using tsc and the tsconfig.json files I have set up, but when trying to run via ts-node, I get

TSError: x Unable to compile Typescript:
src/myfile.ts:23:8 - error TS2307: Cannot find module '@myworkspace/mycomponent' or its corresponding type declarations

I'm using:

  • TypeScript 4.2.4
  • ts-node v10.0.0
  • node: v14.17.1
  • Windows 10, no WSL

@fourpastmidnight
Copy link

Hmm, my issue seems to also be related to #897, as I'm working in a monorepo (not lerna, just a yarn PnP monorepo) and the module not being found is being referenced as @myworkspace/mycomponent, and in package.json, the dependency entry for it is @myworkspace/mycomponent: workspace:packages/mycomponent. However, given that ts-node is transpiling on the fly, I would expect it to be able to find the references in tsconfig.json and build the required dependencies. (Perhaps I expected too much—as I read in another issue, I hadn't fully appreciated the implications of "supporting project references" with respect to ts-node. 😛 )

So on second-thought, maybe these two issues are actually related—and it all comes down to ts-node's support for TS project references?

One way that I had this working in the past is by updating the package.json so that the main field points to the typescript entry point, e.g. "main": "./src/index.ts", instead of "main": "./lib/index.js". Pointing to the typescript file, ts-node works. However, this breaks actual builds of the packages, then, because during a build, the referenced dependencies are expecting to find a TypeScript file, but instead, the transpiled JavaScript should be used.

And now that I think through everything, this makes sense why that change worked. ts-node isn't building the referenced projects, but when the package.json's main field points to a dependendent package's main entry point TS file, ts-node will happily transpile and everything just works--well, everything except actual builds of the component packages.

@cspotcode
Copy link
Collaborator

See also: #1111 adds the necessary changes for ts-node to understand project references. It also explains what is happening behind the scenes, with TS's internal, monkey-patching of the Host. This monkey-patching teaches the language service that ./lib/index.js corresponds to ./src/index.ts

If anyone is motivated to finish that pull request, I will be happy to offer guidance, explain what's going on, and list what's missing.

@cspotcode
Copy link
Collaborator

cspotcode commented Aug 18, 2021

@fourpastmidnight you understand the core issue pretty well. As you said, there are 2 possibilities when @myworkspace/mylib tries to load @myworkspace/mycomponent:

it is precompiled

@myworkspace/mycomponent has been pre-compiled and ./lib/index.js and ./lib/index.d.ts exist. This may technically work, but is non-ideal: TS will typecheck against .d.ts and node will resolve to .js. ts-node will not be executing the source .ts, which is what we probably want.

it is not precompiled

@myworkspace/mycomponent has not been compiled yet. #1111 teach ts-node to do the necessary configuration, so that TS understands ./lib/index.js corresponds to ./src/index.ts. It understands because the referenced tsconfig has rootDir and outDir, which map between src and lib. This is halfway there: the typechecker understands to typecheck against index.ts, but node does not understand which file to load. Node will attempt to require ./lib/index.js which does not exist. We need to teach node to do the same mappings that TypeScript does. I talk about this in #1111 under "Missing is a runtime resolver"

@fourpastmidnight
Copy link

I'm fairly new to all of this, having been thrown into a last-minute project that was a perfect use-case for React. I've learned a lot in the last 3 months, but still not 100% on this ecosystem 😉 I had a look at #1111, but I'm unable to quickly make heads or tails of it. Glad to see, however, that I do understand the core issue even if I'm not quite sure (yet) what it would take to bring #1111 to completion.

@fourpastmidnight
Copy link

I wound up here again, reading my 14-day old self and your replies because I was once again trying to get ts-node to work with my (the same) project.... LOL. Anyway, it would be super sweet to have #1111 done and implemented. So I guess I should now try to see if I can't figure out what the heck that's all doing! LOL. No promises, I'm busy, but I'd really like to have a quick "F5" experience.

@cspotcode
Copy link
Collaborator

@fourpastmidnight yeah, it's like everything in open-source. Someone's got to do it, but the benefits are huge once it gets done.

If/when you start working on this, please do not hesitate to create draft pull requests, ask questions, create issues, whatever you need to feel productive. The PR and issue trackers exist to "get things done" and I'm a big proponent of pushing WIP, chatting, whatever fits your workflow.

@jonaskello
Copy link
Contributor

jonaskello commented Sep 10, 2021

I landed here after trying to setup an experimental project with yarn workspaces, typescript project references, snowpack for the client side and node --loader ts-node/esm for the server side. I'm trying to feed both snowpack and ts-node/esm the raw .ts files. This works until they cross a package border (across the monorepo's own packages).

I give snowpack the packages/client/src/index.ts file to start with. Any file it imports in it's own package snowpack will find the .ts file. However when it does import x from "@myapp/shared" snowpack will find the symlinked package in node_modules, lookup the package.json, lookup the main field and find lib/index.js. Then it will continue to build it's dependency chain using .js files instead. So when a .ts file is changed in the shared package snowpack does not have it in it's chain and nothing happens. To fix this it is possible to invent a new field instead of main, let's say tsMain and set it to src/index.ts. Then in snowpack config you can set packageLookupFields: ["tsMain", "main"] and snowpack will prefer tsMain and it will build a chain of only ts files across the package borders.

Now for node --loader ts-node/esm that does not work of course since there is no equivalent of snowpack's packageLookupFields config. From what I understand here ts-node is trying to solve this by working in the same way as the language server in vscode. Looking up in tsconfig.json what is the input and output dirs (usually lib and src) and rewriting the main field to map to it's corresponding input file (from the compiler option rootDir).

@cspotcode Is this a correct understanding of the situation?

I think it would be great if the logic to do the re-mapping of lib/index.js to src/index.ts could be externalized from ts-node but I don't think it really belongs in tsconfig-paths either since this is not related to the paths field in tconfig.json? If it is externalized perhaps tools like snowpack can leverage this logic too instead of having the hack with tsMain field I described above.

If I can help with this I will try subject to time constraints (still haven't got those 36h days I wished for Christmas for the last 20 years :-)).

EDIT: Here is an example repo for what i described above, and this note is about the limitation of module resolution.

@gund
Copy link

gund commented Apr 11, 2023

+1 this does not work in NX workspaces when I tried to run a node CLI tool that is using some other packages withing the monorepo which are configured via tsconfig paths property in NX.
Would be great to have an option to somehow make it resolve those aliases, as for now it cannot find those workspace packages.
I was able to use tsx instead for now to execute my cli project locally which resolves all tsconfig paths out of the box.

@crobinson42
Copy link

tsx is the clear winner! Thx @gund

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants