TL;DR — if you use ts-node
or ts-node-dev
and care about RAM usage, use --tranpileOnly
or tsc
directly to reduce it by X6.
It turns out that runningts-node-dev
/ ts-node
is constantly consuming hundreds of megabytes of RAM even for small and simple applications. In development, it is usually not a big concern, but it can be if your application is running inside a docker container with limited resources (for example, with Docker Desktop on Mac which allocates by default only 2GB of RAM to all the containers in total).
TypeScript code should be transpiled to Javascript. This can be done before running the process (tsc) or in runtime (ts-node). The most efficient way is transpiling before running, however, this is very not developer-friendly as it takes forever.ts-node-dev
loads everything into memory, watches the changes the developer is making and transpiles the project fast on every change.
I encountered the issue while building a demo application to showcase our product at Aspecto. I run multiple typescript services with docker-compose, and begun to see arbitrary ts-node-dev
processes exiting without even running the application with the message “Done in 79.06s”. This was due to a lack of memory - each typescript service was using ~600MB of RAM out of the total 2GB available for all containers.
After digging a bit, I found a few possible solutions and wanted to share them:
Run ts-node-dev with option --transpile-only
In my case, adding the --transpile-only
option to ts-node-dev
reduced the consumed RAM from ~600MB to ~170MB.
The price is that, well, the typescript code will only be transpiled, and typechecking will be skipped. Most modern IDEs (vscode, web storm), has built-in typescript IntelliSense which highlights errors, so for me, it was a fair price to pay.
If you use ts-node
to run code in production which was already successfully compiled and tested in the CI, you can only benefit from setting this option.
Compile the code with tsc and monitor file changes with nodemon
Instead of using ts-node-dev
which consumes a lot of memory, it is possible to compile the application directly with tsc
and then run it from dist/build like this: node dist/index.js
. For automatic reload on source file changes, nodemon
/ node-dev
can be used. This is my “start” script in package.json:
This approach reduced the RAM on my service from ~600MB to ~95MB (but there is still a spike in RAM to 600Mb for few seconds while tsc
is compiling).
Unlink the previous option, this approach WILL check for typescript errors and warnings, and the service will not start if errors exist in the code.
The price to pay here is a longer compilation time. In my setup, it’s about 10 seconds from saving the file until the service restarts.
Increase Docker Desktop Available RAM
This is the easiest fix. Just allocate more Memory to Docker Desktop by going to Preferences => Resources => Memory, and increase the value.
While it fixes the immediate problem, the containers will still consume a lot of memory, and if you have plenty of them, it might be a problem soon enough.
Also, changing the default configuration should be done by every user that wants to run the system with docker-compose, which introduces complexity in installation and usage.
Summary
- If memory consumption is not an issue for you (most cases), just use
ts-node
in production andts-node-dev
in development. - If you care about memory, then you have a tradeoff between fast restart time after modifications but typechecking only in the IDE (set
--transpileOnly
), or have typechecking in compilation, but slower restart on each modification (directly usetsc
andnodemon
/node-dev
)