Fork me on GitHub
#clojurescript
<
2020-11-20
>
gekkou02:11:48

Trying to compile cljs into a single js file, but it is failing to pull npm dependencies into the file and eliminate dead code

gekkou02:11:33

I have an extern.js file that includes function names and have a shadow-cljs.edn but neither of them are bundling everything

thheller09:11:18

you did not provide enough information to help. which command did you run? what is the build config? which :target?

gekkou02:11:15

do I need webpack for this? seems like there would be a lot of redundant code would be packed which the closure compiler should eliminate

Braden Shepherdson02:11:55

so I have a lein CLJS project and want to depend on the JavaScript version of the protobuf runtime library. that's the NPM package google-protobuf, which ships Closure code that should fit nicely in CLJS code.

Braden Shepherdson02:11:44

but I'm not sure what's the most practical way to include that in a build.

greensponge08:11:47

Hello, I have a lein project using shadow-cljs, and it works great. I have one question regarding npm dependencies: Context: I started a CLJS prototype to evaluate for the NextCompanyProject™ by using Emacs, but now I also tried setting up IntelliJ with Cursive to see how that works (and to sell it more easily to devs in my team). Cursive and IntelliJ work pretty well out of the box in this case, I just have one minor annoyance, which leads me into the actual question. I tried a couple of things from shadow-cljs docs and Cursive docs, I for example generated a pom.xml file with lein pom, but I suppose this doesn't help for npm dependencies? Question: Is it possible, given the context above, to have npm dependencies not complain about not being able to resolve when using IntelliJ with Cursive? For reference, I would have something like this in the require:

["@material-ui/core" :as ui :refer (IconButton Fab)] ; ui, IconButton and Fab cannot be resolved in this example

cfleming09:11:43

Sadly, no, although I’m planning to fix this soon. I had hoped that this would make it into the next release but it’s looking like I will have to bump it. However in the next release there will be some changes so that at least it doesn’t shout at you.

👍 3
greensponge09:11:20

@U0567Q30W It actually helps me a lot just knowing something is in the pipeline and that I can stop trying to solve the problem. I haven't used Cursive for very long yet, but from what I've used it's working really well, thank you for your work!

cfleming10:11:53

No problem, I’m glad it’s helping!

Braden Shepherdson15:11:18

I can't find a good reference on what happens when I tell CLJS :compiler {:libs ["some/dir"]} - does it look for all .js files in that directory and take their goog.provide namespaces as gospel? does the folder structure have to match up a la Java? I'm getting /path/to/my/project/target/public/cljs-out/dev/somefile.js is not a relative path and I'm not sure what that's trying to say - the file does actually exist, though the path is certainly not relative.

Filipe Silva16:11:17

Heya, I think I've found a bug with js->clj. If you try to convert {"constructor": true} with js->clj, you get #object[Object [object Object]] instead of a map. Converting it with transit will get you a map though.

;; src/buggy_js_clj/core.cljs
(ns buggy-js-clj.core
  (:require [cognitect.transit :as t]))

