Fork me on GitHub
#clojurescript
<
2019-11-03
>
ro600:11:21

Can anyone recommend canonical examples of how common DOM types (eg NamedNodeMap, HTMLCollection, NodeList, etc...) can/should implement CLJS protocols? I found pangloss/synergize (https://github.com/pangloss/synergize/blob/master/src/synergize/browser.cljs#L69) and clojure.data.xml (https://github.com/clojure/data.xml/blob/master/src/main/clojure/clojure/data/xml/js/dom.cljs#L172), but wanted to see if there's a set of impls commonly accepted by the community. P.S. I'm aware of the discussions online regarding why these aren't part of CLJS core, and why one shouldn't extend native/platform types in a library. My use case is an application.

thheller08:11:46

@robert.mather.rmm it isn't a great idea to make the DOM list types look like clojure datastructures (ie. protocols) since they are mutable underneath

thheller08:11:13

one easy way of working with them is (array-seq html-coll-or-node-list) (but they are still mutable underneath, so depending on what you are doing might be a good idea to (vec (array-seq ...))

athomasoriginal14:11:43

When would you suggest using (vec (array-seq ...)) as opposed to just the first example?

thheller14:11:41

whenever you are changing the dom while iterating

thheller14:11:24

(doseq [node (array-seq (js/document.querySelectorAll "div"))] (.remove node)) will end badly 😛

thheller14:11:41

I spent a lot of time debugging the weirdest bugs only to later find out that things were mutable when I thought they weren't

athomasoriginal14:11:04

Ahh. Is the idea that the vec will realize the lazy seq thus making it more predictable to work with?

thheller14:11:25

yes, it turns things immutable (the underlying seq isn't lazy, just mutable)

athomasoriginal14:11:46

Awesome. Thanks!

borkdude14:11:09

what is a thing called that you can :import in CLJS? as class? object? thing?

borkdude14:11:28

I can't find any docs on this on the CLJS site

thheller14:11:35

class is closest I guess

thheller14:11:15

technically it just aliases a name. that name could resolve to anything

dnolen14:11:48

@borkdude :import exists primarily for the annoying case where a goog ctor is the same as the namespace itself

borkdude14:11:57

I was fixing a false positive in clj-kondo (a clojure linter) because people seem to be using it like this:

(ns foo (:import [goog.date UtcDateTime]))
(defn utc [x] (UtcDateTime.fromTimestamp x))
Maybe they shouldn't, but it has been seen in a couple of code bases

borkdude14:11:43

is UtcDateTime a class? I don't know if this is even a concept in JS (outside ES6, TS, etc.), hence my question

dnolen14:11:13

that's idiomatic

dnolen14:11:29

goog.date.UtcDateTime is a library and a ctor

thheller14:11:46

UtcDateTime is a class with static methods

dnolen14:11:11

ok, but that doesn't matter so much - :require won't let you do what you want

borkdude14:11:27

that's informative, thanks

dnolen14:11:29

the point is that the thing you want to use and the namespace are the same

dnolen14:11:13

(:import [foo.bar Baz]) lets you require a namespace called foo.bar.Baz & use Baz as if referred

borkdude14:11:35

according to the issuer you can also:

(:require [goog.date.UtcDateTime :as udt])
udt/fromTimestamp

dnolen14:11:12

sorry if it's a class with only static methods and you don't need to use it as a ctor

dnolen14:11:16

then :require is fine

thheller14:11:57

IMHO it should probably be (defn utc [x] (UtcDateTime/fromTimestamp x)). that is how clojure accesses static class methods

thheller14:11:06

but I don't think that works currently in CLJS

dnolen14:11:41

well that used to work years ago but we disallowed that to remove ambiguities about namespaces

dnolen14:11:10

if we can know that UtcDateTime is a class then we could recover that

dnolen14:11:20

but this was before externs parsing

thheller14:11:22

yeah its a bit messy since closure "provides" don't actually always describe namespaces

dnolen14:11:32

so we couldn't - now we could I suppose?

thheller14:11:18

dunno I would prefer allowing calling the actual :as alias as a function. like we have for other JS

dnolen14:11:21

@constructor is present in UtcDateTime case

thheller14:11:24

makes many issues go away

thheller14:11:38

(:require [goog.string.format :as format]) (format ...)

dnolen14:11:43

so we could support static method pattern w/ this information

thheller14:11:13

(:require [goog.date.UtcDateTime :as udt]) (udt/fromTimestamp ...) (udt. ...)

borkdude14:11:02

From a linter/tooling perspective it would be nice if UtcDateTime/... could be restored

thheller14:11:33

:as can be strict and not callable for CLJS, but ok for JS

borkdude14:11:39

and be as close to the JVM Clojure syntax as possible

dnolen14:11:13

@thheller right, still kind of meh about doing that though - though to be fair we already have invokeable nses for JS modules

dnolen14:11:46

so would be easy to extend that to Closure nses

thheller14:11:16

yeah, it isn't great but can't think of anything cleaner

dnolen14:11:38

@borkdude sure - I'm ok w/ supporting / again for known classes

dnolen14:11:47

doesn't seem like particularly hard ticket

dnolen14:11:03

yeah externs parsing returns :ctor

dnolen14:11:42

code might need some minor tweaks to support - but I think it's 95% of the way there

borkdude14:11:04

:ctor means it's a class?

dnolen14:11:22

annotated with @constructor in the JS

borkdude14:11:02

what if you only have static functions and no constructor?

dnolen14:11:39

then you can just use the thing as a namespace

dnolen14:11:50

er correction just what I said above

dnolen14:11:46

so only static functions and no constructor is a namespace

borkdude14:11:54

so this would only be supported for a limited set of classes baked into the compiler?

dnolen14:11:18

only static functions and no constructor always works

borkdude14:11:25

I mean, the class inference

dnolen14:11:16

if you mean only Closure style JS (possibily ES6) yes

dnolen14:11:25

i.e. foreign libs are out

borkdude14:11:29

Rephrased: CLJS has to know if some import is a class. How can it know from arbitrary libraries.

dnolen14:11:54

I guess it should always work for :import since no ambiguity

dnolen14:11:02

so that would work for foreign

borkdude14:11:04

ah I see, you were referring to the :require case

dnolen14:11:29

:require some lib and if you :refer a known ctor then Foo/static should work

dnolen14:11:01

:import some lib then Foo/static works no checking

thheller14:11:44

:refer (Foo) and Foo/static? that shouldn't work?

dnolen14:11:04

that should work if you know Foo is ctor

dnolen14:11:47

like I said I'd like to see cases in Closure where you have static methods on a Foo where you don't have constructor

dnolen14:11:17

that is foo.bar.Foo if that's just a namespace and not a constructor seems to break the convention I've seen

thheller14:11:26

hmm I kind disagree

dnolen15:11:13

like I said I don't want to go back to the old thing

thheller15:11:13

(:require [foo.bar :refer (Foo)]) is problematic because the mapping isn't 1:1 and could be defined in foo/bar.js or foo/bar/foo.js

dnolen15:11:39

I mean I don't want to just interpret / as always working

thheller15:11:42

I would say / is for namespace aliases only, . for everything else

borkdude15:11:57

except for imports

borkdude15:11:03

like in Clojure

dnolen15:11:57

probably should move this convo into #cljs-dev 🙂

Jimmy Miller15:11:13

So I followed the code splitting guide and have a clojurescript application built that properly code splits and I can load things dynamically using cljs-loader. That all works great. Now I’d like to take those same code split modules (my home page, my about page, etc) and have node server where I require those modules and server side render them. Sadly, that does not seem to work because when I try to require [mysite.about] it tries to require cljs.loader which does not exist in the node environment. Is there are way around this or a better way to accomplish the same goal? Does anyone have a setup like this working?

dnolen15:11:58

I don't think anybody's asked for what you're trying to do

dnolen15:11:13

I think the issue w/ trying to use the loader in Node.js is that we're just using the Google Closure thing - so I doubt that would work under node - definitely meant for the browser

dnolen15:11:59

that said I don't really see why you need to bother w/ all this if you just want to server side render

dnolen15:11:25

just don't use the code split version of your code base - it does means you need to compile twice

Jimmy Miller16:11:18

I’m not actually trying to use the loader directly. The problem I’m running into is that, from my understanding, any modules I want to code split, I need to add the loader/set-loaded! call to. So if I have a simple about page like this:

(ns my-site.about
  (:require [cljs.loader :as loader]))

(defn main []
  [:div [:h1 "About"]])

(loader/set-loaded! :about)
When I try to require my-site.about, it will try to require cljs-loader which will fail. I’m fine with compiling twice, but how would that get around this issue? Or am I doing something wrong with code splitting?

Jimmy Miller16:11:21

(Just for context, I’m basically trying to make a nextjs like framework in clojurescript)

dnolen16:11:26

right so you just have to think differently about the loader code

dnolen16:11:45

instead of putting it directly in, you make a thing which is just for the loader bit

dnolen16:11:04

i.e. a ns called my-site.about.loader

dnolen16:11:15

which refers to loader directly

dnolen16:11:21

but my-site.about does not

dnolen16:11:35

you can load my-site.about into Node.js then w/o issue

Jimmy Miller16:11:30

@dnolen Makes sense. Thanks!

erwinrooijakkers16:11:35

I’m trying to interact with WASM from ClojureScript

erwinrooijakkers16:11:56

On https://emscripten.org/docs/api_reference/module.html it’s explained to create a Module object that overrides print and printerr

erwinrooijakkers16:11:13

How do we do this in ClojureScript?

erwinrooijakkers16:11:40

(defprotocol Module
  (^:export print [this tex] ...)
  (^:export printErr [this text] ...))
?

erwinrooijakkers16:11:14

One approach would be to create a JavaScript object from cljs named Module, that has a method print and printErr The question is: how can I overwrite print so I can access the stdout of the webassembly package in clojurescript and do something with it?

erwinrooijakkers17:11:01

An approach I thought of is overwriting the module from javascript and then calling externed clojurescript methods

erwinrooijakkers17:11:10

but I can’t overwrite module from js either 😕

dnolen18:11:51

@erwinrooijakkers you can just make an JS object just like the example shows

erwinrooijakkers22:11:20

Oh that’s half of what I meant

erwinrooijakkers22:11:32

I also want to assign the object to the js namespace

erwinrooijakkers22:11:38

js/Module basically

erwinrooijakkers22:11:40

Is that possible?

erwinrooijakkers22:11:13

(set! js/Module # js {:print ...}) maybe

erwinrooijakkers22:11:31

Oh that actually works 🙂

erwinrooijakkers22:11:17

But the webassembly is not routed through it

erwinrooijakkers22:11:10

Do you happen to have any experience overwriting js/Module? For WebAssembly? I think I am missing some key piece of insight. (set! js/Module #js {:print (fn [this text] (println "halllllo" text))}) is not doing anything

erwinrooijakkers22:11:18

Also defining it in javascript does not do anything

erwinrooijakkers22:11:33

While in the example of https://dev.zenroom.org/demo/ it does do something

dnolen18:11:10

(js-obj "print" ...) or #js {:print ...}

lilactown19:11:25

has anyone ever done a write up comparing hiccup and factory functions? esp. when using React

lilactown19:11:00

I think Om (Next) used factory functions primarily. any lessons learned from that?