Fork me on GitHub
#clojurescript
<
2020-06-22
>
pwalsh05:06:25

Hi. I would like to use advanced optimization on my code, but, I want to protect a select number of function names from being modified. These are function names in my code, not in 3rd party libraries. Is this possible? Use case: I am writing a bunch of apps script ( https://developers.google.com/apps-script/ ) and want to start doing it in clojurescript. I have everything working from end to end but due to advanced optimization I have spreadsheet functions like =C() instead of =MYEASIERTOREMEMBERNAME() . There is an approach here that uses an extern javascript file for the interface https://lambdaisland.com/blog/2016-10-01-clojurescript-and-google-apps-script - I could do that, but it would be completely sidestepped if I could mark some functions for no renaming. I am using Shadow CLJS right now but I am happy to use any approach. ( and, I have tried to use the externs/<your-build>.txt approach in Shadow to declare externs but it doesn't have the results I was expecting - perhaps I am missing something there https://shadow-cljs.github.io/docs/UsersGuide.html#_simplified_externs ) Any pointers appreciated.

p-himik05:06:30

Have you tried setting the ^:export metadata on such functions?

p-himik05:06:24

Like in that LambdaIsland article:

(defn ^:export create-menu []
  ...)
The article itself talks about "the ^:externs metadata" - that's probably a typo.

dpsutton05:06:26

i believe that's the right answer but i can't find any documentation about it

dpsutton05:06:46

https://shadow-cljs.github.io/docs/UsersGuide.html#_working_with_optimizations from shadow users guide but not finding anything on the cljs side

pwalsh05:06:04

yes I set export. that does not prevent function renaming

dpsutton05:06:54

@U3HP1K63A what is your function named? there is munging still going on

p-himik05:06:50

Yeah, a function my-fn in a namespace my-ns will be available as my__fn.

pwalsh05:06:51

export AFAIK puts a name on a global object like window['original-name'] = munged-name That does not work in an apps script context. Apps script needs to see function definitions like function original-name {}

pwalsh05:06:58

so specifically what I need is (defn my-name []) always outputs function my-name {}

p-himik05:06:38

Ah, in this case the approach with an extra JS file outlined in the LambdaIsland article seems like the only solution. That's how I would do it. But I'm not an expert in compilation, so I may be wrong.

dpsutton05:06:10

yeah it seems the output will always look like the following:

foo.foo_with_export = (function foo$foo_with_export(){
return null;
});
goog.exportSymbol('foo.foo_with_export', foo.foo_with_export);

pwalsh06:06:00

ok got it. I'll stick with the approach in the lambdaisland article. thx.

Vaibhav Domadia07:06:59

i am getting this error, can anyone help me with this

thheller08:06:15

@vjdomadia google suggests its a disk error? google the "cyclic redunancy check" error

gunar08:06:28

Hi. My name is Gunar. I'd like to contribute with the ecosystem. Do you know of some impactful projects that are in need of a hand or two? Thanks in advance!

👍 12
Harshana14:06:29

Hey all! I want to get all checkboxes by Name "checkbox", I'm using the cljs-js interop for that : def all-checkboxes (. js/document getElementsByName "checkbox") ; I see it returns a NodeList. How do I iterate through this and get only those checkboxes that are checked? A for-loop doesn't seem to work here. Also, I came across clojure-interop/cljs-web-api How to use this in my cljs file? Thanks in advance!

souenzzo14:06:04

(seq all-checkboxes) do not work?

noisesmith14:06:18

for already implicitly calls seq

Derek14:06:18

I think you should be using (array-seq all-checkboxes)

💯 3
Harshana14:06:20

Yes I did try (array-seq all-checkboxes) . When I gave (nth all-checkboxes 0) , it gives the element as in <input type="checkbox" id=.. value=..> .

noisesmith14:06:14

prim-seq explicitly says it supports NodeList https://cljs.github.io/api/cljs.core/prim-seq

Harshana14:06:33

I get an IndexedSeq now. But giving (`nth .. 0)` still gives <input type="checkbox" id=.. value=..>

noisesmith14:06:27

once you have a seq, you can use filter or equivalently use a :when clause in a for comprehension

👍 3
noisesmith14:06:02

for isn't a loop and only works on inputs that are seqable?

daniel.spaniel14:06:44

is there an official clojurescript version of resolve? I am trying to resolve keywords like :my.namespace/my-function to the function .. for clojure i can do this

(-> my-keyword-fn symbol resolve)

daniel.spaniel14:06:54

but can't get away with that in cljs

thheller15:06:13

@dansudol not supported in CLJS. there are no vars at runtime.

daniel.spaniel15:06:08

eww .. ok .. thanks Thomas

dvingo16:06:28

is there a way to have a similar effect as import-vars from potemkin in clojurescript?

noisesmith16:06:37

there are no vars at runtime

dvingo16:06:21

so not at macroexpansion time either?

noisesmith16:06:29

there are while expanding macros afaik, but import-vars is mostly about keeping two vars in sync

noisesmith16:06:04

you can use def otherwise

lilactown16:06:09

you would have to hook into the CLJS analyzer and find all defs

lilactown16:06:26

then re-`def` them in the namespace you want

lilactown16:06:51

it sounds very finnicky. you would have to worry about build caching

noisesmith16:06:59

or just you know call (def foo other-ns/foo)

☝️ 3
noisesmith16:06:39

import-vars fixes problems with that def that cljs ensures you won't see

noisesmith16:06:05

(mostly... I guess there's the issue of hot-loading the target ns...)

noisesmith16:06:58

yeah, hot-loads will cramp your style with that

lilactown16:06:02

by default CLJS tooling will at least reload immediate dependents. so if you are importing defs from bar into foo, then changes to bar will trigger a reload of foo

lilactown16:06:53

however, it will probably hit the cache for the compilation of foo since it hasn’t changed

lilactown16:06:29

which is why (def foo bar/foo) will reload fine, but if you have a macro like:

(ns foo
  (:require [bar]))

(import-defs 'bar)
and you add a new def to bar, it probably won’t pick it up because foo won’t have changes -> won’t re-compile and re-run the macro

lilactown16:06:07

so your macro needs some way of busting the cache.

dvingo17:06:36

thanks for the info, I think it's simplest to just pile up a bunch of defs

jjttjj18:06:49

I have a massive data structure in an atom, and have a map containing that atom as a value, and am passing that map to a number of functions. This seems to slow things down considerably, even if none of the functions actually use that value in the map. Does this make sense?

phronmophobic18:06:12

that seems very odd. are any of the functions doing some sort of memoization?

jjttjj18:06:24

One of them is resetting the value of a seperate atom to contain that map, I guess that might be doing it?

jjttjj18:06:23

Or should the structure still be shared between both atoms?

phronmophobic18:06:33

can you confirm that the atom isn't being used somehow by instead passing:

(reify IDeref
    (-deref [_] (throw (js/Error. "not supposed to happen")))

phronmophobic18:06:19

that doesn't cover all ways an atom could be used, but might catch some

phronmophobic18:06:47

the only other thing I could think to do is have function that loops while calling the functions you're interested in and running the profiler to see what turns up

jjttjj18:06:09

ok yeah I was accidentally rendering the whole printed map somewhere, woops!, thanks for the help though

jjttjj18:06:18

Hmmm yeah doesn't seem to be that. Trying to make a minimal case now

kkruit18:06:29

Hey all, I'm using the CLI tool for my current project and I'm trying to install and include a npm library I've read it's possible to do via :npm-deps but i'm not entirely sure where that needs to go. Would it go in the deps.edn or maybe in the prod and dev.clj files when I do the api/build? Would this be a question for tools-deps channel?

kkruit18:06:28

Use case is I'm developing an application that has to run in a QtWebkit browser that dosen't support ES6 so I'm wanting to use core-js to polyfill ES6 in order to use react/reagent. Am I going about this the wrong way?

jjttjj20:06:06

I have a bunch of different components which can be rendered as dom objects. When designing a system for these to interact, is there a clear winner between these two approaches: 1. Renderability as a protocol

(defn codemirror-element
  "Given a codemirror object, return a codemirror element"
  [codemirror-object]
  (.-parentElement (.getScrollerElement codemirror-object)))

(defprotocol IRender
  (render [this]))

(specify! (CodeMirror.)
  IRender
  (render [this] (codemirror-element this)))
2. a "renderable" thing as an attribute in a map:
{::cm       codemirror-object
 ::dom/node (codemirror-element codemirror-object)}
I realize this probably depends on a lot of other factors in the system. But with no further info is there a clear winner between these two?

phronmophobic20:06:45

just between these two, there is a difference of timing. not for this specific example, but in a general sense, 1) renders when the caller decides based on the data at that time 2) the render happens ahead of time. this can make a difference even for the case of a non mutable renderable thing

jjttjj20:06:49

Oh yeah that make sense

phronmophobic20:06:00

i'm also working on some ui design stuff. currently, all elements are pure data, but there are some cases where I'd like to eventually allow the embedding of mutable ui components (eg. code mirror, video, webviews, etc). Ideally, I'd like a design that can turn these mutable embedded components into values, but I haven't quite figured out all the details yet.

phronmophobic20:06:03

how does option 2 handle equality?

phronmophobic20:06:46

it seems like having a map with mutable stuff inside could break equality semantics in a subtle way

phronmophobic20:06:12

for that reason, option 1 might avoid some of those issues?

jjttjj20:06:27

wouldn't an element always jsut = itself?

jjttjj20:06:03

My thinking was sort of that the mutable node would be controlled through the ::cm key

phronmophobic20:06:16

right, so when the state of code mirror changes, you can't use = to find out if the value has changed

phronmophobic20:06:58

and you can't do any caching using that ui component as part of the key

jjttjj20:06:29

Oh yeah, true. I was think of, as I add features to associate other keys in the map, such as ::clean-save-state? that could be an atom keeping a boolean, which would be set by a wrapping process, and I could build up features this way by just adding things to the map

jjttjj20:06:46

but this could be done with the protocol approach as well

phronmophobic20:06:59

yea, the two approaches seem pretty similar. I guess some of the differences are:

;; implies computation is being done here
;; can delay computation of dom nodes until they're actually needed
(render elem)
;; computed ahead of time
;; requires all views to precompute dom nodes,
;; even if this element isn't always attached to the DOM
(::dom/node elem)

jjttjj20:06:06

Yeah I'm leaning towards the protocol appoach now

phronmophobic20:06:29

so to me, approach 2 feels like it belongs in the part of the code that is only modifying the DOM. approach 1 feels like it's a protocol that a user could extend to provide a way for their data to be visualized

jjttjj20:06:24

Yes, I've been dabbling with that

👍 3
phronmophobic20:06:15

fwiw, I think approach 2 is becoming more popular, but I haven't tried it in any real application, so I'm not sure what benefits I might be missing there

jjttjj20:06:22

Yeah I guess that's really what I'm trying to think about: does ::dom/node make sense in the context of the rising emphases on "gloablly namespaced attributes", ie with specs. But I'm starting to think it doesn't because ::dom/node doesn't really make sense globally as something that can be provided without context

jjttjj20:06:22

Like once I start to want to compose the things,I think render looks better:

(defn editor [{:keys [modebar?]}]
  (let [cm (codemirror m)]
    (reify IRender
      (render [this]
        (if modebar?
          (div
            (render cm)
            (render (modebar cm)))
          (render cm))))))


(defn editor [{:keys [modebar?]}]
  (let [cm (codemirror m)]
    (if modebar?
      {::dom/node
       (div
         (::dom/node cm)
         (::dom/node (modebar cm)))}
      cm)))

phronmophobic20:06:55

is there any way to achieve:

(defn editor [{:keys [modebar?]}]
  (let [cm (codemirror m)]
    (reify IRender
      (render [this]
        (if modebar?
          (div
            cm
            (modebar cm))
          cm)))))

phronmophobic20:06:08

allowing div to receive any renderable

jjttjj20:06:45

Yes actually that's definitely better 🙂

phronmophobic20:06:28

further simplifying:

(defn editor [{:keys [modebar?]}]
  (let [cm (codemirror m)]
    (if modebar?
      (div
       cm
       (modebar cm))
      cm)))
      cm)))))

👍 3
jjttjj20:06:48

I think I will want other data/methods associated with the editor though and will probably keep it a map with render set via specify or a metadata protocol

👍 3
adam20:06:07

I have a monolith Clojure server-rendered app with multiple unrelated sections that I want to add some JavaScript to (re-frame/reagent). How do I go about the compilation of such setup? I am using Leiningen and prefer to keep the JS of every section separate.

dnolen21:06:52

you can use code splitting for that as a way to avoid N builds

adam22:06:36

Does code splitting work with cljsbuild? I generated main.js and it's 1.6 MB. Is it possible to add more CLJS in different files without having the file being that big for each?

adam00:06:25

Aha, I see the optimized version is only 95 KB