Skip to content

Commit c24801b

Browse files
committed
Fix ESM loader to work with Node 20
`process.send` is no longer accessible from ESM loader in Node 20, therefore loader needs to use `context.port` and a proxy function to send messages to the parent. See https://nodejs.org/api/esm.html#globalpreload
1 parent 9ab4eb6 commit c24801b

File tree

6 files changed

+23
-24
lines changed

6 files changed

+23
-24
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ jobs:
44
script: npm run lint
55
language: node_js
66
node_js:
7+
- 20
78
- 18
89
- 16
910
- 14

lib/loaders/ipc.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ const cmd = 'NODE_DEV';
33
export const send = m => {
44
if (process.connected) process.send({ ...m, cmd });
55
};
6+
7+
export const sendPort = (port, m) => {
8+
if (port) port.postMessage({ ...m, cmd });
9+
};

lib/loaders/load.mjs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { createRequire } from 'module';
22
import { fileURLToPath } from 'url';
3-
import { send } from './ipc.mjs';
3+
import { sendPort } from './ipc.mjs';
44

55
const require = createRequire(import.meta.url);
66

7+
// Port used for communication between the loader and ESM modules
8+
// https://nodejs.org/api/esm.html#globalpreload
9+
let port;
10+
711
export async function load(url, context, defaultLoad) {
812
const required = url.startsWith('file://') ? fileURLToPath(url) : url;
913

10-
send({ required });
14+
sendPort(port, { required });
1115

1216
try {
1317
return await defaultLoad(url, context, defaultLoad);
@@ -19,3 +23,15 @@ export async function load(url, context, defaultLoad) {
1923
});
2024
}
2125
}
26+
27+
export const globalPreload = (context) => {
28+
// Store port
29+
port = context.port;
30+
31+
// Inject code to forward loader events to the parent
32+
return `
33+
port.on('message', (m) => {
34+
if (process.connected) process.send(m);
35+
});
36+
`;
37+
};

test/fixture/experimental-specifier-resolution/index.mjs

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/fixture/resolution.mjs

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/spawn/esmodule.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,6 @@ const tap = require('tap');
22

33
const { spawn, touchFile } = require('../utils');
44

5-
tap.test('Supports ECMAScript modules with experimental-specifier-resolution', t => {
6-
spawn('--experimental-specifier-resolution=node resolution.mjs', out => {
7-
if (out.match(/touch message.js/)) {
8-
touchFile('message.js');
9-
return out2 => {
10-
if (out2.match(/Restarting/)) {
11-
t.match(out2, /\[INFO\] \d{2}:\d{2}:\d{2} Restarting/);
12-
return { exit: t.end.bind(t) };
13-
}
14-
};
15-
}
16-
});
17-
});
18-
195
tap.test('Supports ECMAScript modules', t => {
206
spawn('ecma-script-modules.mjs', out => {
217
if (out.match(/touch message.mjs/)) {

0 commit comments

Comments
 (0)