Unitech / pm2

Node.js Production Process Manager with a built-in Load Balancer.
https://pm2.keymetrics.io/docs/usage/quick-start/
Other
41.31k stars 2.61k forks source link

support "type": "module" (esm for node 13) #4540

Open siric opened 4 years ago

siric commented 4 years ago

esm is no longer experimental in node 13, however pm2 does not read out "type": "module" from package.json, which means that esm imports/exports can not be used with pm2:

import { foo } from './foo.js';
       ^

SyntaxError: Unexpected token {
r3gisc commented 4 years ago

Some investigations: (node v13.3.0, pm2 4.2.0, MacOs 10.14.6)

7|xxxx  | (node:24598) Warning: require() of ES modules is not supported.
7|xxxx  | require() of xxxx.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
7|xxxx  | Instead rename loader-scanner.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from xxxxx/package.json.
7|xxxx  | Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: xxxx.js
7|xxxx  |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1163:13)
9|xxxx  | Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: xxxxx.mjs
9|xxxx  |     at Module.load (internal/modules/cjs/loader.js:981:11)
9|xxxx  |     at Function.Module._load (internal/modules/cjs/loader.js:891:14)
8|xxxx  | (node:24843) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
8|xxxx  | xxxxx.js:10
8|xxxx  | import { Run } from 'xxxxxxx';
8|xxxx  | ^^^^^^
8|xxxx  | SyntaxError: Cannot use import statement outside a module
8|xxxx  |     at wrapSafe (internal/modules/cjs/loader.js:1050:16)
8|xxxx  |     at Module._compile (internal/modules/cjs/loader.js:1098:27)
8|xxxx  |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)

The only workaround I've found so far is to keep esm is to use ESM loader with node args: -r esm (https://www.npmjs.com/package/esm) But it's really slow compared to native node 13 loader

related to [https://github.com/Unitech/pm2/issues/4385]

NB: There is absolutely no require() in the code

bradocchs commented 4 years ago

Same here. I'm using .mjs and node 13, but esm workaround not even working for me. App runs fine using systemd daemon.

NeonCreativeStudios commented 4 years ago

This is my first time using pm2 and I thought I was going insane! Have been googling about this for hours. I hope there will be a fix soon

r3gisc commented 4 years ago

A quick and dirty fix for pm2/lib/ProcessContainerFork.js : @Unitech

26 if (process.env.pm_exec_path)

27 // require('module')._load(process.env.pm_exec_path, null, true);   //REPLACE

27 import(process.env.pm_exec_path); // (we don't use await here)      //BY

Workaround: I'm using a 'custom loader' which is faster than esm-loader and use Node 13 native one. Its goal is to bypass ProcessContainerFork.js

We need to use node interpreter args: (here as api, but can be done using command line/ecosystem file)

pm2Options.interpreterArgs = '-- path/to/loader.js index.mjs' 

// "--" is mandatory because we don't want node to parse our custom args.
// they will be placed before pm2's (ProcessContainerFork.js)
//
// index.mjs is our ES6 target script
// we should need "type":"module" in package.json if .mjs extension is not used
const moduleFile = process.cwd() + '/' + process.argv[2]
process.argv.splice(2, 2)
import (moduleFile)
Unitech commented 4 years ago

Thanks for this report and suggestion, will dig this

AlbertMarashi commented 4 years ago

Is this seriously not working?

AlbertMarashi commented 4 years ago

@Unitech how can I help? This is pretty critical for my team as we're using modules in production.

It's been enabled officially by node, although still experimental (it works great on my end)

AlbertMarashi commented 4 years ago

@Unitech Plz comment, this has been out for some time now and it has not been addressed

Unitech commented 4 years ago

Will work on it by the next 7 days

AlbertMarashi commented 4 years ago

Thanks, I've had to resort to using forever, but keen to switch back to pm2 once it's ready

Unitech commented 4 years ago

Ok I landed the ES module support

Unitech commented 4 years ago

Can anyone try it out please?

npm install Unitech/pm2#development -g
pm2 update
pm2 start app.mjs

Thank you!

vitalets commented 4 years ago

I've checked:

AlbertMarashi commented 4 years ago

@Unitech

Works for me, but I had an error with this applying to saved processes.

I had to delete the process from pm2 and then reinstall it for it to work. Not a huge deal, but it might affect users wanting to move over to ESM

Unitech commented 4 years ago

Release on pm2 4.2.2

npm install pm2@latest -g
pm2 update
r3gisc commented 4 years ago

It works ! thanks a lot

r3gisc commented 4 years ago

Just a detail: according to Node.js doc: https://nodejs.org/api/esm.html

The nearest parent package.json is defined as the first package.json found when searching in the current folder, that folder’s parent, and so on up until the root of the volume is reached.

Here pm2 is checking package.json in the current folder and it's first parent. In my case js is transpiled from src to a/deeper/folder then I have to copy project root's package.json to deployment folder, but it's not a big deal.

vitaliytv commented 4 years ago

with ESM on windows my success only with:

pm2 start node -- src/index.js

chrisstone07 commented 4 years ago

@vitalets @r3gisc With node 12.18 and pm2 4.4.0 pm2 all combinations that you mentioned fails with ERR_REQUIRE_ESM error, which version of node did you use to verify that it's working?

iot-resister commented 4 years ago

This does not work with the docker pm2-runtime.

RUN yarn global add pm2@4.2.2
COPY . .
EXPOSE 8080
CMD [ "pm2-runtime", "index.js" ]

Gives:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /usr/src/app/index.js
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1154:13)
    at Module.load (internal/modules/cjs/loader.js:986:32)

