Fork me on GitHub
#figwheel-main
<
2018-09-26
>
benzap05:09:01

So I got all of my development environments setup, but i'm wondering if my workflow is wrong. In all of the cljs repls, it starts out in the namespace cljs.user, so I have a cljs/user.cljs file that contains some code. Only problem is, it doesn't appear to have my functions that I wrote in there (reloaded workflow) until after i've edited a file. I've noticed this behaviour in both my node build and my browser build. Is there a different way I should be preloading development tools into the cljs repl?

benzap07:09:20

Was able to solve this by pointing :main at cljs.user and pulling in my original dev environment.

bhauman11:09:31

according to this it needs to be a user.cljs file

bhauman11:09:49

not a cljs/user.cljs

benzap14:09:00

thanks, that explains more things!

plexus09:09:19

@bhauman thanks for looking into this, but I'm starting to doubt we'll be able to use it. We're using a custom list of foreign-libs for the node build to exclude a whole bunch of cljsjs dependencies, which on node we load directly from node_modules instead. Unless we come up with a different solution there I think we'll have to keep the build separate.

plexus09:09:40

still pretty cool that it works! I'm sure it'll be useful for people.

pesterhazy13:09:56

Trying out the :ring-handler config option, I discovered that the fn I specify seems to acts as a fallback handler, rather than replacing the default ring ahdnler.

pesterhazy13:09:25

My handler looks like this

(defn handler [{:keys [request-method uri]}]
  (if (and (= :get request-method)
           (or (= uri "/app") (str/starts-with? uri "/app/")))
    (ring.response/resource-response "index.html" {:root "public"})
    {:status 404
     :headers {"Content-Type" "text/plain"}
     :body "Not Found!"}))

pesterhazy13:09:18

But the handler isn't called when I request /index.html, i.e. something that is on a valid resource on the class path

pesterhazy13:09:23

Is that the expected behavior?

pesterhazy13:09:46

So the handler is invoked for /index.html1 but not for /index.html (which exists)

pesterhazy13:09:14

The handler is also not called for /, which caught me by surprise

bhauman13:09:10

@pesterhazy yes static resources shadow the handler

bhauman13:09:12

I’m not sure about the root path

pesterhazy13:09:29

It looks like / defaults to index.html, if one exists

bhauman13:09:40

yes as it should

bhauman13:09:05

yes if there is an index.html it will be picked up

pesterhazy13:09:51

Personally I find this nonobvious

pesterhazy13:09:15

Especially given that it's so easy to implement the fallback myself

pesterhazy13:09:49

But I guess figwheel needs to serve the .js files as well?

bhauman13:09:04

it has to serve all the compiled assets

bhauman13:09:23

and it just won’t work

bhauman13:09:27

without it

bhauman13:09:48

its optimized so that when people start figwheel they get a working system

pesterhazy13:09:55

of course, I understand

bhauman13:09:32

@pesterhazy but if you want to whole hog take over the server, I’m happy to add an option for that

bhauman13:09:54

but keep in mind serving the assets efficiently is a trick

bhauman13:09:12

@pesterhazy you can also customize the :ring-stack

bhauman13:09:31

but it might take some experimenting to get what you want

bhauman13:09:31

but interestingly enough this is how ring defaults works

bhauman13:09:55

you normally wrap your ring-handler as the final handler and the resource handler comes before it

bhauman13:09:14

@plexus I did some experimenting this morning and I have :target :nodejs working for extra mains and I have the node and web targets sharing a webpack bundle so that the requires stay consistent for shared web and node code

bhauman13:09:47

:global-exports is working fantasticly

bhauman13:09:26

of course I only did this for one library on a simple demo project, I’m not sure how it would stand up against a large project, I also wouldn’t expect Nodejs corner cases to be compiled correctly

bhauman13:09:35

or if there are any corner cases

mkvlr14:09:25

@bhauman we (I’m working with @plexus on this) probably need to do some work to unify our node target with others regarding foreign-libs first.

mkvlr14:09:51

but figwheel-main especially the :extra-mains option is really fantastic, thank you!

mkvlr14:09:04

we’ve been able to collapse five builds into one. We jumped through some hoops before to keep the feedback cycles down (figuring out what needs to be rebuilt and providing options to switch builds on and off) and that code could all go away now.

bhauman14:09:18

this is so good to hear

pesterhazy14:09:55

@bhauman I think it would be great to have a way to truly override the default ring handler, especially for /. But mainly I think the documentation could be amended to note that the handler is a fallback, not the primary handler

bhauman14:09:12

yes that would be a good doc to have

pesterhazy14:09:13

I guess it would be possible to provide a :ring-handler-override option that can return nil, in which case the figwheel handler kicks in

pesterhazy14:09:51

using the ring convention, (or (handle1 req) (handler2 req) (handler3 req))

bhauman14:09:35

this makes sense, but at the same time folks are going royally bork this up

