Fork me on GitHub
#shadow-cljs
<
2019-11-11
>
Eliraz10:11:15

Hello, I have an issue with shadow-cljs

Eliraz10:11:40

I keep getting this error when I try to watch / compile my dead simple app: Illegal UTF8 string in constant pool in class file jdk/nashorn/internal/runtime/linker/Bootstrap

thheller10:11:42

hmm never seen that one before

thheller10:11:01

seems like a general JVM error. what is your java -version?

Eliraz10:11:27

openjdk version "13.0.1" 2019-10-15

thheller10:11:47

did you try a different version?

thheller10:11:50

I'm on

openjdk version "13" 2019-09-17
OpenJDK Runtime Environment AdoptOpenJDK (build 13+33)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 13+33, mixed mode, sharing)

thheller10:11:20

runs without issues, maybe something is bad in your release?

Eliraz10:11:48

which means?

Eliraz10:11:28

my java release or my simple app release?

Eliraz10:11:42

I can try install the openJDK 13.u33 and see if it works

thheller10:11:40

the error is in a internal JDK class, nothing to do with shadow-cljs as far as I can tell

thheller10:11:36

or does running lein or something work?

thheller10:11:46

seems like that would fail as well?

Eliraz10:11:10

I'm sorry. i'm new to all this.. lein with what command?

thheller10:11:51

just lein repl maybe?

Eliraz10:11:27

I just downgraded from Java 13.0.1 to Java 11 and it compiles

thheller11:11:56

maybe there was just something broken in your release

thheller11:11:08

everything from jdk8 upwards should work fine. I usually upgrade when a new release is out and never had issues with v13, but I haven't tried the latest

Eliraz11:11:18

how would I fix the broken release?

thheller11:11:31

I don't know. there is a jdk-13.0.1+9 release. maybe that fixes it? or just try the regular 13, not 13.0.1?

Eliraz11:11:39

I actually got here to question about the workflow with emacs and CIDER, would you usually jack-in? or connect to a watch? how would you get auto-complete in the editor ?

thheller11:11:49

I've never seen this error before and a quick google suggests it isn't very common either

thheller11:11:08

I don't use emacs so can't comment on any workflow issues

Eliraz11:11:25

I'll try or I'll just wait for a new release, I don't really care what JAVA version I'm running as long as it compiles to JS 😛

thheller11:11:06

yeah doesn't matter as far as shadow-cljs is concerned either

Eliraz11:11:43

now JAVA 11 also doesnt work anymore.. ?!!

Eliraz11:11:51

I don't know.. changing back to 13 and to 11 makes it work

Eliraz11:11:43

where is the HTML file? I can't find it anywhere?

Eliraz12:11:38

How would I connect from the nRepl to the browser? I mean, trying to run this code: (js/console.log "dsad") is not working from nRepl beacuse it says there's no js namespace

aisamu13:11:22

Have you picked a CLJS REPL env after connecting to the Clojure REPL?

shadow.user> (shadow/repl :test-dev)
To quit, type: :cljs/quit
[:selected :test-dev]
cljs.user>

Eliraz13:11:03

I guess not. thank you!

Eliraz13:11:01

any of you is using hx instead of reagent?

Eliraz13:11:27

how can you rebuild from repl after adding dependency from npm ?

thheller13:11:01

what do you mean?

thheller13:11:38

"after adding dependency from npm" means you ran npm install the-thing?

Eliraz14:11:05

after adding a library how would you rebuild from the repl?

thheller14:11:17

@eliraz.kedmi please specfiy what you mean by "rebuild"

thheller14:11:31

do you just want to use the library

thheller14:11:43

or did you already use it and upgraded the version?

thheller14:11:10