adding pm2 update doesn't fix either.

Per @vitaliytv suggestion I chcnged the following.

CMD [ "pm2-runtime", " node -- index.js" ]
AlbertMarashi commented 4 years ago

Having issues with this again

pmikolajek commented 4 years ago

@Unitech I'm getting Error [ERR_REQUIRE_ESM]: Must use import to load ES Module on pm2 4.4.0 with node 12.18.2.

alanyong91 commented 4 years ago

I don't understand why need to switch to .mjs extension. I don't understand why after I add "type": "module" it still not working. I don't understand why need esm at the first place. I don't understand after install esm, alias in .babelrc totally not working.

pm2 doesn't seems fix the problem but create more problem.

alanyong91 commented 4 years ago

Can we reopen this issue. pm2 4.4.0 have the same problem again

zimtsui commented 4 years ago

why folks above said it works with v4.2.2. ? I have v4.4.1 installed but it still doesn't work

Unitech commented 4 years ago

This test: https://github.com/Unitech/pm2/blob/0d44c41b07193bb793420ea23c476e020f8e99d5/test/e2e/esmodule.sh is passing

What is wrong with these tests? Let me know so I can fix whatever is missing

dthree commented 3 years ago

@Unitech I figured out what is wrong.

In pm2/lib/ProcessUtils.js:

    try {
      data = JSON.parse(fs.readFileSync(path.join(path.dirname(exec_path), 'package.json')))
      if (data.type === 'module')
        return true
      else
        return false
    } catch(e) {
    }

