Fork me on GitHub
#beginners
<
2019-07-22
>
sova-soars-the-sora04:07:15

If my surmising is correct: my clojurescript app cannot have SEO unless i implement a server that can feed cached (not js) pages

gklijs05:07:01

You don't really need a server. As long as you can generate HTML files. You can use Netlify for hosting for example.

seancorfield04:07:31

@sova Pretty much. If you have an SPA -- literally a single page -- you can have <noscript> content which might be read by search engines, and Google is supposed to understand some JS and render it, but the reality is: if you want a good SEO footprint, you probably need a page-based SEO version of your app that search engines can read.

seancorfield04:07:08

But a lot depends on how much of your app is public (vs behind a login wall) and whether you can get enough of a footprint from a single page.

sova-soars-the-sora04:07:07

For this application, an online magazine, it's important every article be indexable by a search engine.

seancorfield04:07:16

At work, since we need an SEO footprint based on global locations and keywords, we have our main SPA -- React.js with a Clojure REST API -- and we also have a full page-based SEO app. You can see them here: https://soulsingles.com -- the React.js SPA -- and if you scroll to the bottom, you'll see a link to Black Singles which is our SEO app, written in Clojure, generating pages based on keywords and geography.

sova-soars-the-sora04:07:21

But rum actually has a neat serverside rendering function, that can actually be up-loaded or 1-up-loaded by clientside js once it's sent. it's just not my easiest task to conquer, but i figure the capability is there.

seancorfield04:07:02

Yeah, we had to deal with SSR as well, but mostly for performance reasons TBH.

sova-soars-the-sora04:07:25

Is it correct to say that you have many simple html pages with SEO tags that later have the javascript app embedded ?

sova-soars-the-sora04:07:03

thanks for the link... checking

sova-soars-the-sora04:07:59

Hmmmm. Yeah, well at least I can prototype in CLJS and then try and figure out how to SSR everything

sova-soars-the-sora04:07:11

Thanks for confirming my suspicions about SEO and SPAs

seancorfield04:07:06

We have an index.html that loads the JS and it links out to the SEO Geo app.

seancorfield04:07:45

The original version of the SEO app used Hiccup to generate the HTML. The new version generates data and we use Selmer to render it in templates -- because that allows our designer to work with the (HTML) templates more easily

seancorfield04:07:20

There are actually four backend Clojure apps in play there: the REST API, the OAuth2 Server, the Login Server (traditional HTML pages rendered on the server), and the SEO Geo app (again, traditional SSR HTML pages) -- and the front end app (React.js/Redux/Immutable.js/etc).

Crispin05:07:43

App I work on we do SSR on the cljs based frontend using the Continuous Delivery system. The build pipeline builds the jar, then runs it up, then connects to it with phantomjs and hits all the pages and saves them. These are then post-processed with enlive and then the result of that is then 'injected' back into the jar and replaces the templates used by the server to render the pages. It's a bit time consuming, but it works well. So, for example, the base url is hit, the page is saved, then its processed down into a new 'index.html' file, that replaces resources/templates/index.html in the jar. This is also to raise its organic rankings in search engines and up its lighthouse score. (it gets a great lighthouse score btw)

jayesh-bhoot10:07:05

Hi. a cljs question. Can anyone give me pointer on how to use async/take! to get a result resolved by a Promise from a Javascript lib?

jayesh-bhoot10:07:10

For example, a JS API useDocumentFonts.get({}) returns a Promise. I need to use the result of the promise in my cljs code.

Crispin11:07:55

in the .then clause, push the answer onto a channel. Then you can pull from that channel. eg

