Fork me on GitHub
#shadow-cljs
<
2018-08-16
>
Logan Powell13:08:05

Is it just me, or is there some issue with shadow kicking me out of the REPL (`CompilerException java.lang.RuntimeException...`) when debugging core.async code?

Logan Powell13:08:28

I'm getting an unusual number of these REPL terminations when working with core.async. It looks as though, when something goes wrong within a chan (e.g., an error like nil is not allowed in a channel), stuff breaks

heyarne13:08:39

I'm sometimes receiving SyntaxErrors when running karma tests. the message looks like this:

{
    "message": "Uncaught SyntaxError: missing ) after argument list\nat ci.js:114889:322\n\nSyntaxError: missing ) after argument list",
    "str": "Uncaught SyntaxError: missing ) after argument list\nat ci.js:114889:322\n\nSyntaxError: missing ) after argument list"
  }

thheller15:08:49

@loganpowell do you have an example of code that kicks you out? how are you debugging core.async code? async in general is pretty tough to debug given its async nature but I don't know what you mean by getting kicked out?

thheller15:08:25

@arne-clojurians I believe there was an issue with karma running tests too early, meaning it started running tests while the file was still being written?

thheller15:08:56

try setting a super high autoWatchBatchDelay http://karma-runner.github.io/2.0/config/configuration-file.html (2000 or so should be good)

šŸ‘ 4
Logan Powell15:08:38

@thheller right now, it looks like anytime I get an error inside a chan I'm getting kicked out.

thheller15:08:43

can be please be more specific what you mean by getting kicked out?

Logan Powell15:08:20

I have to restart the Cursive REPL with (shadow.cljs.devtools.api/node-repl)

thheller15:08:52

ah. its a node repl. node crashes on uncaught exceptions I think?

Logan Powell15:08:35

well, the shadow-cljs node server keeps running, but Cursive breaks

Logan Powell15:08:41

maybe a Cursive issue

thheller15:08:17

probably not. might be a nREPL issue though

thheller15:08:10

wait ... what do you mean by node server?

thheller15:08:16

you mean shadow-cljs itself right?

thheller15:08:23

the node-repl process probably dies

Logan Powell15:08:30

Ī» shadow-cljs clj-repl
shadow-cljs - config: C:\Users\Surface\Projects\clojure\cljs\census-geojson\shadow-cljs.edn  cli version: 2.4.25  node: v8.11.3 shadow-cljs - starting ...
shadow-cljs - server version: 2.4.25
shadow-cljs - server running at 
shadow-cljs - socket REPL running on port 50413
shadow-cljs - nREPL server started on port 3333
shadow-cljs - REPL - see (help)
To quit, type: :repl/quit
[1:0]~shadow.user=>

Logan Powell15:08:33

ok, not a big deal, I can just keep restarting the node-repl session in Cursive. This only seems to happen when working with core.async so I hope to be done with that soon šŸ˜„

thheller15:08:00

it is a big deal ... just need more info to piece together what exactly you are doing

Logan Powell15:08:01

so, anything that goes wrong inside a chan seems to kick me out of the session. I'm trying to think of a better way to explain...

thheller15:08:01

I'm still not clear on what you mean by kicked out

thheller15:08:08

cursive gets disconnected?

Logan Powell15:08:29

I get kicked out for putting nil into a chan, when a function that's being called in a go block errors out, when I forget to eval a defn before eval'ing a go block that contains it... yes cursive gets disconnected

Logan Powell15:08:51

the nREPL session doesn't stop in the shadow-cljs clj-repl session. I just have to re-eval (shadow.cljs.devtools.api/node-repl) to start the Cursive REPL again and then re-eval the namespace, functions, etc.

thheller15:08:39

I'm so confused ... can you do a step by step?

Logan Powell15:08:42

I don't have to shadow-cljs clj-repl again

Logan Powell15:08:12

