Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/lib/libatomic.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,12 @@ addToLibrary({

emscripten_has_threading_support: () => !!globalThis.SharedArrayBuffer,

#if ENVIRONMENT_MAY_BE_NODE
emscripten_num_logical_cores__deps: ['$nodeOs'],
#endif
emscripten_num_logical_cores: () =>
#if ENVIRONMENT_MAY_BE_NODE
ENVIRONMENT_IS_NODE ? require('node:os').cpus().length :
ENVIRONMENT_IS_NODE ? nodeOs.cpus().length :
#endif
navigator['hardwareConcurrency'],

Expand Down
8 changes: 6 additions & 2 deletions src/lib/libcore.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ addToLibrary({
},
#endif

#if ENVIRONMENT_MAY_BE_NODE
$nodeOs: "{{{ makeNodeImport('node:os') }}}",
$nodeChildProcess: "{{{ makeNodeImport('node:child_process') }}}",
_emscripten_system__deps: ['$nodeChildProcess'],
#endif
_emscripten_system: (command) => {
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
Expand All @@ -380,8 +385,7 @@ addToLibrary({
var cmdstr = UTF8ToString(command);
if (!cmdstr.length) return 0; // this is what glibc seems to do (shell works test?)

var cp = require('node:child_process');
var ret = cp.spawnSync(cmdstr, [], {shell:true, stdio:'inherit'});
var ret = nodeChildProcess.spawnSync(cmdstr, [], {shell:true, stdio:'inherit'});

var _W_EXITCODE = (ret, sig) => ((ret) << 8 | (sig));

Expand Down
2 changes: 1 addition & 1 deletion src/lib/libnodepath.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// operations. Hence, using `nodePath` should be safe here.

addToLibrary({
$nodePath: "require('node:path')",
$nodePath: "{{{ makeNodeImport('node:path', false) }}}",
$PATH__deps: ['$nodePath'],
$PATH: `{
isAbs: nodePath.isAbsolute,
Expand Down
18 changes: 17 additions & 1 deletion src/lib/libsockfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
*/

addToLibrary({
#if ENVIRONMENT_MAY_BE_NODE && EXPORT_ES6
// In ESM mode, require() is not natively available. When SOCKFS is used,
// we need require() to lazily load the 'ws' npm package for WebSocket
// support on Node.js. Set up a createRequire-based polyfill.
$nodeRequire: `ENVIRONMENT_IS_NODE ? (await import('node:module')).createRequire(import.meta.url) : undefined`,
$SOCKFS__deps: ['$FS', '$nodeRequire'],
#else
$SOCKFS__deps: ['$FS'],
#endif
$SOCKFS__postset: () => {
addAtInit('SOCKFS.root = FS.mount(SOCKFS, {}, null);');
},
$SOCKFS__deps: ['$FS'],
$SOCKFS: {
#if expectToReceiveOnModule('websocket')
websocketArgs: {},
Expand Down Expand Up @@ -216,7 +224,11 @@ addToLibrary({
var WebSocketConstructor;
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
#if EXPORT_ES6
WebSocketConstructor = /** @type{(typeof WebSocket)} */(nodeRequire('ws'));
#else
WebSocketConstructor = /** @type{(typeof WebSocket)} */(require('ws'));
#endif
} else
#endif // ENVIRONMENT_MAY_BE_NODE
{
Expand Down Expand Up @@ -522,7 +534,11 @@ addToLibrary({
if (sock.server) {
throw new FS.ErrnoError({{{ cDefs.EINVAL }}}); // already listening
}
#if EXPORT_ES6
var WebSocketServer = nodeRequire('ws').Server;
#else
var WebSocketServer = require('ws').Server;
#endif
var host = sock.saddr;
#if SOCKET_DEBUG
dbg(`websocket: listen: ${host}:${sock.sport}`);
Expand Down
11 changes: 9 additions & 2 deletions src/lib/libwasi.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,14 +569,21 @@ var WasiLibrary = {

// random.h

#if ENVIRONMENT_MAY_BE_SHELL
#if ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 190000
$nodeCrypto: "{{{ makeNodeImport('node:crypto') }}}",
#endif

#if ENVIRONMENT_MAY_BE_SHELL && ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 190000
$initRandomFill__deps: ['$base64Decode', '$nodeCrypto'],
#elif ENVIRONMENT_MAY_BE_SHELL
$initRandomFill__deps: ['$base64Decode'],
#elif ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 190000
$initRandomFill__deps: ['$nodeCrypto'],
#endif
$initRandomFill: () => {
#if ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 190000
// This block is not needed on v19+ since crypto.getRandomValues is builtin
if (ENVIRONMENT_IS_NODE) {
var nodeCrypto = require('node:crypto');
return (view) => nodeCrypto.randomFillSync(view);
}
#endif // ENVIRONMENT_MAY_BE_NODE
Expand Down
5 changes: 4 additions & 1 deletion src/lib/libwasm_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,12 @@ if (ENVIRONMENT_IS_WASM_WORKER
_wasmWorkers[id].postMessage({'_wsc': funcPtr, 'x': readEmAsmArgs(sigPtr, varargs) });
},

#if ENVIRONMENT_MAY_BE_NODE
emscripten_navigator_hardware_concurrency__deps: ['$nodeOs'],
#endif
emscripten_navigator_hardware_concurrency: () => {
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) return require('node:os').cpus().length;
if (ENVIRONMENT_IS_NODE) return nodeOs.cpus().length;
#endif
return navigator['hardwareConcurrency'];
},
Expand Down
23 changes: 23 additions & 0 deletions src/parseTools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,27 @@ function makeModuleReceiveWithVar(localName, moduleName, defaultValue) {
return ret;
}

function makeNodeImport(module, guard = true) {
assert(ENVIRONMENT_MAY_BE_NODE, 'makeNodeImport called when environment can never be node');
var expr;
if (EXPORT_ES6) {
expr = `await import('${module}')`;
} else {
expr = `require('${module}')`;
}
if (guard) {
return `ENVIRONMENT_IS_NODE ? ${expr} : undefined`;
}
return expr;
}

function makeNodeFilePath(filename) {
if (EXPORT_ES6) {
return `new URL('${filename}', import.meta.url)`;
}
return `__dirname + '/${filename}'`;
}

function makeRemovedFSAssert(fsName) {
assert(ASSERTIONS);
const lower = fsName.toLowerCase();
Expand Down Expand Up @@ -1241,6 +1262,8 @@ addToCompileTimeContext({
makeModuleReceive,
makeModuleReceiveExpr,
makeModuleReceiveWithVar,
makeNodeFilePath,
makeNodeImport,
makeRemovedFSAssert,
makeRetainedCompilerSettings,
makeReturn64,
Expand Down
2 changes: 1 addition & 1 deletion src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ function instantiateSync(file, info) {
var binary = getBinarySync(file);
#if NODE_CODE_CACHING
if (ENVIRONMENT_IS_NODE) {
var v8 = require('node:v8');
var v8 = {{{ makeNodeImport('node:v8', false) }}};
// Include the V8 version in the cache name, so that we don't try to
// load cached code from another version, which fails silently (it seems
// to load ok, but we do actually recompile the binary every time).
Expand Down
2 changes: 1 addition & 1 deletion src/runtime_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ if (ENVIRONMENT_IS_NODE) {
// depends on it for accurate timing.
// Use `global` rather than `globalThis` here since older versions of node
// don't have `globalThis`.
global.performance ??= require('perf_hooks').performance;
global.performance ??= ({{{ makeNodeImport('perf_hooks', false) }}}).performance;
}
#endif

Expand Down
15 changes: 9 additions & 6 deletions src/runtime_debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@
var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times

// Used by XXXXX_DEBUG settings to output debug messages.
#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS)
// Node modules for dbg() output. Loaded lazily on first use since dbg() can
// be called from --pre-js before module initialization, and await import()
// cannot be used inside the non-async dbg() function.
var dbg_node_fs, dbg_node_utils;
#endif
function dbg(...args) {
if (!runtimeDebug && typeof runtimeDebug != 'undefined') return;
#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS)
// Avoid using the console for debugging in multi-threaded node applications
// See https://github.com/emscripten-core/emscripten/issues/14804
if (ENVIRONMENT_IS_NODE) {
// TODO(sbc): Unify with err/out implementation in shell.sh.
var fs = require('node:fs');
var utils = require('node:util');
if (ENVIRONMENT_IS_NODE && dbg_node_fs) {
function stringify(a) {
switch (typeof a) {
case 'object': return utils.inspect(a);
case 'object': return dbg_node_utils.inspect(a);
case 'undefined': return 'undefined';
}
return a;
}
fs.writeSync(2, args.map(stringify).join(' ') + '\n');
dbg_node_fs.writeSync(2, args.map(stringify).join(' ') + '\n');
} else
#endif
// TODO(sbc): Make this configurable somehow. Its not always convenient for
Expand Down
27 changes: 12 additions & 15 deletions src/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,9 @@ if (ENVIRONMENT_IS_PTHREAD) {
#endif
#endif

#if ENVIRONMENT_MAY_BE_NODE && (EXPORT_ES6 || PTHREADS || WASM_WORKERS)
#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS)
if (ENVIRONMENT_IS_NODE) {
#if EXPORT_ES6
// When building an ES module `require` is not normally available.
// We need to use `createRequire()` to construct the require()` function.
const { createRequire } = await import('node:module');
/** @suppress{duplicate} */
var require = createRequire(import.meta.url);
#endif

#if PTHREADS || WASM_WORKERS
var worker_threads = require('node:worker_threads');
var worker_threads = {{{ makeNodeImport('node:worker_threads', false) }}};
global.Worker = worker_threads.Worker;
ENVIRONMENT_IS_WORKER = !worker_threads.isMainThread;
#if PTHREADS
Expand All @@ -128,7 +119,6 @@ if (ENVIRONMENT_IS_NODE) {
#if WASM_WORKERS
ENVIRONMENT_IS_WASM_WORKER = ENVIRONMENT_IS_WORKER && worker_threads.workerData == 'em-ww'
#endif
#endif // PTHREADS || WASM_WORKERS
}
#endif // ENVIRONMENT_MAY_BE_NODE

Expand Down Expand Up @@ -199,11 +189,13 @@ if (ENVIRONMENT_IS_NODE) {

// These modules will usually be used on Node.js. Load them eagerly to avoid
// the complexity of lazy-loading.
var fs = require('node:fs');
var fs = {{{ makeNodeImport('node:fs', false) }}};

#if EXPORT_ES6
if (_scriptName.startsWith('file:')) {
scriptDirectory = require('node:path').dirname(require('node:url').fileURLToPath(_scriptName)) + '/';
var nodePath = {{{ makeNodeImport('node:path', false) }}};
var nodeUrl = {{{ makeNodeImport('node:url', false) }}};
scriptDirectory = nodePath.dirname(nodeUrl.fileURLToPath(_scriptName)) + '/';
}
#else
scriptDirectory = __dirname + '/';
Expand Down Expand Up @@ -351,10 +343,15 @@ if (!ENVIRONMENT_IS_AUDIO_WORKLET)
var defaultPrint = console.log.bind(console);
var defaultPrintErr = console.error.bind(console);
if (ENVIRONMENT_IS_NODE) {
var utils = require('node:util');
var utils = {{{ makeNodeImport('node:util', false) }}};
var stringify = (a) => typeof a == 'object' ? utils.inspect(a) : a;
defaultPrint = (...args) => fs.writeSync(1, args.map(stringify).join(' ') + '\n');
defaultPrintErr = (...args) => fs.writeSync(2, args.map(stringify).join(' ') + '\n');
#if (ASSERTIONS || RUNTIME_DEBUG || AUTODEBUG) && (PTHREADS || WASM_WORKERS)
// Initialize the lazy-loaded modules for dbg() now that fs/utils are available.
dbg_node_fs = fs;
dbg_node_utils = utils;
#endif
}
{{{ makeModuleReceiveWithVar('out', 'print', 'defaultPrint') }}}
{{{ makeModuleReceiveWithVar('err', 'printErr', 'defaultPrintErr') }}}
Expand Down
12 changes: 6 additions & 6 deletions src/shell_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ var ENVIRONMENT_IS_WEB = !ENVIRONMENT_IS_NODE;

#if ENVIRONMENT_MAY_BE_NODE && (PTHREADS || WASM_WORKERS)
if (ENVIRONMENT_IS_NODE) {
var worker_threads = require('node:worker_threads');
var worker_threads = {{{ makeNodeImport('node:worker_threads', false) }}};
global.Worker = worker_threads.Worker;
}
#endif
Expand Down Expand Up @@ -99,7 +99,7 @@ if (ENVIRONMENT_IS_NODE && ENVIRONMENT_IS_SHELL) {
var defaultPrint = console.log.bind(console);
var defaultPrintErr = console.error.bind(console);
if (ENVIRONMENT_IS_NODE) {
var fs = require('node:fs');
var fs = {{{ makeNodeImport('node:fs', false) }}};
defaultPrint = (...args) => fs.writeSync(1, args.join(' ') + '\n');
defaultPrintErr = (...args) => fs.writeSync(2, args.join(' ') + '\n');
}
Expand Down Expand Up @@ -181,13 +181,13 @@ if (!ENVIRONMENT_IS_PTHREAD) {
// Wasm or Wasm2JS loading:

if (ENVIRONMENT_IS_NODE) {
var fs = require('node:fs');
var fs = {{{ makeNodeImport('node:fs', false) }}};
#if WASM == 2
if (globalThis.WebAssembly) Module['wasm'] = fs.readFileSync(__dirname + '/{{{ TARGET_BASENAME }}}.wasm');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this code (which uses __dirname) simply not work with EXPORT_EST today?

If not, this seems like maybe a separate fix that we could land in isolation. e.g. Fix for EXPORT_ES6 + MINIMAL_RUNTIME + ???

else eval(fs.readFileSync(__dirname + '/{{{ TARGET_BASENAME }}}.wasm.js')+'');
if (globalThis.WebAssembly) Module['wasm'] = fs.readFileSync({{{ makeNodeFilePath(TARGET_BASENAME + '.wasm') }}});
else eval(fs.readFileSync({{{ makeNodeFilePath(TARGET_BASENAME + '.wasm.js') }}})+'');
#else
#if !WASM2JS
Module['wasm'] = fs.readFileSync(__dirname + '/{{{ TARGET_BASENAME }}}.wasm');
Module['wasm'] = fs.readFileSync({{{ makeNodeFilePath(TARGET_BASENAME + '.wasm') }}});
#endif
#endif
}
Expand Down
7 changes: 6 additions & 1 deletion test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8662,7 +8662,12 @@ def test(assert_returncode=0):
js = read_file(self.output_name('test_hello_world.support'))
else:
js = read_file(self.output_name('test_hello_world'))
assert ('require(' in js) == ('node' in self.get_setting('ENVIRONMENT')), 'we should have require() calls only if node js specified'
# In ESM mode, we use dynamic import() instead of require() for node modules
if self.get_setting('WASM_ESM_INTEGRATION'):
has_node_imports = 'import(' in js
else:
has_node_imports = 'require(' in js
assert has_node_imports == ('node' in self.get_setting('ENVIRONMENT')), 'we should have node imports only if node js specified'

for engine in self.js_engines:
print(f'engine: {engine}')
Expand Down
Loading