Skip to content

Commit

Permalink
add basic browser-repl util
Browse files Browse the repository at this point in the history
  • Loading branch information
thheller committed Feb 28, 2018
1 parent a4af72f commit acc6808
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 31 deletions.
7 changes: 7 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@
{:java-opts ^:replace []
:dependencies
[]
:repl-options
{:nrepl-middleware
[shadow.cljs.devtools.server.nrepl/cljs-load-file
shadow.cljs.devtools.server.nrepl/cljs-eval
shadow.cljs.devtools.server.nrepl/cljs-select
;; required by some tools, not by shadow-cljs.
cemerick.piggieback/wrap-cljs-repl]}
:source-paths
["src/dev"
"src/gen"
Expand Down
100 changes: 76 additions & 24 deletions src/main/shadow/cljs/devtools/api.clj
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
(ns shadow.cljs.devtools.api
(:refer-clojure :exclude (compile test))
(:require [clojure.core.async :as async :refer (go <! >! >!! <!! alt!!)]
[clojure.java.io :as io]
[clojure.tools.logging :as log]
[clojure.pprint :refer (pprint)]
[shadow.runtime.services :as rt]
[shadow.build :as build]
[shadow.build.api :as build-api]
[shadow.build.node :as node]
[shadow.build.npm :as npm]
[shadow.build.classpath :as cp]
[shadow.build.babel :as babel]
[shadow.cljs.devtools.server.worker :as worker]
[shadow.cljs.devtools.server.util :as util]
[shadow.cljs.devtools.server.common :as common]
[shadow.cljs.devtools.config :as config]
[shadow.cljs.devtools.errors :as e]
[shadow.cljs.devtools.server.supervisor :as super]
[shadow.cljs.devtools.server.repl-impl :as repl-impl]
[shadow.cljs.devtools.server.runtime :as runtime]
[shadow.build.output :as output]
[shadow.build.log :as build-log]
[clojure.set :as set]))
(:require
[clojure.core.async :as async :refer (go <! >! >!! <!! alt!!)]
[clojure.java.io :as io]
[clojure.tools.logging :as log]
[clojure.pprint :refer (pprint)]
[clojure.java.browse :refer (browse-url)]
[clojure.set :as set]
[shadow.runtime.services :as rt]
[shadow.build :as build]
[shadow.build.api :as build-api]
[shadow.build.node :as node]
[shadow.build.npm :as npm]
[shadow.build.classpath :as cp]
[shadow.build.babel :as babel]
[shadow.cljs.devtools.server.worker :as worker]
[shadow.cljs.devtools.server.util :as util]
[shadow.cljs.devtools.server.common :as common]
[shadow.cljs.devtools.config :as config]
[shadow.cljs.devtools.errors :as e]
[shadow.cljs.devtools.server.supervisor :as super]
[shadow.cljs.devtools.server.repl-impl :as repl-impl]
[shadow.cljs.devtools.server.runtime :as runtime]
[shadow.build.output :as output]
[shadow.build.log :as build-log]))

;; nREPL support

Expand Down Expand Up @@ -255,7 +257,7 @@

(defn repl
([build-id]
(repl build-id {}))
(repl build-id {}))
([build-id {:keys [stop-on-eof] :as opts}]
(if *nrepl-active*
(nrepl-select build-id)
Expand All @@ -270,6 +272,7 @@
(when stop-on-eof
(super/stop-worker supervisor build-id))))))))

;; FIXME: should maybe allow multiple instances
(defn node-repl
([]
(node-repl {}))
Expand All @@ -281,7 +284,56 @@
(or (super/get-worker supervisor :node-repl)
(repl-impl/node-repl* app opts))]

(repl :node-repl {:stop-on-eof true})
(repl :node-repl)
)))

