Typescript – How to solve the problem with unresolved path aliases in transpiled .js files

211116 – 1121
Summary
This is a somehow detailed description of a well know issue related to the fact that the tsc compiler (transpiler) does not resolve/emit correctly the path aliases to the output JavaScript .js files. As a result, when you try to run the output file via node you get an error. The solution presented here uses a tiny external specialized package and more specifically as a demo it uses the @ef-carbon/tspm.
The problem
Setting up path aliases in the tsconfig.json file is really a nice approach to making your imports look clear and tidy. So instead of using absolute paths like:
import { capitalize } from './src/app/modules/myfunction'
you can use:
import { capitalize } from '@modules/myfunction' ;
The path alias definition in the tsconfig.json file should look like the one below:
. . . "baseUrl": "./", "paths": { "@modules/*":["./src/app/modules/*"] }, . . .
You can find dozens of posts about it in Medium and elsewhere. This seems working OK in most of the cases, if all your source files and subfolders are located under the same rootDir.
However, sometimes this can cause error and warning challenges, which is especially true, when your project has a customized folder structure, where not all of the sub-folders containing source files are located under the same rootDir folder (.g. having various source peer sub-folders, similar to projects using different packages or monorepos folder structures).
In such cases, -if everything else has been set up OK- even if the tsc compiler builds (transpiles) your code without any error when you run your code using the node you may get an error similar to:
Error: Cannot find module ‘modules/myfunction’
This error actually points to the transpiled(’emitted’) JavaScript .js file containing the line with the path alias.
The actual cause of the issue is that the tsc compiler does not transpiles correctly the path aliases to absolute (real) paths. So, when the node runs the .js file and tries to find the required module, it simply fails.
NB: Please note, that the issue is a node run issue. Compilation works OK without showing errors.
Of course, a solution is to go back and use only absolute paths when we import from our own project subfolders. This solves the issue, but then we cannot benefit from the clean and tidy usage of path aliases.
Reproducing the issue
Assumptions and settings
- we work in a relatively simple Typescript project – with minimum possible packages (no toolchains or bundlers such as: babel, webpack, esbuild, etc.)
- the scaffold includes peer (‘sibling’) sub-folders for our source .ts files
- we make use of baseUrl and paths in our tsconfig.json file
- we use node as project runner -and not ts-node
- we use just one tsconfig.json (the one generated by tsc –init) which is directly located under our project folder (no ‘base’ – ‘children’ tsconfig.json files)
- our project folder is also declared with baseUrl in tsconfig.json – all adjustments in tsconfig.json file are given below.
- we use just one -the default- package.json (created with npm init) and which also is directly located under our project folder – the scripts section as well as other settings in package.json file are as below.
Our project folder structure, is similar to the one below:
The rootDir is the /src subfolder and contains all of our source files. The beseUrl is the project folder -where the tsconfig.json is.
The whole tsconfig.json file is given below:
The package.json file:
Code examples
This is the ./src/index.ts file (in the project rootDir). It needs to import a module using path aliases:
This is the ./src/app/modules/myfunction.ts file containing the function which is imported in ./src/index.ts file, above:
The build process works OK (npm run build):
➜ test3 git:(main) ✗ npm run build > testproject@0.0.2 build > ./node_modules/typescript/bin/tsc ➜ test3 git:(main) ✗
And creates the build sub-folder structure:
However, the run fails (npm start):
So, the problem is that the tsc does not resolve correctly the path aliases (well, it does not resolve them at all :-().As you can see below, they remain as in index.ts @modules/myfunction :
Searching for a solution
What should be done
There are several ways to deal with the issue. One ‘brave’ approach is to solve it on your own, using for instance a bash script (sed and jq should be your first-class tools in such a case), or make your very own node / npm package.
Generally, what should be done can be summarized as follows:
Take a look into tsconfig.json and check if there are path aliases defined in it. Path aliases are defined as an array of key-value objects with the “paths”: key. Paths setting, usually require to set also the “baseUrl” key. Each alias defined corresponds to an absolute path. So, if you can get this info, the next step is to look for the values of the “rootDir” and “outDir” keys. After that, look for any transpiled JavaScript .js file under the outDir folder, and for each one check to see if there are aliases. You can see the official documentation here for more detailed info.
For the example above the paths has just 1 alias the @modules, which corresponds to the absolute path ./src/app/modules:
“paths”: {“@modules/*”:[“./src/app/modules/*”]}.
Since the outDir is the “build” subfolder, we have to check one by one all the .js files under it, to find if the “@modules” string exists, and then we have to replace it with the “./app/modules” string. The replacing string is the one defined with “paths” key, without the /src prefix. This is because the building (transpile) process mirrors the /src (rootDir) folder to the build (outDir) folder.
Use an external -specialized for the above case- package.
As you can understand, you might have to dedicate some no-no-sense time to deal with it on your own. So, if you want a quick solution then you have to search for a ready-made tool. Playing around trying to find such a tool, you will probably conclude that using a tiny external npm package that deals with paths is a relatively simple and quick solution. There are a lot of such packages. Just do a search in npm and you will see dozens related to the issue:

I’ve grabbed some of them just to showcase:

Since I was looking for a minimalistic approach to solve the issue, I decided to go with the @ef-carbon/tspm package -the last one at the table above). A quite light and simple solution that seems to do the job!
So, let’s see how it behaves.
Because it is a bit older package (it is made for typescript version 2 or 3), we should use the — force flag to install it with npm:
test3 git:(main) ✗ npm i @ef-carbon/tspm npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: testproject@0.0.2 npm ERR! Found: typescript@4.4.4 npm ERR! node_modules/typescript npm ERR! dev typescript@"^4.4.4" from the root project npm ERR! npm ERR! Could not resolve dependency: npm ERR! peer typescript@"^2 || ^3" from @ef-carbon/tspm@2.2.5 npm ERR! node_modules/@ef-carbon/tspm npm ERR! @ef-carbon/tspm@"*" from the root project npm ERR! npm ERR! Fix the upstream dependency conflict, or retry npm ERR! this command with --force, or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution. npm ERR! npm ERR! See /Users/zafeiropoulospanos/.npm/eresolve-report.txt for a full report. npm ERR! A complete log of this run can be found in: npm ERR! /Users/zafeiropoulospanos/.npm/_logs/2021-11-16T10_21_46_238Z-debug.log ➜ test3 git:(main) ✗ npm i @ef-carbon/tspm --force npm WARN using --force Recommended protections disabled. npm WARN ERESOLVE overriding peer dependency npm WARN While resolving: @ef-carbon/tspm@2.2.5 npm WARN Found: typescript@4.4.4 npm WARN node_modules/typescript npm WARN peer typescript@">=3.8 <5.0" from ts-jest@27.0.7 npm WARN node_modules/ts-jest npm WARN dev ts-jest@"^27.0.7" from the root project npm WARN 1 more (the root project) npm WARN npm WARN Could not resolve dependency: npm WARN peer typescript@"^2 || ^3" from @ef-carbon/tspm@2.2.5 npm WARN node_modules/@ef-carbon/tspm npm WARN @ef-carbon/tspm@"*" from the root project npm WARN npm WARN Conflicting peer dependency: typescript@3.9.10 npm WARN node_modules/typescript npm WARN peer typescript@"^2 || ^3" from @ef-carbon/tspm@2.2.5 npm WARN node_modules/@ef-carbon/tspm npm WARN @ef-carbon/tspm@"*" from the root project npm WARN deprecated core-js@2.6.12: core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js. added 90 packages, and audited 426 packages in 8s 25 packages are looking for funding run `npm fund` for details 7 moderate severity vulnerabilities To address issues that do not require attention, run: npm audit fix Some issues need review, and may require choosing a different dependency. Run `npm audit` for details. ➜ test3 git:(main) ✗
After installation you can see that it has been added in dependencies section in package.json file:
. . . "dependencies": { "@ef-carbon/tspm": "^2.2.5", "@types/node": "^16.11.7" }, . . .
Then, we can add an appropriate script, e.g. “postbuild” in the scripts section (in package.json):
“postbuild”: “ef-tspm”,
. . . "scripts": { "build": "./node_modules/typescript/bin/tsc", "postbuild": "ef-tspm", "build:watch": "npm run build --- --w", "clean": "rm -rf build/*.js build/*.map build/*", "start": "node build/my-app.js", "test": "jest --coverage", "test:watch": "jest --coverage --watchAll" }, . . .
Now, clean everything and build the project again:
npm run clean
➜ test3 git:(main) ✗ npm run clean > testproject@0.0.2 clean > rm -rf build/*.js build/*.map build/* ➜ test3 git:(main) ✗ ➜ test3 git:(main) ✗ npm run build > testproject@0.0.2 build > ./node_modules/typescript/bin/tsc > testproject@0.0.2 postbuild > ef-tspm Wrote 'build/my-app.js' ➜ test3 git:(main) ✗
Check the index.js in the output (build) folder – Now, the path is resolved from alias (@modules/myfunction) back to absolute (./app/modules/myfunction) .
It’s OK!
Run the app- now its OK!
➜ test3 git:(main) ✗ npm start > testproject@0.0.2 start > node build/my-app.js Hi There! Panos is here! ➜ test3 git:(main) ✗
That’s it!
Let me know if you find problems or if there is something missing.
Enjoy, and stay tuned.
Thanx for reading!