Fork me on GitHub
#shadow-cljs
<
2019-10-07
>
currentoor02:10:55

Is there a way to specify a Clojure flag per build? So then my macros can see it at compile time?

currentoor02:10:11

similar to a JVM property, but per build

thheller10:10:10

@currentoor that is generally not a good idea since those flags won't be accounted for in the caching logic

thheller10:10:15

so changing them won't invalidate the cache

thheller10:10:44

what kind of flag do you have in mind though? is that cross-platform macro or just a macro expanding CLJS code?

currentoor15:10:07

@thheller I’ve got macros and functions that need to emit different stuff depending on whether I’m targeting the browser or react native

thheller15:10:33

do you have an example?

currentoor15:10:05

#?(:cljs
   (defsc ErrorBoundary [this {:keys [body-fn]}]
     {:shouldComponentUpdate    (fn [np ns] true)
      :getDerivedStateFromError (fn [error]
                                  {:error true
                                   :cause error})
      :componentDidCatch        (fn [error info]
                                  (log/error :componentDidCatch (ex-message error)))}
     (let [{:keys [error]} (comp/get-state this)
           error-msg (div :.ui.error.message
                       (div :.header "Error Encountered")
                       (dom/p "Please try again or contact support if error persists."))]
       (if error
         error-msg
         (try
           (body-fn)
           (catch :default error
             (log/error error :render-try-catch)
             error-msg))))))

#?(:cljs (def ui-error-boundary (comp/factory ErrorBoundary)))

