Fork me on GitHub
#clojurescript
<
2022-09-08
>
Yang08:09:22

Has anybody here tried using shadow-cljs's ESM output with https://razzlejs.org? They have examples for being used with other compile-to-js languages like https://github.com/jaredpalmer/razzle/tree/master/examples/with-reason-react and https://github.com/jaredpalmer/razzle/tree/master/examples/with-elm, but I think the whole Google Closure thing that thkeller talks about https://github.com/thheller/shadow-cljs/blob/master/doc/esm.md is getting in the way again lol... Things seem to work fine if I just write components in pure CLJS (I'm using https://github.com/lilactown/helix), but interop with JS React libraries that use hooks doesn't work. The browser says that the hook is called improperly either because 1. it's' not used in a function component, 2. it's violating the rules of hooks, or 3. there are multiple copies of react in existence. The same code with :browser target without going through Razzle just works though, so I suspect it's somehow the "multiple copies of react" thing.

thheller08:09:36

not a clue what razzle is but it might be expecting to build the output and as such gets confused by shadow-cljs already doing that. see https://shadow-cljs.github.io/docs/UsersGuide.html#_third_party_tool_integration

Yang08:09:55

Razzle is a SSR library like Next or Remix, but without the whole framework attached. The js-provider option did fix the hooks thing. Thanks!

Yang08:09:20

Another question though: does shadow-cljs watch inject code into the output? And is there a way to change this behavior? I get all kinds of issues with the framework trying to run what seems to be stuff watch is doing in the browser on node lol, like saying WebSocket() is undefined

Yang08:09:57

Everything is fine with shadow-cljs compile

thheller08:09:07

yes, it injects the :runtime specific (default :browser) tools

thheller08:09:26

you can set :devtools {:enabled false} or set :runtime :custom to stop that (in the build config)

Yang08:09:17

Ah, nice; that worked. Thanks again!

Fredrik Andersson15:09:01

is there a macro to create a go-block and a channel in one operation?

Fredrik Andersson15:09:23

preferably coupled with a return macro or something like that

Fredrik Andersson15:09:32

also whats the convention for channels that should just block until all operations are done and not pass any information

p-himik15:09:49

> is there a macro to create a go-block and a channel in one operation? Hard to understand what you mean. What would the macro expand to? Also, just in case - go itself creates a channel within it that it returns to the caller. > also whats the convention for channels that should just block until all operations are done and not pass any information Just return something that's not nil. Maybe true - up to you. Or close! that channel - that's what go does when its body results in nil.

☝️ 1
Fredrik Andersson16:09:23

sorry was away for dinner - aha! so how do i put stuff on the channel that go creates?

p-himik16:09:37

That would be the result of the go's contents - you don't put anything there explicitly.

Fredrik Andersson16:09:21

okay so if i just put true on the last line or even a <! or <p! that will be put on the channel?

Fredrik Andersson16:09:07

while i'm at it I have another question about core async

Fredrik Andersson16:09:37

i have a function that is called by a framework

Fredrik Andersson16:09:54

the framework expect my function to return a promise

Fredrik Andersson16:09:04

the framework is firebase cloud functions

Fredrik Andersson16:09:16