(defn promise->chan [promise]
  (let [c (chan)]
    (.then promise #(put! c %))
    c))

;; use like
(do-something (<! (promise->chan myprom)))

Crispin11:07:28

You could handle both success and errors perhaps like

Crispin11:07:36

(defn promise->chan [promise]
  (let [c (chan)]
    (-> promise
        (.then #(put! c [:ok %]))
        (.catch #(put! c [:error %])))
    c))

(match (<! (promise->chan myprom))
       [:ok res] (success-func res)
       [:error err] (error-func err))

Crispin11:07:59

(this is using core.match... or you could use standard conditionals)

jayesh-bhoot11:07:09

alright great. so I do need to use Promise interop. I had no idea about that; https://cljs.info/cheatsheet/ didn't list promise constructs in interop.

Crispin11:07:53

promises are just a fancy wrapper around a callback. they're not interop

๐Ÿ‘ 1
jayesh-bhoot11:07:21

ok now that you put it that way, I agree

jayesh-bhoot11:07:31

its just a chain of functions.

danielneal11:07:56

You can use the standard js interop for promises, which is what Iโ€™d reach for for basic stuff. i.e. (.then promise (fn [result] (do-something-with result)))

๐Ÿ‘ 1
danielneal11:07:38

A step up from that would be to use e.g. promesa library, which will wrap the js promises https://github.com/funcool/promesa for more async/await like functionality

jayesh-bhoot11:07:52

ok so the promise interop was the missing link for me. https://cljs.info/cheatsheet/ does not list promise constructs in interop section.

noisesmith18:07:34

that interop is just a method call, nothing about the syntax / usage is specific to promises

noisesmith18:07:56

(of course the semantics of what the method does is all about promises, but that applies generally to any interop)

jayesh-bhoot09:07:19

I agree. Later on I thought a bit on that. Behind all the promise syntax, its just method calls!

jayesh-bhoot09:07:28

That clarity helped a lot.

Mario C.15:07:30

If you have a list that needs to be filtered if a condition is met, is it better to have a remove and add the condition to the remove function or have an if-else gate a filter function?

Mario C.15:07:31

I keep thinking if-else are bad and should be avoided

Cora16:07:52

cond-> is good for that

Cora16:07:06

but if/else is fine when it's one condition. usually clearer, too, imo

dmaiocchi17:07:13

I personally prefer the or

Scot20:07:08

or is nice if your predicate is your return, but I find that it can end up pretty ugly if you have to do anything other than that (i.e. (or (when (pred? x) x) (filter pred2? x)))

noisesmith20:07:44

@scot-brown I've frequently written the equivalent of (defn when-pred [p? x] (when (p? x) x)) in various codebases

noisesmith20:07:12

so that chains like that can look like (or (when-pred pred? x) (filter pred2? x))

noisesmith20:07:48

a valid alternate name for when-pred is whitelist

Scot20:07:20

@noisesmith valid approach. I personally prefer the cond-> or the wrapping in an if, but I can definitely see cases where the whitelist approach is nice.

noisesmith20:07:27

to me cond-> is for a series of conditional transforms, not for managing multiple options (it doesn't carry the result of the previous transform to the next condition or short circuit)

Scot21:07:50

Right, but if you have a pattern of conditionally applying transformation fns, cond-> works well. it handles a special case, but it is along the lines of what @UB0S1GF47 was asking about. The problem of conditionally applying transformation fns is one that comes in many shapes and sizes, and there are as many ways to solve it. In the case of one conditional filter, if works best. In the case of many conditional operations where the predicates are independent, cond-> works best. In the case where your predicate is on the collection itself, something like whitelist works well.

noisesmith21:07:24

oh - I lost track of the initial question, and here cond-> does fit, you are right

Ashley Smith21:07:20

Sorry guys it's me again. I'm trying to set up a project that uses Figwheel, Reagent and ansible so that I have a server and web app. I was following this book: https://www.braveclojure.com/quests/deploy/ but I really want to use figwheel as I've really enjoyed using that. I've asked a lot about the differences between boot and leiningen and stuff, but now I'm simply asking for help, as I don't really know if trying to get figwheel-boot working is easier than setting up a project with ansible reagent and figwheel in lein?

noisesmith21:07:41

I haven't used that boot template, but as a rule of thumb lein stuff will be easier if you are OK with default behaviors

noisesmith21:07:51

boot is much easier to customize in fine-grained ways though

Ashley Smith21:07:34

I imagine it is easier with leiningen but this is my first time setting up the project myself, I've just done hackerrank and cloned starter projects until noiw

noisesmith21:07:18

creating a new project with leiningen isn't much different from cloning a starter project, you call lein new sometemplate foo.bar/baz and it creates a skeleton project from that template but using foo.bar.baz as the primary namespace

Scot21:07:46

Figwheel is much easier to use in leiningen than in boot or clj.deps. If you want to explore boot, I'd recommend looking at shadow-cljs or figwheel.main. They offer similar features, but are more tooling agnostic.

Ashley Smith21:07:24

I know there's a template that does figwheel and reagent, but I then will need to add ansible to it and learn how to split the project into server / client. Never heard of figwheel.main, that sounds interesting. I've been reading at shadow-cljs, but is it a figwheel alternative or a boot alternative?

noisesmith21:07:11

both are alternatives to lein-figwheel that work better with boot

Ashley Smith21:07:39

that sounds interesting! Thank you

Ashley Smith21:07:07

have you used any of them? Do you prefer one over the other?

noisesmith21:07:02

I've only used lein-figwheel among those, by the time boot came out I already know how to customize lein tasks enough for my needs

Scot21:07:52

I personally prefer shadow-cljs to figwheel.main. It requires NPM, but you end up getting much better interop with js dependencies

yogthos21:07:03

it's modelled on heroku, and it's fairly easy to manage

yogthos21:07:51

and seconded for shadow-cljs, I've had the best experience using NPM modules with it

Ashley Smith21:07:38

I've never really used NPM but i have heard that it is great for managing NPM. So if I use shadow-cljs, do I use boot?

Scot21:07:28

It's up to you, really. shadow-cljs is a standalone tool. I am most familiar with leiningen so I can say that it has some convenient integration there, but it shouldn't be too hard to use with boot or any other build tools.

Ashley Smith21:07:12

thank you for all your help ๐Ÿ™‚

Scot21:07:35

No problem! ๐Ÿ™‚

Daouda22:07:11

Hey folks, does memoize store exception? I mean want to use it on http call, but I donโ€™t know if memoize store exception in case it occur. I donโ€™t want exception to be memoized.

Crispin01:07:41

the exception will rise on (apply f args) and so never reach the swap!

Crispin01:07:58

just be aware that it is a very naive function. There is no facility to purge or empty the atom. So if your programme is long running, and there are many different arguments passed into the function, then memory use will grow, possibly indefinitely.

andy.fingerhut22:07:28

I don't recall. Try out a small example function that throws exceptions for at least one input, and see what happens in a REPL session.

andy.fingerhut22:07:59

I believe that memoize will behave repeatably with regard to how it handles exceptions thrown from the function being memoized.

andy.fingerhut22:07:45

by which I mean, however it behaves in your experiment, it is likely to behave that way for any memoized function

dpsutton22:07:16

(let [random-error-on-zero (fn [n] (do (prn "working: " n)
                                       (case n
                                         0 (if (zero? (rand-int 2))
                                             (throw (ex-info "boom" {}))
                                             :fine)
                                         n)))
      f (memoize random-error-on-zero)]
  (map #(try (f %) (catch Throwable _ :caught)) (repeat 15 0)))
"working: " 0
(:caught"working: " 0
 :caught"working: " 0
 :caught"working: " 0

 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine
 :fine)

dpsutton22:07:53

it won't memoize the error because the error will take control flow out of memoize