;; FIXME: should maybe allow multiple instances
(defn start-browser-repl* [{:keys [config supervisor] :as app}]
(let [cfg
{:build-id :browser-repl
:target :browser
:output-dir (str (:cache-root config) "/builds/browser-repl/js")
:asset-path "/cache/browser-repl/js"
:modules
{:repl {:entries '[cljs.core]}}
:devtools
{:autoload false
:async-require true}}]

(super/start-worker supervisor cfg)))

(defn browser-repl
([]
(browser-repl {}))
([{:keys [verbose open] :as opts}]
(let [{:keys [supervisor config http] :as app}
(runtime/get-instance!)

worker
(or (super/get-worker supervisor :browser-repl)
(let [worker
(start-browser-repl* app)

out-chan
(-> (async/sliding-buffer 10)
(async/chan))]

(go (loop []
(when-some [msg (<! out-chan)]
(try
(util/print-worker-out msg verbose)
(catch Exception e
(prn [:print-worker-out-error e])))
(recur)
)))

(worker/watch worker out-chan)
(worker/compile! worker)

(browse-url (str "http" (when (:ssl http) "s") "://localhost:" (:port http) "/browser-repl-js"))
worker))]

(repl :browser-repl)
)))

(defn dev*
Expand Down
4 changes: 2 additions & 2 deletions src/main/shadow/cljs/devtools/cli.clj
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
(api/with-runtime
(do-clj-run config opts))

(contains? #{:watch :node-repl :cljs-repl :clj-repl :server} action)
(contains? #{:watch :node-repl :browser-repl :cljs-repl :clj-repl :server} action)
(server/from-cli action builds options)
)))

Expand Down Expand Up @@ -168,7 +168,7 @@
;;
;; actions that may potentially block
;;
(contains? #{:watch :node-repl :cljs-repl :clj-repl :server :clj-eval :clj-run} action)
(contains? #{:watch :node-repl :browser-repl :cljs-repl :clj-repl :server :clj-eval :clj-run} action)
(blocking-action config opts)

:else
Expand Down
2 changes: 2 additions & 0 deletions src/main/shadow/cljs/devtools/cli_opts.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
:release

:node-repl
:browser-repl

:cljs-repl
:clj-repl
:clj-eval
Expand Down
9 changes: 9 additions & 0 deletions src/main/shadow/cljs/devtools/client/browser.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,13 @@
(set! (.-onclose s) (fn [e]))
(.close s)
(vreset! socket-ref nil))

;; for /browser-repl in case the page is reloaded
;; otherwise the browser seems to still have the websocket open
;; when doing the reload
(js/window.addEventListener "beforeunload"
(fn []
(when-let [s @socket-ref]
(.close s))))

(ws-connect))
3 changes: 3 additions & 0 deletions src/main/shadow/cljs/devtools/server.clj
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@
:node-repl
(api/node-repl options)

:browser-repl
(api/browser-repl options)

;; makes this a noop if server is already running
:server
(if already-running?
Expand Down
110 changes: 105 additions & 5 deletions src/main/shadow/cljs/devtools/server/web.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require
[clojure.string :as str]
[hiccup.core :refer (html)]
[hiccup.page :refer (html5)]
[shadow.http.router :as http]
[shadow.server.assets :as assets]
[shadow.cljs.devtools.server.web.common :as common]
Expand All @@ -11,7 +12,10 @@
[clojure.java.io :as io]
[clojure.edn :as edn]
[ring.middleware.file :as ring-file]
[ring.middleware.file-info :as ring-file-info]))
[ring.middleware.file-info :as ring-file-info]
[shadow.cljs.devtools.server.supervisor :as super]
[shadow.cljs.devtools.server.worker :as worker]
[shadow.cljs.devtools.api :as api]))

(defn index-page [{:keys [dev-http] :as req}]
(common/page-boilerplate req
Expand Down Expand Up @@ -47,6 +51,12 @@
(edn/read-string (slurp file)))
)))))

(defn no-cache! [res]
(update-in res [:headers] assoc
"cache-control" "max-age=0, no-cache, no-store, must-revalidate"
"pragma" "no-cache"
"expires" "0"))

(defn serve-cache-file
[{:keys [config ring-request] :as req}]
(let [root (io/file (:cache-root config "target/shadow-cljs") "builds")
Expand All @@ -55,12 +65,101 @@
(or (some->
(ring-file/file-request ring-req root {})
(ring-file-info/file-info-response ring-req {"transit" "application/json"})
(update-in [:headers] assoc
"cache-control" "max-age=0, no-cache, no-store, must-revalidate"
"pragma" "no-cache"
"expires" "0"))
(no-cache!))
(common/not-found req))))

