Self-hosted ClojureScript vs. cljs.user namespace

419 views
Skip to first unread message

J David Eisenberg

unread,
Feb 25, 2016, 12:17:38 PM2/25/16
to ClojureScript
I've written a minimal test for self-hosted ClojureScript; when running it with lein figwheel I can enter a definition like this in the text area:

(defn cube [n] (* n n n))

and it compiles just fine. When running as a stand-alone web page, entering the same definition in the text area gives me this error:

#error {:message "ERROR", :data {:tag :cljs/analysis-error}, :cause #object[TypeError TypeError: cljs.user is undefined]}

If I type (ns cljs.user) in the text area and compile/evaluate it, and then re-type the definition, it works great.

You can see the standalone version at http://langintro.com/clojurescript/selfhost/ (there's a link to a tarfile for the project on that page).

Here is my project.clj file (Sorry, I can't seem to do rich formatting when making a post):
=============
(defproject selfhost "0.1.0-SNAPSHOT"
:description "Small test of self hosting"
:url "http://langintro.com/clojurescript/selfhost/"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}

:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.7.228"]
[org.clojure/core.async "0.2.374"]
[org.clojure/tools.reader "0.10.0"]
[cljsjs/codemirror "5.11.0-0"]
[figwheel "0.5.0-3"]]

:plugins [[lein-cljsbuild "1.1.1"] [lein-figwheel "0.5.0-3"]]

:source-paths ["src"]

:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]

:cljsbuild {:builds
[{:id "dev"
:source-paths ["src" "dev_src"]

:figwheel {:on-jsload "selfhost.core/on-js-reload"}

:compiler {:main selfhost.dev
:asset-path "js/compiled/out"
:output-to "resources/public/js/compiled/selfhost.js"
:output-dir "resources/public/js/compiled/out"
:source-map true
:source-map-timestamp true
:optimizations :none
:cache-analysis true}}
;;
;; This "min" build doesn't minimize; I did this to make as
;; little difference as possible between the dev and non-dev
;; environments.

{:id "min"
:source-paths ["src"]
:compiler {:output-to "resources/public/js/compiled/selfhost.js"
:main selfhost.core
:optimizations :none
:source-map true
:source-map-timestamp true
:cache-analysis true}}]}

:figwheel {
:css-dirs ["resources/public/css"] ;; watch and update CSS
})
===========

And here is core.cljs:

==========
(ns ^:figwheel-always selfhost.core
(:require [clojure.string :as str]
[cljs.tools.reader :refer [read-string]]
[cljs.js :refer [empty-state eval-str compile-str js-eval analyze-str]]))

(enable-console-print!)

;; define your app data so that it doesn't get over-written on reload
(defonce compiler-state (empty-state))

(defn do-evaluation [src]
(eval-str compiler-state src nil
{:eval js-eval
:source-map true
:context :expr}
(fn [resultmap] (do
(set! (.-innerHTML (.getElementById js/document "result")) (:value resultmap))
(set! (.-innerHTML (.getElementById js/document "error")) (:error resultmap))))))

(defn do-compilation [evt]
(let [id (.-id (.-target evt))
src (.-value (.getElementById js/document "src"))
output-field (.getElementById js/document "js")]
(print src)
(compile-str compiler-state src nil {:source-map true}
(fn [resultmap]
(if (contains? resultmap :value)
(do
(set! (.-innerHTML (.getElementById js/document "js")) (:value resultmap))
(do-evaluation src))
(set! (.-innerHTML (.getElementById js/document "js")) (:error resultmap)))))))

(defn setup []
(do
(.removeEventListener (.getElementById js/document "compile_button") "click" do-compilation)
(.addEventListener (.getElementById js/document "compile_button") "click" do-compilation)
(print "Setup complete.")))

(defn on-js-reload []
;; optionally touch your app-state to force rerendering depending on
;; your application
;; (swap! app-state update-in [:__figwheel_counter] inc)
)

(set! (.-onload js/window) setup)
=============

Mike Fikes

unread,
Feb 25, 2016, 12:48:36 PM2/25/16
to ClojureScript
Yeah, cljs.js defaults to cljs.user, but generally self-host environments ensure that any namespace they are issuing forms in are defined.

If you have an `in-ns` REPL special, a user could switch to an entirely different namespace, and then you would pass in :ns, but you'd still need to ensure that the namespace exists before evaluating forms in it. In other words, cljs.user is not special in any way—it is just the default that cljs.js will assume if you don't specify anything.

J David Eisenberg

unread,
Feb 25, 2016, 12:52:09 PM2/25/16
to ClojureScript
On Thursday, February 25, 2016 at 9:48:36 AM UTC-8, Mike Fikes wrote:
> Yeah, cljs.js defaults to cljs.user, but generally self-host environments ensure that any namespace they are issuing forms in are defined.
>
> If you have an `in-ns` REPL special, a user could switch to an entirely different namespace, and then you would pass in :ns, but you'd still need to ensure that the namespace exists before evaluating forms in it. In other words, cljs.user is not special in any way—it is just the default that cljs.js will assume if you don't specify anything.

OK; I just wanted to be sure I'm not doing something stupidly wrong. I can have my code do a surreptitious eval-str() of "(ns cljs.user)" when the page loads to solve the problem.

Mike Fikes

unread,
Feb 25, 2016, 12:57:10 PM2/25/16
to ClojureScript
Yep, or you may be able to get away with putting something in your source, like this:

https://github.com/clojure/clojurescript/blob/e531c34e04adc815a6c25c8d2499465296ca290d/src/test/self/self_host/test.cljs#L8

Reply all
Reply to author
Forward
0 new messages