Typescript: Recursively Resolving Modules

Created on 31 Aug 2016  ยท  15Comments  ยท  Source: microsoft/TypeScript

TypeScript Version: 2.1.0

I'm using ts-loader and Webpack to compile my .ts files. In My Webpack config I have the following configuration:

resolve: {
  modulesDirectories: ['shared', 'node_modules']
}

This allows me to import a component that lives in the shared directory without spelling out the full relative path. Example:
import Button from 'components/Button'

Webpack will walk up the directory tree and use the first shared directory it finds (the same way it does with the node_modules directories). This way, the component can live in the ../../shared/components/Buttton.tsx and Webpack will find it and bundle it.

My files compile fine because I am using ts-loader but I am getting can not find module errors in my editor.

Is there a way to tell the TS compiler to recursively look for the component in the shared directories?

I noticed the new baseUrl and paths configuration option but to my knowledge you can not use paths to recursively look for a directory.

Needs Proposal Suggestion

Most helpful comment

this is a huge blocker to adopting typescript on larger projects. I've been trying to come up with a flow to typescript migration for a lot of our projects and without this option it's just infeasible.

We need an option that allows customizing which modules are node_modules

All 15 comments

I noticed the new baseUrl and paths configuration option but to my knowledge you can not use paths to recursively look for a directory.

well not fully. but it can emulate the behavior, e.g.

"paths" : {
    "*": [
             "./node_modules/*",
             "../node_modules/*", 
             "../../node_modules/*", 
             "../../../node_modules/*" 

              // you get the idea..
          ] 
}

@mhegazy

I just added this to my tsconfig:

"paths": {
    "*": [
      "./shared/*",
      "../shared/*",
      "../../shared/*",
      "../../../shared/*",
      "../../../../shared/*",
      "../../../../../shared/*",
      "../../../../../../shared/*"
    ]
  },

but I'm still getting the errors.

I ran tsc with traceResolution and grep'd for shared but nothing came up. It doesn't seem like tsc is looking in the shared folders for the modules.

Can u share a repro.

@mhegazy Here is a repro: https://github.com/bezreyhan/ts-paths

After you npm install, you can run node_modules/.bin/tsc and see that the helper function in the shared folder can not be found.

Also the output of node_modules/.bin/tsc --traceResolution | grep shared is empty.

A few of issues here.

  • "paths" is a compiler option, so it should be under the "compilerOptions" node. otherwise it is ignored.
  • the paths on the right hand-side of the paths map are all relative to "baseUrl", so you need to specify one
  • you do not need the recursive look ups in this case since you have only one "shared" folder, the recursive lockups would be needed if you had multiple "shared" folders each on a different level, and you wanted the compiler to try them all.

here is a working tsonfig for you:

{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "module": "commonjs",
    "target": "es6",
    "jsx": "react",
     "baseUrl": "./",
     "paths": {
        "*": [
          "src/shared/*"
        ]
    }
  },
  "include": [
    "./src/first/second/third/page.ts"
  ]
}

@mhegazy Not putting paths within compilerOptions was a silly oversight by me. Thanks for pointing me the right way.

However, I still need recursive lookups (In the repro I wanted to keep it simple so I only put one shared folder). If my baseUrl is set to './' the resolution will begin at the root of the project. I wanted the resolution to begin at the file that is importing a module and then recursively move up the directory tree.

Can you give me any pointers here?

i miss understood then. I do not think there is an easy way to do this.

Hmm, would this be something the TS team would consider implementing? Being able to designate another directory name other than node_modules would be a useful.

For example, many people in the React community use Webpack's modulesDirectories config option to implement this sort of folder structure: https://gist.github.com/ryanflorence/daafb1e3cb8ad740b346

would it be possible to have the same compoenent "overridden" in multiple "shared" folder, e.g.:

app
โ”œโ”€โ”€ screens
โ”‚   โ”œโ”€โ”€ Admin
โ”‚   โ”‚   โ”œโ”€โ”€ shared
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Module.js
โ”‚   โ”‚   โ””โ”€โ”€ index.js   // imports ./shared/Module
โ”œโ”€โ”€ shared
โ”‚   โ””โ”€โ”€ Module.js
โ””โ”€โ”€ index.js // imports ./shared/Module and not ./screens/Admin/Shared/Module

Sorry for the delayed response.

Yes it would. It function the same way that Node looks for the node_modules.

Here is the Webpack documentation: https://webpack.github.io/docs/configuration.html#resolve-modulesdirectories

We do something similar to this in classic module resolution that walks up the folder structure to find a match. we will need a proposal on how to update the resolution to enable this.

It would be very useful if TS supported more advanced ala webpack resolution strategies. https://webpack.github.io/docs/configuration.html#resolve-alias

but no need be as much sophisticated it believe

Edit2 I can't get the webpack to work with my below trick, it works for some files.

Btw, for now it's easiest to just drop a symbolic link "Components" to the node_modules. It works even on Windows. Then one can simply do import { Blaa } from "Components/Blaa";And create a simple NPM post install script that uses node's fs.symlink and it should make it pretty good.

I know this was made in 2016 but I was going to use this approach on a new project and ran into this issue. I don't know much about TSC internals but I might spend some time to see what this might entail.

I would be apt to propose a similar compiler option as rootDirs but just moduleDirs that the Node resolution strategy would use when it walks up the directory tree. Similar to other tools, by default the value of moduleDirs is just ["node_modules"] but when overridden you can provide other directories, e.g. ["node_modules", "shared"]. I think the same resolution strategy logic should work, it just will change from only considering node_modules to considering all entries in the moduleDirs option.

this is a huge blocker to adopting typescript on larger projects. I've been trying to come up with a flow to typescript migration for a lot of our projects and without this option it's just infeasible.

We need an option that allows customizing which modules are node_modules

Was this page helpful?
0 / 5 - 0 ratings