For example: step 1) shadow-cljs clj-repl => REPL session starts step 2) Error put into a chan => Error thrown in Cursive REPL (`shadow-cljs` still running, doesn't break) step 3) (shadow.cljs.devtools.api/node-repl) => restart node-repl in Cursive step 4) eval (ns...) and functions to get me back to debugging the core.async logic step 5) bug in code = repeat from step 2)

thheller15:08:35

I'm even more confused now

thheller15:08:46

you are running shadow-cljs clj-repl externally?

thheller15:08:55

and CONNECT remotely with cursive?

thheller15:08:20

or is this all in a terminal outside of cursive?

Logan Powell15:08:53

Separate terminal

Logan Powell15:08:06

Would it be better to use the terminal in IDEA?

Logan Powell15:08:57

Sweet! I will do that! Any particular command I should use in there, just the terminal included with Idea or something to do with Cursive?

thheller15:08:24

any terminal is fine. point is that it doesn't use nrepl and I can see the full session

thheller15:08:46

I think the node process just dies due to the uncaught exception

Logan Powell15:08:47

Gotcha. I'll do that and report on my findings. Thank you again Thomas!

thheller15:08:59

but I don't know what you mean by kicked out still

Logan Powell15:08:18

I don't know how else to describe the behavior of having the shadow-cljs clj-repl still running, but the Cursive REPL dies on me, requiring me to re-eval (shadow.cljs.devtools.api/node-repl) in there to kick start Cursive REPL again. It feels like getting kicked out

Logan Powell15:08:52

Let me try the Idea REPL

thheller15:08:27

cursive REPL dies on you: does it display anything? can you still type?

thheller15:08:44

cursive ALWAYS starts out on CLJ mode when you connect

thheller15:08:53

running node-repl will switch to CLJS mode

thheller15:08:02

when the node-repl dies you should just be in CLJ mode again

Logan Powell15:08:08

Yes, I'm using Node/NPM interop atm

thheller15:08:31

thats why I don't understand what "kicked out" means

Logan Powell15:08:32

I think that's what's happening, yes, I'm getting "kicked out" of the CLJS session and into CLJ

thheller15:08:36

the connection should still be fine

Logan Powell15:08:50

Sorry! šŸ˜„

thheller15:08:56

so you are just in CLJ mode and can eval CLJ code normally

Logan Powell15:08:28

Yes, it becomes a Java runtime

Logan Powell15:08:39

CompilerException java.lang.RuntimeException: Unable to resolve symbol: defn in this context, compiling:(null:1:1)

thheller15:08:25

ok the REPL is just stuck in the wrong namespace probably

thheller15:08:42

try (in-ns 'shadow.user) after you get "kicked out"

thheller15:08:54

then just 1 or whatever

thheller15:08:04

just so confirm that the CLJ repl still works

urbanslug15:08:04

@thheller I can't find a link to a demo project you had that used shadow-cljs and was reading configs from the html file.

thheller15:08:26

just calling <script>starter.browser.init({foo: "bar"});</script> instead?

thheller15:08:46

@loganpowell forget about it. I think I know whats happening.

urbanslug15:08:05

yeah I got that I just thought that it had it right there in the example

urbanslug15:08:12

mine has a window.onload

urbanslug15:08:31

maybe that is the issue but I don't see why because the fn gets called but the args don't

thheller15:08:31

same idea. just pass data to the init fn

urbanslug15:08:47

doesn't work on reload

urbanslug15:08:03

let me pull your example and try replicate

thheller15:08:20

init is never supposed to get called on reload?

thheller15:08:32

the whole point of init is to only get called once

urbanslug15:08:50

ok that makes sense

thheller15:08:02

see the entire setup

urbanslug15:08:09

so what I do is transact app state in the init to add the vars from the html

thheller15:08:11

init is called once on startup from HTML

thheller15:08:16

it calls start

thheller15:08:36

on live-reload first stop is called and then start after loading the code

thheller15:08:49

whatever happened in init never happens again

thheller15:08:55

and is not supposed to

urbanslug15:08:24

ok that makes sense

thheller15:08:25

if it gets called again then yes it won't have any arguments

urbanslug15:08:41

then in my case it does get called again

thheller15:08:06

do you either have it marked as an :dev/after-load or :after-load in the config?

urbanslug15:08:31

yes I do šŸ˜¬

thheller15:08:17

ok. so you split the init into 2 functions. one that can be called again (ie. doesn't use args) and one that puts the config somewhere safe

urbanslug15:08:34

Thanks for that I was reading the docs trying to figure out how to handle it all

urbanslug15:08:03

Also is there a client side routing lib you recommend?

thheller15:08:27

no I don't do recommendations šŸ˜‰

thheller15:08:59

I don't know any to be honest. I have my own super basic one that I use for my work projects

thheller15:08:35

@loganpowell

[7:1]~cljs.user=> (require '[cljs.core.async :as a])
nil
[7:1]~cljs.user=> (a/go (throw (ex-info "foo" {})))
<eval>:1
(function (){var c__34073__auto__ = cljs.core.async.chan.call(null,(1));
                                                         ^

TypeError: Cannot read property 'call' of undefined
    at <eval>:1:58
    at <eval>:89:3
    at Script.runInThisContext (vm.js:65:33)
    at runInThisContext (vm.js:199:38)
    at global.SHADOW_NODE_EVAL ([stdin]:75:30)
    at Object.shadow$cljs$devtools$client$node$node_eval [as node_eval] (C:\Users\thheller\code\shadow-cljs\.shadow-cljs\builds\node-repl\dev\out\cljs-runtime\shadow\cljs\devtools\client\node.cljs:25:16)
    at shadow.cljs.devtools.client.node/node-eval (C:\Users\thheller\code\shadow-cljs\.shadow-cljs\builds\node-repl\dev\out\cljs-runtime\shadow\cljs\devtools\client\node.cljs:48:30)
    at Object.repl-expr (C:\Users\thheller\code\shadow-cljs\.shadow-cljs\builds\node-repl\dev\out\cljs-runtime\shadow\cljs\devtools\client\env.cljs:91:16)
    at Object.shadow$cljs$devtools$client$node$repl_invoke [as repl_invoke] (C:\Users\thheller\code\shadow-cljs\.shadow-cljs\builds\node-repl\dev\out\cljs-runtime\shadow\cljs\devtools\client\node.cljs:48:13)
    at shadow.cljs.devtools.client.node/repl-invoke (C:\Users\thheller\code\shadow-cljs\.shadow-cljs\builds\node-repl\dev\out\cljs-runtime\shadow\cljs\devtools\client\node.cljs:116:6)
[7:1]~cljs.user=> (+ 1 2)
3

thheller15:08:37

@loganpowell when I do the above in nrepl it breaks

Logan Powell15:08:52

I see, so nREPL issue

thheller15:08:53

it doesn't help me if you post an example I cannot execute myself

thheller15:08:02

I have absolutely no clue what you code does

Logan Powell15:08:16

I was being lazy

thheller15:08:26

but I can reproduce that the nrepl gets stuck in an unusable state

Logan Powell15:08:54

forgive my laziness. You're being generous and I, greedy

Logan Powell15:08:36

I just don't know exactly what I'm doing wrong

thheller15:08:15

yeah understandable. nrepl is a total mess and I don't understand whats going on half the time either

Logan Powell15:08:31

Your example helps me understand what you need for me to help you help me

Logan Powell15:08:50

ok, no worries! Without a hitch otherwise. The NPM interop is flawless

thheller16:08:19

FWIW just executing (js/process.exit 1) breaks the entire thing

thheller16:08:41

if the node process dies it breaks the entire nrepl session

thheller16:08:50

but only the nrepl session, normal repl is fine

Logan Powell16:08:12

correct, shadow's repl keeps on humming

Logan Powell16:08:33

ok, onwards! Thank you again

Logan Powell16:08:40

always a model of patience

hlolli16:08:50

After some experimenting with wrapping AudioWorklet with clojurescript, I found goog.defineClass, it works with "watch", but when I release, I encounter

Closure compilation failed with 1 errors
--- csound_wasm/browser.cljs:201
Unsupported class definition expression.
makes me wonder why it works with watch to begin with...

hlolli16:08:43

maybe there's another way around this https://developers.google.com/web/updates/2017/12/audio-worklet somehow grabbing the prototypes and convert it to object...

thheller16:08:44

well in watch the code isn't processed by closure

thheller16:08:00

so maybe you are just doing something you are not supposed to

hlolli16:08:22

yes, there's a lot of not supposed to, when it comes to mixing classes with clojurescript...

hlolli16:08:40

I've never dealt with this kind of interop.

thheller16:08:12

yeah classes are a bit annoying

thheller16:08:45

this might do it

hlolli16:08:52

what I came up with was

(def CsoundWorkletNode
  (goog.defineClass
   js/AudioWorkletNode
   #js {:constructor
        (this-as that
          (fn [ctx]
            ((.-superClass_ that) ctx "csound-processor")))}))
mocking this
class MyWorkletNode extends AudioWorkletNode {
  constructor(context) {
    super(context, 'my-worklet-processor');
  }
}
and it worked surprisingly, luckily the superClass that's injected from closure library helped a lot.

thheller16:08:01

just replace React.Component with AudioWorkletNode

thheller16:08:17

first of all this goog.defineClass is totally invalid

thheller16:08:36

(goog/defineClass ...) or (js/goog.defineClass ...)

thheller16:08:43

that the other one works is accidental

thheller16:08:09

and this-as must be the first inside the fn not outside

šŸ‘ 4
thheller16:08:28

and what is .-superClass_?

hlolli16:08:34

the error changed

Closure compilation failed with 1 errors
--- csound_wasm/browser.cljs:192
The class must be defined by an object literal

hlolli16:08:44

I guess super

thheller16:08:26

(def CsoundWorkletNode
  (js/goog.defineClass
    js/AudioWorkletNode
    #js {:constructor
         (fn [ctx]
           (js/super ctx "csound-processor"))}))

hlolli16:08:14

your version

Closure compilation failed with 1 errors
--- externs.shadow.js:11
Parse error. 'identifier' expected

thheller16:08:53

haha doh. it probably tried to generate externs for super

hlolli16:08:36

I thought I was doing simple optimizations

thheller16:08:51

don't tell anyone I posted this but try

thheller16:08:54

(def CsoundWorkletNode
  (js/goog.defineClass
    js/AudioWorkletNode
    #js {:constructor
         (fn [ctx]
           (js* "super({}, {});" ctx "csound-processor"))}))

hlolli16:08:56

[slack] it's back up

hlolli16:08:43

@thheller this is the error message from "who knows who"

Closure compilation failed with 1 errors
--- csound_wasm/browser.cljs:193
Parse error. Semi-colon expected

MĆ­cheĆ”l Ɠ CathĆ”in09:09:32

For anyone trying to get audioWorkletNode working in GClosure format, I found that the following worked... goog.provide('sounds'); sounds.mySoundProcessor = class extends AudioWorkletProcessor ({ constructor(){ super(); //example excitation let excitationLength = 0.5*sampleRate; // 0.5 second excitation this.excitation = new Array(excitationLength).fill(0); } // dirac impulse this.excitation[0] = 1.0; this.ptr = 0; // pointer to iterate through excitation or whatever sound is set up... }) sounds.mySoundProcessor.prototype.process = function(inputs, outputs, parameters){ // etc // this.excitation is available // this.ptr is available } The "semi-colon" expected error goes away when writing sounds.mySoundProcessor = class extends AudioWorkletProcessor ... instead of class sounds.mySoundProcessor extends AudioWorkletProcessor .... Other than this one constructor function in the format of the webaudio API (), GClosure format seems OK for all other functions, as I try to show above with the sounds.mySoundProcessor.prototype.process = ... expression. None of the above is in clojurescript of course, but it does compile in GClosure. I've not tried getting this to work in Clojurescript yet but perhaps it will work for anyone who is trying to get around this issue?

thheller16:08:31

hmm yeah js* is weird. better not use it

thheller16:08:10

what is the point of the class exactly?

hlolli16:08:13

I don't get it. I guess the logic is, that only the newest browser support audioworklet, and old ecmascript can't compile classes anyway. Not sure what's the logic.

hlolli16:08:44

And if this is something that is decided by w3c, then I guess more is coming.

thheller16:08:17

might be like web components where you MUST use class with extends or else it breaks

thheller16:08:55

CLJS can not currently emit that

thheller16:08:30

you could try writing actual JS and calling a CLJS fn

thheller16:08:39

dunno how much sense that makes though

hlolli16:08:26

hmm yes... I need this shit twice, one for the global scope, another for script processor scope.

thheller16:08:19

do the worklets behave like workers?

hlolli16:08:20

I could write some js and pass into it a js object from clojurescript, to run the code defined in cljs land

thheller16:08:28

ie. can they call importScripts?

hlolli16:08:54

almost, in the audioworklet, the import script is a fetch, that must be over https

hlolli16:08:14

so not file:// protocol, localhost tough considered "safe"

hlolli16:08:32

it's shit if you ask me

thheller16:08:06

it seems extremely weird to me

thheller16:08:16

chrome devtools also don't seem to show how the code is loaded

hlolli16:08:35

lol, it's even crackling a bit, which is the main point of preventing with audioworklet

thheller16:08:52

I know absolutely nothing about audio processing so I have no idea what the point of any of this is šŸ˜›

hlolli16:08:31

previously there was audioProcessorNode, that would get latencies if you changed the dom or something.

thheller16:08:35

did you try the other method from the gist I posted? no goog.defineClass?

hlolli16:08:29

uh, did your gist get lost when the slack was down?

hlolli16:08:55

ah no.. wait

thheller16:08:58

just replace React.Component with whatever class

thheller16:08:17

the Reflect.construct call might also work. it was necessary for web components a while back

thheller16:08:32

not sure if it still is though. that was 2 years ago

hlolli16:08:56

ok nice, I saw similar thing here http://www.50ply.com/blog/2012/07/08/extending-closure-from-clojurescript/ surprised to see mutations on defn

thheller16:08:03

yeah its weird

thheller17:08:11

hang on though. how are you building those extra files?

thheller17:08:22

looks like you use a dedicated build for that?

hlolli17:08:54

yes, I have a dedicated build for the processor/worker

thheller17:08:00

don't do that šŸ˜›

hlolli17:08:51

compile then as a module?

thheller17:08:52

:modules
{:shared {:entries [cljs.core] :depends-on #{}}
 :main {:entries [demo.app] :depends-on #{:shared}}
 :audio1 {:entries [demo.audio1] :depends-on #{:base} :web-worker true}}

thheller17:08:04

in the main browser you load shared.js + main.js

thheller17:08:37

context.audioWorklet.addModule('/js/audio1.js')

thheller17:08:05

no idea if this works for the audio things though

hlolli17:08:11

yes I think I would trip on this sooner or later

thheller17:08:16

it was intended for actual webworkers

thheller17:08:41

the audio files will become huge otherwise since each will contain its own cljs.core version

thheller17:08:50

not exactly ideal either way but worth a try

thheller17:08:55

doesn't fix the class problem though

hlolli17:08:05

the idea here

(set! (.. component-fn -prototype -constructor) my-component)
is that component-fn is its own function and not my-component?

thheller17:08:26

oh thats copy&paste error

thheller17:08:33

should be my-component instead of component-fn

hlolli17:08:44

makes sense

hlolli17:08:00

I think I have to sweat over this a bit, im getting

Uncaught TypeError: Illegal invocation
    at Object.goog.object.extend (goog.object.object.js:610)
    at browser.cljs:212
code
(goog.object/extend CsoundWorkletNode.prototype js/AudioWorkletNode.prototype)

hlolli17:08:34

it compiles tough, good news

thheller17:08:37

CsoundWorkletNode.prototype this is rarely valid CLJS code

thheller17:08:53

(.. CsoundWorkletNode -prototype)

thheller17:08:19

don't try to cheat the system. you never know when it breaks

thheller17:08:42

but yeah the illegal invocation is the exception I got with web components I think

thheller17:08:43

try the web components Reflect stuff

hlolli17:08:05

yup, I still get the same error, changed it to see what would happen, the double dot macro I don't find to be so good looking, but will use it when/if needed šŸ™‚

hlolli17:08:22

I'm getting confused about that HTMLComponent parts, I assume I don't need them?

(defn component []
  (js/AudioWorkletNode #js [] component))

(set! (.-prototype component)
      (js/Object.create (.-prototype js/AudioWorkletNode)
                        #js {:constructor (fn [ctx])}))

thheller17:08:29

you removed the Reflect call

wilkerlucio17:08:29

@hlolli just a small tip, I suggest you try to using goog.object/set instead of set!, it's easier to read and is adv compilation proof

šŸ‘ 4
thheller17:08:33

thats the only important bit

hlolli17:08:50

ah ok, my misunderstanding

thheller17:08:04

I don't think this is gonna work either

hlolli17:08:28

(defn component []
  (js/Reflect.construct js/AudioWorkletNode #js [] component))

(set! (.-prototype component)
      (js/Object.create  (.-prototype js/AudioWorkletNode)
                         #js {:constructor (fn [ctx])}))

(def context (new js/AudioContext))

(-> (.addModule context.audioWorklet "./csound-wasm-worklet-processor.js")
    (.then (fn [] (new component context))))
Uncaught (in promise) TypeError: Failed to construct 'AudioWorkletNode': 2 arguments required, but only 0 present.
    at new csound_wasm$browser$component (browser.cljs:207)

thheller17:08:20

the arguments is the #js [] in the reflect call

thheller17:08:50

but I don't think this is going to work anyways

thheller17:08:04

the :web-worker stuff definitely doesn't work at all

thheller17:08:12

the context created is not like a webworker

thheller17:08:19

it can't eval or do other stuff

thheller17:08:22

it looks like its a strict es6 module environment which makes sense

hlolli17:08:56

I think it worked, just need to plugin the worker correctly now

(defn component [ ctx node-name ]
  (js/Reflect.construct js/AudioWorkletNode #js [ ctx node-name ] component))

(set! (.-prototype component)
      (js/Object.create  (.-prototype js/AudioWorkletNode)
                         #js {:constructor (fn [ctx])}))

(def context (new js/AudioContext))

(-> (.addModule context.audioWorklet "./csound-wasm-worklet-processor.js")
    (.then (fn [] (this-as that (new component context "csound-processor")))))
Uncaught (in promise) DOMException: Failed to construct 'AudioWorkletNode': AudioWorkletNode cannot be created: The node name 'csound-processor' is not defined in AudioWorkletGlobalScope.

hlolli17:08:11

ok, then I skip the :web-worker flag and keep the modules?

thheller17:08:23

no modules won't work with this

hlolli17:08:35

ok, so I keep it like this šŸ™‚

thheller17:08:39

to be honest all of this looks downright hostile towards CLJS

thheller17:08:09

OOP all over the place and special loading restrictions

hlolli17:08:23

I bet you've never seen this šŸ™‚

Uncaught TypeError: Cannot use 'in' operator to search for 'StopIteration' in undefined
    at csound-wasm-worklet-processor.js:187
    at csound-wasm-worklet-processor.js:2133

hlolli17:08:47

I think I should just write the processor in javascript, that can be forgiven...

hlolli17:08:32

goog.iter.StopIteration="StopIteration"in goog.global
no global in this secure environment

hlolli17:08:23

which means, f*, I need to write even more in the processor script than I imagined

thheller18:08:25

yeah its a ES6 module scope

thheller18:08:54

goog.global = this; but this is undefined in a module scope

hlolli18:08:56

is there a closure option to compile to this?

hlolli18:08:28

there's :es6-strict and :es6-typed, maybe :es6-module smth... dunno

thheller18:08:17

dunno. the closure library release used by CLJS is very old. it might be fixed in newer versions already.