(def logo-svg
(let [s-path
"M247.183941,141.416413 C247.183941,74.7839971 148.383423,78.9723529 148.383423,141.416413 C148.383423,203.860473 265.090698,171.864644 265.090698,248.900057 C265.090698,325.93547 135,325.851749 135,251.708304"]

(html
[:svg
{:id "shadow-cljs-logo"
:version "1.1"
:viewBox "0 0 400 400"
:style {:display "block"}
:height "200px"
:width "200px"}
[:title "shadow-cljs"]
[:defs
[:mask#shadow-cljs-logo-mask {:fill "#fff"}
[:circle {:r "200" :cy "200" :cx "200"}]]]
[:g
{:fill-rule "evenodd"
:fill "none"
:stroke-width "0"
:stroke "none"
:mask "url(#shadow-cljs-logo-mask)"}

[:g.circles
[:circle.blue {:r "200" :cy "200" :cx "200" :fill "#4F80DF"}]
[:circle.light-blue {:r "71.5" :cy "200" :cx "370" :fill "#89B4FF"}]
[:circle.dark-green {:r "180" :cy "360" :cx "60" :fill "#40B400"}]
[:circle.light-green {:r "129" :cy "320" :cx "280" :fill "#76E013"}]]

;; S shadow
[:g {:transform "translate(10,10)"}
[:path
{:stroke-linecap "square"
:stroke-width "16"
:stroke "#aaa"
:d s-path}]]
;; S
[:path
{:stroke-linecap "square"
:stroke-width "16"
:stroke "#FFFFFF"
:d s-path}]]])))

(defn browser-repl-js [{:keys [config supervisor] :as req}]
(let [{:keys [state-ref] :as worker}
(or (super/get-worker supervisor :browser-repl)
(-> (api/start-browser-repl* req)
(worker/compile)))]

(worker/sync! worker)

;; FIXME: technically when loading this page the worker should be reset/recompiled
;; since all previous JS state is lost

(-> (cond
(nil? worker)
{:status 404
:header {"content-type" "text/plain"}
:body "browser-repl not running."}

;; test twice in case the websocket disconnect happened concurrently (ie. reload)
(and (not (empty? (get-in @state-ref [:eval-clients])))
(do (Thread/sleep 500)
(not (empty? (get-in @state-ref [:eval-clients])))))
{:status 503
:header {"content-type" "text/plain"}
:body "browser-repl already open elsewhere."}

:else
{:status 200
:headers {"content-type" "text/html; charset=utf-8"}
:body
(html5
{:lang "en"}
[:head [:title "shadow-cljs browser-repl"]]
[:body {:style "font-family: monospace; font-size: 14px;"}

[:div#app]
[:div#root]
[:div {:style "padding-top: 20px; margin: 0 auto; width: 300px;"}
[:div {:style "text-align: center;"}
[:h1 "shadow-cljs"]
logo-svg]

[:p "Code entered in a browser-repl prompt will be evaluated here."]

[:pre {:style "display: block; padding: 10px; border: 1px solid #ccc; background-color: #eee;"}
"shadow-cljs browser-repl"]]

[:script {:src "/cache/browser-repl/js/repl.js" :defer true}]])})
(no-cache!))))

(defn root [req]
(http/route req
(:GET "" index-page)
Expand All @@ -70,4 +169,5 @@
(:ANY "^/ws" ws/process-ws)
(:ANY "^/worker" ws/process-req)
(:GET "^/cache" serve-cache-file)
(:GET "/browser-repl-js" browser-repl-js)
common/not-found))
1 change: 1 addition & 0 deletions src/main/shadow/cljs/devtools/server/web/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
[shadow.cljs.devtools.server.util :as server-util]
[shadow.cljs.devtools.api :as api]
[shadow.cljs.util :as util]
[hiccup.page :refer (html5)]
[clojure.java.io :as io]))

(defn index-page [req]
Expand Down

0 comments on commit acc6808

Please sign in to comment.