bhauman14:09:19

and why not run your own server at this point?

bhauman14:09:01

this is easy enough though and it makes sense

bhauman14:09:52

and I kinda feel like if you use ring-handler-override it should be an override and not an or relationship

bhauman14:09:21

as that leaves no way to truely override the server

bhauman14:09:28

@pesterhazy did you see my comment on how this is how folks normally use ring-defaults? and how they normally wrap around the api-ring-handler?

pesterhazy14:09:15

I saw that comment but I guess I didn't understand it

bhauman14:09:55

this is no big deal

bhauman14:09:25

I’m just saying that its common for the static resource handler to come before the api handler

bhauman14:09:41

but that doesn’t mean its better or correct

pesterhazy14:09:18

maybe it's best then to just make the docs a bit more explicit

pesterhazy14:09:32

if I built my own web server, is there a simple example of how to hook into figwheel to serve the compile artifacts?

pesterhazy14:09:48

happy to make a docs PR if that's desirable

bhauman14:09:29

@pesterhazy when you have your own server, you serve the compiled artifacts

bhauman14:09:42

just like you would when you deploy it

pesterhazy14:09:54

I just serve them out of target/?

bhauman14:09:15

or you can change the output-to and serve them from whereever

pesterhazy14:09:20

right, I'm guessing I don't get the benefit of Figwheel's clever caching defaults etc then

bhauman14:09:41

but that is middleware that you can add

bhauman14:09:54

its just one peice of middleware

pesterhazy14:09:16

to be clear, the way it works today works well for my use case, it just took me like 30min of debugging to figure out how it works

bhauman14:09:39

yeah, its on my list of docs to write

pesterhazy14:09:54

it's the kind of thing that, as an experienced web developer, you really want to understand the serving logic exactly

pesterhazy14:09:04

(plus for the beginner it should "just work")

bhauman14:09:50

oh yeah you normally deploy your app as an SPA

pesterhazy14:09:49

right, I was talking about development time

bhauman14:09:50

but if you are starting to add a server it normally is better to create your own server, that way you do get control, and as a bonus you have something you can deploy

pesterhazy14:09:17

in my view, the best way to deploy a SPA in 2018 is not to mess with servers at all

pesterhazy14:09:43

S3 + CloudFront does that job for you

bhauman14:09:02

and then there is lambda when you just have to handle that odd thing

pesterhazy14:09:14

today I learned that you customize the behavior of CloudFront to precisely define the routing logic

bhauman14:09:27

that’s cool 🙂

pesterhazy14:09:45

the lambda actually hooks into CloudFront directly - it's called Lambda@Edge

pesterhazy14:09:17

so it's javascript that runs in the Edge region - it's like Apache's mod_rewrite, except you write real code

pesterhazy14:09:48

it's a bit annoying to set up at first, but very powerful when you get the hang of it

bhauman14:09:05

AWS is going to get total buy in, the idea that you can write something that will run on any cloud service is pretty much over in practical terms

bhauman14:09:22

if you want to take advantage of the good stuff

pesterhazy14:09:46

yeah I don't like to think about an additonal abstraction layer on top of aws

pesterhazy14:09:10

to be fair, other CDN providers like Fastly or Cloudflare give you Edge-side workers as well

bhauman14:09:43

for sure but portability has to be rough

bhauman14:09:51

and or impossible

pesterhazy14:09:05

yeah depends on your requirements

pesterhazy14:09:45

to me the important take away is that static assets belong on S3 (or the GCP equivalent)

pesterhazy14:09:15

then you can serve them off S3 directly, or using a CDN, or using nginx or a similar reverse proxy

pesterhazy14:09:28

that simplifies the whole process a lot

pesterhazy14:09:45

example of a Lambda@Edge script for URL rewriting for SPAs: https://gist.github.com/pesterhazy/05639a992fdaa965464e7ce680832712

bhauman14:09:03

so just like a middleware handler

bhauman15:09:36

personally I’m really excited by the fact that I have :extra-main-files working for a Node target alongside a web target

bhauman15:09:05

this makes working with Node even more seemless than it was before

pesterhazy15:09:45

we actually have a use for that as well, that's pretty cool

bhauman15:09:15

and it can share your webpack bundle

bhauman15:09:25

so requires all line up

bhauman15:09:19

you just need to do goog.global.Moment = Moment instead of using window

bhauman15:09:38

hmmm I wonder if that will work for advanced compile

pesterhazy15:09:54

well I wouldn't use webpack for node, that's pretty unusual

bhauman15:09:18

yeah but let’s say you are sharing code with the front end

bhauman15:09:48

like you have calendar lib that relies on moment js

bhauman15:09:46

how are you going to import it in a way that’s supported by both platforms?

pesterhazy15:09:50

I guess you could use global exports with a simple file that goes

global.moment = require("moment");
- no webpack needed for that right?

pesterhazy15:09:46

