[SOLVED] Typescript transpile errors appear only on production server

Issue

Somewhere along the lines of updating my node version / dependencies for both machines, I noticed
that building my app in production on another machine outputs an error while building for production on my main machine does not.

The errors go away if I move the affected devDependencies to the dependencies array in my package.json, but that’s more of a workaround and I would like to know why I’m seeing the different behavior.

As far as I understand, when running npm install in production, it will not install my dev dependencies.
This is fine since I tested this on my dev machine, and the app built fine even without my dev dependencies.

However, in production, I get errors such as these:

Cannot find module 'sinon' or its corresponding type declarations.

Could not find a declaration file for module 'cors'. '/home/ubuntu/brobot/source/node_modules/cors/lib/index.js' implicitly has an 'any' type. Try `npm i --save-dev @types/cors` if it exists or add a new declaration (.d.ts) file containing `declare module 'cors';

They go away if I move the affected packages to the dependencies array instead of dev dependencies, but why? And why does it build fine without my dev dependencies on my dev machine?

Here are my configurations:

package.json shortened:

{
  "main": "dist/src/index.js",
  "type": "module",
  "scripts": {
    "build": "rimraf dist && tsc",
    "test": "mocha",
    "dev:old": "cross-env NODE_ENV=development concurrently \"tsc -w\" \"nodemon -q -r dotenv/config --es-module-specifier-resolution=node dist/src/index.js\"",
    "deploy": "pm2 deploy ecosystem.config.cjs production",
    "preserve": "npm run build",
  },
  "dependencies": {
    "@pkmn/randoms": "^0.5.11",
    "@pkmn/sim": "^0.5.11",
    "@twurple/api": "^5.0.18",
    "@twurple/auth": "^5.0.18",
    "@twurple/chat": "^5.0.18",
    "@twurple/eventsub": "^5.0.18",
    "@twurple/eventsub-ngrok": "^5.0.18",
    "@types/cookie-session": "^2.0.44",
    "@types/express": "^4.17.13",
    "@types/express-ws": "^3.0.1",
    "@types/mocha": "^9.1.0",
    "@types/node": "^17.0.23",
    "@types/passport": "^1.0.7",
    "@types/request": "^2.48.8",
    "axios": "^0.26.1",
    "cookie-session": "^2.0.0",
    "cors": "^2.8.5",
    "dotenv": "^16.0.0",
    "express": "^4.17.3",
    "express-ws": "^5.0.2",
    "helmet": "^5.0.2",
    "mocha": "^9.2.2",
    "moment-timezone": "^0.5.34",
    "mongoose": "^6.2.8",
    "passport": "^0.5.2",
    "passport-oauth": "^1.0.0",
    "request": "^2.88.2",
    "rimraf": "^3.0.2",
    "typescript": "^4.6.2",
    "winston": "^3.6.0",
    "ws": "^8.5.0"
  },
  "devDependencies": {
    "@types/chai": "^4.3.0",
    "@types/cors": "^2.8.12",
    "@types/sinon": "^10.0.11",
    "@types/ws": "^8.5.3",
    "@typescript-eslint/eslint-plugin": "^5.16.0",
    "@typescript-eslint/parser": "^5.16.0",
    "chai": "^4.3.6",
    "concurrently": "^7.0.0",
    "cross-env": "^7.0.3",
    "eslint": "^8.11.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-promise": "^6.0.0",
    "lint-staged": "^12.3.7",
    "nodemon": "^2.0.15",
    "pm2": "^5.2.0",
    "prettier": "^2.6.0",
    "sinon": "^13.0.1",
    "ts-node": "^10.7.0"
  },
  "lint-staged": {
    "*.{js,ts}": [
      "eslint --cache --fix",
      "prettier --check"
    ]
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",                                /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
    "module": "ESNext",                             /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */     /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                              /* Generates corresponding '.map' file. */
    "outDir": "dist",                               /* Redirect output structure to the directory. */
    "strict": true,                                 /* Enable all strict type-checking options. */
    "moduleResolution": "node",                     /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "esModuleInterop": true,                        /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true,                           /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true        /* Disallow inconsistently-cased references to the same file. */
  }
}

PM2 ecosystem.config.cjs:

require('dotenv').config();

// Default configuration
module.exports = {
    apps: [...],
    deploy: {
        production: {
            user: process.env.AWS_USER,
            host: process.env.AWS_PUBLIC_IP,
            key: process.env.AWS_SSH_KEY,
            ref: 'origin/main',
            repo: '...',
            path: process.env.AWS_EC2_PATH,
            node_args: "--experimental-modules --es-module-specifier-resolution=node",
            env: { NODE_ENV: 'production', },
            'post-deploy':
                'npm install && npm run build && pm2 startOrRestart ecosystem.config.cjs --env production'
                             ^^^^^^^^^^^^^^^ this is where errors are thrown in production. `npm run build` works fine on development without dev dependencies installed.
        }
    }
};

Things I’ve considered:

  • Don’t transpile in production. I am cloning my repo during the deploy process so I can’t do this unless I include my build in my repo.
  • Switch devDependencies to dependencies: I really want to avoid doing this since they should be dev dependencies

Other things I’ve tried:

  • Clearing npm cache
  • Deleting my PM2 instance completely and reconfiguring it
  • Deleting package-lock.json (I know this is bad but I’m desperate, no conflicts seen)
  • Removing –experimental-modules and adding back ".js" extension imports
  • Excluding my node_modules in tsconfig.json

There’s gotta be some misconfiguration between my dev/prod machines. Although they are running the same version of node and Typescript.
Both machines are not using globally installed packages as far as I’m aware. The weird thing is that this worked on my production machine just fine before, although I may have not been running it with the production flag correctly. Even though my node_env was "production", it might have been installing my dev dependencies without me noticing.

I’m running out of options here and would really appreciate any help.

Solution

sinon is a dev dependency so it’s not bundled for production. If you need it for production you can add it to a dependency, if not you can remove the reference to sinon and rebundle and it should resolve itself.

Edit 1:

The errors go away if I move the affected devDependencies to the dependencies array in my package.json

I initially glossed over this. Yeah, it sounds like you are using these libraries in your production code but since they aren’t in dependencies they aren’t included in your production bundle. They work in development because you have them as dev dependencies.

Answered By – Dillan Wilding

Answer Checked By – Clifford M. (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *