Fork me on GitHub
#shadow-cljs
<
2019-01-18
>
lilactown00:01:55

I ran into a really bizarre thing today

lilactown00:01:32

evaluating a module that contained code instantiating this type:

(deftype MockRemote [query-map error-map]
  dp/IDataRemote
  (query! [this query config]
    (-> (p/delay 100)
        (p/then
         (fn []
           (when (contains? error-map query)
             (throw (error-map query)))
           (if (contains? query-map query)
             (query-map query)
             (query-map :mock/default)))))))
completely failed in a release build

lilactown00:01:44

the offending line: (p/delay 100)

lilactown00:01:03

(p is promesa.core)

lilactown00:01:03

the error at runtime:

Error: 1
    at modulemanager.js:588
    at $goog$module$ModuleLoadCallback$$.execute (moduleloadcallback.js:60)
    at $JSCompiler_StaticMethods_callCallbacks_$$ (moduleinfo.js:324)
    at $JSCompiler_StaticMethods_onError$$ (moduleinfo.js:287)
    at $JSCompiler_StaticMethods_dispatchModuleLoadFailed_$$ (modulemanager.js:1261)
    at $JSCompiler_StaticMethods_requeueBatchOrDispatchFailure_$$ (modulemanager.js:1193)
    at $goog$module$ModuleManager$$.$APP.$JSCompiler_prototypeAlias$$.$handleLoadError_$ (modulemanager.js:1146)
    at $JSCompiler_StaticMethods_evaluateCode_$$ (moduleloader.js:373)
    at $goog$module$ModuleLoader$$.$APP.$JSCompiler_prototypeAlias$$.$handleSuccess_$ (moduleloader.js:256)
    at $JSCompiler_StaticMethods_fireListeners$$ (eventtarget.js:284)
    at $goog$net$BulkLoader$$.$APP.$JSCompiler_prototypeAlias$$.dispatchEvent (eventtarget.js:381)
    at $goog$net$BulkLoader$$.$APP.$JSCompiler_prototypeAlias$$.$handleSuccess_$ (bulkloader.js:168)
    at $goog$net$BulkLoader$$.$APP.$JSCompiler_prototypeAlias$$.$handleEvent_$ (bulkloader.js:118)
    at $JSCompiler_StaticMethods_fireListeners$$ (eventtarget.js:284)
    at $goog$net$XhrIo$$.$APP.$JSCompiler_prototypeAlias$$.dispatchEvent (eventtarget.js:381)
    at $JSCompiler_StaticMethods_onReadyStateChangeHelper_$$ (xhrio.js:867)

lilactown00:01:36

changing it to

