Fork me on GitHub
#clojurescript
<
2022-04-08
>
Mícheál Ó Catháin09:04:52

Hi! I'm trying to use npm module nodemailer. I expect I'm making some basic errors, and would really appreciate a steer from anyone with more experience! My dev.edn looks like this:

^{:watch-dirs ["test" "src"] 
  :css-dirs ["resources/public/css"]                                 
  :auto-testing true                                                 
  }                                                                 
{:main simplyonmyway.node-test-2                                     
 :install-deps true                                                  
 :npm-deps {:nodemailer "6.7.3"}} 

Mícheál Ó Catháin09:04:34

I require nodemailer in main namespace as follows

(ns ^:figwheel-hooks simplyonmyway.node-test-2 
 (:require                                                         
  [goog.dom :as gdom]                                              
  [nodemailer])) 
This throws error in REPL: #object[Error Error: goog.require could not find: nodemailer] Can anyone shed some light please? Thanks !

dnolen13:04:10

@micheal.ocathain you need to use the :target :bundle - I would double check whatever Figwheel requires for that compiler option

pinkfrog13:04:40

Is there a quick way to achieve (format “%s” “some-str”) as in clojure?

pinkfrog13:04:11

I need a format function, but don’t want to require goog.string.format every time.

p-himik13:04:34

Given your description, the quickest way is to just write "some-str". :) But assuming you actually want a full functionality of format - it's not possible even if you require goog.string.format, simply because the two aren't equivalent at all. The latter is much more limited. In the end, it all depends on what exactly you want.

pinkfrog13:04:33

I want string interpolation.

pinkfrog13:04:41

So basically support the “%s” formatter.

pinkfrog13:04:52

Given that, I will stick with gstring

p-himik13:04:08

The absolute simplest option is to use str. If you have to have a template string, then you can combine str, str/split (with %s probably), and interpose. Note that it won't handle cases like %%s correctly. The next simplest is gstring, yes. If you don't like the funky way you have to require it, just create a wrapper for it in some util ns.

thanks3 1
pinkfrog08:04:02

Hw do you achieve the string template functionality in js?

p-himik10:04:48

I don't write in JS, can't tell.

pinkfrog13:04:40

How can I read a file with clojurescript. The slurp and http://clojure.java.io (of course) are gone.

p-himik13:04:36

Which platform?

pinkfrog13:04:47

react-native

p-himik13:04:41

I've never had to deal with that but I know that the platform itself has some constraints. And seems like people use https://github.com/itinance/react-native-fs Nothing CLJS-specific about it, although maybe someone has written an alternative or wrapper for react-native-fs in CLJS.

fadrian19:04:11

There are a couple ways I've explored and one I haven't. If you can read the file at compile time, you can use clojure's slurp in a macro: See https://gist.github.com/noprompt/9086232. Another way is to open the file using showOpenFilePicker and weave your way through a sea of promises to eventually read the file. See https://web.dev/file-system-access/. There's a corresponding showSaveFilePicker routine that works to spit your file. Finally, you can use a FileReader to open a file and read it in. See https://web.dev/read-files/#:~:text=To%20read%20a%20file%2C%20use,a%20data%20URL%2C%20or%20text.. This is probably closest to what you want, but I haven't tried it. Here's some (probably crappy) code that reads and writes json using the second method:

(def json-file-types (clj->js {"types" [{"description" "JSON documents"
                                         "accept" {"text/json" [".json"]}}
                                        {"description" "Text documents"
                                         "accept" {"text/plain" [".txt"]}}]}))
