squint

john 2024-10-14T00:16:56.977889Z

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 ๐Ÿงต

john 2024-10-14T00:17:46.850679Z

;; 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 1

john 2024-10-14T00:19:38.021309Z

Is it possible to embed cherry in cljs on node/workers? Should I be running a cherry repl on the main thread instead?

john 2024-10-14T00:21:16.422859Z

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');

john 2024-10-14T05:45:43.699059Z

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

๐Ÿ‘€ 1