In my use case, index.js is in a subdirectory, such as app/lib/index.js. The issue is that path.join(path.dirname(exec_path) looks for package.json in the same directory as index.js, silently fails, and then can't find it.

Can you modify this to look for package.json at the root of the directory?

dthree commented 3 years ago

Something like this would do the trick.

FbN commented 3 years ago

I think the problem is that ESModules support is disabled for version below 13 (current LTS is 12.18.4) See here

zimtsui commented 3 years ago

@Unitech if pm2 calls the user scripts with require('userscript.js') in an wrapper rather than with process.fork('userscript.js') directly, this issue technically cannot be fixed, because node v13+ forbids require() on es module with type: module, which has nothing to do with whether the function require is hooked by -r esm.

it's needed to make a big change about how pm2 calls user scripts. it should be:

bytedeveloperr commented 3 years ago

@Unitech it's still not working with "type": "module" in the package.json file, like in the picture below

Screenshot from 2021-01-01 04-22-47

BlueskyFR commented 3 years ago

I can confirm that the "type": "module" in package.json does not work properly on NodeJS LTS

dthree commented 3 years ago

I don't know why this isn't getting any priority, I literally can't use PM2 with ESM modules. Just discovered I also can't use the ecosystem file when type: "module" is enabled in package.json because it tries to import the file using require and errors out.

This is the reason ESM modules is erroring, changing this code resolves it for me:

https://github.com/Unitech/pm2/issues/4540#issuecomment-692296125

alanyong91 commented 3 years ago

use babel to build file and run with pm2

nickpharrison commented 3 years ago

Best workaround I've found for this is to change the name of the entry script from ending in ".js" to ending in ".mjs", not exactly an ideal solution, but it gets the job done as a temporary fix until this actually gets some attention

adamhl8 commented 3 years ago

Using an Ecosystem file still doesn't work with ESM. pm2 needs to use import syntax or it could simply try to look for an Ecosystem file ending in .cjs.

For anyone having this issue, a really simple "workaround" is to just write your config file in YAML (see here). i.e. ecosystem.config.yaml and point your scripts to it.

ghost commented 3 years ago

Same feeling as @dhtree, pm2 is blocking our migration to ESM. It's frustrating.

4lgar commented 3 years ago

If it can help someone, I found the following workaround :

pm2 start "node -- /path/to/app.js"

For this configuration:

pm2: 4.1.2 node: 14.16.1 (using n) package.json: has "type": "module" file extension: js os: centOS

Unitech commented 3 years ago

With pm2#development:

app.js

// app.mjs
import { addTwo } from './addTwo.js';

// Prints: 6
console.log(addTwo(4));

addTwo.js

// addTwo.mjs
function addTwo(num) {
  return num + 2;
}

export { addTwo };

package.json:

{
  "name": "esm",
  "version": "1.0.0",
  "description": "",
  "main": "addTwo.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
❯ node -v
v15.12.0

Then when I start:

pm2 start app.js

It works:

4|app    | 6
PM2      | App [app:4] exited with code [0] via signal [SIGINT]
PM2      | App [app:4] starting in -fork mode-
PM2      | App [app:4] online
4|app    | 6
PM2      | App [app:4] exited with code [0] via signal [SIGINT]
PM2      | App [app:4] starting in -fork mode-
PM2      | App [app:4] online
4|app    | 6
PM2      | App [app:4] exited with code [0] via signal [SIGINT]
PM2      | App [app:4] starting in -fork mode-
PM2      | App [app:4] online
4|app    | 6
PM2      | App [app:4] exited with code [0] via signal [SIGINT]
PM2      | App [app:4] starting in -fork mode-
PM2      | App [app:4] online
4|app    | 6
ghost commented 3 years ago

This is fantastic!

HRK44 commented 3 years ago

It works if the app.js file is at the root of the project, if it's deeper, doesn't work

Unitech commented 3 years ago

Tried to start a script in children folder and it still works:

image

nickpharrison commented 3 years ago

For anyone that wants another workaround, I have found that creating a separate entry script for the ecosystem file with extension .mjs works well, which then just imports the "real" entry script. ie having a file called entry.mjs which just contains:

import * as entry from './app.js';
HRK44 commented 3 years ago

Tried to start a script in children folder and it still works:

image

Try one level deeper... esm/deep/app.js I just tried and 1 level deep seems to work, 2 levels deep doesn't work :

Dhruv-Garg79 commented 3 years ago

node version : 14.16.0 start script in package.json - cross-env NODE_ENV=development nodemon --es-module-specifier-resolution=node --experimental-modules --no-warnings src/common/bin/app.js

getting below error on running pm2 start npm -- run start

0|npm      | Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/Programs/npm' is not supported resolving ES modules imported from /home/dhruv/.nvm/versions/node/v14.16.0/lib/node_modules/pm2/lib/ProcessContainerFork.js
0|npm      |     at finalizeResolution (internal/modules/esm/resolve.js:272:17)
0|npm      |     at moduleResolve (internal/modules/esm/resolve.js:699:10)
0|npm      |     at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
0|npm      |     at Loader.resolve (internal/modules/esm/loader.js:86:40)
0|npm      |     at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
0|npm      |     at Loader.import (internal/modules/esm/loader.js:165:28)
0|npm      |     at importModuleDynamically (internal/modules/cjs/loader.js:1006:27)
0|npm      |     at exports.importModuleDynamicallyCallback (internal/process/esm_loader.js:30:14)
0|npm      |     at Object.<anonymous> (/home/dhruv/.nvm/versions/node/v14.16.0/lib/node_modules/pm2/lib/ProcessContainerFork.js:30:16)
0|npm      |     at Module._compile (internal/modules/cjs/loader.js:1063:30) {
0|npm      |   code: 'ERR_UNSUPPORTED_DIR_IMPORT',
0|npm      |   url: 'file://Programs/npm'
0|npm      | }
DurandSacha commented 3 years ago

pm2 start "node -- index.js" and pm2 start index.js

seems to cause less error

ckcr4lyf commented 3 years ago

If it can help someone, I found the following workaround :

pm2 start "node -- /path/to/app.js"

For this configuration:

pm2: 4.1.2 node: 14.16.1 (using n) package.json: has "type": "module" file extension: js os: centOS

This is what worked for me, since the app.js file was not in the root. many thanks!

EskelCz commented 2 years ago

What worked for me to make it run with ecosystem config was naming it ecosystem.config.cjs, and starting with pm2 start ecosystem.config.cjs

bentaber commented 2 years ago

I'll echo @EskelCz in that renaming your ecoystem file to end with .cjs, and of course using common js module syntax inside of that file for requires and exports, seems to be working

CallumWalterWhite commented 2 years ago

If it can help someone, I found the following workaround :

pm2 start "node -- /path/to/app.js"

For this configuration:

pm2: 4.1.2 node: 14.16.1 (using n) package.json: has "type": "module" file extension: js os: centOS

This worked for me, thank you so much!