Fork me on GitHub
#clojurescript
<
2019-02-08
>
Matt00:02:55

I’m looking for some insights on using React. It seems to be a go to library that cljs embraces and wraps. Am I crazy, or is “setState” incredibly coupled to the UI? I’m interested if anyone has suggestions with respect to management of non UI state. I’m finding it hard to deal with non DOM/SVG state.

Matt00:02:05

I feel like datascript solves this problem, but it would be interesting if a seasoned dev had some words of wisdom.

dpsutton05:02:18

has anyone ever migrated a mature product from lein and figwheel over to shadow cljs? what was your experience? work is considering doing a late night trying to shift this over and I'm wondering if anyone has done this and if it is a single day type of goal or not

henrik07:02:46

Not a huge thing, but decent size. I'm not sure there's much difference in effort relating to size, unless you do custom, weird stuff in project.clj.

Audrius07:02:15

Does anyone of you know how to find possible memory leaks with re-frame applications. we have app with React and Processing. The problem is that we have a Canvas element with Processing.js, but when we destroy it the Canvas elements are still in the heap. I tried to look into it but always end up at ReactDom or Processingjs object. What tools you guys use to debug such memory leaks?

👙 5
🥃 5
erwinrooijakkers11:02:10

No experience with this, but did you try taking Heap Snapshots? https://dev.to/kepta/a-toddlers-guide-to-memory-leaks-in-javascript-25lf And I know from the re-frame docs that subscribe in a subscription leads to a memory leak.

The Wrong Way

You should NOT do this:

(re-frame.core/reg-event-db
  :event-id 
  (fn [db v]
    (let [sub-val  @(subscribe [:something])]   ;; <--- Eeek
       ....)))

erwinrooijakkers11:02:34

Oh wait I see you did just that 🙂

erwinrooijakkers11:02:56

Have to see the code of the Canvas element

👍 5
practicalli-johnny10:02:10

It's sad to know that the UK parliament and government also have very few rules to prevent people being the bad guys, just as with the USA Congress, Senate and president https://twitter.com/CarolineLucas/status/1093655388636045313?s=19

practicalli-johnny10:02:27

Yes was supposed to be UK.

zyxmn11:02:28

How many folks here use clojurescript on server side? Does it make sense, have enough support or Is it just a party trick?

wilkerlucio11:02:36

I've been using node+cljs in http://www.daveconservatoire.org for years and I think works great

wilkerlucio11:02:01

this one is open source if you like to check, altough code is quite old https://github.com/daveconservatoire/dcsite-cljs

zyxmn11:02:51

@U066U8JQJ that is very cool. Has it catered to all your requirements ? For example imagine implementing a graphQL server .

wilkerlucio12:02:03

yup, the interop with JS is very complete, you might need to write some wrappers but thats it

wilkerlucio12:02:26

its actually easier to require NPM stuff on the server than it is on the client, since you can actually use the node.js require

👍 5
zyxmn12:02:07

Awesome . Will take a look . Thanks :D

borkdude11:02:23

People also use CLJS for scripting in e.g. Planck, or Lumo or just cljs.main on NodeJS.

zyxmn12:02:16

Is that setup straightforward ? In my case its more implementing a graphQL server.

borkdude12:02:53

I haven’t much experience with running Node as a server, sorry

lukas.rychtecky12:02:22

@its.ramzi

