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.
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.