
clojure.browser.repl.cljs Maven / Gradle / Ivy
;; Copyright (c) Rich Hickey. All rights reserved.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.
(ns ^{:doc "Receive - Eval - Print - Loop
Receive a block of JS (presumably generated by a ClojureScript compiler)
Evaluate it naively
Print the result of evaluation to a string
Send the resulting string back to the server Loop!"
:author "Bobby Calderwood and Alex Redington"}
clojure.browser.repl
(:require [goog.dom :as gdom]
[goog.object :as gobj]
[goog.array :as garray]
[goog.userAgent.product :as product]
[clojure.browser.net :as net]
[clojure.browser.event :as event]
;; repl-connection callback will receive goog.require('cljs.repl')
;; and monkey-patched require expects to be able to derive it
;; via goog.basePath, so this namespace should be compiled together
;; with clojure.browser.repl:
[cljs.repl]))
(goog-define HOST "localhost")
(goog-define PORT 9000)
(def xpc-connection (atom nil))
(def print-queue (array))
(defn flush-print-queue! [conn]
(doseq [str print-queue]
(net/transmit conn :print str))
(garray/clear print-queue))
(defn repl-print [data]
(.push print-queue (pr-str data))
(when-let [conn @xpc-connection]
(flush-print-queue! conn)))
(set! *print-fn* repl-print)
(set! *print-err-fn* repl-print)
(set! *print-newline* true)
(defn get-ua-product []
(cond
product/SAFARI :safari
product/CHROME :chrome
product/FIREFOX :firefox
product/IE :ie))
(defn evaluate-javascript
"Process a single block of JavaScript received from the server"
[conn block]
(let [result
(try
{:status :success
:value (str (js* "eval(~{block})"))}
(catch :default e
{:status :exception
:ua-product (get-ua-product)
:value (str e)
:stacktrace
(if (.hasOwnProperty e "stack")
(.-stack e)
"No stacktrace available.")}))]
(pr-str result)))
(defn send-result [connection url data]
(net/transmit connection url "POST" data nil 0))
(defn send-print
"Send data to be printed in the REPL. If there is an error, try again
up to 10 times."
([url data]
(send-print url data 0))
([url data n]
(let [conn (net/xhr-connection)]
(event/listen conn :error
(fn [_]
(if (< n 10)
(send-print url data (inc n))
(.log js/console (str "Could not send " data " after " n " attempts.")))))
(net/transmit conn url "POST" data nil 0))))
(def order (atom 0))
(defn wrap-message [t data]
(pr-str {:type t :content data :order (swap! order inc)}))
(defn start-evaluator
"Start the REPL server connection."
[url]
(if-let [repl-connection (net/xpc-connection)]
(let [connection (net/xhr-connection)]
(event/listen connection
:success
(fn [e]
(net/transmit
repl-connection
:evaluate-javascript
(.getResponseText (.-currentTarget e)
()))))
(net/register-service repl-connection
:send-result
(fn [data]
(send-result connection url (wrap-message :result data))))
(net/register-service repl-connection
:print
(fn [data]
(send-print url (wrap-message :print data))))
(net/connect repl-connection
(constantly nil))
(js/setTimeout #(send-result connection url (wrap-message :ready "ready")) 1000))
(js/alert "No 'xpc' param provided to child iframe.")))
(def load-queue nil)
(defn bootstrap
"Reusable browser REPL bootstrapping. Patches the essential functions
in goog.base to support re-loading of namespaces after page load."
[]
;; Monkey-patch goog.provide if running under optimizations :none - David
(when-not js/COMPILED
(set! (.-require__ js/goog) js/goog.require)
;; suppress useless Google Closure error about duplicate provides
(set! (.-isProvided_ js/goog) (fn [name] false))
;; provide cljs.user
(goog/constructNamespace_ "cljs.user")
(set! (.-writeScriptTag__ js/goog)
(fn [src opt_sourceText]
;; the page is already loaded, we can no longer leverage document.write
;; instead construct script tag elements and append them to the body
;; of the page, to avoid parallel script loading enforce sequential
;; load with a simple load queue
(let [loaded (atom false)
onload (fn []
(when (and load-queue (false? @loaded))
(swap! loaded not)
(if (zero? (alength load-queue))
(set! load-queue nil)
(.apply js/goog.writeScriptTag__ nil (.shift load-queue)))))]
(.appendChild js/document.body
(as-> (.createElement js/document "script") script
(doto script
(gobj/set "type" "text/javascript")
(gobj/set "onload" onload)
(gobj/set "onreadystatechange" onload)) ;; IE
(if (nil? opt_sourceText)
(doto script (gobj/set "src" src))
(doto script (gdom/setTextContext opt_sourceText))))))))
;; queue or load
(set! (.-writeScriptTag_ js/goog)
(fn [src opt_sourceText]
(if load-queue
(.push load-queue #js [src opt_sourceText])
(do
(set! load-queue #js [])
(js/goog.writeScriptTag__ src opt_sourceText)))))
;; we must reuse Closure library dev time dependency management, under namespace
;; reload scenarios we simply delete entries from the correct private locations
(set! (.-require js/goog)
(fn [src reload]
(when (= reload "reload-all")
(set! (.-cljsReloadAll_ js/goog) true))
(let [reload? (or reload (.-cljsReloadAll__ js/goog))]
(when reload?
(let [path (gobj/get js/goog.dependencies_.nameToPath src)]
(gobj/remove js/goog.dependencies_.visited path)
(gobj/remove js/goog.dependencies_.written path)
(gobj/remove js/goog.dependencies_.written
(str js/goog.basePath path))))
(let [ret (.require__ js/goog src)]
(when (= reload "reload-all")
(set! (.-cljsReloadAll_ js/goog) false))
ret))))))
(defn connect
"Connects to a REPL server from an HTML document. After the
connection is made, the REPL will evaluate forms in the context of
the document that called this function."
[repl-server-url]
(let [repl-connection
(net/xpc-connection
{:peer_uri repl-server-url})]
(swap! xpc-connection (constantly repl-connection))
(net/register-service repl-connection
:evaluate-javascript
(fn [js]
(net/transmit
repl-connection
:send-result
(evaluate-javascript repl-connection js))))
(net/connect repl-connection
(constantly nil)
(fn [iframe]
(set! (.-display (.-style iframe))
"none")))
(bootstrap)
repl-connection))
© 2015 - 2025 Weber Informatics LLC | Privacy Policy