you'd need a separate package.json for the node part of your app (but you'll need separate deps anyway)

plexus15:09:37

this is quite relevant to our interests as well... we share a bunch of code between node and frontend, and quite often cljsjs packages get pulled in by libraries (or by us) that are not compatible with node

bhauman15:09:09

@pesterhazy yeah that would work

bhauman15:09:42

so goog.global.Moment = moment; doesn’t work for advanced compile on the webside

bhauman15:09:07

so that needs to be reconsidered

bhauman15:09:22

probably better to require a goog = goog || {}; goog.global = window || global;

bhauman15:09:36

or something like that

pesterhazy15:09:42

googl.global.Moment = ... wouldn't go thru advanced compilation, being a foreign lib right?

bhauman15:09:12

I’d like to support this though

pesterhazy15:09:40

@plexus I think for node I wouldn't use cljsjs if possible

bhauman15:09:52

I don’t think he is

pesterhazy15:09:15

package.json, (def fs (js/require "fs")) -> works

bhauman15:09:33

but not for shared libs

pesterhazy15:09:08

that's quite rare in my experience though

pesterhazy15:09:17

moment is an example that makes sense

pesterhazy15:09:06

you could always use (def moment (if (exists? goog.global.moment) goog.global.moment (js/require "moment")))

pesterhazy15:09:26

not pretty, I know, but maybe acceptable for one of two libs

bhauman15:09:12

yeah that’s possible

bhauman15:09:47

but not as nice as what I just made work (:require [moment :as moment])

bhauman15:09:20

and that could work with global exports via a node index and a webpack bundle

plexus15:09:08

@pesterhazy for context, we have a "renderer" node process that runs our Reagent views on the backend to pre-render them. Reagent pulls in cljsjs.react, there are I think about two dozen of these cases. We work around it with a fake :foreign-libs entry that "provides" cljsjs.react, and load react from node_modules instead, but it's a fidgety approach. I'm curious if there would be a better alternative.

pesterhazy15:09:23

I think loading react from node_modules is the right approach

pesterhazy15:09:53

obviously this uses Webpack, which I wouldn't recommend for Node

pesterhazy15:09:24

I don't think you need to provide cljsjs.react anymore, you just need to supply an exclusion

bhauman15:09:13

var goog = goog || {global: window};
import * as moment from 'moment';
goog.global.Moment = moment;

bhauman15:09:38

that works for webpack bundling to allow a Node extra main

bhauman15:09:48

and allows for consistent requires in all namespaces

bhauman15:09:15

so this allows you to build your code with one compiler build

bhauman15:09:16

when you deploy Node you could use a npm index.js file for your global exports instead of the webpack bundle

bhauman16:09:55

I’m currently writing the editor integration documentation,

bhauman16:09:29

I’ve written an introduction and now I’m working on a decent guide on how to get up and running with emacs

bhauman16:09:43

then I’ll discuss Cursive

pesterhazy16:09:52

var goog = goog || {} - is that safe?

bhauman16:09:11

in what sense?

pesterhazy16:09:24

> var w = v || {}
ReferenceError: v is not defined
> var v = v || {}
undefined

pesterhazy16:09:39

I don't understand JS after all these years

pesterhazy16:09:51

how is the first different from the second?

bhauman16:09:55

well I guess goog is defined

bhauman16:09:15

what’s strange is that it works

bhauman16:09:31

the code above works

bhauman16:09:46

in advanced mode apparently goog isn’t defined

pesterhazy16:09:52

it only works if it's the same var left and right of the equals sign

pesterhazy16:09:25

maybe the var is already in scope (but undefined)

bhauman16:09:42

man it works

bhauman16:09:48

JS is a strange beast

pesterhazy16:09:10

I believe you, I'm only in disbelief about how I didn't know that before

bhauman16:09:31

yeah the v example you demonstrated

bhauman16:09:40

that is freaking nutsy

bhauman16:09:15

I think its a special form for definition

pesterhazy16:09:00

yeah, I've definitely seen that pattern in the wild before

shaun-mahood16:09:48

@bhauman If you haven't gotten to it yet, I have time this morning to write up the basics of how I've got Cursive working nicely with figwheel-main

❤️ 4
bhauman16:09:13

@shaun-mahood yeah that sounds great

bhauman16:09:37

can you give a simple use case and and advanced use case?

shaun-mahood16:09:27

Yeah - do you want me to work from a particular starting point (like the lein template)?

bhauman16:09:10

not necessarily the template

shaun-mahood16:09:22

The other doc I think I'm ready for is using figwheel with Integrant, so I will try and get something on that afterwards.

bhauman16:09:22

just your average project

bhauman16:09:21

the rest of the docs assume a simple project

shaun-mahood16:09:10

Sounds good, I'll try to follow the existing docs as much as I can

cfleming17:09:38

@shaun-mahood Thanks! That sounds awesome.