My clojure/reagent project was initiated using a static resources/public/index.html, where src/test/client.cljs provides the rendering function.
Now I would like to switch the server side to a dynamic renderer (in order to provide a REST API to the client).
This is the structure of the project:
test
|___src
| |___test
| |___core.clj
| |___client.cljs
|___resources
| |___public
| |___index.html
| |___style.css
|___project.clj
And the main files:
;; src/test/core.clj
(ns test.core
(:use compojure.core)
(:require
[ring.adapter.jetty :as jetty]
[clostache.parser :as clostache]
[compojure.route :as route]
[compojure.handler]
[clojure.data.json :as json]
))
(def home
"<!DOCTYPE html>
<html>
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\">
<link rel=\"icon\" href=\"https://clojurescript.org/images/cljs-logo-icon-32.png\">
</head>
<body>
<div id=\"container\"></div>
<script src=\"/client.js\" type=\"text/javascript\"></script>
</body>
</html>")
(defn handler [request]
(if (and (= :get (:request-method request))
(= "/" (:uri request)))
{:status 200
:headers {"Content-Type" "text/html"}
:body home}
{:status 404
:headers {"Content-Type" "text/plain"}
:body "Not Found"}))
;; src/test/client.cljs
(ns test.client
(:require
[reagent.dom :as rdom]))
(defn test-container []
[:div
[:h1 "Test ring-handler"]
])
(defn ^:export run []
(rdom/render [test-container] (js/document.getElementById "container")))
;; project.clj
(defproject test-ring-handler "0.1.2-SNAPSHOT"
:min-lein-version "2.0.0"
:description "test-ring-handler"
:license {:name "GNU GPL v3+"
:url "http://www.gnu.org/licenses/gpl-3.0.en.html"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/clojurescript "1.11.54"]
[reagent "1.1.1"]
[cljsjs/react "18.0.0-rc.0-0"]
[cljsjs/react-dom "18.0.0-rc.0-0"]
[ring "1.9.5"]
[ring/ring-core "1.9.5"]
[figwheel "0.5.20"]
]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.19"]
]
:resource-paths ["resources" "target"]
:clean-targets ^{:protect false} [:target-path]
:profiles {:dev {:cljsbuild
{:builds {:client
{:figwheel {:on-jsload "test.client/run"}
:compiler {:main "test.client"
:optimizations :none}}}}}}
:figwheel {:repl false
;; :http-server-root "public"
:ring-handler test.core/handler
}
:cljsbuild {:builds {:client
{:source-paths ["src"]
:compiler {:output-dir "target/public/client"
:asset-path "client"
:output-to "target/public/client.js"
:main "test.core"}}}}
)
When the :ring-handler option is commented, everything is compiled correctly and client.cljs correctly renders the title "Test ring-handler".
If :ring-handler is activated, the compilation fails:
$ lein figwheel
Figwheel: Validating the configuration found in project.clj
Figwheel: Configuration Valid ;)
java.lang.IllegalArgumentException: unable to require the namespace of the handler test.core/handler for :ring-handler
at figwheel_sidecar.utils$illegal_argument.invokeStatic(utils.clj:53)
at figwheel_sidecar.utils$illegal_argument.doInvoke(utils.clj:52)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at figwheel_sidecar.components.figwheel_server$create_initial_state$fn__19826.invoke(figwheel_server.clj:323)
...
What is the syntax to use so that the server requests are redirected to test.core/handler?
Related
I'm writing a web app which uses Overtone. When I try running the app using lein run or when I try to start the repl while in the project's directory I get the same error: java.lang.ExceptionInInitializerError at clojure.main.<clinit>(main.java:20) Caused by: java.lang.Exception: Server needs to be connected before you can perform this action.
It seems to me that both of those actions make all the files in my project compile. Is there any way to compile the namespaces which use Overtone after I run the server? Or maybe this isn't the issue, and the problem is coming from something else?
Here is my project.clj file:
(defproject comusic "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:dependencies [[buddy "1.3.0"]
[compojure "1.5.2"]
[conman "0.6.3"]
[cprop "0.1.10"]
[funcool/struct "1.0.0"]
[luminus-immutant "0.2.3"]
[luminus-migrations "0.3.0"]
[luminus-nrepl "0.1.4"]
[markdown-clj "0.9.98"]
[metosin/muuntaja "0.1.0"]
[metosin/ring-http-response "0.8.2"]
[mount "0.1.11"]
[org.clojure/clojure "1.8.0"]
[org.clojure/tools.cli "0.3.5"]
[org.clojure/tools.logging "0.3.1"]
[org.postgresql/postgresql "42.0.0"]
[org.webjars.bower/tether "1.4.0"]
[org.webjars/bootstrap "4.0.0-alpha.5"]
[org.webjars/font-awesome "4.7.0"]
[org.webjars/jquery "3.1.1"]
[org.webjars/webjars-locator-jboss-vfs "0.1.0"]
[ring-webjars "0.1.1"]
[ring/ring-core "1.6.0-RC1"]
[ring/ring-defaults "0.2.3"]
[selmer "1.10.7"]
[overtone "0.10.1"]]
:min-lein-version "2.0.0"
:jvm-opts ["-server" "-Dconf=.lein-env"]
:source-paths ["src/clj"]
:test-paths ["test/clj"]
:resource-paths ["resources"]
:target-path "target/%s/"
:main ^:skip-aot comusic.core
:migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")}
:plugins [[lein-cprop "1.0.1"]
[migratus-lein "0.4.4"]
[lein-immutant "2.1.0"]]
:profiles
{:uberjar {:omit-source true
:aot :all
:uberjar-name "comusic.jar"
:source-paths ["env/prod/clj"]
:resource-paths ["env/prod/resources"]}
:dev [:project/dev :profiles/dev]
:test [:project/dev :project/test :profiles/test]
:project/dev {:dependencies [[prone "1.1.4"]
[ring/ring-mock "0.3.0"]
[ring/ring-devel "1.5.1"]
[pjstadig/humane-test-output "0.8.1"]]
:plugins [[com.jakemccrary/lein-test-refresh "0.18.1"]]
:source-paths ["env/dev/clj"]
:resource-paths ["env/dev/resources"]
:repl-options {:init-ns user}
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}
:project/test {:resource-paths ["env/test/resources"]}
:profiles/dev {}
:profiles/test {}})
EDIT: After booting a SC server and trying to connect to it in my main function I still get the same error. File which contains main function:
(ns comusic.core
(:require [comusic.handler :as handler]
[luminus.repl-server :as repl]
[luminus.http-server :as http]
[luminus-migrations.core :as migrations]
[comusic.config :refer [env]]
[clojure.tools.cli :refer [parse-opts]]
[clojure.tools.logging :as log]
[mount.core :as mount]
[overtone.core :as overtone])
(:gen-class))
(def cli-options
[["-p" "--port PORT" "Port number"
:parse-fn #(Integer/parseInt %)]])
(mount/defstate ^{:on-reload :noop}
http-server
:start
(http/start
(-> env
(assoc :handler (handler/app))
(update :port #(or (-> env :options :port) %))))
:stop
(http/stop http-server))
(mount/defstate ^{:on-reload :noop}
repl-server
:start
(when-let [nrepl-port (env :nrepl-port)]
(repl/start {:port nrepl-port}))
:stop
(when repl-server
(repl/stop repl-server)))
(defn stop-app []
(doseq [component (:stopped (mount/stop))]
(log/info component "stopped"))
(shutdown-agents))
(defn start-app [args]
(doseq [component (-> args
(parse-opts cli-options)
mount/start-with-args
:started)]
(log/info component "started"))
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
(defn -main [& args]
(cond
(some #{"migrate" "rollback"} args)
(do
(mount/start #'comusic.config/env)
(migrations/migrate args (select-keys env [:database-url]))
(System/exit 0))
:else
(do
(overtone/connect-external-server 57110)
(start-app args))))
I'm new to clojure and i'm having a hard time figuring out how to reload/ refresh the browser when changes have been made to either html/ js/ css etc.
this is my current setup project.clj
(defproject app2 "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://exampl.com/FIXME"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.89"]
[ring/ring-core "1.5.0"]
[ring/ring-jetty-adapter "1.5.0"]
[enfocus "2.0.0-SNAPSHOT"]]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-ring "0.9.7"]]
:cljsbuild {:builds [{:source-paths ["src/cljs"],
:compiler {
:main "scripts.client"
:output-to "resources/public/js/main.js"
:output-dir "resources/public/js/out"
:asset-path "js/out"
;;:pretty-print true
;;:optimizations :none
}}]}
:main app2.server/app
:ring {:handler app2.server/app :auto-reload? true :auto-refresh? true :reload-paths ["src" "resources"]}
:profiles {
:dev {
:ring {
:nrepl {
:start? true
:port 9000
}
}
}
}
)
This is my server.clj
(ns app2.server
(:use [ring.middleware.resource :only [wrap-resource]]
[ring.middleware.file-info :only [wrap-file-info]]
[ring.middleware.reload :refer [wrap-reload]])
;;(:require app2.repl)
)
(defn handler
[request]
{:status 200}
)
;handling routing "/" -> "/index.html"
(defn wrap-index [handler]
(fn [req]
(println (pr-str req))
(if (= (:uri req) "/")
(handler (assoc req :uri "/index.html"))
(handler req))))
;setting up a simple resource handler for ring
(def app (-> handler
(wrap-resource "public")
(wrap-file-info)
(wrap-index)
(wrap-reload app {:dirs ["src" "resources"]})
))
How can this be accomplished?
I'm used to developing in node and you have tools like browser sync, weinre and supervisor. What are the equivalents in clojure?
I suggest you have a look at figwheel, which lets you do hot reloading of your ClojureScript and CSS in the browser.
There is of course not one good way of setting up your build, but my way to go for languages like SASS etc. is to watch and compile them as a separate process, and have Figwheel watch the generated CSS.
For example, on one of my ClojureScript projects, I had a script file for LESS compilation which used the LESS compiler and the wr utility directly:
#!/usr/bin/env bash
lessc src/styles/main.main.less resources/public/css/main.css --source-map && cp src/styles/*.less resources/public/css
wr "lessc src/styles/main.main.less resources/public/css/main.css --source-map && cp src/styles/*.less resources/public/css" src/**/*.less
Of course you can also use things like Gulp, Webpack - or any tool you're used to.
The alternative is to use Leiningen plugins, see the list here.
I'm new to clojure and I'm trying to wrap my head around live reload in in clojure I want to be able to monitor/ watch all/ any project file and update the browser automatically
So far I have the following
(defproject app2 "0.1.0-SNAPSHOT"
:description "FIXME: write this!"
:url "http://exampl.com/FIXME"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.89"]
[ring "1.5.0"]
[stasis "2.2.0"]
[hiccup "1.0.5"]
[clj-tagsoup "0.3.0"]
[optimus "0.18.5"]
[ring/ring-jetty-adapter "1.5.0"]
[cljs-ajax "0.5.8"]
[enfocus "2.1.1"]]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-ring "0.9.7"]]
:cljsbuild {:builds [
{:id "dev"
:incremental true
:source-paths ["src/cljs"]
:compiler {
:main "scripts.client"
:output-to "resources/public/js/main.js"
:output-dir "resources/public/js/out"
:asset-path "js/out"
}}]}
:aliases {
"start-dev" ["pdo" ["cljsbuild" "auto"] ["ring" "server-headless"]]
}
:source-paths ["src"]
:resource-paths ["resources"]
:main app2.server
:repl-options {
:prompt (fn [ns] (str "your command for <" ns ">, master? " ))
:welcome (println "Welcome to the magical world of the repl!")
:init-ns app2.hawk
:init (app2.hawk/init)
:caught clj-stacktrace.repl/pst+
:skip-default-init false
:host "0.0.0.0"
:port 9000
:timeout 40000
}
:ring {
:init app2.hawk/init
:handler app2.server/app
:auto-reload? true :auto-refresh? true :reload-paths ["resources/public"]
:refresh-paths ["resrouces/public"]
}
:profiles {
:dev {
:repl-options {
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]
}
:source-paths ["src"]
:ring {
:nrepl {
:start? true
:port 9000
:host "0.0.0.0"
}
}
:dependencies [
[ring-refresh "0.1.1"]
[http-kit "2.0.0"]
[org.clojure/tools.nrepl "0.2.11"]
[com.cemerick/piggieback "0.2.1"]
[hawk "0.2.10"]
]
:plugins [
[lein-pdo "0.1.1"]
]
}
}
)
for my handler I have
(ns app2.server
(:use [ring.middleware.resource :only [wrap-resource]]
[ring.middleware.file-info :only [wrap-file-info]]
[ring.middleware.file :only [wrap-file]]
[ring.middleware.reload :refer [wrap-reload]]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.refresh :refer [wrap-refresh]]
[ring.util.response :refer [file-response]]
)
(defn wrap-utf-8
"This function works around the fact that Ring simply chooses the default JVM
encoding for the response encoding. This is not desirable, we always want to
send UTF-8."
[handler]
(fn [request]
(when-let [response (handler request)]
(if (.contains (get-in response [:headers "Content-Type"]) ";")
response
(if (string? (:body response))
(update-in response [:headers "Content-Type"] #(str % "; charset=utf-8"))
response)))))
(defn handler [request]
(file-response (:uri request) {:root "resources/public"}))
(def app (-> handler
wrap-content-type
wrap-reload
wrap-refresh []
wrap-utf-8))
And then I am running hawk from repl
(ns app2.hawk
(:require [hawk.core :as hawk])
(:require [app2.server :refer [export-pages]]))
(defn init
[]
(export-pages)
(hawk/watch! [{:paths ["src/app2/templates" "resources/templates"]
:filter hawk/modified?
:handler (fn [ctx e]
(export-pages) ;; i'm compiling html pages dynamically to the server root but how do I then notify browser than html has changed? can i force server to reload from here?
ctx)}]))
If you don't mind using Boot instead of Leiningen, the system project will give you all the reload goodness you can expect from a Lisp, with plenty of examples.
Be sure to check the Holy Grail demo, which leverages system, to get started with minimal fuss.
It sounds like you have put together a great setup so far.
and for managing this workflow Figwheel is the logical next step. If you are doing any Clojurescript + clojure-web stuff you almost certainly should start with figwheel, you will have a much more pleasant experience.
I have two files of interest:
build.boot
(set-env!
:source-paths #{"src/clj" "src/cljs" "test/clj"}
:resource-paths #{"html" "target/main.js"}
:dependencies '[[adzerk/boot-cljs "0.0-3308-0"]
[adzerk/boot-cljs-repl "0.1.10-SNAPSHOT"]
[adzerk/boot-reload "0.3.1"]
[adzerk/boot-test "1.0.4"]
[cljsjs/hammer "2.0.4-4"]
[compojure "1.3.1"]
[com.datomic/datomic-pro "0.9.5186"]
[hiccup "1.0.5"]
[org.clojure/clojure "1.7.0-RC1"]
[org.clojure/clojurescript "0.0-3308"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
[org.clojure/test.check "0.7.0"]
[org.omcljs/om "0.8.8"]
[pandeiro/boot-http "0.6.3-SNAPSHOT"]
[ring/ring-devel "1.4.0-RC1"]
[http-kit "2.1.18"]])
(require
'[adzerk.boot-cljs :refer [cljs]]
'[adzerk.boot-cljs-repl :refer [cljs-repl start-repl]]
'[adzerk.boot-reload :refer [reload]]
'[adzerk.boot-test :refer [test]]
'[pandeiro.boot-http :refer [serve]])
(task-options!
cljs {:source-map true
:optimizations :none
:pretty-print true})
(deftask build
"Build an uberjar of this project that can be run with java -jar"
[]
(comp
(cljs)
(aot :namespace '#{vidiot.server})
(pom :project 'vidiot
:version "0.1.0")
(uber)
(jar :main 'vidiot.server)))
and src/clj/vidiot/server.clj
(ns vidiot.server
(:gen-class)
(:require
[compojure.core :refer :all]
[compojure.route :as route]
[hiccup.core :refer :all]
[org.httpkit.server :refer :all]
[ring.middleware.reload :as reload]
[ring.util.response :as response]))
(defonce server (atom nil))
(defroutes all-routes
(GET "/" [] (response/redirect "index.html"))
(GET "/ws" [request]
(with-channel request channel
(on-close
channel
(fn [status]
(println "channel closed: " status)))
(on-receive
channel
(fn [data] ;; echo it back
(send! channel data)))))
(route/files "/" {:root "target"})
(route/not-found (response/response (html [:div#erro "Page Not Found"]))))
(defn -main [& args]
(run-server all-routes {:port 8080}))
Then I,
> boot build
> java -jar target/vidiot-0.1.0.jar
Followed by going to localhost:9090 in my browser, the terminal prints.
java.lang.IllegalArgumentException: No implementation of method: :route-matches of protocol: #'clout.core/Route found for class: clout.core.CompiledRoute
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:554)
at clout.core$eval5590$fn__5591$G__5581__5598.invoke(core.clj:39)
at compojure.core$if_route$fn__5887.invoke(core.clj:40)
at compojure.core$if_method$fn__5879.invoke(core.clj:27)
at compojure.core$routing$fn__5918.invoke(core.clj:127)
at clojure.core$some.invoke(core.clj:2568)
at compojure.core$routing.doInvoke(core.clj:127)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:630)
at compojure.core$routes$fn__5922.invoke(core.clj:132)
at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
I can fix this issue by downgrading :dependencies in build.boot to [compojure "1.1.6"].
So, my question is, why can't I use [compojure "1.3.4"] (the most recent version at this writing) when building my uberjar?
Placing aot task after uber fixes the issue.
(task-options! pom {:project 'my-project
:version "0.1.0"}
jar {:main 'my-project.core}
aot {:namespace '#{my-project.core}})
(deftask build []
(comp (pom)
(uber)
(aot)
(jar)))
I was able to fix this by adding an exclude for the clout folder. It looks like uberjar is unpacking some compiled clout files on top of those compiled from project source. Example from my project:
(comp (cljs :compiler-options {:output-to "js/main.js"})
(aot :namespace '#{zoondka-maps.server zoondka-maps.handler})
(pom :project (symbol (:name project))
:version (:version project))
(uber :exclude (conj pod/standard-jar-exclusions #".*\.html" #"clout/.*"))
(jar :file (str (:name project) ".jar")
:main 'zoondka-maps.server)))
I'm just beginning with clojure and I'm trying to build a small web app. I wanted to try out hiccup but it doesn't seem to be working. My code is below.
Project.clj
(defproject WebTest "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:dependencies [[org.clojure/clojure "1.4.0"]
[compojure "1.1.5"]
[hiccup "1.0.3"]
[org.clojure/java.jdbc "0.2.3"]
[net.sourceforge.jtds/jtds "1.2.4"]
]
:plugins [[lein-ring "0.8.2"]
[lein-idea "1.0.1"]]
:ring {:handler WebTest.handler/app}
:profiles
{:dev {:dependencies [[ring-mock "0.1.3"]]}})
handler.clj
(ns WebTest.handler
(:use compojure.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]
[WebTest.Content :as pages]
[hiccup.core :as templ]))
(defroutes app-routes
(GET "/" [] (templ/html [h1 "Hello world"]))
(GET "/Greeting/:name" [name] (str "<h1>Hello " name "</h1>"))
(GET "/Date/:year/:month/:day" [year month day] (str "<h1>It is " month "/" day "/" year "</h1>"))
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
And the error that I get is
Exception in thread "main" java.io.FileNotFoundException: Could not locate hiccu
p/core/as__init.class or hiccup/core/as.clj on classpath:
at clojure.lang.RT.load(RT.java:432)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
A very long stack trace follows that. Any insight into what I'm doing wrong?
Attempting to require WebTest.Content as you do fails for me, Though the rest works fine if I remove that one:
(ns WebTest.handler
(:use compojure.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]
;[WebTest.Content :as pages]
[hiccup.core :as templ]))
The error you mention would be the case if there where mismatched []s in the :require section of handler.clj's ns form though they are not caused by it as you show it.