magnars / optimus

A Ring middleware for frontend performance optimization.
364 stars 23 forks source link

How to deploy ClojureScript optimized with Optimus to Heroku? #31

Closed xavi closed 9 years ago

xavi commented 9 years ago

I am working on a Clojure and ClojureScript app that is deployed to Heroku. Before using Optimus, ClojureScript was compiled on deployment by hooking lein-cljsbuild into Leiningen. Once I added Optimus, that wouldn't work anymore because Leiningen compiles the Clojure code before the ClojureScript code, which means that when this code is compiled...

(def application (-> #'app-routes
                     (optimus/wrap  get-assets
                                    ...)))

the compiled ClojureScript files (.js files) don't exist yet.

To work around this I removed the lein-cljsbuild hook from project.clj and configured Heroku using the following commands to explicitly compile ClojureScript before compiling Clojure (got the idea from this tip)

heroku config:add BUILD_CONFIG_WHITELIST=BUILD_COMMAND
heroku config:add BUILD_COMMAND="lein with-profile production do cljsbuild once, compile :all"

Now the app compiles successfully but it fails to start...

2015-01-09T18:35:13.533073+00:00 heroku[web.1]: Starting process with command `java $JVM_OPTS -cp target/myapp-standalone.jar clojure.main -m myapp.server`
2015-01-09T18:35:14.147782+00:00 app[web.1]: Picked up JAVA_TOOL_OPTIONS: -Xmx384m  -Djava.rmi.server.useCodebaseOnly=true
2015-01-09T18:35:14.254493+00:00 app[web.1]: Error: Could not find or load main class clojure.main

I understand that this failure may not be related to Optimus, but at least I would like to share and know about others' experience deploying ClojureScript optimized with Optimus. What's your approach when deploying ClojureScript optimized with Optimus? Do you know if there's a way to keep using the lein-cljsbuild hook? (I don't like the BUILD_COMMAND approach, it seems like a fragile hack, and in fact it doesn't work for my app, at least as-is.)

magnars commented 9 years ago

I'm not sure what causes your second issue, but I've got an idea for your first issue:

I would suggest using Stuart Sierras component pattern, which means there is no global application to be defined at compile time, but that the application is instantiated at runtime. This will delay the get-assets call until after compilation.

That should give you both a nicer workflow in the repl, and a working app in production. :)

xavi commented 9 years ago

Ah... I suspected Sierra's Component would serve as a workaround, but I've been delaying to make the changes necessary to use it. I guess it's about time.

Thanks!

xavi commented 9 years ago

Inspired by the Component approach, finally I simply replaced the aplication var that I had previously with...

(defn make-handler []
  (-> #'app-routes
      (optimus/wrap get-assets
                    ...)))

and then...

(ring/run-jetty (make-handler) {:port port :join? false :configurator configurator}))

This obviously doesn't give all the benefits that Component provides, but it works as a simpler solution to this particular problem (i.e. this way there's no need for .js files to be present at compile time).

magnars commented 9 years ago

Excellent :)

On Sat Jan 10 2015 at 12:29:12 PM Xavi Caballé notifications@github.com wrote:

Closed #31 https://github.com/magnars/optimus/issues/31.

— Reply to this email directly or view it on GitHub https://github.com/magnars/optimus/issues/31#event-216946665.