to accomplish this i do this (def go-idkollen-se-callback (functions/https (fn [req res] (js/Promise. (fn [_resolve _reject] (go (try

Fredrik Andersson16:09:41

now i can do the actual thing i want to do beneath the try

Fredrik Andersson16:09:05

when im done i call resolve or if i catch an error i forward it to reject

Fredrik Andersson16:09:39

basically, i wonder if i can convert the channel that is created by the go block into a promise

p-himik16:09:11

Pretty sure your current solution is the way to go, assuming you even need core.async.

Fredrik Andersson16:09:30

i do a lot of async operations in my cloud function

Fredrik Andersson16:09:49

thats why im converting my code from nestled promises

Lone Ranger15:09:43

Is there a way to pre-inject or monkey patch code in ClojureScript in any sensible way? I'm struggling with a strange figwheel issue (although this isn't a FW question directly) where goog.debug.Logger is not in the expected place. (the desired object in the version I am using is in goog.log.Logger ). One workaround is while debugging I can set goog.debug.Logger = goog.log.Logger in the javascript console. Curious if anyone can think of a good way to monkeypatch this in.

Lone Ranger15:09:55

Also, yes, I realize this is probably not the right way to solve this. (while the correct solution is also welcome, I'm specifically asking about the technique of monkeypatching javascript code before the user submitted code executes -- because by then it will be too late)

Lone Ranger22:09:35

(turns out the real answer was just a version conflict)

Yosevu Kilonzo15:09:19

Hi, I'm trying to use nbb (https://github.com/babashka/nbb/blob/main/examples/puppeteer/example.cljs)to write a script to migrate mysql data using https://github.com/sidorares/node-mysql2#using-promise-wrapper (snippets above). I have it working with JavaScript, but I'm having trouble translating to CloureScript.

borkdude15:09:56

@yosevuk We also have an #nbb channel :)

Yosevu Kilonzo15:09:52

Ok! I will post there next time. Thanks!

borkdude15:09:34

I'm looking at your example now. The confusion probably is that for in clojure is not an imperative loop like .forEach in JS

borkdude15:09:57

for generates a lazy seq of results. if you want to cause side effects, you can use doseq

💡 1
borkdude15:09:37

but with promises this is a bit more complicated. hmm

borkdude15:09:52

doseq assumes that you're doing things synchronously

borkdude15:09:13

but so does .forEach

borkdude15:09:34

anyway, changing for to doseq should match your JS closer

Yosevu Kilonzo15:09:38

Ok, I will start down the doseq path first! I wasn't sure if I needed to use another utility from promesa.

borkdude15:09:30

you could maybe use chain + map to create functions that are executed serially, but this isn't what the JS example is doing either

Yosevu Kilonzo15:09:57

Oh interesting, I wil look at that.

Yosevu Kilonzo15:09:28

doseq works though. It looks like that was all I needed to change.

Yosevu Kilonzo15:09:19

Is this the best approach to deal with async code like this or is there a more idiomatic Clojure way?

borkdude15:09:00

(apply p/chain (map (fn [i] #(p/do (println "Waiting" i "seconds") (p/delay (* i 1000)))) (range 5)))

borkdude15:09:19

well, as I said, in JS you didn't serialize the promise operations either

👍 1
borkdude15:09:10

there might be synchronous API library available as well for mysql

Yosevu Kilonzo16:09:33

Hmm, I would just use async/await, but it doesn't look like that is easily available in ClojureScript.

borkdude16:09:08

True. It is available in #cherry (experimental CLJS compiler) and #clavascript (experimental CLJS syntax to JS compiler)

borkdude16:09:22

In nbb and normal CLJs you just chain promises, like I did above

Yosevu Kilonzo16:09:19

Got it. I'll try that too and also check out the compilers you mentioned. Appreciate it!

borkdude16:09:52

This would probably also work:

(p/all (map (fn [table] (.execute conn ...)) table-names)

zimablue19:09:15

(read-string (pr-str (js/Error. "my specific error"))) this fails, and passing a :default doesn't help because it fails before defaults happen, I guess because it interprets the inside as an invalid symbol, is there some workaround and is this like expected behaviour?

dpsutton19:09:30

❯ clj -A:cljs -M -m cljs.main -re node -r
ClojureScript 1.10.773
cljs.user=> (js/Error. "my specific error")
#object[Error Error: my specific error]
cljs.user=> (pr-str (js/Error. "my specific error"))
"#object[Error Error: my specific error]"
cljs.user=> (read-string (pr-str (js/Error. "my specific error")))
WARNING: Use of undeclared Var cljs.user/read-string at line 1 <cljs repl>
Execution error (TypeError) at (<cljs repl>:1).
Cannot read properties of undefined (reading 'call')
there is no such thing as read-string in ClojureScript. But also what you want to read is not readable: #object[Error Error: my specific error] is not a readable form

dpsutton19:09:44

cljs.user=> (require 'cljs.reader)
nil
cljs.user=> (cljs.reader/read-string (pr-str (js/Error. "my specific error")))
Execution error (ExceptionInfo) at (<cljs repl>:1).
Invalid symbol: Error:.

zimablue19:09:06

Yes, but it's what errors seem to serialize to by default, sorry I should have declared the import

dpsutton19:09:04

the error is a javascript native object. I don’t think it serializes at all. That’s just a string representation of it

dpsutton19:09:23

how would you do this in pure js? ie, make an error, and then eval it to get back an error object

zimablue19:09:32

I get you but a lot of the time in cljs you can just write fairly arbitrary stuff to edn and pull it back out with then defaulttagreader, it seems weird to me that a "primitive" would cause an error, but I just wanted to check that I'm not doing anything mad and this is just what happens

zimablue19:09:01

I guess I need to find the hook for pr-str for error, and modify that then at least these datatructures will lossy roundtrip without error

dpsutton19:09:15

You could hack on the printer or you could walk your datastructure and replace them with clojure data

dpsutton19:09:34

I’d transform data before I would attempt to work on the printer/reader

zimablue19:09:26

I see what you mean

dpsutton19:09:57

it does say that they are serializable. I don’t see how to render to a string and back. But if you can figure that out you can walk and turn them into some special data structure that includes the serialized form. And then on the other side can rehydrate them with a walk that undoes the serializing side

markbastian19:09:29

Interop question. I’m trying to port a simple https://reactflow.dev/docs/examples/overview/ example to cljs. One of its features is custom styling. Here’s the js example:

export const nodes = [
  {
    id: '1',
    type: 'input',
    data: {
      label: (
        <>
          Welcome to <strong>React Flow!</strong>
        </>
      ),
    },
    position: { x: 250, y: 0 },
  }]
This provides an HTML-styled node as shown. I can’t seem to get this interop working in cljs. I’ve tried something like the following:
{:id       "2"
 :data     {:label "<div>Default <strong>Node</strong></div>"}
 :position {:x 100 :y 125}}
This data eventually goes through a clj->js transform and the node just displays the string (with div). I’ve also tried hiccup-style input, which doesn’t work. Anyone know how to make this work?

skylize19:09:26

>

data: {
>       label: (
>         <div>
>           Welcome to <strong>React Flow!</strong>
>         </div>
>       ),
>     },
This is JSX, not JS. The (<div>...</div>) part gets transpiled into a React Component. So your library is expecting a React Component here, not a string of html.

isak19:09:03

I would try this:

{:id       "2"
 :data     {:label (reagent/as-element [:div "Default Node"])}
 :position {:x 100 :y 125}}

markbastian19:09:38

Does that survive the cljs->js conversion?

isak19:09:39

I think so

markbastian21:09:25

That worked! Thanks!

1
skylize19:09:35

What is a "tag" supposed to look like for clojure.browser.dom/element?

skylize19:09:16

Documented function forms are

(element tag-or-text)
(element tag & children)
So using the first form (`tag-or-text`), with div as a tagname, and a target of an empty div element, I can came up with these options, which both fail.
(element "div")
Interprets as text instead of tag.
Returns a text node containing string "div".

(element :div)
Throws:
Execution error (Error) at (<cljs repl>:1).
No protocol method DOMBuilder.-element defined for type cljs.core/Keyword: :div

skylize02:09:53

I'm even more confused after trying to make sense of the source. https://github.com/clojure/clojurescript/blob/r1.11.60/src/main/cljs/clojure/browser/dom.cljs#L13-L15

(extend-protocol DOMBuilder

  string
  (-element
    ([this]
       (log "string (-element " this ")")
       (cond (keyword? this) (gdom/createElement  (name this))
             :else           (gdom/createTextNode (name this))))

...

defn element
  ([tag-or-text]
     (log "(element " tag-or-text ")")
     (-element tag-or-text))

...
Seems we have a protocol providing a -element function, which accepts one of string, PersistenVector, or js/Element. In the case of string it tests if it is a keyword. But how can something be a keyword if we already know it is a string? For good measure I tried (dom/element ":div"), which unsurprisingly just gave me a text node.

thheller05:09:19

you probably want to look for other ways to work with the DOM. I doubt anyone uses clojure.browser.dom and its not exactly "maintained"

skylize12:09:29

It's a built in package. Shouldn't it at least do what it claims to on the cover?

thheller05:09:31

if you ask me these namespaces should have been removed a decade ago. but they are staying for historic reasons just in case someone might be using them. if you ask me nowadays its better to look elsewhere.

thheller05:09:09

the test for keyword is there for historic reasons as well. very long time ago keywords were just represented as a string and didn't have their own type

thheller05:09:48

the code was never adjusted accordingly I guess

skylize07:09:25

A deprecation notice sure would be nice, if they're just going to abandon something, but still ship the eroding code.

Lone Ranger23:09:59

Any advice for chunking cljs_base.js into smaller files? The bulk of the size comes from the webpack index.bundle.js file. I'm using code-splitting to get the rest of the size reasonable but it's still wild to make users load 5.5 MB before the first paint

hiredman00:09:55

I am not too up on the latest in javascript development, but I have a little toy app with some clojurescript which uses reagent. with the :optimizations :advanced option to the cljs compiler the output is a single js file that is 0.4 megabytes

Lone Ranger10:09:07

That is with advanced compilation 😅 In this case I'm using code splitting [1], which is why there are 4 files.

:modules        { :main {:entries #{app.core}
                         :output-to "resources/public/js/compiled/app.js"}
                  :init {:entries #{app.lib.actions.init}
                         :output-to "resources/public/js/compiled/init.js"}
                  :dom {:entries #{app.dom}
                        :output-to "resources/public/js/compiled/dom.js"}
                  :cljs-base {:output-to "resources/public/js/compiled/cljs_base.js"}
                  }
The problem is that a lot of the npm code being used in the app takes up like 4.5 MB by itself, and all that crap gets stuffed into the cljs-base module. But a lot of it isn't needed initially, so I'm trying to figure out how to defer loading it until after the first paint -- or to break it into smaller chunks that are more TCP friendly. [1] https://clojurescript.org/guides/code-splitting

hiredman16:09:43

can you not turn on advanced optimizations when splitting?

Lone Ranger17:09:11

The screenshot I showed you was with advanced compilation. The files are much larger without advanced compilation. One of the problems is that the npm code itself takes up 4.9M -- and this has already been compressed by webpack.

Lone Ranger17:09:15

the npm code is included into that cljs_base.js file from earlier via advanced compilation. So only 0.6 MB accounts for the app code, while 4.9MB (presumably) is from dependencies.

Lone Ranger17:09:04

Now the thing is, a good portion of that code is not needed on page load. The majority of it could be loaded asyncronously on-demand (from a dependency ordering perspective). I'm just not sure how to accomplish this technically yet.

Omar06:09:52

you could use a shadow-cljs flag to look at the output of the build report and see what's taking up space.

alex03:09:20

^ npx shadow-cljs run shadow.cljs.build-report <your-build-id> report.html

alex03:09:12

Without knowing too much about your app, it seems like you might want to employ more aggressive code-splitting. Maybe consider splitting by routes if that is an option or splitting out the components w/ heavy dependencies into their own files

alex03:09:41

I'm also slightly curious what your heavy NPM dependencies are :)

Lone Ranger13:09:51

Antd and materialui are pretty hefty

Lone Ranger13:09:17

Yeah going with aggressive code splitting and side loading the npm deps on demand instead of advanced compiling them