Cannot find Typescript module even though tsc successfully manages to resolve it
Asked Answered
S

2

11

I have a Node.js project written in Typescript which is expected to run as a CLI, and am having trouble to import a module located out of the node_modules directory using an absolute path (relative paths work fine). It might be important to mention that I am using the oclif framework for building my CLI.

My project is organized as follows:

cli
 |--node_modules
 |--src
     |--my-module.ts
     |--subdir
          |--index.ts

Within my-module.ts I have:

 export class MyClass {
     myClassFcn(s: string) {
         return 'result'
     }
 }

The index.ts script contains something like:

 import {MyClass} = require('my-module')

When I try to execute my app with ts-node, I get

(node:10423) [MODULE_NOT_FOUND] Error Plugin: cli: Cannot find module 'my-module'
    module: @oclif/[email protected]
    task: toCached
    plugin: cli
    root: /home/eschmidt/Workspace/cli
    Error Plugin: cli: Cannot find module 'my-module'
        at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
        at Function.Module._load (internal/modules/cjs/loader.js:497:25)
        at Module.require (internal/modules/cjs/loader.js:626:17)
        at require (internal/modules/cjs/helpers.js:20:18)
        at Object.<anonymous> (/home/eschmidt/Workspace/cli/src/commands/create/index.ts:5:1)
        at Module._compile (internal/modules/cjs/loader.js:678:30)
        at Module.m._compile (/home/eschmidt/Workspace/cli/node_modules/ts-node/src/index.ts:403:23)
        at Module._extensions..js (internal/modules/cjs/loader.js:689:10)
        at Object.require.extensions.(anonymous function) [as .ts] (/home/eschmidt/Workspace/cli/node_modules/ts-node/src/index.ts:406:12)
        at Module.load (internal/modules/cjs/loader.js:589:32)
    module: @oclif/[email protected]
    task: toCached
    plugin: my-plugin
    root: /home/eschmidt/Workspace/cli

What I can't understand is that when I run tsc --traceResolution the module is correctly resolved:

======== Module name 'my-module' was successfully resolved to '/home/eschmidt/Workspace/cli/src/my-module.ts'. ========

My tsconfig.json file contains:

{
  "compilerOptions": {
    "declaration": true,
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true,
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./lib",
    "pretty": true,
    "rootDirs": [
      "./src/"
    ],
    "strict": true,
    "target": "es2017",
    "baseUrl": "src"
  },
  "include": [
    "./src/**/*"
  ]
}

I would greatly appreciate it if anyone could shed some light on this issue, or at least suggest where to look for further help. In case more details are needed, please let me know.

Thanks in advance!

Skylar answered 29/5, 2018 at 22:16 Comment(2)
Are you running ts-node from within the subdir directory, and tsc from your root project directory ? Does importing from '../my-module' work instead ?Bleb
As far as I understand, ts-node is registered from within the oclif framework, and called automatically when I run the sh script I have in /bin and don't have any transpiled JS in the lib directory. I run tsc with yarn from the project root directory. Importing from a relative path as in '../my-module' works just fine, as expected.Skylar
S
18

It turns out that the problem was due to the fact that although both tsc and ts-node use baseUrl for absolute path resolution, neither of them perform any type of actual mapping from absolute to relative paths in the generated Javascript code. In other words, both the transpiled JS files and the code produced internally by ts-node end up having:

import  {MyClass} = require('my-module')

whereas I was expecting them to contain something like:

import  {MyClass} = require('../my-module')

which prevented node's module loader from finding the module. ts-node also did not work, I believe, because there was simply no tsconfig.json file to indicate the path mappings.

Although confusing IMO, and not properly documented, this is expected behavior, though, as discussed here. As of now, absolute to relative path mapping is not supported by Typescript (see https://github.com/Microsoft/TypeScript/issues/15479).

In order to avoid the situation known as path hell, which means having very deep relative import paths, I found module-alias and tsmodule-alias to be very useful. These modules alter the behavior of the module loader so that it automatically maps aliases to relative paths.

For more information about the problem, refer to this issue on Github.

Skylar answered 31/5, 2018 at 22:14 Comment(2)
Kudos for the big research and Gitlab posts. Helped me to understand that this is a bug-feature that looks like it's gonna work same as options in jsconfig. Only it doesn't. Hit us up in here if you've maybe found some solution in these 6 years.Arlin
Happy to hear you found this useful. Unfortunately I haven't had the time/interest to look for better solutions. Also worth highlighting the fact that at the time of writing I was working on a CLI app, and one of the main reasons for me to use TS was the existence of oclif, which would allow me to prototype fast. With time, however, I concluded that there were more suitable options, and ended up abandoning TS and migrating to Go.Skylar
S
2

Another solution that may be relevant is to run node with NODE_PATH=dist node dist/index.js. This essentially specifies to node what path each absolute import (relative to baseUrl) should use

Sweepback answered 12/7, 2020 at 9:3 Comment(1)
I have been banging my head against the wall for the last two days. I simply could not get my app to run due to Cannot find module (my own module), even though tsc --traceResolution was all happy. I have the following setup: myproj/src/.... The tsconfig.json I have set "outDir": "dist" and "rootDir": ".". Simply adding the following to package.json/Scripts solved the problem "start": "NODE_PATH=dist/src node dist/src/app.js". Thanks for the tip!!!!Samhita

© 2022 - 2024 — McMap. All rights reserved.