(p/promise
         (fn [resolve _]
           (js/setTimeout #(resolve "resolved") 100)))
resolved the issue

joelsanchez09:01:08

say I want to distribute a cljs component through clojars. the component has some css and depends on some npm modules. - how do I indicate that the component depends on certain npm modules? - how do I deal with css? I know I can put it under resources, but is there a better way? thx

thheller09:01:20

for the deps you can includes a deps.cljs with {:npm-deps {"the-dep" "version"}}

👍 5
thheller09:01:06

css is highly dependent on what users use to build their css. don't think there is a good way for this currently. css support is still in the planning stages for shadow-cljs.

joelsanchez09:01:54

thank you for the npm-deps tip. regarding the css, re-com is just bundling it inside resources/public/assets/css and I think that's good enough

thheller10:01:55

no thats bad

thheller10:01:07

well the path is bad imho

thheller10:01:40

but I guess it works when using a using a java webserver serving the public resources

joelsanchez10:01:25

what do you suggest? the main user of this "component" is me. I'd like to @import it in my scss code but I won't be able to if it's a resource

joelsanchez10:01:56

my plan was to simply require it in the HTML code along the css file compiled from scss

thheller10:01:09

yeah need something to copy it to the filesystem so sass can find it

joelsanchez10:01:28

I already have code to run the scss compiler from clojure, more code to move files around won't bother me much but, regarding the path, is it a good idea to do this? resources/{groupId}/{artifactId}/{version}/main.css?

joelsanchez10:01:42

or without the version

thheller10:01:23

nah without version. I would just namespace it normally. resources/your/lib/foo.css

👍 5
cmal11:01:01

Hi, I split my code using the method described on https://shadow-cljs.github.io/docs/UsersGuide.html#_modules , but :advanced optimization errors Uncaught ReferenceError: Oe is not defined. I tried :simple optimization, no error occurs. Is that related to the code splitting during :advanced?

thheller11:01:51

@cmal try :advanced with shadow-cljs release your-build --pseudo-names to find out what Oe is. I always use :advanced with :modules so it should work

thheller11:01:13

are you doing anything with the code after shadow-cljs is done with it? any other processing like adding a wrapper or so?

thheller11:01:37

and do you load modules in the correct order?

cmal11:01:02

I added prepend and append

thheller11:01:26

do they create a function wrapper?

cmal11:01:32

wrap those in a (function(){...}.call(this); and call a exported method

thheller11:01:43

ok don't do that. use :output-wrapper true

thheller11:01:56

that does that and ensures things work correctly

cmal11:01:45

ok. But can I append a method call?

thheller11:01:59

:modules just assumes everything is in a global scope by default

thheller11:01:04

if you wrap it that won't be true

cmal11:01:20

Thank you very much. It works perfectly now!

hoopes17:01:41

hi, is it possible to shadow-cljs release some-build and get source maps?

justinbarclay17:01:24

This is what I use

:app
  {:target :browser
   :output-dir "./min"
   :compiler-options {:source-map true}
   :modules {:application {:init-fn app.core/run}}}}}

thheller17:01:06

correct. :compiler-options {:source-map true}

theasp17:01:46

Hello, is there an easy way to use libs that use :foreign-libs in deps.cljs?

thheller20:01:55

:foreign-libs are intentionally not support at all. so no there is no way to use it with shadow-cljs. https://shadow-cljs.github.io/docs/UsersGuide.html#cljsjs

hoopes18:01:41

@emoarmy thanks very much!

Pavel Klavík18:01:00

Hi, I am building a full stack web app and currently using lein and cljsbuild for compilation. (Based on reframe lein template.) I am thinking of switching to shadowcljs for npm support. What is the workflow for web server compilation, to produce uberjar?

lilactown18:01:08

pretty much the same as cljsbuild

lilactown18:01:02

if you're deploying an uberjar with your front-end CLJS compiled code inside of it (e.g. in resources), then you'll run the CLJS compilation step first. Then create the uberjar. Then deploy the uberjar

Pavel Klavík18:01:46

is it recommended to use shadow-cljs from lein project file?

lilactown18:01:07

sure! shadow-cljs works wherever you need CLJS

lilactown18:01:40

what a lot of people do is put their CLJ (Java) dependencies in their project.clj, and their CLJS dependencies in their shadow-cljs.edn

Pavel Klavík18:01:40

I see, do you have an example of some simple project so I could take a look how is everything set up?

alexandergunnarson18:01:18

I wonder — could I use React Native as the host for shadow-cljs? https://code.thheller.com/blog/shadow-cljs/2017/10/14/bootstrap-support.html says that First you need a “host” build which will be your “app” (currently limited to :browser builds).

alexandergunnarson18:01:53

Basically what I have is some code that needs to be run in a bootstrapped environment that I want to be able to be run in both simple and advanced compilation modes in both the browser and in React Native. Also in simple mode I'd like it to be able to be auto-reloadable like Figwheel. I know shadow-cljs can do most of this but the things in question are 1) advanced compilation mode for bootstrapped and 2) bootstrap + React Native

alexandergunnarson18:01:21

A big ask, I know 😄 If there's code to be written I'd be happy to lend a hand. But if there's an out-of-the-box solution I'll take that

lilactown19:01:56

so I know off the bat that advanced compilation + bootstrapped don't mesh

lilactown19:01:51

I'm not sure if anyone's tried to put bootstrapped CLJS in React Native before

lilactown19:01:53

could be interesting

alexandergunnarson19:01:56

Hmm, that’s disappointing @lilactown… I had figured that if I used google-closure-compiler-js or rather the JS version of google-closure-compiler it might work

alexandergunnarson19:01:35

Basically what I’m needing is for the source language to be the same as the compilation language. This is because I’m trying to create a type system that is able to leverage arbitrary spec-like predicates by evaluating them at compile time to elide as many runtime checks as possible.

alexandergunnarson19:01:07

I assume that requires a bootstrap build

lilactown19:01:13

I have no idea what you're talking about lol

alexandergunnarson19:01:36

So like (defmacro abc [] (do-this-in-cljs-not-clj)) e.g. (defmacro abc [x] (js/parseInt x))

lilactown19:01:28

still not clear to me. this sounds like what reader conditionals are for

alexandergunnarson19:01:52

Currently when compiling CLJS, any code inside macros is evaluated in CLJ

alexandergunnarson19:01:03

Only the code outputted from the macro is evaluated in CLJS

alexandergunnarson19:01:46

So like in CLJS if you did (defn abc [x] (js/parseInt x)) that’s fine, but (defmacro abc [x] (js/parseInt x)) it would fail off the bat since you can’t defmacro in CLJS without bootstrap anyway

alexandergunnarson19:01:55

Ehh never mind, maybe I answered my own question

alexandergunnarson19:01:29

What are you surprised about haha?

lilactown19:01:11

I'm still trying to figure out what your question is 😅 and what the answer is

alexandergunnarson19:01:20

Never mind haha I think I answered it

lilactown19:01:34

I think if I'm understanding correctly, you actually want shadow-cljs, the tool to run in CLJS?

alexandergunnarson19:01:44

No, I don’t think so

lilactown19:01:04

because you want to evaluate these CLJS forms at compile-time, not at run-time?

lilactown19:01:29

shadow-cljs is a JVM program, so it's always going to evaluate your macros etc. at compile-time in a JVM context

lilactown19:01:04

I haven't explored this at all, but maybe something like lumo, which is a completely self-hosted compiler, could do what you're looking for

lilactown19:01:42

shadow-cljs bootstrap' support is simply going to push those macro expansions to runtime, which it doesn't sound like is what you want?

alexandergunnarson19:01:52

Ohh okay yeah then perhaps shadow-cljs is not what I’m looking for

alexandergunnarson19:01:06

I do want to evaluate the CLJS forms at compile-time

alexandergunnarson19:01:41

Though I’m not sure Lumo supports useful things like auto-reloading etc.

alexandergunnarson19:01:45

Might have to hack this together

lilactown19:01:05

I'm curious why you need the compile-time evaluation context to be the same as the runtime?

alexandergunnarson19:01:28

I’m trying to build a type system. An example might be this: (t/defn append! [s #?(:clj (t/isa? java.lang.StringBuilder) :cljs (t/isa? js/StringBuffer)), x string?] #?(:clj (.append s x) :cljs (.join s x)))

alexandergunnarson19:01:51

So a typed function that takes a StringBuilder in CLJ or js/StringBuffer in CLJS

alexandergunnarson19:01:31

The type system will need to know at compile time whether callers of append! are indeed providing a js/StringBuffer or not

alexandergunnarson19:01:15

So like (append! 123 "abc") would fail whereas (append! (js/StringBuffer.) "abc") would succeed, both at compile time

lilactown19:01:23

how will your CLJS program know at compile time it's providing a StringBuffer?

alexandergunnarson19:01:51

If you wrap it in a macro, e.g. let’s call it t/dotyped, it can analyze the forms and see in (js/StringBuffer.) that it’s a constructor (`new` form), which returns a class of whatever the first argument is (in this case js/StringBuffer). The compiler can then evaluate the symbol 'js/StringBuffer to the actual class

alexandergunnarson19:01:20

Then do an inheritance (well, equality) check of the evaluated symbol against the actual js/StringBuffer

alexandergunnarson19:01:34

I’ve got it to work with something similar in CLJ (and much more complex things)

alexandergunnarson19:01:43

I don’t see why it couldn’t in CLJS if the compiler were written in JS/CLJS

lilactown19:01:16

what about this:

(def str-buf #(js/StringBuffer.))

(append! (str-buf) "abc")
?

alexandergunnarson19:01:16

Good point — it would only know the type of str-buf which is a js/Function; it wouldn’t have any knowledge about the internals of str-buf

alexandergunnarson19:01:22

This is why I would use t/fn in that case

alexandergunnarson19:01:38

So like (def str-buf (t/fn [] (js/StringBuffer.)))

alexandergunnarson19:01:58

In that case the compiler would know what the return type of str-buf is

lilactown19:01:24

so, I haven't done too many things w.r.t. type systems / prog langs

alexandergunnarson19:01:29

Because the type of str-buf would be (t/ftype [> (t/isa? js/StringBuffer)]) i.e. a function that takes no args and returns a js/StringBuffer, when str-buf is called, (dotyped (append! (str-buf) "abc") would know the type

lilactown19:01:42

but it sounds like at some point you're going to have to eval the entire program with this strategy

alexandergunnarson19:01:01

That’s what macros do anyway in CLJ

alexandergunnarson19:01:11

Yes they emit code but then that code is evaluated in CLJ

lilactown19:01:20

well, no. macros operate on clojure code

lilactown19:01:29

it doesn't evaluate the users code

👍 5
alexandergunnarson19:01:47

I’m speaking only about compile time

lilactown19:01:40

why does it need to be able to evaluate js/StringBuffer at compile time, though?

lilactown19:01:44

why not look for the symbol?

alexandergunnarson19:01:00

That is definitely one strategy

alexandergunnarson19:01:09

But I want this to work for arbitrary predicates

alexandergunnarson19:01:23

Not just concrete classes / interfaces

lilactown19:01:59

I think you're walking into a quagmire 😉

alexandergunnarson19:01:11

The type system does work in Clojure 🙂

lilactown19:01:47

evaluating arbitrary predicates at compile time is an open area of research. see experimental languages like Idris that implement dependent types

lilactown19:01:08

but I'm not going to dissuade you! best of luck

alexandergunnarson19:01:30

For instance (t/defn f [m (t/and t/map? my-arbitrary-predicate)] ....), then (t/dotyped (f {:a 1 :b 2})) can be checked at compile time

alexandergunnarson19:01:19

Granted, in the case of (t/fn [m t/map?] (f m)), my-arbitrary-predicate will have to be checked at runtime

alexandergunnarson19:01:52

So there are quagmire-y things to be sure

lilactown19:01:21

and what happens if I'm running in a context that doesn't have js/StringBuffer?

alexandergunnarson19:01:37

With something like “cannot resolve symbol”

alexandergunnarson19:01:02

No at compile time (assuming a CLJS-in-CLJS compiler)

alexandergunnarson19:01:40

Of course maybe the runtime has a different environment than the compile-time environment i.e. runtime has js/StringBuffer, compile-time doesn’t

alexandergunnarson19:01:45

But I feel like that point you’re on your own haha

lilactown19:01:58

well that happens all the time on the web 🙂

alexandergunnarson19:01:37

I mean yeah this is why if you’re using the type system on things that may or may not be there you might have to polyfill as a fallback

alexandergunnarson19:01:43

Or just don’t use those things that may or may not be there

alexandergunnarson19:01:01

There may be other strategies of course, like not using the type system for those things, or falling back to runtime type checking in those cases

lilactown19:01:56

i guess my point is that I don't know what you buy for moving your type system to run in CLJS at compile-time

alexandergunnarson19:01:09

Perhaps the effort is more than it’s worth, I don’t know

alexandergunnarson19:01:13

But it seems like more of a pain to support an arbitrary predicate that uses some CLJS-specific thing but can only be symbolically analyzed (i.e. does the symbol js/StringBuffer equal the symbol js/StringBuffer?)

alexandergunnarson20:01:31

For instance if you did (t/defn f [x my-arbitrary-predicate] ...) and (t/dotyped (f "asd")) you could run my-arbitrary-predicate in CLJ at compile-time to figure out type-satisfaction, whereas in CLJS you’d always have to resort to runtime type checking

alexandergunnarson20:01:53

Maybe that’s a small price to pay to stay within the non-bootstrapped ecosystem, not sure

alexandergunnarson20:01:52

Perhaps what I’ll do for CLJS is only do symbolic analysis falling back to runtime checking to begin with. Then for those use cases where that isn’t good enough, weigh that against switching to bootstrapped CLJS. I can’t think up all the cases ahead of time, to be fair

alexandergunnarson20:01:02

Anyway thanks for your help!

lilactown20:01:38

but I think that the fact that js/StringBuffer in your type-checking program is the same as js/StringBuffer at runtime is only accidental

lilactown20:01:03

it's equivalent in sound-ness as checking the symbol

lilactown20:01:37

anyway, fun chat 🙂 I'll leave you be! Sorry for sticking my nose in

alexandergunnarson20:01:19

Yeah fun chat! 🙂 If you want to keep talking about it that’s fine! I just didn’t want to be a pain with my counterarguments haha

alexandergunnarson20:01:42

'js/StringBuffer' in your type-checking program is the same as 'js/StringBuffer' at runtime is only accidental → I get what you’re saying. For instance js/StringBuffer might mutate or something, or be completely different in compilation than at runtime

alexandergunnarson20:01:23

Clojure doesn’t have any of these (ahem) fun little intricacies… it would be nice for CLJS to, well, play just as nicely as CLJ does haha

lilactown20:01:19

well, Clojure does too

alexandergunnarson20:01:38

Clojure definitely has its own idiosyncrasies

alexandergunnarson20:01:03

But none so frustrating as the compile-time platform being (completely — not just like V8 vs SpiderMonkey) different than the runtime platform

lilactown20:01:04

host interop is kind of easiest thing to point at, but even something like:

(def a 1)

;; nested in some other code
   .... (def a "foo")

(defn inc-a []
  (+ a 1))

alexandergunnarson20:01:31

;; nested in some other code
   .... (def a "foo")
My answer is just don’t do that haha

lilactown20:01:56

what you end up doing is either using static analysis at a best guess (walking the AST, checking symbols, etc.), or you have to evaluate the persons entire program

alexandergunnarson20:01:31

You can find out a surprising amount of information — no “best guess” required — but yes if you look at the whole program, and evaluate key parts of it

lilactown20:01:33

being able to evaluate (def a 1) and resolve that to a Number or java.lang.Integer doesn't buy you any more guarantees

alexandergunnarson20:01:22

If you did (t/def a 1) then the type of a would be (t/value 1) which is a lot more useful than Number or java.lang.Integer

alexandergunnarson20:01:35

I guess you could also just do a normal def and it would resolve from the environment

alexandergunnarson20:01:59

But yeah you would have to evaluate the whole program

alexandergunnarson20:01:05

Anything that’s top-level anyway — anything that the (CLJ) compiler would normally evaluate

lilactown20:01:40

and you'll have to load any dependencies at compile-time as well

alexandergunnarson20:01:50

That’s already done in CLJ though, no?

lilactown20:01:01

it doesn't evaluate the dependencies

alexandergunnarson20:01:20

Perhaps my understanding of CLJ compilation is flawed but I would be surprised

alexandergunnarson20:01:22

That’s interesting to me what you say

alexandergunnarson20:01:11

Like if ns A depends on ns B which is not in your source code (e.g. let’s say it’s in a jar on the classpath imported via Leiningen), my understanding was that the compiler evaluates ns A, and in order to do that, has to evaluate ns B

alexandergunnarson20:01:56

Of course if it’s a Java class then it just loads the class (but may not necessarily use it in such a way that its static block is run as in Class/forName)

lilactown20:01:49

so this gets murky because CLJ isn't a compiled language, and CLJS is

alexandergunnarson20:01:12

Or as some say, “the bytecode is the .js file”

lilactown20:01:19

I'm not sure how CLJ evaluation or AOT works

lilactown20:01:26

under the hood

alexandergunnarson20:01:01

For CLJS compilation my understanding is that it works kind of like you seem to be getting at — it doesn’t actually evaluate the code, but rather analyzes it just enough (macroexpanding in the process) to generate JS code

alexandergunnarson20:01:11

Yeah so I’m suggesting that it would be much easier to enforce the contracts of the type system if the code were actually able to be evaluated, just as CLJ evaluates it

alexandergunnarson20:01:29

So yeah Lumo might be what I’m looking for

alexandergunnarson20:01:33

Like (t/defn [x my-arbitrary-predicate] ...) requires that my-arbitrary-predicate will have had to have been evaluated at compile time, not just symbolically analyzed, to truly enforce it

alexandergunnarson20:01:50

CLJ plays nicely with that; CLJS does not (barring e.g. Lumo)

alexandergunnarson20:01:44

So perhaps I’m misunderstanding what bootstrapping actually does; I thought [bootstrapping] = [CLJS in CLJS] = [CLJS compiled (well, transpiled) to JS via a CLJS compiler]

alexandergunnarson20:01:01

Or is bootstrapping just CLJS compiled as usual by CLJ, but provided with a compiler such that cljs.core/eval works at runtime?

lilactown20:01:13

"self-hosting" is what you're looking for

alexandergunnarson20:01:43

Ohhh so self-hosting ≠ bootstrapping?

lilactown20:01:49

they are related, but what you want is a self-hosted compiler

alexandergunnarson20:01:04

Thanks so much for the clarification haha this explains quite a few things