Hello!
I'm trying to embed a wasm component I'm working on in a webpack application: https://github.com/gchq/CyberChef
I'm quite new with the wasm ecosystem, and I'm facing an issue I don't really get.
jco is generating a JS glue file with a .wasm core file, and tries to fetch and compile it with fetchCompile.
When using node, it uses node:fs. Otherwise, it falls back to the classic fetch.
The issue here is that CyberChef is a webpack application that can build a web app and a node app.
Building the node app works like a charm :sparkles:
However, I experience fetch issues with the web version.
First, it doesn't seem to choose correctly if it should use node:fs or fetch. Because node is installed in the CyberChef app, it detects the process.versions.node and tries to use node:fs which is not handled in the webpack web app:
ERROR in node:fs/promises
Module build failed: UnhandledSchemeError: Reading from "node:fs/promises" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
at /home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/NormalModule.js:984:10
at Hook.eval [as callAsync] (eval at create (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/tapable/lib/Hook.js:18:14)
at Object.processResource (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/NormalModule.js:980:8)
at processResource (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/loader-runner/lib/LoaderRunner.js:220:11)
at iteratePitchingLoaders (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/loader-runner/lib/LoaderRunner.js:171:10)
at runLoaders (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/loader-runner/lib/LoaderRunner.js:398:2)
at NormalModule._doBuild (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/NormalModule.js:965:3)
at NormalModule.build (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/NormalModule.js:1155:15)
at /home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/Compilation.js:1422:12
at NormalModule.needBuild (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/NormalModule.js:1477:32)
at Compilation._buildModule (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/Compilation.js:1403:10)
at /home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/util/AsyncQueue.js:340:10
at Hook.eval [as callAsync] (eval at create (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
at AsyncQueue._startProcessing (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/util/AsyncQueue.js:330:26)
at AsyncQueue._ensureProcessing (/home/alex/ghq/github.com/dolphinau/CyberChef/node_modules/webpack/lib/util/AsyncQueue.js:317:12)
at process.processImmediate (node:internal/timers:476:21)
@ ../../../ditto/dittojs/target/transpiled_node/dittojs.js 1035:43-70
@ ./src/core/operations/ObfuscatePowershell.mjs 18:0-53 41:13-25 61:47-56
@ ./src/core/config/modules/Obfuscation.mjs 8:0-75 11:26-4
So I force the package not to use node. Two options here:
jco --no-node-compatconst isNode = typeof process !== 'undefined' && process.versions && process.versions.node && process.env.NO_NODE === undefined;
Thanks to the second method, I can just run NO_NODE=1 npm run build to select manually when I want node or not.
But even without node:fs, it looks like the fetch method also falls back to node. I get the following stack trace when building with NO_NODE=1:
TypeError: fetch failed
at node:internal/deps/undici/undici:12637:11 {
cause: Error: not implemented... yet...
at makeNetworkError (node:internal/deps/undici/undici:5851:35)
at schemeFetch (node:internal/deps/undici/undici:10757:34)
at node:internal/deps/undici/undici:10634:26
at mainFetch (node:internal/deps/undici/undici:10653:11)
at fetching (node:internal/deps/undici/undici:10601:7)
at fetch (node:internal/deps/undici/undici:10465:20)
at Object.fetch (node:internal/deps/undici/undici:12636:10)
at fetch (node:internal/process/pre_execution:281:25)
at fetchCompile (file:///home/alex/ghq/github.cert.corp/CERT/ditto/dittojs/target/transpiled_node/dittojs.js:667:10)
at init (file:///home/alex/ghq/github.cert.corp/CERT/ditto/dittojs/target/transpiled_node/dittojs.js:3399:21)
}
The JS glue calls to the node:internal fetch implementation, that is not implemented... yet...
It is maybe not related to jco are bytecodealliance, but I'm a bit lost so far so if anyone can help me don't hesitate :pray:
My last option was to embed the base64 of the wasm core directly inside the JS glue to avoid using fetch at all but I would prefer another solution :tear:
Hey @dolphino I think you've run into the fact that the web version of Jco is still experimental (and in general we don't support NodeJS modules that are imported outside the browser -- we don't have a shim for that). The import of node:fs/promises is not being replaced because there isn't a polyfill for that as you've found.
Based on what you have here I think the quickest current solution is to shim out node:fs with webpack -- and keep the nodejs compat in.
Would you mind filing an issue on jco and a small reproduction? This is probably something we should track as to how to avoid people getting bit by this so easily (or at least until NodeJS support is available via some shim).
We should also have a browser-fetch example similar to the NodeJS example.
There's a plan and work going to support node interfaces at a lower level than Jco, but that work is still far away from being done, unfortunately.
Here's an example of how you might shim this stuff, thanks to the awesome work of @Tomasz Andrzejak :
https://github.com/andreiltd/probot-wasm/blob/main/shims/fs.js
what I ended up doing is generating the code with --no-nodejs-compat and async mode and then provide a platform specific fetchCompile to instantiate
Ah that's definitely a simpler solution if node:fs was the only requirement that you needed!
Thanks for your answer @Victor Adossi ! jco is already doing an amazing job, can't wait for the future of this project!
I'll try to provide a minimal example to reproduce the issue :+1:
@Ramon Klass do you have an example of a fetchCompile instantiation? Just to get a glimpse of what it should look like :smile:
from the generated code:
export function instantiate(getCoreModule, imports, instantiateCore = WebAssembly.instantiate) {
...
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
let _fs;
async function fetchCompile (url) {
if (isNode) {
_fs = _fs || await import('node:fs/promises');
return WebAssembly.compile(await _fs.readFile(url));
}
return fetch(url).then(WebAssembly.compileStreaming);
}
...
if (!getCoreModule) getCoreModule = (name) => fetchCompile(new URL(./${name}, import.meta.url));
so you have to provide it as the first argument to instantiate and for node it is
(url) => fs.readFile(url).then(WebAssembly.compile)
and for browsers it is
(url) => fetch(url).then(WebAssembly.compileStreaming)
Thanks I'll try out
In the meantime I put --base64-cutoff 10000000 (bigger than my .core.wasm) so it's embedded directly in the js file and avoids the fetch
Last updated: Dec 06 2025 at 05:03 UTC