tsconfig paths not working

This might help someone - if you use tsc or a tool to compile your TS code to a separate folder such as dist, tsconfig-paths register does NOT work out the box. I have a tsconfig.json like this:

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "lib": ["dom", "esnext"],
        "baseUrl": ".",
        "jsx": "react",
        "removeComments": true,
        "sourceMap": true,
        "outDir": "dist"
        "rootDir": ".",
        "paths": {
            "shared/*": ["./shared/*"],
        }
    },
    "include": ["./client/**/*", "./server/**/*"]
}

You can see that a path such as shared/someFolder/someScript will resolve correctly to the shared folder in my project, which is a load cleaner than lots of relative ../../../../ paths.

However, this was throwing me the error:

➜  game git:(game-dev) ✗ node --inspect -r tsconfig-paths/register dist/server/runProd.js
Debugger listening on ws://127.0.0.1:9229/f69956aa-d8d6-4f39-8be1-9c3e8383d4be
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.
internal/modules/cjs/loader.js:584
    throw err;
    ^

Error: Cannot find module 'shared/types/UserTypes'

I did a bit of digging and found that the tryPaths array produced by tsconfig-paths has absolute URLs relative to the project/cwd base, rather than the build dist folder.

inspect screengrab

This seems obvious in retrospect. There doesn't seem to be an obvious way to handle this with the library, so I have solved this by copying the tsconfig.json into the dist folder and running node -r tsconfig-paths/register main.js.


I did also struggle with .tsconfig not recognizing my aliases (while in another project that supposed to have the save config it worked perfectly).

As it turned out, it was a rookie mistake: I put the paths prop to the end of the JSON object, but it has to be a nested property of the compilerOptions part:

// This does't work ❌
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
     //...
    "baseUrl": ".",
  },
  "include": ["next-env.d.ts", "twin.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"],
  "paths": {
      "@components/*": ["components/*"],
      "@lib/*": ["lib/*"]
    }
}
// This should work ✅
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
     //...
    "baseUrl": ".",
    "paths": {
      "@components/*": ["components/*"],
      "@lib/*": ["lib/*"]
    }
  },
  "include": ["next-env.d.ts", "twin.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"],
}

I have no idea why this is now working on the eleventh time I tried (yet didn't the first 10), but the /* seems to be the secret sauce, and the example in the docs is apparently pointing to a specific file (and the file extension is omitted).

{
    "compilerOptions": {
        "baseUrl": "./src", // setting a value for baseUrl is required
        "moduleResolution": "node", // was not set before, but is the default
        "paths": {
            "@client/*": [
                "client/*",
            ],
            "@suir/*": [ // notice the `/*` at the end
                "../node_modules/semantic-ui-react/dist/commonjs/*", // notice the `/*`
            ],
        },
        // …
    },
    "include": [
        "./src/client/**/*",
    ],
}

As mentioned in the comments by Emily Zhai, this can sometimes just require a language server restart.

In VSCode, you can press Cmd/Ctrl + Shift + P and search for Typescript: Restart TS Server.

After restarting, everything started working for me.