tsconfig-paths in production not working with pm2 cluster mode

671 Views Asked by At

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.

0

There are 0 best solutions below