ClojureScript is a robust, practical, and fast programming language with a set of useful features that together form a simple, coherent, and powerful tool.
ClojureScript is a compiler for Clojure that targets
JavaScript.
In LiveCodes, it runs in the browser using the official
ClojureScript compiler, self-hosted
in the compiler worker. The compiler is provided by the
@live-codes/cljs-selfhosted-compiler
package.
The clojurescript, cljs, clj, cljc, edn and clojure aliases currently use the
Cherry-based ClojureScript compiler and will switch to this self-hosted compiler in the future.
Use the clojurescript-cherry alias to keep using Cherry.
Language Info
Name
clojurescript-selfhosted
Aliases
clojurescript, cljs-selfhosted, cljs, clj, cljc, edn, clojure
Editor
script
Compiler
The official ClojureScript compiler, self-hosted in the compiler worker.
Version
@live-codes/cljs-selfhosted-compiler: v0.3.0
Usage
LiveCodes compiles ClojureScript to JavaScript in the browser using the official compiler,
self-hosted in the compiler worker. This provides real cljs.core semantics and real analyzer
diagnostics.
(:require ...) can resolve the namespaces that are bundled with the compiler:
cljs.core
clojure.string
clojure.set
clojure.walk
clojure.edn
cljs.pprint
clojure.core.reducers
clojure.core.protocols
clojure.data
clojure.zip
clojure.datafy
cljs.math
cljs.proxy
cljs.stacktrace
cljs.test
Three reader namespaces are also require-able, because they are already present in the page runtime:
cljs.reader
cljs.tools.reader
cljs.tools.reader.edn
They (and the cljs.tools.reader.reader-types and cljs.tools.reader.impl.* namespaces under them)
are already present in the page runtime because clojure.edn pulls them in transitively, so they
cost nothing extra; they are listed here because they are require-able and work.
defmacro works and is evaluated at compile time. Source maps are on by default.
cljs.test's macros expand in the compiler worker like any other macro. deftest, is,
testing and run-tests come from its cljs/test.cljc macros half, and are expands through
clojure.template, which is served to the worker next to it. The compiled test file is page
JavaScript that calls cljs.test's runtime (cljs/test.cljs), which the page provides alongside
the other bundled namespaces.
This example demonstrates usage and JavaScript interoperability:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Preact
- Web Components
import { createPlayground } from 'livecodes';
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
return (<LiveCodes {...options} />);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import LiveCodes from 'livecodes/svelte';
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
</script>
<LiveCodes {...options} />
import LiveCodes from 'livecodes/solid';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
return (<LiveCodes {...options} />);
}
import LiveCodes from 'livecodes/preact';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
}
};
return (<LiveCodes {...options} />);
}
<live-codes></live-codes>
<script type="module">
import "livecodes/web-components";
const playground = document.querySelector("live-codes");
playground.config = {
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<div class=\"container\">\n <h1>Hello, <span id=\"title\">World</span>!</h1>\n <p>You clicked <span id=\"counter\">0</span> times.</p>\n <button id=\"counter-button\">Click me</button>\n</div>\n"
},
"style": {
"language": "css",
"content": ".container,\n.container button {\n text-align: center;\n font: 1em sans-serif;\n}\n"
},
"script": {
"language": "clojurescript-selfhosted",
"content": "(ns demo)\n\n(def title \"ClojureScript\")\n\n(set! (.-innerText (js/document.querySelector \"#title\")) title)\n\n(def counter (atom 0))\n\n;; you can use JS interop to wire up DOM events\n(.addEventListener\n (js/document.querySelector \"#counter-button\")\n \"click\"\n (fn [_]\n (swap! counter inc)\n (set! (.-innerText (js/document.querySelector \"#counter\")) (str @counter))))\n\n;; check console\n(js/console.log (str \"Hello, \" title \"!\"))\n"
}
};
</script>
npm Imports
npm packages are not supported, unlike the Cherry-based ClojureScript,
which imports npm packages as ES modules. For example, (:require ["react" :as React]) fails with
No such namespace. Use the Cherry-based ClojureScript for
npm-dependent code.
Starter Template
https://livecodes.io/?template=clojurescript-selfhosted
Links