node.jspassengeres6-modules

Node JS (cpanel) Error: I'm getting an error [ERR_REQUIRE_ESM]: Must use import to load ES Module


So, I've spent quite a few hours today trying to put my nodeJS app that's fully using ESM (modules), and I've deployed it via cPanel on a server that's using Node v. 14.20.1. I'm constantly getting an error:

 App 1153856 output: internal/modules/cjs/loader.js:948
App 1153856 output:     throw new ERR_REQUIRE_ESM(filename);
App 1153856 output:     ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /<serverlocation>/app.js
    App 1153856 output:     at new NodeError (internal/errors.js:322:7)
    App 1153856 output:     at Module.load (internal/modules/cjs/loader.js:948:11)
    App 1153856 output:     at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    App 1153856 output:     at Module.require (internal/modules/cjs/loader.js:974:19)
    App 1153856 output:     at Module.require (/opt/cpanel/ea-ruby27/root/usr/share/passenger/helper-scripts/node-loader.js:80:25)

All files are written as modules, I don't have one "require()" anywhere.

Since the Node started fully supporting JS modules from v14 on, I'm taking a wild guess that the hosting server I'm using (and their Passenger for NodeJS) is using a loader that's using "require()" when calling my app.js file. I've tried multiple solutions, I've even switched my app.js file to a CommonJS type, but then it required me to switch all other files to CJS as well, which would be too much hassle.

Has anyone managed to find a proper solution to this issue?


Solution

  • For anyone trying to solve this issue, this is how I solved it:

    1- Create a loader script: Not necessarily in the same folder as the main app.js file of your app, but that's where I created it. Call it something like loader.cjs. The extension being .cjs is important if you have "type": "module" in your package.json. As you might've guessed, this will be the new main of your app. Since the passenger's loaders have an issue with ES modules, just let it load a commonjs file instead.

    2- Dynamic import of app.js: Did you know that you can still load ES modules in commonjs files? You just need a little extra bit to do so. Apparently, ES modules are loaded asynchronously, and this doesn't work well with synchronous commonjs files. That's why you got the issue in the first place, right? Therefore, the solution is: dynamic imports. Just like async functions, treat the imports of ES modules as promises. I don't really like using .then(), so I opted with await:

    async function loadApp() {
        await import("/path/to/app.js");
    }
    
    loadApp();
    

    3- Rename your app.js's extension: I don't exactly know why this is necessary, but I got error along the lines of "couldn't find /path/to/app.mjs" and so I changed it to so. Then it worked. You can keep the name of the file as "app.js" in the path in the previous point, and the import will sstill correctly look for "app.mjs";

    There might be more efficient ways to do this, but that's what my 2 bits of brain could come up with. Hopefully it helps others as well.