ERR_REQUIRE_ESM on node.js app – only on web host and not on local server

I’m trying to implement this Google Wallet pass converter (https://github.com/google-wallet/pass-converter) on my website as a node.js app. I have it working locally on my computer running a local WordPress installation with npm version 10.3.0, node.js 20.11.0, PHP 7.4.30, and an Apache web server.

When I install the app through cPanel on my host using the node.js web app installation, after running npm install and trying node app.js demo I receive the following error:

.../public_html/wp-content/plugins/tickera-wallet-passes/pass-converter/config.js:17
const stripJsonComments = require('strip-json-comments');
                          ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /public_html/wp-content/plugins/tickera-wallet-passes/pass-converter/node_modules/strip-json-comments/index.js from /public_html/wp-content/plugins/tickera-wallet-passes/pass-converter/config.js not supported.
Instead change the require of index.js in /public_html/wp-content/plugins/tickera-wallet-passes/pass-converter/config.js to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/public_html/wp-content/plugins/tickera-wallet-passes/pass-converter/config.js:17:27) {
  code: 'ERR_REQUIRE_ESM'
}

Node.js v20.14.0

When I run node app.js demo locally it comes back with Listening on http://127.0.0.1:3000 — which is the intended response I’m looking for.

My config.js file is

const stripJsonComments = require('strip-json-comments');
const stripJsonTrailingCommas = require('strip-json-trailing-commas').default;
const fs = require('fs');
const path = require('path');

// The Proxy wrapper ensures config is lazily loaded, so we can redefine
// PASS_CONVERTER_CONFIG_PATH at runtime, which we do in the tests.
module.exports = new Proxy(
  {},
  {
    get(target, prop, receiver) {
      if (this._config === undefined) {
        const configPath = process.env.PASS_CONVERTER_CONFIG_PATH || path.resolve(__dirname, 'config.json');
        this._config = JSON.parse(stripJsonTrailingCommas(stripJsonComments(fs.readFileSync(configPath, 'utf8'))));
        // Most of the entries default to empty strings, but code may expect them to be
        // undefined since they were originally env vars, so filter these out to retain the behavior.
        this._config = Object.fromEntries(Object.entries(this._config).filter(([key, value]) => value !== ''));
      }
      return this._config[prop];
    },
  },
);

I am aware this is related to the way node.js handles module imports (‘require’ instead of ‘import’), but I’m not comfortable rewriting everything. I have tried creating a loader script to dynamically import app.js (Node JS (cpanel) Error: I’m getting an error [ERR_REQUIRE_ESM]: Must use import to load ES Module), I have tried using “jsonc-parser” instead of “strip-json-comments” since it is a CommonJS compatible library but that didn’t work (my JS programming skills are lacking, I probably did it wrong), and I have tried a bunch of different versions of node.js.

I am baffled because I had this error come up 6 months ago when I first tried to implement this on my website, yet somehow I was able to fix it (and of course didn’t write down what I did). I only recently noticed that it stopped working again and can’t figure it out anymore. My web host is running npm 10.7.0, PHP 8.0 (could this be the problem?), and I have it configured to be node.js 20.14.0 (and tried it with 18.20.3 and 16.20.2).

Any advice anyone has is appreciated. The fact that it works locally and not on my web host is the most frustrating!