Search code examples
javascriptnode.jstypescriptsymlinkmonorepo

Typescript: resolve relative import path when using symlinks


This seems like a dumb question, but I struggle to find the answer.

The situation

Here is my folder structure:

myProject/
├── module1/
│   ├── config.ts
│   └── init.ts #symlink
├── module2/
│   ├── config.ts
│   └── init.ts #symlink
└── symlinks/
    └── init.ts # the real not duplicated file

The file init.js import local files like so:

import config from './config'

// ...

The problem

The problem is that typescript throws Cannot find module './config' or its corresponding type declarations

I tried playing with typescript option preserveSymlinks but it didn't solve my problem

I know about other ways to achieve my goal but it's overkill for just one file and it doesn't solve relaying on a relative path (like creating a npm module, creating a function and pass relative file content as parameter or even generating files at runtime...)

=> I am working with typescript in a monorepo.

Is it possible to use symlinks this way? If no, are there other (simple) alternatives?

EDIT: I have made this example repo, you can use it as a base for experiments


Solution

  • By default, TypeScript resolves modules based on the physical path of the file, not the resolved path after following any symbolic links. So, in your case, when you try to import config from ./config, TypeScript looks for the file at the physical path myProject/module1/config.ts or myProject/module2/config.ts, but it's actually located at myProject/symlinks/config.ts.

    You can tell TypeScript to resolve modules based on the resolved path by setting the resolveSymlinks option to true in your tsconfig.json file:

    {
      "compilerOptions": {
        // ...
        "resolveSymlinks": true
      },
      // ...
    }
    

    This will cause TypeScript to follow symbolic links when resolving modules, so it should be able to find your config file. Note that you may also need to set preserveSymlinks to true if you're using tools like Webpack that may alter the resolved path.

    Another option is to use module aliases in your tsconfig.json file to map the ./config path to the correct location. For example:

    {
      "compilerOptions": {
        // ...
        "baseUrl": ".",
        "paths": {
          "*": ["*", "symlinks/*"]
        }
      },
      // ...
    }
    

    This will tell TypeScript to look for all modules in the symlinks directory, as well as in their original locations. Then, you can import config like this:

    import config from 'module1/config';
    

    This should work regardless of whether init.ts is a symlink or a regular file.