if you just want to use it (require '["the-thing" :as x])

Eliraz14:11:23

I mean, when I'm installing a new library via npm, I want to use it in my files..

Eliraz14:11:53

yes, but that requires recompile the app, isnt it?

thheller14:11:15

I have no idea what you are doing

thheller14:11:24

please describe your setup

thheller14:11:30

what kind of setup do you have?

thheller14:11:37

build config etc?

thheller14:11:42

are you working on a browser build?

thheller14:11:51

which REPL do you use?

Eliraz14:11:54

okay. say I have the app running in dev mode with repl on. and now I'm adding the Material-UI library..

thheller14:11:21

by "app" I'm going to assume that you have a build config with :target :browser?

Eliraz14:11:24

I can't just require the button from that library now, I need to quit the repl and rebuild it

thheller14:11:46

ok and when you say REPL you did shadow-cljs cljs-repl the-build?

Eliraz14:11:58

oh okay hmm

Eliraz14:11:07

I just jack-in with CIDER

thheller14:11:21

ok, that works too

thheller14:11:46

so you do (require '["@material/core" :as x]) or whatever the package for that is nowadays

Eliraz14:11:01

I'm guessing that you could run a command from the repl that stops the watch and restarts it

thheller14:11:16

no you don't need to staop any watch or anything

Eliraz14:11:43

well. it doesn't work if I don't restart

thheller14:11:01

well then tell me what it told you after you ran the require I just told you?

Eliraz14:11:33

an error saying you can't have an undefiend as a react element

Eliraz14:11:49

it's like it does't know that new package

thheller14:11:07

so you skipped 3 steps ahead already I guess?

thheller14:11:12

did you run the require I told you?

Eliraz14:11:23

that's the behavior in regular React application

Eliraz14:11:33

I didn't run in the repl

Eliraz14:11:39

I put it on the file

Eliraz14:11:50

`(:require ["@reach/router" :refer [Router]] ["@material-ui/core" :refer [Button]] [hx.hooks :as hooks] [hx.react :as hx :refer [defnc]]))(:require ["@reach/router" :refer [Router]] ["@material-ui/core" :refer [Button]] [hx.hooks :as hooks] [hx.react :as hx :refer [defnc]]))`

Eliraz14:11:10

the material is the new package for instance..

Eliraz14:11:41

It didn't work until I quit the repl and reconnected again

thheller14:11:42

ok then try adding ["@material-ui/core" :as x :refer [Button]]

thheller14:11:52

and try (js/console.log "mui" x)

thheller14:11:56

see what that says

Eliraz14:11:20

it's already working. I just though you need to restart the watch somehow

Eliraz14:11:26

I guess it's not required?

thheller14:11:33

no, that is not required

Eliraz14:11:48

BTW what's the command that builds a single js bundle file?

thheller14:11:58

shadow-cljs release your-build

Eliraz14:11:24

thank you.

Eliraz14:11:14

I'm transferring an existing project to clojurescript, I currently do that in a very idiomatic way, how would you recommend to take advantage of the clojurescript power? any good courses you know about?

lilactown20:11:34

I'm backing off of attempting to add an option for shadow-cljs to reload the whole dependency tree of a changed ns. You're right it's the wrong way to approach this. We shouldn't hamstring our tools to make React happy. Also it doesn't fix REPL-driven development 😖

lilactown20:11:12

Instead I'm going to hack on React to see if I can add a command to refresh the whole app

thheller20:11:02

your react-refresh work triggered me to work on my vdom stuff again. got it into a state where I might start using it for some of the shadow-cljs UI stuff. would be nice to get rid of react completely 🙂

👀 4
lilactown20:11:29

Excited to see more!

lilactown20:11:53

Svelte has interested me in what a React-less world might look like...

thheller20:11:34

my stuff is more like 50% svelte 50% react

thheller20:11:03

now also has "hooks" although kinda different. can't come up with a different name though 😛

thheller20:11:30

maybe you fine folks have an opinion on this. I'm trying to decide the best way to define event handlers in my library. the goal is to have events "declarative" in the code that generates the actual event. so instead of creating a function you just create a vector of data.

thheller20:11:03

not [:div {:on-click (fn [e] ...)}] but [:div {:on-click [::some-event! 1 2 3]]

thheller20:11:30

I'm not sure I want all events to go through one big multi-method

thheller20:11:55

so experimenting defining them in the component definition. currently at 2 different approaches

thheller20:11:57

(defc dummy1
  {:init-state {:num 0}
   ::inc!
   (fn [env e]
     (sac/swap-state! env update :num inc))}
  [props {:keys [num] :as state}]
  []

  (<< [:div
       [:button {:on-click [::inc!]} "click me: " num]]))

(defc dummy2
  {:init-state {:num 0}}
  [props {:keys [num] :as state}]
  [::inc!
   (fn [env e]
     (sac/swap-state! env update :num inc))]

  (<< [:div
       [:button {:on-click [::inc!]} "click me: " num]]))

thheller20:11:59

the second vector after [props state] is "magic" for hooks. you can think of it as a special let if that helps

(defc dummy2
  {:init-state {:num 0}}
  [props {:keys [num] :as state}]
  (let [::inc!
        (fn [env e]
          (sac/swap-state! env update :num inc))]

    (<< [:div
         [:button {:on-click [::inc!]} "click me: " num]])))

thheller20:11:47

probably doesn't make much sense to ask this without explaining how all of it works

thheller20:11:51

(defnc my-refreshed-component
  [props]
  (let [[count set-count] (react/useState 0)
        [name set-name] (react/useState "React Refresh")]
    ;; kept it's state!
    ($ react/Fragment
      ($ :div {:style {:background "pink"}}
        ($ :div (b/greet name))
        ($ :div ($ :button {:onClick #(set-count inc)} "+ " count))
        ($ :div ($ :input {:type "text"
                           :value name
                           :onChange #(set-name (.. % -target -value))})))
      ($ :br)
      ($ z/component {:name "child in another file"})
      ($ :br)
      ($ z/memo-component {:name "memoized child in another file"}))))

(defc my-refreshed-component [props]
  [[count set-count] (react/useState 0)
   [name set-name] (react/useState "React Refresh")
   ::inc! #(set-count inc)
   ::set-name! #(set-name (.. % -target -value))]
  (<< [:div {:style {:background "pink"}}
       [:div (b/greet name)]
       [:div [:button {:on-click [::inc!]} "+ " count]]
       [:div [:input {:type "text"
                      :value name
                      :on-change [::set-name!]}]]]
      [:br]
      [z/component {:name "child in another file"}]
      [:br]
      [z/memo-component {:name "memoized child in another file"}]))

thheller20:11:14

basically the first let you pretty much always have anyways becomes "special" with "hook" semantics

thheller21:11:12

but yeah the functions definitely need to go.

thheller21:11:22

maybe I'll just allow both styles .. they aren't mutually exclusive 😛

lilactown21:11:50

what’s the upsell for using keywords instead of a bound symbol for events?

thheller21:11:20

can't differentiate between a bound symbol and a function created inline via :on-click (fn [e] ...)

thheller21:11:42

must get rid of function creation inline since they are never identical?

☝️ 4
thheller21:11:56

the main reason is that I want declarative events though. easier to translate for server-rendered code if there isn't (fn [e] ...) all over the place

lilactown21:11:49

I think having a special “let” block is nice for setup that gets run on each render. I keep dancing with the idea of creating my own special let macro for certain purposes

thheller21:11:05

no the point is that it DOESN'T run on each render

thheller21:11:14

that is my main gripe with hooks. all shit runs all the time.

lilactown21:11:42

hmm. how does it know when to re-run things?

lilactown21:11:42

FWIW, I think on the surface it’s feasible to do enough static analysis at macro-time to determine values (like functions) that could be automatically memoized to maintain identity across renders

lilactown21:11:52

that’s my eventual goal with hx

lilactown21:11:08

right, I’m dealing with React 🙂 greenfield is another story!

lilactown21:11:57

if you have some way of determining when e.g. an effect or render should trigger through some other means, like a signal graph, then you don’t need to call it on every render

thheller21:11:26

yep. the value returned by the "hook" functions can implement a protocol

thheller21:11:50

so basically they can tell the component to invalidate their value

thheller21:11:00

when they do all subsequent hooks that used the binding will also run

thheller21:11:36

(defc dummy3 [props]
  [a (subscribe [:bar])
   b (subscribe [:x a])]

  (<< [:div b]))

thheller21:11:45

re-frame ish style is easy

thheller21:11:56

b only runs again if a changes

thheller21:11:08

if b doesn't change render is skipped because a isn't used in render

thheller21:11:08

b of course can invalidate itself but that won't affect a

lilactown21:11:25

I think where the friction starts with that approach is when you want to do something like React’s Concurrent mode. you have to figure out where scheduling all the work occurs.

thheller21:11:54

well, yes that is the hard part but everything is in place for it

lilactown21:11:50

if you let every state be mutated and trigger a re-render ad-hoc, then you either need to implement scheduling in the signal graph (not the component tree like React has), or you risk having state and what’s rendered diverge in degenerate cases

thheller21:11:17

(defc dummy4 [{:keys [id] :as props}]
  [{:keys [text]}
   (-> (js/fetch (str "/some/" id))
       (.then #(.json %))
       (.then js->clj))]

  [:div text])

thheller21:11:35

I have scheduling in the signal graph

thheller21:11:17

actually it is all a whole lot simpler than React overall since I can do magic like this

thheller21:11:34

the code the macro generates wouldn't be feasible to write in react

thheller21:11:58

so the model they came up with only looks like that because it is something people can actually write

thheller21:11:34

although I'm not too convinced about that given the manual [all, the, deps] args you need to construct

thheller21:11:18

but they are exploring babel plugins and shit like that for that if I read that correctly somewhere

thheller21:11:26

so basically we can skip all that shit because of macros

lilactown21:11:43

scheduling is about more than just fetching data (though it helps a ton with that). it also has to do with things that take up a lot of CPU. Concurrent mode allows React to prioritize certain renders higher than others, pause or abort them and restart them later

thheller21:11:55

I can do that now

lilactown21:11:57

yeah the deps args stuff with hooks is tiresome but something we can just compile away

thheller21:11:09

extremely easy since everything is immutable anyways

thheller21:11:25

componnents add themselves to a dirty "set"

thheller21:11:33

that set is processed in tree-depth order

thheller21:11:52

parent node can "suspend" child updates until all ready (at some point, still pending)

thheller21:11:14

can stop processing at any point since the scheduler controls all work done

thheller21:11:54

I'm familiar with how react works ... there isn't anything in there that this can't do eventually

thheller21:11:05

and I won't have to throw promises to stop processing something

👏 8
lilactown21:11:51

nice yeah, just trying to level set my understanding. this sounds really cool!

💯 4
thheller21:11:05

i have a working todomvc but need something more complex to test all this on

lilactown21:11:46

I’ve been dog-fooding hx on my own REBL clone; sounds like shadow-cljs’ UI is a good use case 😄

thheller22:11:39

yeah just need to figure out how to get all the fulcro bits just without react 😉

thheller22:11:01

the db normalization stuff is not actually something I want to write 😛

nickmbailey22:11:30

is there a way to make shadow-cljs export everything in my code that isn't marked as 'private'?

nickmbailey22:11:11

adding the ^:export metadata to everything seems a bit tedious or possibly brittle

thheller22:11:45

why do you want to export everything?

nickmbailey22:11:40

i'm using clojurescript to share schema code between the backend and frontend

nickmbailey22:11:51

so i'm consuming this from regular javascript

nickmbailey22:11:23

and basically every schema definition in a namespace should be available to the javascript consumer

thheller22:11:38

what is a schema definition?

thheller22:11:22

to answer your question: no there is no way to make it export everything currently but that doesn't mean the couldn't be 😉

thheller22:11:46

problem with exporting everything is that it prevents DCE

thheller22:11:52

but not the renaming of stuff

nickmbailey22:11:24

a single schema definition is just a var

thheller22:11:43

but how is that used from JS?

nickmbailey22:11:10

we end up with a namespace that has a bunch of 'schemas' like User, Email, BookingRecord...

nickmbailey22:11:33

and then from js we take json blobs and coerce/validate them against a given schema

nickmbailey22:11:56

so in js its 'import * as schema from 'schema.general' followed by 'coerce(jsonBlob, schema.BookingRecord);'

nickmbailey22:11:59

(what acronym is DCE btw heh)

thheller22:11:59

well you could do it in a build-hook but since none of the api for that is documented it wouldn't be too easy I guess 😛

thheller22:11:05

dead code elimination

nickmbailey22:11:25

yeah we want to prevent that entirely

nickmbailey22:11:36

since this is like a utility library most of it will look 'dead'

nickmbailey22:11:12

well i suppose i can add export tag to everything...

thheller22:11:30

simplest way might be a macro that just adds the export

thheller22:11:55

so instead of (def ^:export foo {...}) (defexported foo {...}

thheller22:11:59

but that isn't exactly better

nickmbailey22:11:06

we've been using this for awhile, we were just using the 'compile' target instead of the 'release' target. i was trying to bring our bundle size down by switching to release and that breaks everything heh

nickmbailey22:11:23

well we actually wrote an internal macro for building most of these schemas

thheller22:11:27

you can always use :optimizations :simple

nickmbailey22:11:29

so thats not a bad option actually

thheller22:11:44

that'll still be alot smaller than compile and doesn't need ^:export

nickmbailey22:11:52

ok let me try that

thheller22:11:58

but I'd say just export stuff. can't be that many things 😛

Nolan23:11:54

@thheller i regularly have small isolated/contained front-end projects pop up, and very much enjoy experimenting with alternatives when youre looking for external feedback (`hx` is another im excited to play with @lilactown). events as vectors seems like it could lead to some interesting datalog compatibility… i appreciated a lot of the ideas in factui, but thats unmaintained last i checked

thheller23:11:23

I'll let you know as soon as I'm comfortable with the code. there are still some cases I want to figure out first

💯 4
wizard 4
👍 8
Nolan23:11:32

i know exactly what you mean—enjoy the process

thheller23:11:59

40.9 KB todomvc build looks promising 🙂 no more chunky react-dom nonsense 😛

🙏 8
👏 12
👀 4
Nolan23:11:27

hallelujah.

thheller23:11:28

going through some of the advanced optimized code to find obvious code issues makes the appreciate the closure compiler so much more

thheller23:11:58

always insane to see what kind of optimizations it does 🙂

☝️ 4
Nolan23:11:29

i havent dug into the advanced output itself enough to build an intuition of what it really does, but the artifact size difference alone is enough to instill a deep sense of appreciation for whatever its up to down there

thheller23:11:18

I have a (deftype ComponentConfig [component-name ...]) so it is constructed as (def x (ComponentConfig. "some.ns/foo-bar" ...))

thheller23:11:29

that name however is only used in debugging code which is all removed

thheller23:11:39

so closure strips the entire property from the generated object

thheller23:11:08

no trace left in release builds