(defn read-file
  [f]
  (-> (. js/window showOpenFilePicker json-file-types)
      (.then #(.getFile (first %)))
      (.then #(.text %))
      (.then #(f %) #(fn [_] nil))))

(defn writable-data-structure [data]  (clj->js {"type" "write" "data" data}))

(defn save-file
  [data]
  (-> (.showSaveFilePicker js/window json-file-types)
      (.then (fn [^js handle]
               (.createWritable handle)) (fn [_] nil))
      (.then (fn [^js writable]
               (-> (.write writable (writable-data-structure data))
                   (.then (fn [_] (.close writable)) (fn [_] nil))
                   (.then (fn [_]  true))) (fn [_] nil)))))
The read-file function allows one to pick a file, opens it (just one, even though showOpenFilePicker allows multi-select) and reads it by passing a function that takes the data into it. If something goes wrong, you get a nil for the data. Similarly, save-file allows one to pick a file and save the data. It returns true if everything goes well and nil if something goes wrong. Anyhow, if you want to be able to grab a file, and you don't mind asking the user for the file path at run time, this is how to do it.

thanks3 1
👍 1
pinkfrog14:04:35

(aset some-obj some-attr value) Is this a guaranteed behavior to set attribute on js object? I tested the function works, but dunno if it is officially supported.

p-himik14:04:14

Don't use aset for anything that's not an array. It works but it's not guaranteed to work because it's not in its contract.

p-himik14:04:56

If some-attr is a dynamic value, use goog.object/set. If it's a static value, use set!.

thanks3 1
pinkfrog14:04:07

I’d assume to GET on dynamic value, the way to go is goog.object/get

seancorfield18:04:04

I haven't been paying a great deal of attention to cljs evolution over the last seven-ish years but will be getting back into it later this year (I get to rewrite our last legacy app and I'm considering an SPA with cljs/clj as one of the options) but I wondered whether cljs is still targeting "whole programs" only or whether the story for using cljs instead of JS for "small amounts of interactivity" has changed/improved? For example, could cljs be used for the event handling stuff in an HTMX app, to avoid JS?

phill22:04:34

One thing that happened during that time (and I don't know up from down, I just read the news) had something to do with the cljs compiler + g.closure being able to emit multiple distinct js script files and modules, so your app need not be literally an SPA but might be a 2PA or 3PA, yet the distinct js files and modules would be optimally & automatically determined?

dnolen18:04:36

hasn't changed, but it was always optimal for that use case IMO

dnolen18:04:36

and whole program optimization is still the only viable option

1
thheller19:04:40

you can use htmx just fine too if you want. no need to go the full SPA route, although as David said given the baseline size of a CLJS output thats what most people do

seancorfield19:04:29

Yeah, this app doesn't really need to be an SPA so HTMX would give it enough interactivity (mostly just reloading panels when sidebar links are clicked) but I would still prefer to avoid JS if I can -- you know my feelings about JS/Node/npm stuff 🙂

thheller19:04:38

yeah, you can just use the CDN version of htmx. thats totally fine. no need for npm or so

thheller19:04:48

and you could define extensions via CLJS as well

thheller19:04:23

but you always pay for cljs.core overhead so the minimum baseline for a 3 line htmx extension then becomes 30kb or so

thheller19:04:52

but that might be fine depending on how complex your extensions might become

thheller19:04:46

and it might become a little JS interop heavy because of htmx being written in JS 😉

seancorfield20:04:43

Good information. Thanks.

dgb2321:04:25

I don’t think you need a ton of interop when using htmx - not in my experience. There are a couple of htmx specific events you might want to listen to and a little bit of configuration, that’s pretty much it on the client side. On the server you want to add a bit of special handling and structure depending on htmx specific http headers. 30kb for cljs isn’t much, but likely unnecessary if you only have minimal state on the client such as toggling attributes/classes etc. in that case it would easily more than double your JS payload. Htmx is very small. If you do however want to have any client side state that isn’t isolated or want to render templates, consider that htmx is deceptive. You suddenly need to coordinate things across (ajax) events. Htmx works best if thought of as an enhancement over pure SSR in a HTTP/Hateoas kind of way.

dgb2321:04:27

Oh and be careful with caching if you serve htmx specific responses, you need htmx specific caching. I think it’s an easy thing to miss 😅.

JohnJ23:04:32

But is there really any benefit for using Cljs without react? with the DOM being very imperative and full of mutation

thheller05:04:55

I still use CLJS regularly for just plain DOM interop. hot-reload doesn't work as well but that's fine. its usually just for swapping a class or similar trivial stuff

magnars09:04:38

We used JavaScript for "a sprinkling of features" in a mainly static webpage for a year or so, but with time all such "not enough JS to warrant CLJS" grow in size and scope. We've recently rewritten it in cljs, and it was a massive improvement.

👍 2
JohnJ23:04:05

using a vdom?

Benjamin C05:04:50

https://parenscript.common-lisp.dev and https://wisp-lang.github.io/wisp/ have both caught my eye for the light-weight use-case. Haven't had a chance to really try either just yet, but I think I would prefer it to writing javascript.

Benjamin C05:04:23

Took a closer look at wisp out of a renewed curiosity, and I'm impressed! Especially for the "a sprinkling of features" use-case; if it grows beyond that, translation to cljs is already mostly done as it shares a lot of the same syntax. https://github.com/wisp-lang/wisp/blob/master/doc/language-essentials.md

thheller05:04:24

I use cljs with and without vdom for simple JS scripts. I consider the baseline of 30kb gzip similar to something like jquery or lodash in JS which I'd otherwise be using

thheller05:04:51

whether or not you want a vdom-style lib depends on what you actually need to do. complex forms and such I do vdom-style. attaching a simple onclick to a server-rendered dom element or so I don't