(require '[goog.dom :as dom])
(require '[goog.dom.forms :as forms])
(forms/get-value (dom/getElement "runAgainstTextarea"))
https://google.github.io/closure-library/api/goog.dom.forms.html

Ramzi12:02:17

Okay thank you. I'll try that now.

Ramzi12:02:25

Uncaught TypeError: goog.dom.forms.get_value is not a function

lukas.rychtecky12:02:53

Did you require goog.dom.forms?

Ramzi12:02:19

Is that only at the top of the cljs file, or do i have to edit a properties file also?

Ramzi12:02:55

I didn't put the leading apostrophe.

lukas.rychtecky12:02:01

Yes adding it to “the top” should be enough.

lukas.rychtecky12:02:13

Apos is just needed in REPL.

Ramzi12:02:30

(ns mindmeld.core (:require [reagent.core :as reagent :refer [atom]] [reagent.session :as session] [reitit.frontend :as reitit] [clerk.core :as clerk] [accountant.core :as accountant] [reagent.core :as r] [goog.dom :as dom] [goog.dom.forms :as forms] ))

Ramzi12:02:19

im using lein figwheel.

Ramzi12:02:24

does it need to restart harder

Ramzi12:02:29

to get the new dependency

lukas.rychtecky12:02:52

No goog is included in CLJS.

thheller12:02:38

@its.ramzi @lukas.rychtecky it isn't forms/get-value it is forms/getValue

lukas.rychtecky12:02:02

Yeah! Sorry, @thheller is right!

thheller12:02:05

@its.ramzi (.-value el) will also just give you the value. no need for the goog stuff

lukas.rychtecky12:02:43

Yeah for a simple textarea yes, but for selectbox is not so easy to get a value.

dominicm15:02:52

Is there a method I can use to identify which namespaces are taking a long time to (re)build? My reload is slow, and I'm not sure where to dig in.

mfikes15:02:45

@dominicm you can turn on the :verbose compiler option

dominicm18:02:19

@U04VDQDDY I've done this now, which got me further! I am not really seeing many stats, but I do see: > Process JS modules, elapsed time: 529.185744 msecs Does that mean that foreign libs is the cause? I don't think we have much of that

mfikes18:02:04

Well, that's only 1/2 second there

mfikes18:02:00

If you watch the :verbose output, you may see spots where it seems to pause for a lengthy period of time

dominicm18:02:53

Fwiw, I do have an error about not being able to find @cljs-oss/module-deps, so it could be that?

dominicm18:02:26

Half a second could be in the range of reload times I see though. It's not giant, just not really quick. Maybe up to 3 seconds.

mfikes18:02:29

Are you using :npm-deps?

dominicm18:02:00

Not directly. But I'm guessing a dependency could be?

dominicm18:02:41

I haven't figured out how to check.

mfikes18:02:24

If you have :npm-deps enabled, then it is possible that the compiler is spending a lot of time scanning the a large node_modules tree. In that case, this patch might help give more insight https://dev.clojure.org/jira/browse/CLJS-2963

mfikes18:02:15

Oh, you said that reload is slow... If you are referring to the :reload flag to require then perhaps more insight into what the compiler is doing could help. If you are using Figwheel or Shadow CLJS, then there could be something going on in those tools. At a certain point, it might help to just start adding diagnostic stuff into the tools / compilers in order to track down where it is spending time.

dominicm18:02:57

Putting the verbose flag on with Figwheel main didn't seem to output anywhere, which wasn't helpful

dominicm18:02:18

The node modules isn't ending up being created.

dominicm22:02:34

Okay, I think this is down to ns compilation time for certain namespaces.

dominicm22:02:56

I also tracked down the module-deps thing, apparently just using foreign-libs is enough to cause that? Is that a bug?

dominicm22:02:56

Anyway, ns compilation: I have 1 ns alone which is a cljc of 1300 loc, That ns has 25 dependent nss which also have to be reloaded on this changing. So that will get big pretty quick.

jsa-aerial16:02:12

Tried in #figwheel, but very low traffic there. Been running figwheel fine for months. Today when I try it on any project I get this:

jsa-aerial16:02:14

This would seem to be an issue with use of non blocking IO, but I'm unclear on why this would start to happen now. This is on Ubuntu 14.01LTS. Still works fine on my Mac...

jsa-aerial16:02:23

Any ideas appreciated

john16:02:36

might be coming from rebel-readline. Are you using that?

jsa-aerial16:02:41

No, well, I don't think so. Isn't that new for 1.10?

john16:02:01

no, just similar name

jsa-aerial16:02:04

Yeah, this thing appears to be using jline

john16:02:42

You could try temporarily disabling rebel-readline to see if that fixes it

jsa-aerial16:02:56

OK, how do I do that?

jsa-aerial17:02:51

Hmmm, looks like setting path :figwheel :readline to false disables. Let me try that

jsa-aerial17:02:41

Well, that doesn't change things...

john17:02:17

something's wrapping the terminal with jline somewhere

jsa-aerial17:02:10

I really don't know the details of the infrastructure here - I am using lein-figwheel. Is jline not standard with this setup? I have done lein clean but that doesn't help. Or do you mean clean out lein-figwheel and figwheel and reinstall?? @john thanks for the help!

john18:02:13

lein clean should do it. rebel-readline (and thus jline) comes pre-installed on some of the figwheel distros. (figwheel-main, at least, I think, though I could be wrong)... Though, I'd imagine a rebel-readline set to false should disable that.

jsa-aerial18:02:27

@john as an FYI, the problem is with Google Chrome 72.0.3626.96, not sure if the next update will fix it but firefox works fine.

john17:02:13

are you using figwheel-lein or figwheel-main?

john17:02:46

(also try cleaning out the build dirs)

john20:02:29

did alias never exist cljs? Could have sworn we had it...

thheller20:02:58

no it never existed

mfikes21:02:00

@john One line of thinking (from a couple of years back) was that there might be some ns-form support or somesuch that might be added to Clojure to make it a bit easier to deal with aliased keywords (especially in the case where the namespace is just a name, not associated with a Clojure namespace). If that were added then that would be one less need for alias in ClojureScript—we could just port that feature to ClojureScript and use it.

john21:02:50

I'm building something akin to a lazy-ns form that, if enabled in :clojure-defines, will do a def for each referred var and hang the var off a (apply (resolve ... in the def. (with some associated lazy cljs.loader/load semantics)... but if lazy is disabled in clojure-defines, I'll just default to requiring the namespace, with the associated vars referred in like normal. My only issue is the :as clause. I don't yet have a way to simulate the aliasing.

john21:02:01

Thanks @thheller, I'll run those down.

mfikes21:02:20

@john Would this be in lieu of a ns form?

john21:02:37

No, just below it

john21:02:16

But you could elide your lazy namespaces from the ns form altogether, since you can require at the top level anyway.

john21:02:33

and if lazy is disabled, the generated js looks pretty much the same

mfikes21:02:31

I think there is some code in the compiler that does some lightweight parsing of ns forms in order to determine the partial compilation order... and that thwarts attempts to be fancy

john21:02:10

Things seem to be mostly working

john21:02:27

tested on vanilla and figwheel, with some kinks I'm ironing out

mfikes21:02:34

Do you end up with an ns form followed by a require form? (That doesn't sound like it is something that is supported.)

john21:02:25

Well, to be clear, the need for aliasing is so that I can get rid of the lazy-ns thing altogether and just annotate hiccup forms with #lazy/comp or some such, and I could do a form like #lazy/comp [some.lazy.ns :as sln :refer [foo bar] [foo "hi"] [sln/long-name "bye"]]

john21:02:06

Well, the lazy-ns thing just adds requires. No extra ns declarations, if that's what you mean

mfikes21:02:16

Cool cool. Just wondering if you're gonna end up fighting the static nature of the compiler

john21:02:33

(and it only adds the extra requires when lazy is disabled)

john21:02:41

Yeah, I was wondering that too

mfikes21:02:02

Yeah, I think if you do

(ns foo.core)
(require baz.core)
that is a no-go

john21:02:15

Works though

mfikes21:02:45

Really? It violates my mental model of how the compiler works. Interested in this now. 🙂

thheller21:02:34

it definitely doesn't work that way 🙂

mfikes21:02:38

That second bit is really

(ns* (:require baz.core))

mfikes21:02:00

So you have an ns form followed by that other "pseudo-ns" form.

mfikes21:02:02

At its core is the fundamental idea that the compiler needs to statically determine the compilation order from, essentially, literal information in the ns forms (or a top level require form in its place if you want)

mfikes21:02:43

It is difficult (impossible?) to hop outside of that model

mfikes21:02:21

You could do really funky stuff like generate some code and then unleash the compiler on it

john21:02:28

so... The reason this is working right now is because... nothing is depending on this particular namespace I'm implementing in? The outputted js is indistinguishable from what it would be otherwise, but the dep graph is corrupted?

mfikes21:02:09

yeah, it could be that you just have (ns* (:require baz.core)) sitting in your code, all lonely

mfikes21:02:44

Singing that Eric Carmen song

john21:02:42

from the perspective of another consuming ns? I'll have to bang on it more, but for whatever reason, things seem to be working.

mfikes21:02:10

When people say "top-level" require I suppose we need to further qualify the meaning to be, a reuqire at the top of the textual file

john21:02:25

I guess I could also build a tag literal that transforms the ns form directly, but then you'd have to wrap your ns form in another ugly form, so I can slip in the parameters 😕

john21:02:45

Top level, outside of a fn body is what I mean

mfikes21:02:00

Yeah, "top-level" is where defs live, right?

mfikes21:02:04

(Normally)

john21:02:06

that's what I mean

mfikes21:02:19

Right. require has more constraints than "top-level"

mfikes21:02:32

It can be used in lieu of ns

mfikes21:02:59

Not sure what you'd call that. "top-top-level"

john21:02:03

Does it bother your mental model that the generated js of (ns foo.bar) (require 'other.thing) is equivalent to (ns foo.bar (:require [other.thing])) after compile?

john21:02:22

Or would you expect that?

mfikes21:02:43

That is OK with my mental model. But just surprising that it does that 🙂

mfikes21:02:58

That is actually pretty cool if true. Hrm.

john21:02:11

Things are working...

john21:02:29

Just now you've got me worried about the dep graph long range

mfikes21:02:45

OK. Gonna look at the source now. 🙂

john21:02:11

Yeah, I should probably push out some bits to play with too, so you've got something to bang on

john21:02:13

there's some untapped gold in them thar' tagged-literal hills too, man. Cool stuff.

mfikes21:02:58

I bet ns followed by require is an accident of implementation...

john21:02:02

Accidents are features!

mfikes21:02:08

Even then, there is the partial compilation order to worry about... this require stuff seems to be useful outside of source tree compilation

mfikes21:02:53

Yes, ns followed by require is not in conflict with Hyrum's Law

john21:02:22

It's strange how well it works then.. I'm going to have to construct a bunch of deps to see how all this plays out

mfikes21:02:45

Yeah, I can point you to the code in the compiler that concerns me... just a sec.

mfikes21:02:53

It is the partial order calculation

mfikes21:02:27

cljs.analyzer/parse-ns

john21:02:35

Yeah, that looks like it's gonna be a problem

john21:02:51

what about the require source though...

mfikes21:02:58

This is that "lightweight" ns form parsing stuff I was referring to

mfikes21:02:41

In parse-ns you can see that require which gets turned into ns* involves a recur to keep looking for more require forms

mfikes21:02:18

But, ns, doesn't involve a recur. This matches António's article explaining that you can have either ns or multiple requires

mfikes21:02:11

I want to say that having require (or multiple require) statements is useful in a file that forms the "root" of the dependency tree. (Because it is only for ns forms that you can actually define namespaces that can be required.)

mfikes22:02:05

Yes, the code line you linked is handling multiple requires. (I say multiple because of the presence of recur in there.)

mfikes22:02:38

require is a macro that turns in to ns*, which has :ns* as its :op in the AST

mfikes22:02:56

Oh, so what you are seeing is the emitted Closure require statements that makes ns-followed-by-`require` look like a supported thing...

mfikes22:02:30

Interestingly, ns-followed-by-`require`s looks like it is a mild extension to the existing code 🙂

john22:02:31

well, it's a provide, followed by a number of require statements. But also the fact that it works is throwing me off... Though it hasn't been without rough edges, so I'm sure you're right that it may not work in the large

john22:02:58

oh, I see what you mean

mfikes22:02:31

If you make use of a function that is pulled in by a require that follows an ns you may find that arity checks don't work

mfikes22:02:09

Also, :as shouldn't work, I would guess, as aliasing would probably not be set up

mfikes22:02:22

Which probably brings us full circle to your original comment 🙂

mfikes22:02:23

I could be wrong though... parse-ns is just for the partial order, I'd guess, and there is other machinery at play when you actually go to compile a file. My hunch is that compilation can occur in a way that violates the needed partial order, and you won't have analysis information present for the vars you are using.

john22:02:01

hmmm. at the repl, require can take an :as clause, which appears to work

john22:02:18

I think def-level in a file has been working too

mfikes22:02:50

Right. I'm thinking you could have some code like this:

(ns foo.core)
(require '[bar.core :as bar])

(bar/some-fn 1 2 3)
and I'd be worried about what is happening with bar/some-fn (whether it even gets resolved), and if it does, whether the analysis metadata for some-fn is available, so you know its arity

mfikes22:02:27

This all works at the REPL, but the above example is meant to be in a file

mfikes22:02:38

Maybe the compiler hits the ns* form and you get the needed side effects. Hrm.

mfikes22:02:16

It is an interesting meta-idea on whether something like :closure-defines could be used to drive conditional dependencies in a codebase. The desire for conditional :require is an evergreen concept. I wonder if that maps to some sane concept down at the Closure layer.

john22:02:19

That's pretty much what I've implemented

john22:02:23

Where if lazy? is false in the closure-defines, it'll revert from cljs.loader stuff to regular requires

thheller22:02:13

you can't really transform something that used cljs.loader back to normal requires

thheller22:02:26

since it may lead to circular requires which are not allowed

john22:02:13

Not sure if we're talking about the same kind of transform

mfikes22:02:46

There's also AOT compilation to worry about (compatablity with :aot-cache feature)

john22:02:22

Only downstream -data_reader files- closure-defines are pulled, in my testing so far

mfikes22:02:25

In Clojure, you'd just conditionally execute a require right in your code

mfikes22:02:48

It makes you wonder if there is some very constrained equivalent of that which could work in ClojureScript

john22:02:38

@thheller The things that use cljs.loader are also transformed, if lazy is disabled... the defs just become defs to the applicable namespace's vars

thheller22:02:22

(ns (:require [cljs.loader ...])) (ns other.ns (:require [])

thheller22:02:45

it is perfectly fine for to load the other.ns via cljs.loader

thheller22:02:59

it can't do so via :require since thats a cycle

thheller22:02:05

doesn't matter if you add a namespace in the middle

thheller22:02:20

also all var invocations via cljs.loader would be async

john22:02:53

Async, until load has completed

thheller22:02:54

so I don't really see what you are trying to do that would make things any easier

mfikes22:02:57

Quarter-baked thought:

(ns foo.core (:require [config.core :as config]))

(when config/use-baz?
  (require '[baz.core: as baz]))

(when config/use-baz?
  (baz/some-fn 1 2 3))
where config/use-baz? is a goog-define Seems very challenging.

john22:02:04

That def works

mfikes22:02:45

The above code looks enough like Clojure where you don't feel like it is introducing fundamentally new concepts. It is just relaxing the model a little.

john22:02:48

But this already works #lazy/ns [(:require [this.that :as blah]) (ns some.ns (:require ....

john22:02:21

where the tagged-literal transforms your ns form before it's even processed, so as to parameterize the top level ns form itself

mfikes22:02:25

parse-ns deals with that?

john22:02:27

But that's super ugly imo

mfikes22:02:48

Ahh, that happens before parse-ns maybe? Cool.

john22:02:03

tagged literals are way early

john22:02:11

before normal macros I guess

john22:02:32

Because that ain't up to spec 😉

john22:02:50

Another reason it's so ugly though

thheller22:02:22

you are doing side effects at read time. every tool author is gonna hate you 😛

mfikes22:02:46

It would just be nice to have a clean solution to the problem of conditionally requiring another namespace, if it could be soundly defined and could work with Closure, :aot-cache, etc.

💯 5
mfikes22:02:03

The problem seems to come up frequently

john22:02:14

Well, now that we're on the topic, closure-defines has the constraint that vals must be numbers, boolean or strings... But, since cljs.edn is edn, we already know it's static, so it couldn't hurt to clojure.edn/read-string on the vals on their way in.

john22:02:43

(which is what I'm doing manually, but it'd be slicker if cljs came that way, since, like I said, it's an edn file anyway)

john22:02:19

I may push out a tiny cljs-env lib that takes care of all that for you, so you can refer in env and pull data out like environ or config does it

john22:02:50

But unfortunately, you've got to declare your whole env map in closure-defines as one string

john22:02:49

@thheller to finish answering your points of contention: I'm not suggesting cyclic deps. And the main purpose of this would be for usecases where you know a thing will not be loaded on start up, like react components or whathaveyou

john22:02:20

So, the async thing isn't an issue, as long as you control the start up of the thing doing the calling

john22:02:10

If you really wanted to get advanced though, you could potentially build a loading system that gets called first, and controls the calling of main in whatever/main.js, so that you could control all loading aspects of loading startup. That may allow us to slip in implicit loads prior to synchronous invocations, application wide

john23:02:47

So, ideally, deps could be conditionally loaded, just in time, just prior to a synchronous js context of execution

john23:02:12

But for right now, I'm just focusing on things that can explicitly be controlled by the user, like with react components.

john23:02:04

Since there's so much confusion around whether these requires outside the ns form will work, I guess I'll first provide an ns re-writing tagged-litteral, where it allows you to annotate which namespaces should be lazy. Then another tagged-literal for hiccup forms, which defer to a fallback component until the module is loaded.

john23:02:02

I'd much rather get it all done with just one macro or tagged literal at the top-def-level, which seems to semi work atm. But that'll require much more testing I suppose.