#?(:clj (defn error-boundary* [[first-arg :as body]]
          `(let [real-parent# comp/*parent*]
             (ucv.util/ui-error-boundary
               {:body-fn (fn [] (comp/with-parent-context real-parent#
                                  ~(if (css-k? first-arg)
                                     `(divs ~@body)
                                     `(comp/fragment ~@body))))}))))

#?(:clj  (defmacro error-boundary
           [& body]
           (error-boundary* body))
   :cljs (defn error-boundary [& body]
           (throw "Use error-boundary as a macro not a function")))

currentoor15:10:34

i can simplify that example if it’s too much

currentoor15:10:37

but essentially it’s just a component that catches exceptions in render and renders an error message

currentoor15:10:09

one of the major schisms is, in browser i need to use DOM in RN i need to use that other stuff

currentoor15:10:54

but ideally i want to re-use components and their queries

thheller16:10:39

I might be blind but what exactly is the react-native specific stuff?

thheller16:10:04

also .. how can you get any work done with all those reader conditionals? my brain hurts from looking at that little snippet

thheller16:10:54

and somehow I'm missing why this is a macro at all?

currentoor16:10:12

lol fair enough, sorry for the pain 😅

currentoor16:10:27

i’ll come up with something simpler

thheller16:10:42

an actual use is more important, now how it is implemented

thheller16:10:56

so how would you use (error-boundary ...)

currentoor16:10:50

(div :.ui.container
  (util/error-boundary :.ui.basic.segment :.ui.middle.aligned.celled.list.massive
    (map ui-tax-list-item (sort-by :tax/title entities))))

currentoor16:10:35

it prevents errors from bubbling up

thheller16:10:51

yeah I know about react error boundaries

lilactown16:10:17

@currentoor why do you care if it’s react native or not?

thheller16:10:33

but I don't quite know why react-native is different from react-dom

lilactown16:10:03

I guess you wouldn’t use div in react-native

wilkerlucio16:10:48

@currentoor maybe better than to abstract that part away? for those common components you could have primitives that change according to the build (by using different source paths, with same ns name)

wilkerlucio16:10:11

so you could have ui/block instead of dom/div / rn/View, makes sense?

currentoor16:10:03

different source paths with the same name? not sure what you mean

wilkerlucio16:10:57

ex: src/ui-rn/my_project/ui.cljs, src/ui-dom/my_project/ui.cljs

wilkerlucio16:10:22

so the RN build uses the first, the DOM build uses the later, so when importing you get the right one

thheller16:10:22

don't ever use different source-paths, that is absolutely horrible

wilkerlucio16:10:35

@thheller what you suggest instead?

thheller16:10:44

proper software architecture ...

thheller16:10:09

sorry .. that different source-paths stuff triggers me 😛

wilkerlucio16:10:14

well, in this case, we need a switch depending on the build, and one source has no relation to the other, they are like interfaces in my POV

wilkerlucio16:10:51

(also worst, loading the wrong one may break the build)

thheller16:10:54

you can use #?(:react-native (do-one-thing) :browser (do-that-other-things) :cljs (the-default-thing))

lilactown16:10:13

yeah I would use reader conditionals

wilkerlucio16:10:15

is there a way to define custom ones like this?

lilactown16:10:35

shadow lets you define your own yes

wilkerlucio16:10:44

cool, I didn't knew that one 🙂

wilkerlucio16:10:09

the only downside I see with that is lock on shadow, I don't mind, but for libraries I wouldn't do that

thheller16:10:17

but in all honesty you should do this properly via application config probably

thheller16:10:27

(def runtime-config (atom {}))

thheller16:10:44

(defn init [] (swap! runtime-config assoc :error-boundary some-fn))

thheller16:10:03

and react-native/browser just initialize differently

wilkerlucio16:10:06

@thheller but how to deal with the fact that some required files will be available in one target but not the other? doens't that still require reader conditionals to do :require?

thheller16:10:24

that way you can ensure that react-native specific stuff is only loaded in react-native and so on

thheller17:10:01

heck you could probably do this as part of the fulcro app

thheller17:10:28

reader conditionals are pretty cancer. host specific stuff should be hidden behind protocols or something

wilkerlucio17:10:13

and I'm curious, what's about the different source paths that triggers you so much? (asking because I have done that a couple times, and I can't see much problem from it, but I guess you have more context than I do)

thheller17:10:30

names are supposed to be unique

wilkerlucio17:10:16

well, they still are, I see them as a "implementation fork", for the compiler there is only one (unless you try to do multiple builds at the same time, which would be crazy with this setup)

currentoor17:10:38

@thheller this approach could work for functions but not macros

thheller17:10:38

they aren't unique anymore if you have 2 versions

thheller17:10:50

they might be unique because you only included one but still

thheller17:10:06

@currentoor I still haven't seen why this is a macro?

currentoor17:10:36

because it wraps the body in a try/catch

thheller17:10:29

(div :.ui.container
  (util/error-boundary :.ui.basic.segment :.ui.middle.aligned.celled.list.massive
    #(map ui-tax-list-item (sort-by :tax/title entities))))

thheller17:10:14

dunno what those keywords are about though

currentoor17:10:19

lol yeah, i should stop trying to have my cake and eat it too

currentoor17:10:28

those keywords generate nested divs

currentoor17:10:35

with those class names

thheller17:10:41

but you could just as well pass a function that you call within the try/catch

currentoor17:10:20

right that’s the syntactic sugar the macro provides

currentoor17:10:30

but i agree it’s possible

thheller17:10:55

you can use the macro of course, but that seems like a lot more code than adding # 😉

currentoor17:10:52

at risk of triggering you again, @wilkerlucio suggestion is looking attractive

thheller17:10:48

I won't stop you. If that works for you go ahead.

currentoor17:10:02

adding RN to a browser project after writing a lot of code is proving cumbersome

currentoor17:10:41

@thheller thanks for the discussion, always helpful simple_smile

currentoor17:10:05

oh wait if i do your approach, of putting the error-boundary-fn in the app config, then have the macro use it

currentoor17:10:28

then i can have it all without separating source paths

currentoor17:10:14

the differences in environment can be encapsulated inside that function and the macro just gives the syntactic sugar

thheller17:10:28

hope that makes sense somehow. the point is that this doesn't require ANY special compiler magic, config or reader conditionals. You just define a "common" shared interface in your code and provide separate implementations when needed

thheller17:10:58

guess I have done too much java with interface too much in the past

wilkerlucio17:10:39

I like this solution idea, I would just don't do with records, a map with the fns works the same (I'm the one triggered by records/protocols :P)

thheller17:10:10

yeah pure fns work just fine. I just use protocols whenever something is a bit more stateful

thheller17:10:30

if you already have an env you pass arround anyways (like in pathom) things are even easier since you don't need to "interface" ns

thheller17:10:05

switching :source-paths is of course similar to this as it replaces the concrete implementation without defining an actual interface

thheller17:10:43

just hate juggling source paths and conditional requires and all that stuff

currentoor17:10:29

yeah i like this approach too

papachan19:10:08

Can we open a browser url just after compilation run?

Chris19:10:21

Has anyone seen this issue with shadow-cljs REPL (CLJS)?

(defn add "Add Two numbers" [a b ] (+ a b))
=> nil
(defn add  [a b ] (+ a b))
=> #'io.example/add
nil being returned from defn when a docstring is present but the var returned when no docstring?

dazld21:10:30

I’m using goog.DEBUG to conditionally run code in dev, but what’s a good way of conditionally requiring something?

dazld21:10:46

I can see the dependencies for this code in the production build, even though the debug code has been removed

thheller21:10:20

@chris547 interesting. works fine in the browser but not in node. not exactly sure why.

thheller21:10:12

@dazld "removed"? conditional requires are only possible with some dirty tricks?

dazld21:10:47

I don’t want development dependencies bundled - something like datafrisk, for example

thheller21:10:20

then don't require them?

dazld21:10:41

heh, but I do want them in dev 🙂

thheller21:10:57

well, how do you require them?

thheller21:10:07

:preloads are meant for stuff like that

dazld21:10:10

you mention strip-type-prefixes

dazld21:10:19

hm, well, the usual way

thheller21:10:24

if you require it elsewhere in the code it is up to you to remove it

thheller21:10:56

code stripping is probably not a good idea, it is kind of a nuclear option that shouldn't be used 😛

dazld21:10:13

(ns foo.bar
  (:require [dev.library :as d]
            [goog :as g))

(defn thing []
  (when g/DEBUG
    (d/some-function)))

dazld21:10:36

ie, in prod, i’d like dev.library and the code behind the DEBUG not to be there

dazld21:10:56

assuming this is the only place dev library is used

dazld21:10:08

or in all places it’s used, it’s behind a DEBUG

thheller21:10:18

likely requires a type hint (when ^boolean g/DEBUG ...)

thheller21:10:24

but the require can't be removed that way

dazld21:10:43

would advanced notice that it’s not being used?

dazld21:10:59

(works ok without a hint btw)

thheller21:10:21

if you follow very strict code style then yes

thheller21:10:29

but a lot of code isn't removeable

thheller21:10:41

I suggest running a build-report to see how much is actually included

dazld21:10:51

i just looked at the file sizes

dazld21:10:00

it’s 40kb bigger, gzipped 😞

thheller21:10:44

create a build report, 40kb doesn't mean anything. seems like a regular CLJS build

dazld21:10:38

that’s a really nice feature, thanks

dazld21:10:42

the dev library is in there 😞

thheller21:10:11

so it wasn't removed

thheller21:10:19

but as I said. you likely need the type hint

thheller21:10:44

it works without yes, but the type hint slightly alters the generated code which means it works better with :advanced

dazld21:10:15

ah hah! ok

thheller21:10:24

but you can easily verify if its actually removed via

(defn thing []
  (when g/DEBUG 
    (js/console.log "thing debug wasn't removed")
    (d/some-function)))

dazld21:10:51

the stuff behind the guard is removed fine

dazld21:10:58

but the require is still bundled

dazld21:10:03

let me give it another go

thheller21:10:08

yes, conditional requires are not possible

thheller21:10:17

(well they are but only with tricks)

dazld21:10:57

what’s the preloads thing you mentioned?

dazld21:10:15

heh sorry

Chris21:10:07

@thheller - thanks for confirming I wasn't crazy - do you think this is a CLJS or shadow/node issue? I'm new to CLJS and don't know where to start looking

dazld21:10:57

o.O cljs.pprint is 96KB!

thheller21:10:52

@chris547 it is a shadow-cljs issue. can't figure out what though.

Chris21:10:25

@thheller Okay, I'll revert to the previous version that I was using and see if it was working there to try to give a baseline

thheller22:10:11

the comment destroys it. no clue why though.

thheller22:10:21

/**
 * foo
 */
(function() {
  cljs.user.x = function cljs$user$x(a) {
    return a;
  };
  return new cljs.core.Var(
    function() {
      return cljs.user.x;
    },
    new cljs.core.Symbol("cljs.user", "x", "cljs.user/x", -156439873, null),
    cljs.core.PersistentHashMap.fromArrays(
      [
        new cljs.core.Keyword(null, "ns", "ns", 441598760),
        new cljs.core.Keyword(null, "name", "name", 1843675177),
        new cljs.core.Keyword(null, "file", "file", -1269645878),
        new cljs.core.Keyword(null, "end-column", "end-column", 1425389514),
        new cljs.core.Keyword(null, "source", "source", -433931539),
        new cljs.core.Keyword(null, "column", "column", 2078222095),
        new cljs.core.Keyword(null, "line", "line", 212345235),
        new cljs.core.Keyword(null, "end-line", "end-line", 1837326455),
        new cljs.core.Keyword(null, "arglists", "arglists", 1661989754),
        new cljs.core.Keyword(null, "doc", "doc", 1913296891),
        new cljs.core.Keyword(null, "test", "test", 577538877)
      ],
      [
        new cljs.core.Symbol(null, "cljs.user", "cljs.user", 877795071, null),
        new cljs.core.Symbol(null, "x", "x", -555367584, null),
        "cljs/user.cljs",
        8,
        "x",
        1,
        1,
        1,
        cljs.core.list(
          new cljs.core.PersistentVector(
            null,
            1,
            5,
            cljs.core.PersistentVector.EMPTY_NODE,
            [new cljs.core.Symbol(null, "a", "a", -482876059, null)],
            null
          )
        ),
        "foo",
        cljs.core.truth_(cljs.user.x) ? cljs.user.x.cljs$lang$test : null
      ]
    )
  );
})();

thheller22:10:32

thats the JS generated for (defn x "foo" [a] a)

thheller22:10:47

(function() {
  cljs.user.y = function cljs$user$y(a) {
    return a;
  };
  return new cljs.core.Var(
    function() {
      return cljs.user.y;
    },
    new cljs.core.Symbol("cljs.user", "y", "cljs.user/y", 558816894, null),
    cljs.core.PersistentHashMap.fromArrays(
      [
        new cljs.core.Keyword(null, "ns", "ns", 441598760),
        new cljs.core.Keyword(null, "name", "name", 1843675177),
        new cljs.core.Keyword(null, "file", "file", -1269645878),
        new cljs.core.Keyword(null, "end-column", "end-column", 1425389514),
        new cljs.core.Keyword(null, "source", "source", -433931539),
        new cljs.core.Keyword(null, "column", "column", 2078222095),
        new cljs.core.Keyword(null, "line", "line", 212345235),
        new cljs.core.Keyword(null, "end-line", "end-line", 1837326455),
        new cljs.core.Keyword(null, "arglists", "arglists", 1661989754),
        new cljs.core.Keyword(null, "doc", "doc", 1913296891),
        new cljs.core.Keyword(null, "test", "test", 577538877)
      ],
      [
        new cljs.core.Symbol(null, "cljs.user", "cljs.user", 877795071, null),
        new cljs.core.Symbol(null, "y", "y", -117328249, null),
        "cljs/user.cljs",
        8,
        "y",
        1,
        2,
        2,
        cljs.core.list(
          new cljs.core.PersistentVector(
            null,
            1,
            5,
            cljs.core.PersistentVector.EMPTY_NODE,
            [new cljs.core.Symbol(null, "a", "a", -482876059, null)],
            null
          )
        ),
        null,
        cljs.core.truth_(cljs.user.y) ? cljs.user.y.cljs$lang$test : null
      ]
    )
  );
})();

thheller22:10:53

thats (defn y [a] a)

thheller22:10:04

only difference I can see is the leading comment

thheller22:10:24

I suspect it is because of the newline (in the comment)

Chris22:10:22

yeah you're right - it was broken in 2.8.45 too

Chris22:10:30

how did you generate that JS like that?

thheller22:10:28

internal API magic 😉

thheller22:10:08

yeah it is definitely the commend breaking it. fun stuff.

👍 4
Chris22:10:25

there's teh 1,1,1 in the first one and the 1,2,2 in the second

Chris22:10:46

I just started working on a cljs node backend - but would this potentially break all code running in node (assuming it was documented with a docstring?)

thheller22:10:07

no, only stuff eval'd at the REPL directly

thheller22:10:53

but yeah I need to rethink the REPL code I think. thats what I get for trying to be clever I guess

thheller22:10:40

fix it but thats just another hack

thheller22:10:15

@chris547 should be fixed in 2.8.61

thheller22:10:57

rather dirty fix, will need to think a bit harder about an actual clean fix

Trevor22:10:21

So I just wanna check my understanding. shadow-cljs and it's ability to work with package.json essentially accomplishes the same thing you would get using cljsjs libs with :foreign-libs and :global-exports? I'm trying to figure out if I can find some way to get intellisense with material-ui and was thought maybe the cljsjs path would get me that, but from what I've looked at it looks like cljsjs is just another way to package js libraries into your clojurescript app. Is that right?

Trevor22:10:44

And is there a way with Spacemacs or Cursive that I could get some kind of autocomplete for material-ui? Something like

(ns your.namespace
  (:require [cljsjs.material-ui :as mui]))
(mui/Grid ...) 
or something

Chris22:10:41

@thheller - Awesome, thank you so much for the fix - I'll update and hopefully will get back to learning more cljs 🙂

thheller22:10:49

@trevor670 I'm not aware of aynthing that would get you autocomplete for JS deps

thheller22:10:37

not sure why you'd think cljsjs would change any of that

Trevor22:10:48

Yeah that's what I was worried about, when I stop and think about what that would mean I realize that would be a pretty dramatic accomplishment. It would have to like process typescript .d.ts files to generate all the function definitions and stuff

thheller22:10:55

its just a fact of life that there is no completion for that stuff

dpsutton22:10:56

There’s experimental support in CIDER for this with the recent clj-suitable completion package but there’s still some rough edges

thheller22:10:24

rough edges .. thats putting it mildly 😛

cider 4
Trevor22:10:25

I'm very new to clojurescript, I initially though cljsjs was something that was more involved (like cljs bindings or something) but I just realized that it's just automating config stuff

Trevor22:10:03

But based on what you said, definitely sounds like there isn't something I'm missing then! Wanna use material-ui I gotta just type it in

thheller22:10:07

yeah cljsjs just prepends javascript. doesn't process it in any way

Trevor22:10:38

And that's the main sell of shadow-cljs right? Now adding npm dependencies is as simple as updating a package.json instead of doing the other config in project.clj and deps.edn? It's way closer to normal js dev workflows?

thheller22:10:29

it is one of the features yes. wouldn't call it the main feature but it is one of them yes.

Trevor22:10:42

Yeah definitely does more than that! Thanks for all your work and answering my questions!

👍 4
thheller22:10:39

I know that IntelliJ ultimate actually supports JS completion for npm packages but I'm not sure if Cursive would be able to tap into that

thheller22:10:57

seems like a lot of work and probably not very reliable

Chris22:10:51

@thheller - looks like it worked! thanks again for being so responsive - your help today and in the past is one of the main reasons I don't bother even looking at anything other than shadow-cljs!

😎 8
✔️ 4