(let [content #js {:constructor true}]
  (print "Parses correctly with js->clj?"
         (-> content
             (js->clj :keywordize-keys true)
             map?)
         ;; false
         )

  (print "Parses correctly with transit?"
         (->> content
              (js/JSON.stringify)
              (t/read (t/reader :json))
              map?)
         ;; true
         ))

;; deps.edn
{:deps {org.clojure/clojurescript {:mvn/version "1.10.764"}
        com.cognitect/transit-cljs {:mvn/version "0.8.264"}}}

;; running repro from cli
;; clj -m cljs.main --target node --output-to main.js -c buggy-js-clj.core && node main.js

thheller17:11:50

hey, js->clj has a long list of known issues that can't really be fixed without breaking others relying on certain behaviour. if you know you have only JSON data then it is advisable to stay away from js->clj completely.

thheller17:11:10

cljs.user=> (require '[shadow.json :as j])
nil
cljs.user=> (j/to-clj #js {:constructor true})
{:constructor true}

Filipe Silva16:11:13

ran into this because one of our JSON returns from an API suddenly stopped parsing via js/JSON.parse followed by js->clj, and narrowed it down to that key

Filipe Silva16:11:26

is the best place to report this sort of thing still the JIRA?

Alex Miller (Clojure team)16:11:55

unless you have a jira acct, in which case that's ok too

Filipe Silva16:11:55

I have a JIRA acc actually, might as well open it there in case I manage to submit a patch for it then

p-himik16:11:09

Given the Mike's comment, doesn't seem like it. In the case of js->clj the culprit is the way the type function is implemented.

Filipe Silva16:11:08

could you add that comment in the JIRA issue?

Filipe Silva16:11:25

sounds like it would be helpful for whoever ends up looking at the issue

p-himik16:11:35

I don't have an account. But I was able to find that out in ~15 seconds so I don't think that would be much of a help anyway. :)

p-himik16:11:47

The js->clj implementation is very straightforward.

Filipe Silva16:11:45

I'll add a comment there saying what you said, I found it helpful

👍 3
p-himik16:11:15

To be more concrete: (assert (true? (type #js {:constructor true}))).

Filipe Silva16:11:45

hmmm it looks like type should be checking it's a function

Filipe Silva16:11:30

so instead of

(defn type
  "Return x's constructor."
  [x]
  (when-not (nil? x)
    (.-constructor x)))
maybe it should be
(defn type
  "Return x's constructor."
  [x]
  (when (and (some? x)
             (fn? (.-constructor x)))
    (.-constructor x)))

p-himik16:11:38

Well, then you should expect that (js->clj #js {:constructor #()}) will still be broken.

Filipe Silva16:11:06

my expectation is that a js map containing the constructor key is converted into a cljs map... but to be honest it's not very clear to me how the full breadth of scenarios should behave

dnolen16:11:36

hrm I don't know about this

dnolen16:11:48

using constructor is problematic because JavaScript

dnolen16:11:05

but if you have solution that doesn't create more problems will take a look

dnolen16:11:20

but my first thought is don't do that

dnolen16:11:21

I think transit works here because it's data exchange only

Filipe Silva17:11:32

my concrete situation is that I have data stored in Firebase containing string keys chosen by users

Filipe Silva17:11:46

when downloading and parsing it, I run into this

dnolen17:11:14

if you look at js->clj you should see you have options

dnolen17:11:19

IEncodeClojure is a thing

dnolen17:11:29

you could extend to that on the fly for your stuff to avoid this

Filipe Silva17:11:20

I wasn't aware I had that option, it does sound good for my case

Filipe Silva17:11:24

thanks for bringing it up

dnolen17:11:43

yeah there needs to be trapdoor for some cases

dnolen17:11:47

I'm switching that issue to minor for now unless you discover for some reason that approach doesn't work for you

Filipe Silva17:11:17

wasn't really sure what the correct priority was, it defaulted to major so left that

dnolen17:11:46

which is fine, can always adjust priority later

Filipe Silva17:11:40

@dnolen I don't think that extending the IEncodeClojure protocol in that case works, because (extend-type true ...) throws a compilation error:

Unexpected error (ClassCastException) compiling at (REPL:1).
java.lang.Boolean cannot be cast to clojure.lang.Named

Filipe Silva17:11:26

but I think I can instead convert the JS data itself into map entries, falling into the previous cond

Filipe Silva17:11:28

(map-entry? x)
                (MapEntry. (thisfn (key x)) (thisfn (val x)) nil)

Filipe Silva17:11:03

either that or removing that entry and adding it later manually...

dnolen18:11:44

extend-type true isn't right

dnolen18:11:59

use (specify! ...)

dnolen18:11:06

on the value directly

dnolen18:11:27

specify is probably good enough too?

dnolen19:11:06

actually you do need specify! because your value isn't ICloneable

dnolen19:11:29

(defn fix-json [json] (specify! json IEncodeClojure ...))

dnolen19:11:59

(js->clj (fix-json json))

Filipe Silva20:11:08

thanks for the specify! pointer

Filipe Silva20:11:17

this is how it looks like now:

Filipe Silva20:11:19

(ns buggy-js-clj.core
  (:require 
   [clojure.string :as str]
   [goog.object :as gobject]))

(let [json #js {:constructor true}]
  
  (defn fix-json [json] 
    (specify! json IEncodeClojure 
              (-js->clj [x {:keys [keywordize-keys] :as options}]
                        (let [keyfn (if keywordize-keys keyword str)]
                          (persistent!
                           (reduce 
                             (fn [r k] (assoc! r (keyfn k) (js->clj (gobject/get x k))))
                             (transient {}) 
                             (remove #(str/starts-with? % "cljs$core$IEncodeClojure$")
                                     (js-keys x))))))))

  (print (-> (fix-json json)
             (js->clj))))

Filipe Silva20:11:51

had to remove keys starting with "cljs$core$IEncodeClojure$" because specify adds them

Filipe Silva20:11:00

{constructor true, cljs$core$IEncodeClojure$ {}, cljs$core$IEncodeClojure$_js__GT_clj$arity$2 #object[Function]}

p-himik04:11:54

At this point, it feels like just converting your JSON to CLJ "manually" (i.e. by copying the code of js->clj and altering it) would be better. It would be much simpler and wouldn't rely on some internal knowledge, which is exactly what "cljs$core$IEncodeClojure$" is.

galdre19:11:21

I’ve been excited to finally go through a hello-world exercise using the CLJS webpack guide. I’ve got a question about managing npm dependencies. It might be a dumb one: I’m don’t really know what I’m doing.

galdre19:11:24

My 10-line hello world just uses reagent, and my deps.edn just uses clojure, clojurescript, and reagent. I’m using a very simple build.edn with :target :bundle, using webpack, pretty much ripped right out of the guide. The question: :optimizations :advanced results in a broken app unless deps.edn excludes [cljsjs/react cljsjs/react-dom] from reagent and I install them in local node modules. I’m trying to understand why this is, and what the ramifications are for maintaining a larger project. Any guidance, even just a LMGTFY with the right search terms, would be much appreciated! I don’t know what I don’t know.

dnolen20:11:28

@filipematossilva (specify! (. json -prototype) ...) to avoid that issue

Filipe Silva20:11:29

prototype of #js {} is undefined

Filipe Silva20:11:14

I thought it was because of #js {:constructor true} but it's just object literal in general

dnolen20:11:49

you could add the prototype then do it

dnolen20:11:53

all this can go in a helper fn

neilyio23:11:50

I'm having a problem with Clojurescript, spec, and spec.test.alpha. Anytime I use spec/fspec with spec.test.alpha/instrument, I'm getting an error: Var clojure.test.check.properties/for-all* does not exist, clojure.test.check.properties never required. Actually putting clojure.test.check.properties in the ns require causes the REPL to hang.

neilyio23:11:52

I mistakenly made an https://github.com/bhauman/figwheel-main/issues/272 about this before I realized this happens with clj -m cljs.main. I have more info and a minimal reproduction at that issue if you'd like to take a look.