I'm trying to figure out the best way to execute code in workers on node and the weird combination of requirements between node workers, esm and everything - I'm having a hard time finding a way to treat a worker as a cherry repl. Example code in thread ๐งต
;; deps.edn
{:paths ["src"]
:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}
thheller/shadow-cljs {:mvn/version "2.28.16"}
funcool/promesa {:mvn/version "6.0.2"}
applied-science/js-interop {:mvn/version "0.2.7"}
io.github.squint-cljs/cherry {:git/sha "bccd994556efca378984c5ac7fd973bda164923b"}}
:aliases
{:cljs
{:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.16"}
binaryage/devtools {:mvn/version "1.0.7"}}}}}
;; shadow-cljs.edn
{:deps {:aliases [:cljs]}
:builds {:whatever {:target :node-library
:output-to "devwhatever.js"
:exports-fn blah.core/whatever!
:modules {:whatever {:init-fn blah.core/whatever}}
:release {:output-to "whatever.js"
:output-dir "build"}
:devtools {:after-load blah.core/whatever}}}}
;; blah.core.cljs
(ns blah.core
(:require
["node:worker_threads" :as threads]
[cherry.embed :as cherry]))
(defn whatever [])
(let [w (threads/Worker.
"console.log('starting');
const { parentPort, workerData } = await import('worker_threads');
var {compileStringEx} = await import('cherry-cljs');
// const { default: cc } = await import('cherry-cljs/cljs.core.js');
const defaultCompilerState = {repl: true, 'elide-exports': true, context: 'return'};
let compilerState = defaultCompilerState;
function removeLastLineIfContains(str, word) {
const lines = str.split('\\n');
if (lines[lines.length - 2].includes(word)) {
lines.pop();
lines.pop();
};
//return lines.join('\\n');
return lines.join('\\n');
};
let evalCode = async (code) => {
try {
compilerState = compileStringEx(`(do ${code}\\n)`, compilerState, compilerState);
let js = removeLastLineIfContains(compilerState.javascript, 'export');
console.log('js: ' + js);
const encodedJs = encodeURIComponent('(async function() {' + js + '})()');
// console.log(encodedJs);
const dataUri = 'data:text/javascript;charset=utf-8;eval=' + Date.now() + ',' + encodedJs;
let result = await import(dataUri);
// let result = await eval(
// `var r = (async function() { ${js} })();
// r;`);
return result;
} catch (e) { console.error(e); }};
let handle = async function (event) {
try { if (event.cmd === 'stop') { process.exit(); };
if (event.cmd === 'eval') {
const result = await evalCode(event.code);
console.log('result ' + result);
parentPort.postMessage(result); };
} catch (error) {
console.error('Failed construct handler or postMessage after', error);
parentPort.postMessage(error.message); } };
parentPort.on('message', handle);
parentPort.postMessage('Worker ready!');
console.log('done');"
#js {:eval true :type "module"})]
(.on w "message" #(do (println :msg-from-worker %) %))
(.on w "error" #(do (println :error-from-worker %) %))
(.on w "exit" #(do (println :exit-from-worker %) %))
(.postMessage
w
#js {:cmd "eval"
:code "(ns foo.core)
(println :in-ins-foo.core)
(def a 1)
a"})
(.postMessage
w
#js {:cmd "eval"
:code "(ns bar.core
(require [foo.core :as f]))
(println :a f/a)
f/a"}))
Then I get the output:
[1] starting
[1] (node:75511) [DEP0151] DeprecationWarning: No "main" or "exports" field defined in the package.json for /Users/john/src/cljs-thread/node/node_modules/cherry-cljs/ resolving the main entry point "index.js", imported from /Users/john/src/cljs-thread/node/[eval1].
[1] Default "index" lookups for the main are deprecated for ES modules.
[1] (Use `node --trace-deprecation ...` to show where the warning was created)
[1] :msg-from-worker Worker ready!
[1] done
[1] Failed construct handler or postMessage after TypeError: Cannot convert object to primitive value
[1] at MessagePort.handle (file:///Users/john/src/whatever/[eval1]:37:47)
[1] :msg-from-worker Cannot convert object to primitive value
[1] :error-from-worker #object[TypeError TypeError: Failed to resolve module specifier "cherry-cljs/cljs.core.js" from "data:text/javascript;charset=utf-8;eval=1728864624869,(async%20function()%20%7Bvar%20cherry_core%20%3D%20await%20import('cherry-cljs%2Fcljs.core.js')%3B%0AglobalThis.user%20%3D%20globalThis.user%20%7C%7C%20%7B%7D%3B%0AglobalThis.foo%20%3D%20globalThis.foo%20%7C%7C%20%7B%7D%3B%0AglobalThis.foo.core%20%3D%20globalThis.foo.core%20%7C%7C%20%7B%7D%3B%0Acherry_core.println.call(null%2C%20cherry_core.keyword(%22in-ins-foo.core%22))%3B%0AglobalThis.foo.core.a%20%3D%201%3B%0Areturn%20globalThis.foo.core.a%3B%3B%0A%7D)()": Invalid relative URL or base scheme is not hierarchical.]
[1] :exit-from-worker 1
If I get rid of the encodeURIComponent method and uncomment the original method:
[1] starting
[1] (node:75511) [DEP0151] DeprecationWarning: No "main" or "exports" field defined in the package.json for /Users/john/src/cljs-thread/node/node_modules/cherry-cljs/ resolving the main entry point "index.js", imported from /Users/john/src/cljs-thread/node/[eval1].
[1] Default "index" lookups for the main are deprecated for ES modules.
[1] (Use `node --trace-deprecation ...` to show where the warning was created)
[1] :msg-from-worker Worker ready!
[1] done
[1] SyntaxError: Cannot use import statement outside a module
[1] at evalCode (file:///Users/john/src/whatever/[eval1]:28:48)
[1] at MessagePort.handle (file:///Users/john/src/whatever/[eval1]:36:46)
[1] at [nodejs.internal.kHybridDispatch] (node:internal/event_target:816:20)
[1] at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)
[1] :msg-from-worker nil
[1] js: var cherry_core = await import('cherry-cljs/cljs.core.js');
[1] globalThis.user = globalThis.user || {};
[1] globalThis.foo = globalThis.foo || {};
[1] globalThis.foo.core = globalThis.foo.core || {};
[1] cherry_core.println.call(null, cherry_core.keyword("in-ins-foo.core"));
[1] globalThis.foo.core.a = 1;
[1] return globalThis.foo.core.a;;
[1]
[1] js: import * as cherry_core from 'cherry-cljs/cljs.core.js';
[1] cherry_core.println.call(null, cherry_core.keyword("a"), f.a);
[1] f.a;
[1]
[1] result undefined
[1] :in-ins-foo.core
[1] :msg-from-worker 1
[1] result 1Is it possible to embed cherry in cljs on node/workers? Should I be running a cherry repl on the main thread instead?
There's probably a few bugs in there - not handling the compiler state correctly, causing me to need to elide the export manually with let js = removeLastLineIfContains(compilerState.javascript, 'export');
weeeeell, I posted in the wrong channel... But, I tried it with squint, running the file off the squint node repl, and I'm getting farther actually