My goal is to run a typescript project, compiled for the production, launched in cluster mode with pm2. For development purposes, I used relative paths.
This is the project in its simplest form :
src/app.ts:
import { ModuleA, ModuleB } from 'TestModules';
console.log('Started !');
new ModuleA();
ModuleB();
console.log('Never stop !')
setInterval(() => { }, 1 << 30);
src/TestModules/ModuleA.ts:
export default class TestModuleA{
constructor(){
console.log('New Test Module A !!');
}
}
src/TestModules/ModuleB.ts:
export default function testModuleB(){
console.log('Test Module B called !!');
}
src/TestModules/index.ts:
export { default as ModuleA } from './ModuleA';
export { default as ModuleB } from './ModuleB';
tsconfig.json:
{
"ts-node": {
"files": true
},
"include": ["src/**/*"],
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"baseUrl": "./src",
"sourceMap": true,
"outDir": "./dist/src",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
package.json
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "if [ \"${NODE_ENV}\" = production ]; then npm run start:prod; else npm run start:dev;fi",
"start:prod": "node -r ts-node/register/transpile-only -r tsconfig-paths/register src/app.js",
"start:dev": "npm run build && node -r ts-node/register/transpile-only -r tsconfig-paths/register dist/src/app.js",
"build": "npm run clean && tsc && npm run copy-files",
"clean": "rm -rf ./dist",
"copy-files": "cp ./package.json ./dist;cp ./tsconfig.json ./dist;"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^4.5.5"
},
"dependencies": {
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1"
}
}
As you can see, I use -r ts-node/register/transpile-only -r tsconfig-paths/register as node parameters to link relative paths. I didn't find a better solution.
Here is the pm2 file to launch it:
{
"apps": [{
"name": "test",
"script": "src/app.js",
"interpreter_args": "-r ts-node/register/transpile-only -r tsconfig-paths/register",
"instances": 2,
"exec_mode": "cluster",
"env": {
"NODE_ENV": "production"
}
}]
}
When I launch that, sometimes it works fine. But most of the time it fails and it generates these errors in ~/.pm2/pm2.log
2022-11-22T09:22:41: PM2 log: App [nix-shell:0] starting in -cluster mode-
2022-11-22T09:22:41: PM2 log: App [nix-shell:0] online
2022-11-22T09:22:41: PM2 log: App [nix-shell:1] starting in -cluster mode-
node:internal/modules/cjs/loader:936
throw err;
^
Error: Cannot find module 'ts-node/register/transpile-only'
Require stack:
- internal/preload
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at Module._preloadModules (node:internal/modules/cjs/loader:1282:12)
at loadPreloadModules (node:internal/bootstrap/pre_execution:539:5)
at prepareMainThreadExecution (node:internal/bootstrap/pre_execution:85:3)
at node:internal/main/run_main_module:7:1 {
code: 'MODULE_NOT_FOUND',
requireStack: [ 'internal/preload' ]
}
2022-11-22T09:22:41: PM2 log: App name:nix-shell id:0 disconnected
2022-11-22T09:22:41: PM2 log: App [nix-shell:0] exited with code [1] via signal [SIGINT]
When I launch it by hand with NODE_ENV=production node -r ts-node/register/transpile-only -r tsconfig-paths/register src/app.js it works fine.
It also works fine when I launch it in fork mode :
{
"apps": [{
"name": "test",
"script": "src/app.js",
"interpreter_args": "-r ts-node/register/transpile-only -r tsconfig-paths/register",
"instances": 2,
"exec_mode": "fork",
"env": {
"NODE_ENV": "production"
}
}]
}
I have replicate it in a repl.it sandbox (sorry, you need an account to use it): https://replit.com/join/uxewvlklxf-themadocarina
All sources are in source directory.
The bulidTest.sh will generate a build for cluster and one for fork in testCluster and testFork.
And in each of these folders, you will find a pm2*.config.json that will start it in given mode and a pm2Clear.sh that will clear everything.
I don't fully understand why it fails in cluster mode but works fine in other launching modes. There must be a better way to handle the relative links in production mode but I didn't find any others despite my searchings.