diff --git a/.gitignore b/.gitignore index 2b15657..e26d706 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ gh-pages/ /dist /.clj-kondo/.cache /.clj-kondo/rewrite-clj +/plugins/demo/resources/public/js/ +.portal +resources/public/test/scratch.html diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d51c5..b2fff67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,103 @@ # Changelog +[Scittle](https://github.com/babashka/scittle): execute Clojure(Script) directly from browser script tags via SCI! + + + + + + + + +- [#114](https://github.com/babashka/scittle/issues/114): Enable source maps ([@jeroenvandijk](https://github.com/jeroenvandijk)) +- [#140](https://github.com/babashka/scittle/issues/140): Enable customizing the nrepl websocket port ([@PEZ](https://github.com/PEZ)) + +## v0.7.28 (2025-09-13) + +- [#137](https://github.com/babashka/scittle/issues/137): fix JS interop with reserved JS keyword (incorrectly munged) by bumping SCI + +## v0.7.27 (2025-08-21) + +- [#95](https://github.com/babashka/scittle/issues/121): support string requires + of `globalThis` js deps ([@chr15m](https://github.com/chr15m)). See + [docs](https://github.com/babashka/scittle/blob/main/doc/js-libraries.md). +- Potentially breaking: `(.-foo-bar {})` now behaves as `{}.foo_bar`, i.e. the property or method name is munged. + +## v0.7.26 (2025-08-20) + +- [#121](https://github.com/babashka/scittle/issues/121): add `cjohansen/dataspex` plugin ([@jeroenvandijk](https://github.com/jeroenvandijk)) +- [#118](https://github.com/babashka/scittle/issues/118): add `goog.string/format` ([@jeroenvandijk](https://github.com/jeroenvandijk)) +- Support alternative `(set! #js {} -a 1)` CLJS syntax (by bumping SCI) +- Add source maps to distribution +- Add dev versions of all modules in the `dev` folder of the distribution + a `dev/scitte.cljs-devtools.js` module + +## v0.7.23 (2025-06-18) + +- [#107](https://github.com/babashka/scittle/issues/107): add `replicant` plugin ([@jeroenvandijk](https://github.com/jeroenvandijk)) +- [#102](https://github.com/babashka/scittle/issues/102): add `applied-science/js-interop` plugin ([@chr15m](https://github.com/chr15m)) +- [#105](https://github.com/babashka/scittle/issues/105): add `goog.string/htmlEscape` ([@ikappaki](https://github.com/ikappaki) ) +- [#113](https://github.com/babashka/scittle/issues/113): add `unchecked-set` and `unchecked-get` + +## v0.6.22 (2024-12-19) + +- [#99](https://github.com/babashka/scittle/issues/99): make `js/import` work + +## v0.6.20 (2024-11-24) + +- [#55](https://github.com/babashka/scittle/issues/55): create gh-pages dir before using. +- [#89](https://github.com/babashka/scittle/issues/89): allow `evaluate_script_tags` to specify individual scripts. +- [#87](https://github.com/babashka/scittle/issues/87): prod build on fresh checkout fails + +## v0.6.19 (2024-10-08) + +- Add `cljs.pprint/code-dispatch` and `cljs.pprint/with-pprint-dispatch` + +## v0.6.18 (2024-04-30) + +- [#77](https://github.com/babashka/scittle/issues/77): make dependency on browser (`js/document`) optional so scittle can run in webworkers, Node.js, etc. + +## v0.6.17 (2024-04-22) + +- [#69](https://github.com/babashka/scittle/issues/69): executing script tag with src + whitespace doesn't work +- [#72](https://github.com/babashka/scittle/issues/72): add clojure 1.11 functions like `update-vals` +- [#75](https://github.com/babashka/scittle/issues/75): Support reader conditionals in source code + +## v0.6.16 (2023-05-04) + +- [#58](https://github.com/babashka/scittle/issues/58): build system for creating scittle distribution with custom libraries. See [plugins/demo](plugins/demo). +- Use `window.location.hostname` for WebSocket connection instead of hardcoding `"localhost"` ([@pyrmont](https://github.com/pyrmont)) +- Upgrade `sci.configs` to `"33bd51e53700b224b4cb5bda59eb21b62f962745"` +- Update nREPL implementation: implement `eldoc` (`info`, `lookup`) ([@benjamin-asdf](https://github.com/benjamin-asdf)) + +## v0.6.15 (2023-01-05) + +- Fix destructuring in `defmethod` (by upgrading SCI) + +## v0.5.13 (2022-12-23) + +- Fix `cljs.pprint` plugin + +## v0.5.12 (2022-12-23) + +- Fix `reagent` `with-let` macro with advanced compiled builds +- Upgrade promesa and shadow-cljs +- Fix `#queue` literal +- SCI: performance improvements + +## v0.4.11 (2022-11-23) + +- Add `scittle.re-frame` plugin. This gives access to the + [re-frame](https://github.com/day8/re-frame) library. +- Fix for [44](https://github.com/babashka/scittle/issues/44): Honoring `SCITTLE_NREPL_WEBSOCKET_PORT` in `scittle.nrepl` +- Add all public vars of `cljs-ajax` `ajax.core` +- Upgrade several built-in libraries + +## v0.3.10 + +- Add `scittle.promesa.js` plugin. This gives access to the [promesa](https://cljdoc.org/d/funcool/promesa/8.0.450/doc/user-guide) library. +- Add `scittle.pprint.js` plugin. This gives access to [cljs.pprint](https://cljs.github.io/api/cljs.pprint/). +- Improve error messages + ## v0.2.8 - Upgrade to SCI 0.3.1 diff --git a/README.md b/README.md index d4095f0..01e1607 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ babashka or Clojure JVM): ``` clojure (require '[babashka.http-server :as http]) -(http/serve {:port 1341 :dir "resoures/public"} +(http/serve {:port 1341 :dir "resources/public"}) @(promise) ;; wait until process is killed ``` @@ -29,6 +29,10 @@ babashka or Clojure JVM): See [doc/nrepl](doc/nrepl). +### Service worker + +See [doc/serviceworker.md](doc/serviceworker.md). + ## Tasks Run `bb tasks` to see all available tasks: @@ -46,6 +50,7 @@ release Updates Github pages with new release build. ## Credits Idea by Arne Brasseur a.k.a [plexus](https://github.com/plexus). +Name by Alessandra Sierra (the name occurs first in [this](https://stuartsierra.com/2019/12/21/clojure-start-time-in-2019/) blog post). ## License diff --git a/bb.edn b/bb.edn index 9a89e71..f76e437 100644 --- a/bb.edn +++ b/bb.edn @@ -1,21 +1,25 @@ {:deps {io.github.babashka/sci.nrepl #_{:local/root "../sci.nrepl"} - {:git/sha "c14b5b4ef4390ff206cdb71f763f327799f5e853"} + {:git/sha "2f8a9ed2d39a1b09d2b4d34d95494b56468f4a23"} io.github.babashka/http-server - {:git/sha "b38c1f16ad2c618adae2c3b102a5520c261a7dd3"}} + {:git/sha "b38c1f16ad2c618adae2c3b102a5520c261a7dd3"} + io.github.scittle/build + {:local/root "build"}} :tasks - {:requires ([babashka.fs :as fs] + {:requires ([scittle.build :as build] + [babashka.fs :as fs] [cheshire.core :as json] [babashka.process :as p :refer [process]]) clean {:doc "Start from clean slate." - :task (do (run! fs/delete (fs/list-dir (fs/file "resources" "public" "js") "**.*")) + :task (do (fs/delete-tree (fs/file "resources" "public" "js")) (fs/delete-tree ".cpcache") (fs/delete-tree ".shadow-cljs"))} shadow:watch {:doc "Development build. Starts webserver and watches for changes." - :task (clojure "-M:dev -m shadow.cljs.devtools.cli watch main")} + :task (build/build {:action "watch" + :args *command-line-args*})} http-server {:doc "Starts http server for serving static files" :requires ([babashka.http-server :as http]) @@ -33,35 +37,40 @@ (deref (promise)))} prod {:doc "Builds production artifacts." - :task (clojure {:extra-env {"SCI_ELIDE_VARS" "true"}} - "-M:dev -m shadow.cljs.devtools.cli release main")} + :task (build/build {}) + :depends [clean]} dist {:doc "Prepare dist folder for npm package" :depends [prod] :task (do (fs/delete-tree "dist") - (fs/create-dirs "dist") - (run! (fn [f] (fs/copy f "dist")) - (fs/glob "resources/public/js" "*.js")))} + (fs/create-dirs "dist/dev") + (run! (fn [f] (fs/copy f "dist" {:replace-existing true})) + (fs/glob "resources/public/js" "*.{js,js.map}")) + (run! (fn [f] (fs/copy f "dist/dev" {:replace-existing true})) + (fs/glob "resources/public/js/dev" "*.{js,js.map}")))} bump-version {:doc "Bumps package.json and pushes new git tag" :task (do (shell "npm version patch") (shell "git push --atomic origin main" (str "v" (:version (json/parse-string (slurp "package.json") true)))))} - npm-publish {:doc "Updates Github pages with new release build." + npm-publish {:doc "Updates NPM ibrary" :task (do (run 'dist) (run 'bump-version) (shell "npm publish"))} - gh-pages {:doc "Updates Github pages with new release build." - :task (shell "script/release.clj")} - - replace-version {:doc "Ported from bash one-liners. Expects two versions. TODO: port to Clojure." + replace-version {:doc "Ported from bash one-liners. Expects two versions. + TODO: port to Clojure. + TODO: skip changelog.md + " :task (let [[prev next] *command-line-args*] (-> (process ["bash" "-c" - (format "rg %s --files-with-matches | xargs sed -i '' 's/%s/%s/g'" + (format "rg %s --files-with-matches -g '!/CHANGELOG.md' | xargs sed -i '' 's/%s/%s/g'" prev prev next)] {:inherit true}) - p/check))}}} + p/check))} + + gh-pages {:doc "Updates Github pages with new release build." + :task (shell "script/release.clj")}}} diff --git a/build/deps.edn b/build/deps.edn new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/build/deps.edn @@ -0,0 +1 @@ +{} diff --git a/build/src/scittle/build.clj b/build/src/scittle/build.clj new file mode 100644 index 0000000..02382e5 --- /dev/null +++ b/build/src/scittle/build.clj @@ -0,0 +1,83 @@ +(ns scittle.build + "Provides bb tasks for building and releasing scittle" + (:require + [babashka.classpath :as classpath] + [babashka.fs :as fs] + [babashka.tasks :refer [clojure]] + [clojure.edn :as edn] + [clojure.string :as str])) + +(defn- feature-files + [] + (filter fs/exists? + (map (fn [d] + (fs/file d "scittle_plugin.edn")) + (classpath/split-classpath (classpath/get-classpath))))) + +(defn- read-configs + [files] + (->> files + (mapcat (comp edn/read-string slurp str)))) + +(defn- build-cmd [cmd scittle-dir] + (let [files (feature-files) + feature-configs (read-configs files) + ;; Each ./src/scittle_plugin.edn has a ./deps.edn + feature-dirs (map (comp fs/parent fs/parent) files) + cmd' (if (seq files) + (format "-Sdeps '%s' %s" + {:deps + (merge (into {} + (map (fn [dir] + [(symbol (str (fs/file-name dir) "/deps")) + {:local/root (str dir)}]) + feature-dirs)) + {'scittle/deps {:local/root scittle-dir}})} + cmd) + cmd)] + (when (seq feature-configs) + (println "Building features:" (str/join ", " (map :name feature-configs)) "...")) + (if (seq feature-configs) + (apply str cmd' + (map (fn [m] (format " --config-merge '%s'" (pr-str (:shadow-config m)))) + feature-configs)) + cmd'))) + +(defn- build* + [cmd] + (let [building-outside-scittle? (not (fs/exists? "shadow-cljs.edn")) + scittle-dir (when building-outside-scittle? + (->> (classpath/get-classpath) + classpath/split-classpath + ;; Pull out scittle from local/root or git/url + (some #(when (re-find #"(scittle/[0-9a-f]+|scittle)/src" %) %)) + fs/parent))] + (when building-outside-scittle? + (fs/copy (fs/file scittle-dir "shadow-cljs.edn") "shadow-cljs.edn")) + (let [cmd (build-cmd cmd (str scittle-dir))] + (println "> clojure" cmd) + (clojure {:extra-env {"SCI_ELIDE_VARS" "true"}} cmd)) + (when building-outside-scittle? + (fs/delete "shadow-cljs.edn")))) + +(defn build + "Build scittle shadow builds using clojure cmd and commandline args. Features on + classpath are automatically added. + + Options: + + * :action - compile action, defaults to release, but may also be compile or watch" + [{:keys [action + args] :or {action "release"}}] + (build* (format "-M -m shadow.cljs.devtools.cli --force-spawn %s main %s" action (str/join " " args))) + (when (= "release" action) + (println "Also building dev release build") + (build* (format "-M -m shadow.cljs.devtools.cli --force-spawn %s main %s %s" + action + "--config-merge '{:compiler-options {:optimizations :simple + :pretty-print true + :pseudo-names true} + :output-dir \"resources/public/js/dev\" + :modules {:scittle.cljs-devtools {:entries [scittle.cljs-devtools] + :depends-on #{:scittle}}}}'" + (str/join " " args))))) diff --git a/deps.edn b/deps.edn index f9461c6..f7a5121 100644 --- a/deps.edn +++ b/deps.edn @@ -1,21 +1,30 @@ {:paths ["src" "resources"] - :deps - {org.clojure/clojure {:mvn/version "1.10.3"} - org.babashka/sci {:mvn/version "0.3.5"} - reagent/reagent {:mvn/version "1.1.0"} - cljsjs/react {:mvn/version "17.0.2-0"} - cljsjs/react-dom {:mvn/version "17.0.2-0"} - cljsjs/react-dom-server {:mvn/version "17.0.2-0"} - cljs-ajax/cljs-ajax {:mvn/version "0.8.3"} - + {org.clojure/clojure {:mvn/version "1.12.2"} + thheller/shadow-cljs {:mvn/version "3.1.8"} + org.babashka/sci {:git/url "https://github.com/babashka/sci" + :git/sha "6758ba028da559c536a06becbbedade7b0ba6448"} + #_{:local/root "../babashka/sci"} + reagent/reagent {:mvn/version "1.1.1"} + no.cjohansen/replicant {:mvn/version "2025.03.27"} + re-frame/re-frame {:mvn/version "1.3.0"} + cljsjs/react {:mvn/version "18.2.0-1"} + cljsjs/react-dom {:mvn/version "18.2.0-1"} + cljsjs/react-dom-server {:mvn/version "18.2.0-1"} + cljs-ajax/cljs-ajax {:mvn/version "0.8.4"} + applied-science/js-interop {:mvn/version "0.4.2"} + funcool/promesa {:mvn/version "11.0.678"} io.github.babashka/sci.nrepl #_{:local/root "../sci.nrepl"} - {:git/sha "e83421ce9349c36df56a2eb936196dbb65b0de63"} + {:git/url "https://github.com/babashka/sci.nrepl" + :git/sha "75f379c685bbd58c3e23f531339eb144e104937d"} io.github.babashka/sci.configs - {:git/sha "fcd367c6a6115c5c4e41f3a08ee5a8d5b3387a18"}} - + #_{:local/root "/Users/borkdude/dev/sci.configs"} + {:git/url "https://github.com/babashka/sci.configs" + :git/sha "aa84a1b4f1fe45735e5b748769309fc842f737c1" + :exclusions [org.babashka/sci]} + binaryage/devtools {:mvn/version "1.0.7"}} :aliases {:dev {:extra-paths ["dev"] - :extra-deps {thheller/shadow-cljs {:mvn/version "2.14.0"}}}}} + :extra-deps {}}}} diff --git a/doc/dev.md b/doc/dev.md index c32b3b0..b6bd354 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -86,18 +86,24 @@ To deploy to Github Pages: script/release.clj ``` -To create a new release: +To create a new NPM release: -To upgrade examples: +- Prepare version `package.json`, except patch (if anything should change here) +- Run `bb npm-publish`: this will compile, bump patch version, create tag and and push to npm and Github +- `bb replace-version 0.6.16 0.7.28` +- Create Github release with updated links from `doc/links.md` +- `bb gh-pages` -``` -rg '0.0.1' --files-with-matches | xargs sed -i '' 's/0.0.2.8.1.0/g' -bb release -cd gh-pages -git checkout -b v0.2.8 -git push --set-upstream origin v0.2.8 -git checkout gh-pages -cd .. -``` + -Then make a new release on Github with the `v0.2.8` tag. + + + + + + + + + + + diff --git a/doc/js-libraries.md b/doc/js-libraries.md new file mode 100644 index 0000000..72cafb8 --- /dev/null +++ b/doc/js-libraries.md @@ -0,0 +1,49 @@ +# Loading JS libraries + +Since `v0.7.28` scittle allows to load libraries from the global enviroment. +This means you can load a library in a ` + + + + + + +``` + +## ES modules + +The async nature of ES modules makes them a litte bit more difficult to work +with in scittle. You need to disable automatic evaluation of script tags first +using `scittle.core.disable_auto_eval()`. In a `module` type ` + + + + + + + +``` diff --git a/doc/links.md b/doc/links.md new file mode 100644 index 0000000..0b4181a --- /dev/null +++ b/doc/links.md @@ -0,0 +1,20 @@ +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.js-interop.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.cljs-ajax.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.reagent.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.re-frame.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.replicant.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.promesa.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.pprint.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/scittle.nrepl.js + +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.js-interop.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.cljs-ajax.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.reagent.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.re-frame.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.replicant.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.promesa.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.pprint.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.nrepl.js +https://cdn.jsdelivr.net/npm/scittle@0.7.28/dist/dev/scittle.cljs-devtools.js diff --git a/doc/nrepl/README.md b/doc/nrepl/README.md index af2abf7..5541357 100644 --- a/doc/nrepl/README.md +++ b/doc/nrepl/README.md @@ -7,7 +7,7 @@ In babashka or Clojure JVM, use the [sci.nrepl](https://github.com/babashka/sci.nrepl) dependency and run: ``` clojure -(require 'sci.nrepl.browser-server :as nrepl) +(require '[sci.nrepl.browser-server :as nrepl]) (nrepl/start! {:nrepl-port 1339 :websocket-port 1340}) ``` @@ -21,7 +21,7 @@ the normal routine: ``` html - + ``` Also include the CLJS file that you want to evaluate with nREPL: @@ -42,20 +42,18 @@ you should be able evaluate expressions in `playground.cljs`. See a demo Note that the nREPL server connection stays alive even after the browser window refreshes. -### CIDER +### Custom host address -Currently when connecting from CIDER, you need to use this snippet: +By default, the browser will connect to a websocket on the same host as it is loaded +from, using `window.location.hostname`. If you need something else you can specify +that setting the window variable `SCITTLE_NREPL_WEBSOCKET_HOST` like so: -``` elisp -(cider-register-cljs-repl-type 'sci-js "(+ 1 2 3)") - -(defun mm/cider-connected-hook () - (when (eq 'sci-js cider-cljs-repl-type) - (setq-local cider-show-error-buffer nil) - (cider-set-repl-type 'cljs))) - -(add-hook 'cider-connected-hook #'mm/cider-connected-hook) +``` html + ``` -Then choose `cider-connect-cljs`, select port `1339`, followed by the `sci-js` -REPL type. +### CIDER + +Choose `cider-connect-cljs`, select port `1339`, followed by the `nbb` REPL +type. If you use multiple REPLs in your project, choose +`sesman-link-with-buffer` to choose the right REPL for the right buffer. diff --git a/doc/nrepl/bb.edn b/doc/nrepl/bb.edn index ddc1105..fdd4518 100644 --- a/doc/nrepl/bb.edn +++ b/doc/nrepl/bb.edn @@ -1,5 +1,6 @@ {:deps {io.github.babashka/sci.nrepl - {:git/sha "c14b5b4ef4390ff206cdb71f763f327799f5e853"} + #_{:local/root "/Users/borkdude/dev/sci.nrepl"} + {:git/sha "4f7f6d652a71b5bdc0c110313a4908d956e7a97d"} io.github.babashka/http-server {:git/sha "b38c1f16ad2c618adae2c3b102a5520c261a7dd3"}} :tasks {http-server {:doc "Starts http server for serving static files" diff --git a/doc/nrepl/index.html b/doc/nrepl/index.html index 1882a05..5890a47 100644 --- a/doc/nrepl/index.html +++ b/doc/nrepl/index.html @@ -1,9 +1,9 @@ - + - + diff --git a/doc/serviceworker.md b/doc/serviceworker.md new file mode 100644 index 0000000..1048f77 --- /dev/null +++ b/doc/serviceworker.md @@ -0,0 +1,26 @@ +# Scittle in a service worker + +You can use Scittle to bootstrap a ClojureScript based service worker. + +Put the following code into e.g. `scittle-sw.js` to create a JavaScript based service worker, load Scittle, then fetch your script and eval it. + +```javascript +importScripts("scittle.min.js"); + +const request = await fetch("sw.cljs"); +const text = await request.text(); +const result = scittle.core.eval_string(text); +``` + +Then load `scittle-sw.js` in your HTML: + +```html + +``` + +This will load `sw.cljs` and eval it in the context of the service worker. + +A ready-made example can be found at [chr15m/scittle-template-serviceworker](https://github.com/chr15m/scittle-template-serviceworker). diff --git a/package-lock.json b/package-lock.json index 56df0fc..e5bd260 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "react": "17.0.1", "react-dom": "17.0.1" }, - "version": "0.2.8" + "version": "0.7.28" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -129,5 +129,5 @@ } } }, - "version": "0.2.8" + "version": "0.7.28" } diff --git a/package.json b/package.json index 79befa3..ab333e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scittle", - "version": "0.2.8", + "version": "0.7.28", "files": [ "dist" ], diff --git a/plugins/datascript/deps.edn b/plugins/datascript/deps.edn new file mode 100644 index 0000000..467f4cd --- /dev/null +++ b/plugins/datascript/deps.edn @@ -0,0 +1,4 @@ +{:deps + {datascript/datascript {:mvn/version "1.3.12"} + io.github.babashka/sci.configs {:git/sha "aa84a1b4f1fe45735e5b748769309fc842f737c1" + :exclusions [org.babashka/sci]}}} diff --git a/plugins/datascript/src/scittle/datascript.cljs b/plugins/datascript/src/scittle/datascript.cljs new file mode 100644 index 0000000..f3dac97 --- /dev/null +++ b/plugins/datascript/src/scittle/datascript.cljs @@ -0,0 +1,9 @@ +(ns scittle.datascript + {:no-doc true} + (:require [sci.configs.tonsky.datascript :refer [config]] + [scittle.core :as scittle])) + +(defn init [] + (scittle/register-plugin! + ::datascript + config)) diff --git a/plugins/datascript/src/scittle_plugin.edn b/plugins/datascript/src/scittle_plugin.edn new file mode 100644 index 0000000..09eae58 --- /dev/null +++ b/plugins/datascript/src/scittle_plugin.edn @@ -0,0 +1,12 @@ +[{:name scittle/datascript + :namespaces [datascript.core datascript.db] + :js "./scittle.datascript.js" + :shadow-config + {:compiler-options {:externs ["datascript/externs.js"]} + :modules + {:scittle.datascript + {:init-fn scittle.datascript/init + ;; From https://github.com/tonsky/datascript/issues/298#issuecomment-813790783 + :prepend "globalThis.datascript = {};" + :depends-on #{:scittle} + :entries [datascript.core]}}}}] diff --git a/plugins/dataspex/deps.edn b/plugins/dataspex/deps.edn new file mode 100644 index 0000000..badf11e --- /dev/null +++ b/plugins/dataspex/deps.edn @@ -0,0 +1,8 @@ +{:deps + {no.cjohansen/dataspex {:git/url "https://github.com/cjohansen/dataspex" + :git/sha "02112200651c2bd932907bb69fba1ff50b881741" + :exclusions [ring/ring-core + ring/ring-jetty-adapter + com.cognitect/transit-clj]} + io.github.babashka/sci.configs {:git/sha "aa84a1b4f1fe45735e5b748769309fc842f737c1" + :exclusions [org.babashka/sci]}}} diff --git a/plugins/dataspex/src/scittle/dataspex.cljs b/plugins/dataspex/src/scittle/dataspex.cljs new file mode 100644 index 0000000..66b3908 --- /dev/null +++ b/plugins/dataspex/src/scittle/dataspex.cljs @@ -0,0 +1,9 @@ +(ns scittle.dataspex + {:no-doc true} + (:require [sci.configs.cjohansen.dataspex :refer [config]] + [scittle.core :as scittle])) + +(defn init [] + (scittle/register-plugin! + ::dataspex + config)) diff --git a/plugins/dataspex/src/scittle_plugin.edn b/plugins/dataspex/src/scittle_plugin.edn new file mode 100644 index 0000000..af629e2 --- /dev/null +++ b/plugins/dataspex/src/scittle_plugin.edn @@ -0,0 +1,8 @@ +[{:name scittle/dataspex + :namespaces [dataspex.core] + :js "./scittle.dataspex.js" + :shadow-config + {:modules + {:scittle.dataspex {:init-fn scittle.dataspex/init + :depends-on #{:scittle :scittle.datascript} + :entries [dataspex.core]}}}}] diff --git a/plugins/demo/README.md b/plugins/demo/README.md new file mode 100644 index 0000000..7bd15f8 --- /dev/null +++ b/plugins/demo/README.md @@ -0,0 +1,29 @@ +# Demo + +A demo project of a custom scittle build. + +This demo project uses the `scittle.javelin` and `scittle.hoplon` plugins which aren't part of the normal scittle distribution. + +To produce release `.js` files, run: `bb release`. + +See: + +- `bb.edn` with + - `:deps` which includes: + - a dependency on the `scittle.build` project to build scittle + custom features + - zero or more plugin dependencies + - helpers like static file server + - development `:tasks`. Run `bb dev` for development and `bb release` to produce release artifacts. +- `deps.edn`: this only contains a dependency on scittle itself + +Available plugins are in the `plugins` directory inside the top level directory of this repo. + +Writing a plugin involves writing + +- SCI configuration (this can be shared via the [sci.configs](https://github.com/babashka/sci.configs) project too) +- Adding a `scittle_plugin.edn` file on the plugin's classpath (e.g. in the `src` directory). This EDN file contains: + - `:name`, name of the plugin + - `:namespaces`: the namespaces exposed to SCI + - `:js`: the name of the produced `.js` module file + - `:shadow-config`: the shadow-cljs configuration specific to this plugin +- A `.cljs` file with an `init` function which calls `scittle/register-plugin!`. diff --git a/plugins/demo/bb.edn b/plugins/demo/bb.edn new file mode 100644 index 0000000..e7486c2 --- /dev/null +++ b/plugins/demo/bb.edn @@ -0,0 +1,22 @@ +{:deps {io.github.babashka/scittle.build {:local/root "../../build"} + ;; datascript plugin + ; io.github.babashka/scittle.datascript {:local/root "../../plugins/datascript"} + io.github.babashka/scittle.dataspex {:local/root "../../plugins/dataspex"} + io.github.babashka/scittle.javelin {:local/root "../../plugins/javelin"} + io.github.babashka/scittle.hoplon {:local/root "../../plugins/hoplon"} + io.github.babashka/http-server + {:git/sha "b38c1f16ad2c618adae2c3b102a5520c261a7dd3"}} + :tasks + {:requires ([scittle.build :as build]) + watch {:doc "Watch build" + :task (build/build {:action "watch"})} + serve {:doc "Starts http server for serving static files" + :requires ([babashka.http-server :as http]) + :task (do (http/serve {:port 1341 :dir "resources/public"}) + (println "Serving static assets at http://localhost:1341"))} + -dev {:depends [watch serve]} + dev {:doc "Run compilation in watch mode and start http server" + :task (do (run '-dev {:parallel true}) + (deref (promise)))} + release {:doc "Release build (advanced compiled JS)" + :task (build/build {})}}} diff --git a/plugins/demo/deps.edn b/plugins/demo/deps.edn new file mode 100644 index 0000000..62915a5 --- /dev/null +++ b/plugins/demo/deps.edn @@ -0,0 +1 @@ +{:deps {io.github.babashka/scittle {:local/root "../.."}}} diff --git a/plugins/demo/resources/public/index.html b/plugins/demo/resources/public/index.html new file mode 100644 index 0000000..7f328cc --- /dev/null +++ b/plugins/demo/resources/public/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + +

Hello Hoplon!

+ + diff --git a/plugins/demo/shadow-cljs.edn b/plugins/demo/shadow-cljs.edn new file mode 100644 index 0000000..47e5deb --- /dev/null +++ b/plugins/demo/shadow-cljs.edn @@ -0,0 +1,32 @@ +{:deps + {:aliases [:dev]} + + :dev-http + {8000 "classpath:public"} + + :builds + {:main + {:target :browser + :js-options + {:resolve {"react" {:target :global + :global "React"} + "react-dom" {:target :global + :global "ReactDOM"}}} + :modules + {:scittle {:entries [scittle.core]} + :scittle.nrepl {:entries [scittle.nrepl] + :depends-on #{:scittle}} + :scittle.promesa {:entries [scittle.promesa] + :depends-on #{:scittle}} + :scittle.pprint {:entries [scittle.pprint] + :depends-on #{:scittle}} + :scittle.reagent {:entries [scittle.reagent] + :depends-on #{:scittle}} + :scittle.re-frame {:entries [scittle.re-frame] + :depends-on #{:scittle.reagent + :scittle}} + :scittle.cljs-ajax {:entries [scittle.cljs-ajax] + :depends-on #{:scittle}}} + :build-hooks [(shadow.cljs.build-report/hook)] + :output-dir "resources/public/js" + :devtools {:repl-pprint true}}}} diff --git a/plugins/hoplon/deps.edn b/plugins/hoplon/deps.edn new file mode 100644 index 0000000..0eab577 --- /dev/null +++ b/plugins/hoplon/deps.edn @@ -0,0 +1,4 @@ +{:deps + {hoplon/hoplon {:mvn/version "7.5.0"} + io.github.babashka/sci.configs {:git/sha "aa84a1b4f1fe45735e5b748769309fc842f737c1" + :exclusions [org.babashka/sci]}}} diff --git a/plugins/hoplon/src/scittle/hoplon.cljs b/plugins/hoplon/src/scittle/hoplon.cljs new file mode 100644 index 0000000..46e5a3b --- /dev/null +++ b/plugins/hoplon/src/scittle/hoplon.cljs @@ -0,0 +1,9 @@ +(ns scittle.hoplon + {:no-doc true} + (:require [sci.configs.hoplon.hoplon :refer [config]] + [scittle.core :as scittle])) + +(defn init [] + (scittle/register-plugin! + ::hoplon + config)) diff --git a/plugins/hoplon/src/scittle/javelin.cljs b/plugins/hoplon/src/scittle/javelin.cljs new file mode 100644 index 0000000..7334a2d --- /dev/null +++ b/plugins/hoplon/src/scittle/javelin.cljs @@ -0,0 +1,9 @@ +(ns scittle.javelin + {:no-doc true} + (:require [sci.configs.hoplon.javelin :refer [config]] + [scittle.core :as scittle])) + +(defn init [] + (scittle/register-plugin! + ::javelin + config)) diff --git a/plugins/hoplon/src/scittle_plugin.edn b/plugins/hoplon/src/scittle_plugin.edn new file mode 100644 index 0000000..5a9f8f6 --- /dev/null +++ b/plugins/hoplon/src/scittle_plugin.edn @@ -0,0 +1,13 @@ +[{:name scittle/hoplon + :namespaces [javelin.core + hoplon.core + hoplon.dom] + :js "./scittle.hoplon.js" + :shadow-config + {:modules + {:scittle.hoplon {:init-fn scittle.hoplon/init + :depends-on #{:scittle :scittle.javelin} + :entries [hoplon.core hoplon.dom]} + :scittle.javelin {:init-fn scittle.javelin/init + :depends-on #{:scittle} + :entries [javelin.core]}}}}] diff --git a/plugins/javelin/deps.edn b/plugins/javelin/deps.edn new file mode 100644 index 0000000..f4b4533 --- /dev/null +++ b/plugins/javelin/deps.edn @@ -0,0 +1,4 @@ +{:deps + {hoplon/javelin {:mvn/version "3.9.3"} + io.github.babashka/sci.configs {:git/sha "aa84a1b4f1fe45735e5b748769309fc842f737c1" + :exclusions [org.babashka/sci]}}} diff --git a/plugins/javelin/src/scittle/javelin.cljs b/plugins/javelin/src/scittle/javelin.cljs new file mode 100644 index 0000000..7334a2d --- /dev/null +++ b/plugins/javelin/src/scittle/javelin.cljs @@ -0,0 +1,9 @@ +(ns scittle.javelin + {:no-doc true} + (:require [sci.configs.hoplon.javelin :refer [config]] + [scittle.core :as scittle])) + +(defn init [] + (scittle/register-plugin! + ::javelin + config)) diff --git a/plugins/javelin/src/scittle_plugin.edn b/plugins/javelin/src/scittle_plugin.edn new file mode 100644 index 0000000..c34873f --- /dev/null +++ b/plugins/javelin/src/scittle_plugin.edn @@ -0,0 +1,8 @@ +[{:name scittle/javelin + :namespaces [javelin.core] + :js "./scittle.javelin.js" + :shadow-config + {:modules + {:scittle.javelin {:init-fn scittle.javelin/init + :depends-on #{:scittle} + :entries [javelin.core]}}}}] diff --git a/resources/public/bookmarklet.html b/resources/public/bookmarklet.html index 15dce60..e2cc947 100644 --- a/resources/public/bookmarklet.html +++ b/resources/public/bookmarklet.html @@ -4,9 +4,8 @@ - - - + + diff --git a/resources/public/cljs/bookmarklet.cljs b/resources/public/cljs/bookmarklet.cljs index 0f9eb15..69d2169 100644 --- a/resources/public/cljs/bookmarklet.cljs +++ b/resources/public/cljs/bookmarklet.cljs @@ -134,6 +134,7 @@ [:br] [(fn [] [:a {:href (str "?name=" (js/encodeURIComponent @*bookmark-name) - "&code=" (js/encodeURIComponent @*code))} "Copy this link to share ⤴️"])]])) + "&code=" (js/encodeURIComponent @*code) + "%20")} "Copy this link to share ⤴️"])]])) (rdom/render [workspace] (.getElementById js/document "app")) diff --git a/resources/public/cljs/codemirror.cljs b/resources/public/cljs/codemirror.cljs new file mode 100644 index 0000000..db28746 --- /dev/null +++ b/resources/public/cljs/codemirror.cljs @@ -0,0 +1,56 @@ +(require '[clojure.string :as str]) +(declare cm) + +(defn eval-me [] + (js/scittle.core.eval_string (-> cm .-state .-doc .toString))) + +(def extension + (.of js/cv.keymap + (clj->js [{:key "Mod-Enter" + :run (fn [] + (eval-me))} + #_{:key (str modifier "-Enter") + :shift (partial eval-top-level on-result) + :run (partial eval-at-cursor on-result)}]))) +(def cm + (let [doc (str/trim " +(require '[reagent.core :as r] + '[reagent.dom :as rdom] + '[re-frame.core :as rf]) + +(rf/reg-event-fx ::click (fn [{:keys [db]} _] {:db (update db :clicks (fnil inc 0))})) +(rf/reg-sub ::clicks (fn [db] (:clicks db))) + +(defn my-component [] + (let [clicks (rf/subscribe [::clicks])] + [:div + [:p \"Clicks: \" @clicks] + [:p [:button {:on-click #(rf/dispatch [::click])} + \"Click me!\"]]])) + +(rdom/render [my-component] (.getElementById js/document \"reagent\")) +")] + (js/cm.EditorView. #js {:doc doc + :extensions #js [js/cm.basicSetup, (js/lc.clojure), (.highest js/cs.Prec extension)] + :parent (js/document.querySelector "#app") + #_#_:dispatch (fn [tr] (-> cm (.update #js [tr])) (eval-me)) + }))) +(set! (.-eval_me js/globalThis) eval-me) +(set! (.-cm_instance js/globalThis) cm) + +(defn linux? [] + (some? (re-find #"(Linux)|(X11)" js/navigator.userAgent))) + +(defn mac? [] + (and (not (linux?)) + (some? (re-find #"(Mac)|(iPhone)|(iPad)|(iPod)" js/navigator.platform)))) + +(let [elt (js/document.getElementById "evalMe") + txt (.-innerText elt) + mod-symbol (if (mac?) + "⌘" + "⌃") + txt (str txt " " mod-symbol"-⏎")] + (set! (.-innerHTML elt) txt)) + +(eval-me) diff --git a/resources/public/cljs/replicant_tictactoe/core.cljs b/resources/public/cljs/replicant_tictactoe/core.cljs new file mode 100644 index 0000000..b5ad81f --- /dev/null +++ b/resources/public/cljs/replicant_tictactoe/core.cljs @@ -0,0 +1,33 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/core.cljs + +(ns replicant-tictactoe.core + (:require [replicant.dom :as r] + [replicant-tictactoe.game :as game] + [replicant-tictactoe.ui :as ui])) + +(defn start-new-game [store] + (reset! store (game/create-game {:size 3}))) + +(defn main [] + ;; Set up the atom + (let [store (atom nil) + el (js/document.getElementById "app")] + + ;; Globally handle DOM events + (r/set-dispatch! + (fn [_ [action & args]] + (case action + :tic (apply swap! store game/tic args) + :reset (start-new-game store)))) + + ;; Render on every change + (add-watch store ::render + (fn [_ _ _ game] + (->> (ui/game->ui-data game) + ui/render-game + (r/render el)))) + + ;; Trigger the first render by initializing the game. + (start-new-game store))) + +(main) diff --git a/resources/public/cljs/replicant_tictactoe/game.cljs b/resources/public/cljs/replicant_tictactoe/game.cljs new file mode 100644 index 0000000..9296baf --- /dev/null +++ b/resources/public/cljs/replicant_tictactoe/game.cljs @@ -0,0 +1,44 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/game.cljs + +(ns replicant-tictactoe.game) + +(defn create-game [{:keys [size]}] + {:next-player :x + :size size}) + +(def next-player {:x :o, :o :x}) + +(defn winner? [tics path] + (when (= 1 (count (set (map tics path)))) + path)) + +(defn get-winning-path [{:keys [size tics]} y x] + (let [flip-y (fn [y] (- size 1 y))] + (or (winner? tics (mapv #(vector y %) (range 0 size))) + (winner? tics (mapv #(vector % x) (range 0 size))) + (when (= y x) + (winner? tics (mapv #(vector % %) (range 0 size)))) + (when (= (flip-y y) x) + (winner? tics (mapv #(vector (flip-y %) %) (range 0 size))))))) + +(defn maybe-conclude [game y x] + (if-let [path (get-winning-path game y x)] + (-> (dissoc game :next-player) + (assoc :over? true + :victory {:player (get-in game [:tics [y x]]) + :path path})) + (let [tie? (= (count (:tics game)) (* (:size game) (:size game)))] + (cond-> game + tie? (dissoc :next-player) + tie? (assoc :over? true))))) + +(defn tic [game y x] + (let [player (:next-player game)] + (if (or (get-in game [:tics [y x]]) + (<= (:size game) x) + (<= (:size game) y)) + game + (-> game + (assoc-in [:tics [y x]] player) + (assoc :next-player (next-player player)) + (maybe-conclude y x))))) \ No newline at end of file diff --git a/resources/public/cljs/replicant_tictactoe/style.css b/resources/public/cljs/replicant_tictactoe/style.css new file mode 100644 index 0000000..7ec052a --- /dev/null +++ b/resources/public/cljs/replicant_tictactoe/style.css @@ -0,0 +1,50 @@ +/* COPIED from https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/resources/public/styles.css */ +.cell { + aspect-ratio: 1 / 1; + background: rgba(255, 255, 255, 0.8); + border-radius: 6%; + border: none; + display: block; + flex: 1 1 0%; + outline: none; + position: relative; + width: 100%; +} + +.cell-content { + opacity: 1; + transition: opacity 0.25s; +} + +.transparent { + opacity: 0; +} + +.cell-dim { + background: rgba(249, 249, 240, 0.3); +} + +.cell-highlight { + background: #fcfcf3; +} + +.clickable { + cursor: pointer; +} + +.board { + --gap: 0.75rem; + background: #833ab4; + background: linear-gradient(90deg, #833ab4 0%, #fd1d1d 50%, #fcb045 100%); + display: flex; + flex-direction: column; + gap: var(--gap); + padding: var(--gap); + max-width: 80vh; +} + +.row { + display: flex; + flex-direction: row; + gap: var(--gap); +} \ No newline at end of file diff --git a/resources/public/cljs/replicant_tictactoe/ui.cljs b/resources/public/cljs/replicant_tictactoe/ui.cljs new file mode 100644 index 0000000..c53dc65 --- /dev/null +++ b/resources/public/cljs/replicant_tictactoe/ui.cljs @@ -0,0 +1,75 @@ +;; COPIED FROM https://github.com/cjohansen/replicant-tic-tac-toe/blob/7a33fb12f0cd6658b2f555ff673dee031d4aa921/src/tic_tac_toe/ui.cljs + +(ns replicant-tictactoe.ui) + +(def mark-x + [:svg {:xmlns "http://www.w3.org/2000/svg" + :viewBox "0 -10 108 100"} + [:path + {:fill "currentColor" + :d "m1.753 69.19.36-1.08q.35-1.09 1.92-2.97 1.58-1.87 3.85-3.84 2.29-1.97 4.6-3.54 2.31-1.57 4.93-3.24 2.62-1.66 4.65-2.9 2.04-1.23 3.91-2.27 1.87-1.05 3.98-2.31 2.11-1.27 4.12-2.5 2.01-1.24 4.33-2.51l4.6-2.52q2.27-1.25 4.84-2.86 2.56-1.62 5.03-3.09 2.47-1.47 4.5-2.88 2.03-1.4 3.82-2.82t3.81-3.47q2.01-2.06 3.7-3.51 1.69-1.46 3.47-3.03 1.77-1.57 4.01-3.69 2.24-2.11 4.13-3.7 1.89-1.58 3.93-2.97 2.04-1.39 4.05-2.49 2.01-1.11 5.26-2.54 3.24-1.44 4.48-1.46 1.24-.01 2.42.37 1.18.37 2.18 1.11 1 .74 1.71 1.75.71 1.02 1.06 2.21.34 1.19.3 2.43-.05 1.24-.5 2.39-.44 1.16-1.23 2.12-.79.95-1.84 1.61-1.05.65-2.26.94-1.21.28-2.44.16-1.23-.11-2.37-.62-1.13-.5-2.04-1.34-.91-.84-1.51-1.93-.6-1.08-.81-2.3-.22-1.22-.04-2.45.18-1.23.75-2.33.56-1.1 1.45-1.97.89-.86 2.01-1.4 1.11-.54 2.35-.69 1.23-.15 2.44.1t2.29.87q1.07.63 1.88 1.56.82.93 1.29 2.08.48 1.14.56 2.38.09 1.24-.23 2.44-.31 1.19-.99 2.23-.68 1.04-1.66 1.8-.98.76-2.15 1.18l-1.16.41-2.28 1.17q-2.28 1.18-4.38 2.7-2.1 1.51-4.2 3.44-2.1 1.92-4.18 3.7-2.08 1.77-3.9 3.44-1.81 1.68-3.41 3.13-1.6 1.46-3.38 3.09-1.79 1.62-3.44 2.97-1.66 1.34-3.53 2.4-1.88 1.06-4.17 2.65-2.3 1.6-4.79 2.74-2.48 1.14-4.98 2.71-2.5 1.57-4.51 2.47-2.01.9-3.99 1.87-1.98.97-3.88 2.02-1.91 1.05-4.38 2.34-2.46 1.28-4.94 2.53-2.47 1.25-4.48 2.38-2 1.12-3.96 2.14-1.95 1.01-3.83 1.99-1.89.98-4.37 2.05-2.48 1.06-2.96 2.01-.48.96-.78 1.49-.3.53-.71.97-.41.44-.92.77-.51.34-1.09.54-.57.2-1.17.25-.6.06-1.2-.03t-1.16-.32q-.56-.23-1.05-.59-.49-.35-.89-.82-.39-.46-.65-1.01-.27-.54-.4-1.14-.13-.59-.12-1.19.02-.6.18-1.19l.16-.59Z"}] + [:path + {:fill "currentColor" + :d "m28.099 4.991 2.69 1.97q2.69 1.96 4.5 3.22 1.8 1.28 4.54 3.46 2.74 2.18 4.57 3.89t3.38 3.72q1.54 2.02 2.88 4.3 1.34 2.28 2.83 4.46 1.48 2.18 2.63 4.14 1.15 1.96 2.74 4.07 1.59 2.1 3.59 4.19 1.99 2.08 4.23 4.48 2.24 2.4 3.7 4.04 1.47 1.64 2.91 3.23 1.44 1.59 3.08 3.58 1.64 1.99 3.51 4.08 1.87 2.09 3.55 3.77 1.69 1.68 4.1 3.51 2.42 1.83 3.9 2.58 1.48.74 2.14 1.34.66.6 1.15 1.33.5.74.8 1.57.31.84.4 1.72.1.88-.02 1.76-.12.88-.44 1.71-.33.82-.84 1.55-.51.72-1.19 1.3-.67.58-1.46.98-.79.41-1.65.61-.87.2-1.76.19-.88-.01-1.74-.24-.86-.22-1.64-.64-.78-.42-2.27-2.72-1.48-2.3-1.52-3.49-.03-1.19.31-2.33.35-1.14 1.04-2.11.69-.97 1.66-1.67.96-.7 2.1-1.05 1.14-.35 2.33-.32 1.19.02 2.31.43t2.05 1.15q.93.75 1.58 1.75.64 1 .93 2.15.29 1.16.2 2.35-.09 1.18-.56 2.28-.47 1.1-1.26 1.99-.79.88-1.83 1.47t-2.2.82q-1.17.23-2.35.07-1.19-.16-2.25-.68-1.07-.53-1.92-1.37-.84-.84-1.37-1.9-.54-1.07-.7-2.25-.17-1.18.06-2.35.22-1.17.8-2.21.58-1.04 1.47-1.84.88-.79 1.98-1.27 1.09-.47 2.28-.57 1.18-.1 2.34.18 1.16.29 2.16.93 1.01.63 1.76 1.56.74.93-.33-.26-1.07-1.18-.41-.58.66.59 1.15 1.33.5.74.8 1.57.31.83.4 1.72.1.88-.02 1.76-.12.88-.44 1.7-.33.83-.84 1.55-.51.73-1.19 1.31-.67.58-1.46.98-.79.41-1.65.61-.87.2-1.75.19-.89-.01-1.75-.24-.86-.22-1.64-.64-.78-.42-2.73-1.57-1.95-1.14-4.26-2.95-2.31-1.8-3.87-3.43-1.57-1.62-3.17-3.29-1.6-1.66-3.55-4.05-1.95-2.39-3.33-4.15-1.39-1.76-2.77-3.4-1.38-1.64-3.07-3.56-1.7-1.91-3.91-4.13-2.2-2.22-3.74-4.1-1.54-1.88-2.79-3.75-1.24-1.87-2.4-4.33t-2.39-4.46q-1.23-2.01-2.4-4.59-1.17-2.59-2.53-5.01-1.36-2.43-3.35-4.44-1.99-2.02-4.52-4.27-2.54-2.25-5.33-4.04-2.81-1.79-3.28-2.21-.47-.41-.83-.92-.35-.51-.58-1.1-.22-.58-.3-1.2-.08-.62-.01-1.23.08-.62.29-1.21.22-.58.58-1.1.35-.51.81-.93.47-.42 1.02-.71t1.16-.45q.61-.15 1.23-.15t1.22.14q.61.15 1.17.44l.55.28Z"}]]) + +(def mark-o + [:svg {:xmlns "http://www.w3.org/2000/svg" + :viewBox "0 0 114 114"} + [:path + {:fill "none" + :stroke "currentColor" + :stroke-linecap "round" + :stroke-width "6" + :d "M74.616 8.935c7.73 2.38 15.96 9.34 21.58 16.04 5.63 6.69 10.57 15.46 12.18 24.11 1.6 8.65.74 19.67-2.53 27.77-3.27 8.11-10.12 15.37-17.09 20.88-6.98 5.51-16.07 10.81-24.76 12.17-8.7 1.35-19.32-.76-27.42-4.06-8.1-3.29-15.73-8.93-21.21-15.73-5.48-6.81-10.32-16.5-11.67-25.09-1.35-8.6.19-18.39 3.57-26.51 3.38-8.11 9.99-16.6 16.71-22.19 6.72-5.59 13.95-10.52 23.63-11.36 9.68-.84 28.04 4.34 34.45 6.32 6.42 1.97 4.37 4.6 4.04 5.55m-48.33-9.69c7.65-3.32 19.78-3.63 28.63-2.01 8.86 1.63 17.85 5.89 24.49 11.76 6.64 5.87 12.7 15.08 15.37 23.48 2.67 8.41 2.5 18.4.65 26.95-1.85 8.54-5.98 17.59-11.77 24.34-5.78 6.74-14.56 13.05-22.93 16.11-8.37 3.06-18.75 4.19-27.29 2.25-8.54-1.93-17.37-7.89-23.96-13.87-6.59-5.97-12.89-13.58-15.57-21.96-2.69-8.39-2.31-19.94-.56-28.34 1.75-8.4 5.21-15.74 11.06-22.09 5.85-6.35 19.92-13.32 24.04-16.01 4.12-2.7.37-1.1.67-.16"}]]) + +(defn render-cell [{:keys [content on-click dim? highlight? clickable?]}] + [:button.cell + {:on {:click on-click} + :class (cond-> [] + dim? (conj "cell-dim") + highlight? (conj "cell-highlight") + clickable? (conj "clickable"))} + (when content + [:div.cell-content + {:replicant/mounting {:class "transparent"} + :replicant/unmounting {:class "transparent"}} + content])]) + +(defn render-board [{:keys [rows]}] + [:div.board + (for [row rows] + [:div.row + (for [cell row] + (render-cell cell))])]) + +(defn render-game [{:keys [board button]}] + [:div + (render-board board) + (when button + [:button {:on {:click (:on-click button)} + :style {:margin-top 20 + :font-size 20}} + (:text button)])]) + +(def player->mark + {:x mark-x + :o mark-o}) + +(defn game->ui-data [{:keys [size tics victory over?]}] + (let [highlight? (set (:path victory))] + {:button (when over? + {:text "Start over" + :on-click [:reset]}) + :board + {:rows + (for [y (range size)] + (for [x (range size)] + (if-let [player (get tics [y x])] + (let [victorious? (highlight? [y x])] + (cond-> {:content (player->mark player)} + victorious? (assoc :highlight? true) + (and over? (not victorious?)) (assoc :dim? true))) + (if over? + {:dim? true} + {:clickable? true + :on-click [:tic y x]}))))}})) \ No newline at end of file diff --git a/resources/public/codemirror.html b/resources/public/codemirror.html new file mode 100644 index 0000000..800624b --- /dev/null +++ b/resources/public/codemirror.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + +

Using @nextjournal/lang-clojure directly from npm in HTML!

+
+
+
+
+ + + diff --git a/resources/public/html/cljs-ajax.html b/resources/public/html/cljs-ajax.html index f5ffb9d..d7a785b 100644 --- a/resources/public/html/cljs-ajax.html +++ b/resources/public/html/cljs-ajax.html @@ -1,7 +1,7 @@ - - + + + - - + + diff --git a/resources/public/html/reagent.html b/resources/public/html/reagent.html index fb4771c..abb68e3 100644 --- a/resources/public/html/reagent.html +++ b/resources/public/html/reagent.html @@ -1,9 +1,9 @@ - - - - + + + + - - + + + + + + + + + + + diff --git a/resources/public/replicant_tictactoe.html b/resources/public/replicant_tictactoe.html new file mode 100644 index 0000000..1f52895 --- /dev/null +++ b/resources/public/replicant_tictactoe.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + +

Scittle tic-tac-toe built with Replicant

+

What is Scittle?

+

Read the main page for more details.

+

The game

+
+

The following source was loaded and interpreted + from cljs/replicant_tictactoe/core.cljs using the + script tag: +


+<script type="application/x-scittle" src="cljs/replicant_tictactoe/ui.cljs"></script>
+<script type="application/x-scittle" src="cljs/replicant_tictactoe/game.cljs"></script>
+<script type="application/x-scittle" src="cljs/replicant_tictactoe/core.cljs"></script>
+
+
+

+
+ + + diff --git a/resources/public/test/codemirror.html b/resources/public/test/codemirror.html new file mode 100644 index 0000000..8bf8299 --- /dev/null +++ b/resources/public/test/codemirror.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + +

Using @nextjournal/lang-clojure directly from npm in HTML!

+
+
+
+
+ + + diff --git a/resources/public/tictactoe.html b/resources/public/tictactoe.html index 5b62e74..4462881 100644 --- a/resources/public/tictactoe.html +++ b/resources/public/tictactoe.html @@ -3,9 +3,8 @@ - - - + + diff --git a/script/changelog.clj b/script/changelog.clj new file mode 100755 index 0000000..1ef014a --- /dev/null +++ b/script/changelog.clj @@ -0,0 +1,17 @@ +#!/usr/bin/env bb + +(ns changelog + (:require [clojure.string :as str])) + +(let [changelog (slurp "CHANGELOG.md") + replaced (str/replace changelog + #" #(\d+)" + (fn [[_ issue after]] + (format " [#%s](https://github.com/babashka/scittle/issues/%s)%s" + issue issue (str after)))) + replaced (str/replace replaced + #"@([a-zA-Z0-9-_]+)([, \.)])" + (fn [[_ name after]] + (format "[@%s](https://github.com/%s)%s" + name name after)))] + (spit "CHANGELOG.md" replaced)) diff --git a/script/release.clj b/script/release.clj index 00a2396..4835975 100755 --- a/script/release.clj +++ b/script/release.clj @@ -3,6 +3,8 @@ (require '[babashka.fs :as fs] '[babashka.tasks :refer [shell]]) +(fs/create-dirs "gh-pages") + (fs/copy "resources/public/index.html" "gh-pages" {:replace-existing true}) @@ -21,6 +23,12 @@ (fs/copy "resources/public/disable_auto_eval.html" "gh-pages" {:replace-existing true}) +(fs/copy "resources/public/codemirror.html" "gh-pages" + {:replace-existing true}) + +(fs/copy "resources/public/replicant_tictactoe.html" "gh-pages" + {:replace-existing true}) + (def html-source-dir (fs/file "resources" "public" "html")) (def html-target-dir (fs/file "gh-pages" "html")) (fs/create-dirs html-target-dir) @@ -57,6 +65,9 @@ {:replace-existing true})) (fs/glob cljs-source-dir "*.cljs")) +(println "Copying dir resources/public/cljs/replicant_tictactoe") +(fs/copy-tree (fs/file cljs-source-dir "replicant_tictactoe") (fs/file cljs-target-dir "replicant_tictactoe") {:replace-existing true}) + (run! (fn [f] (println "Copying" (str f)) (fs/copy f diff --git a/shadow-cljs.edn b/shadow-cljs.edn index fe96550..4b0d654 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -6,7 +6,14 @@ :builds {:main - {:target :browser + {;:compiler-options {:source-map true} + ;; for dev build + #_#_ + :compiler-options {:optimizations :simple + :pretty-print true + :pseudo-names true + :source-map true} + :target :browser :js-options {:resolve {"react" {:target :global :global "React"} @@ -16,10 +23,22 @@ {:scittle {:entries [scittle.core]} :scittle.nrepl {:entries [scittle.nrepl] :depends-on #{:scittle}} + :scittle.promesa {:entries [scittle.promesa] + :depends-on #{:scittle}} + :scittle.js-interop {:entries [scittle.js-interop] + :depends-on #{:scittle}} + :scittle.pprint {:entries [scittle.pprint] + :depends-on #{:scittle}} :scittle.reagent {:entries [scittle.reagent] :depends-on #{:scittle}} + :scittle.replicant {:entries [scittle.replicant] + :depends-on #{:scittle}} + :scittle.re-frame {:entries [scittle.re-frame] + :depends-on #{:scittle.reagent + :scittle}} :scittle.cljs-ajax {:entries [scittle.cljs-ajax] :depends-on #{:scittle}}} :build-hooks [(shadow.cljs.build-report/hook)] - :output-dir "resources/public/js" - :devtools {:repl-pprint true}}}} + :output-dir "resources/public/js" ;; + "/dev" for dev build + :devtools {:repl-pprint true} + }}} diff --git a/src/scittle/cljs_ajax.cljs b/src/scittle/cljs_ajax.cljs index a129e6e..8d1df17 100644 --- a/src/scittle/cljs_ajax.cljs +++ b/src/scittle/cljs_ajax.cljs @@ -1,13 +1,12 @@ (ns scittle.cljs-ajax - (:require [ajax.core :as ajx] + (:require [ajax.core] [sci.core :as sci] [scittle.core :as scittle])) (def ans (sci/create-ns 'ajax.core nil)) (def ajax-namespace - {'GET (sci/copy-var ajx/GET ans) - 'POST (sci/copy-var ajx/POST ans)}) + (sci/copy-ns ajax.core ans)) (scittle/register-plugin! ::ajax diff --git a/src/scittle/cljs_devtools.cljs b/src/scittle/cljs_devtools.cljs new file mode 100644 index 0000000..cbe2082 --- /dev/null +++ b/src/scittle/cljs_devtools.cljs @@ -0,0 +1,6 @@ +(ns scittle.cljs-devtools + (:require [devtools.core :as devtools])) + +(devtools/set-pref! :disable-advanced-mode-check true) + +(devtools/install!) diff --git a/src/scittle/core.cljs b/src/scittle/core.cljs index 12c3afa..c603d9c 100644 --- a/src/scittle/core.cljs +++ b/src/scittle/core.cljs @@ -2,10 +2,19 @@ (:refer-clojure :exclude [time]) (:require [cljs.reader :refer [read-string]] [goog.object :as gobject] - [goog.string] + [goog.string :as gstring] + [goog.string.format] [sci.core :as sci] + [sci.ctx-store :as store] + [sci.impl.unrestrict] [scittle.impl.common :refer [cljns]] - [scittle.impl.error :as error])) + [scittle.impl.error :as error] + [clojure.string :as str])) + +(set! sci.impl.unrestrict/*unrestricted* true) + +;; make document conditional +(def ^js doc js/globalThis.document) (clojure.core/defmacro time "Evaluates expr and prints the time it took. Returns the value of expr." @@ -24,16 +33,42 @@ {'clojure.core {'time (sci/copy-var time cljns) 'system-time (sci/copy-var system-time cljns) - 'random-uuid random-uuid - 'read-string (sci/copy-var read-string rns)} + 'random-uuid (sci/copy-var random-uuid cljns) + 'read-string (sci/copy-var read-string cljns) + 'update-keys (sci/copy-var update-keys cljns) + 'update-vals (sci/copy-var update-vals cljns) + 'parse-boolean (sci/copy-var parse-boolean cljns) + 'parse-double (sci/copy-var parse-double cljns) + 'parse-long (sci/copy-var parse-long cljns) + 'parse-uuid (sci/copy-var parse-uuid cljns) + 'NaN? (sci/copy-var NaN? cljns) + 'infinite? (sci/copy-var infinite? cljns) + 'iteration (sci/copy-var iteration cljns) + 'abs (sci/copy-var abs cljns) + 'Cons cljs.core/Cons} 'goog.object {'set gobject/set - 'get gobject/get}}) + 'get gobject/get} + 'goog.string {'format gstring/format + 'htmlEscape gstring/htmlEscape} + 'goog.string.format {} ;; For cljs compatibility + 'sci.core {'stacktrace sci/stacktrace + 'format-stacktrace sci/format-stacktrace}}) -(def !sci-ctx (atom (sci/init {:namespaces namespaces - :classes {'js js/window - :allow :all} - :disable-arity-checks true}))) +(defn load-fn [{:keys [ctx] :as opts}] + (when-let [lib (and (string? (:namespace opts)) + (gobject/get js/globalThis (:namespace opts)))] + (sci/add-js-lib! ctx (:namespace opts) lib))) +(store/reset-ctx! + (sci/init {:namespaces namespaces + :classes {'js js/globalThis + :allow :all + 'Math js/Math} + :ns-aliases {'clojure.pprint 'cljs.pprint} + :features #{:scittle :cljs} + :load-fn load-fn})) + +(unchecked-set js/globalThis "import" (js/eval "(x) => import(x)")) (def !last-ns (volatile! @sci/ns)) @@ -41,51 +76,54 @@ (sci/binding [sci/ns @!last-ns] (let [rdr (sci/reader s)] (loop [res nil] - (let [form (sci/parse-next @!sci-ctx rdr)] + (let [form (sci/parse-next (store/get-ctx) rdr)] (if (= :sci.core/eof form) (do (vreset! !last-ns @sci/ns) res) - (recur (sci/eval-form @!sci-ctx form)))))))) + (recur (sci/eval-form (store/get-ctx) form)))))))) (defn ^:export eval-string [s] (try (-eval-string s) (catch :default e - (error/error-handler e (:src @!sci-ctx)) - (let [sci-error? (isa? (:type (ex-data e)) :sci/error)] - (throw (if sci-error? - (or (ex-cause e) e) - e)))))) + (error/error-handler e (:src (store/get-ctx))) + (throw e)))) -(defn register-plugin! [plug-in-name sci-opts] - plug-in-name ;; unused for now - (swap! !sci-ctx sci/merge-opts sci-opts)) +(defn register-plugin! [_plug-in-name sci-opts] + (store/swap-ctx! sci/merge-opts sci-opts)) (defn- eval-script-tags* [script-tags] (when-let [tag (first script-tags)] - (if-let [text (not-empty (gobject/get tag "textContent"))] - (let [scittle-id (str (gensym "scittle-tag-"))] - (gobject/set tag "scittle_id" scittle-id) - (swap! !sci-ctx assoc-in [:src scittle-id] text) - (sci/binding [sci/file scittle-id] - (eval-string text)) - (eval-script-tags* (rest script-tags))) - (let [src (.getAttribute tag "src") - req (js/XMLHttpRequest.) + (if-let [src (.getAttribute tag "src")] + (let [req (js/XMLHttpRequest.) _ (.open req "GET" src true) _ (gobject/set req "onload" (fn [] (this-as this (let [response (gobject/get this "response")] (gobject/set tag "scittle_id" src) ;; save source for error messages - (swap! !sci-ctx assoc-in [:src src] response) + (store/swap-ctx! assoc-in [:src src] response) (sci/binding [sci/file src] (eval-string response))) (eval-script-tags* (rest script-tags)))))] - (.send req))))) + (.send req)) + (if-let [text (not-empty (str/trim (gobject/get tag "textContent")))] + (let [scittle-id (str (gensym "scittle-tag-"))] + (gobject/set tag "scittle_id" scittle-id) + (store/swap-ctx! assoc-in [:src scittle-id] text) + (sci/binding [sci/file scittle-id] + (eval-string text)) + (eval-script-tags* (rest script-tags))) + (eval-script-tags* (rest script-tags)))))) -(defn ^:export eval-script-tags [] - (let [script-tags (js/document.querySelectorAll "script[type='application/x-scittle']")] +(defn ^:export eval-script-tags [& [script-tags]] + (let [script-tags (or script-tags + (.querySelectorAll + doc "script[type='application/x-scittle']")) + script-tags (if (or (coll? script-tags) + (aget script-tags "length")) + script-tags + [script-tags])] (eval-script-tags* script-tags))) (def auto-load-disabled? (volatile! false)) @@ -97,10 +135,10 @@ [] (vreset! auto-load-disabled? true)) -(js/document.addEventListener - "DOMContentLoaded" - (fn [] (when-not @auto-load-disabled? (eval-script-tags))), false) +(when doc + (.addEventListener doc + "DOMContentLoaded" + (fn [] (when-not @auto-load-disabled? (eval-script-tags))), false)) (enable-console-print!) (sci/alter-var-root sci/print-fn (constantly *print-fn*)) - diff --git a/src/scittle/impl/error.cljs b/src/scittle/impl/error.cljs index f899fc2..b5edb4e 100644 --- a/src/scittle/impl/error.cljs +++ b/src/scittle/impl/error.cljs @@ -69,8 +69,8 @@ (when-let [m (.-message e)] (println (str "Message: " m))) (when-let [d (ex-data (ex-cause e) #_(.getCause e))] - (print (str "Data: ")) - (prn d)) + (println (str "Data: ") + (pr-str d))) (let [{:keys [:file :line :column]} d] (when line (println (str "Location: " diff --git a/src/scittle/js_interop.cljs b/src/scittle/js_interop.cljs new file mode 100644 index 0000000..603b565 --- /dev/null +++ b/src/scittle/js_interop.cljs @@ -0,0 +1,8 @@ +(ns scittle.js-interop + (:require + [sci.configs.applied-science.js-interop :as j] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::js-interop + j/config) diff --git a/src/scittle/nrepl.cljs b/src/scittle/nrepl.cljs index e7fcad2..ec4c38c 100644 --- a/src/scittle/nrepl.cljs +++ b/src/scittle/nrepl.cljs @@ -1,45 +1,21 @@ (ns scittle.nrepl (:require [clojure.edn :as edn] - [sci.nrepl.completions :refer [completions]] - [scittle.core :refer [!last-ns eval-string !sci-ctx]])) + [sci.nrepl.server :as nrepl-server])) -(defn nrepl-websocket [] - (.-ws_nrepl js/window)) +(defn ws-url [host port path] + (str "ws://" host ":" port "/" path)) -(defn nrepl-reply [{:keys [id session]} payload] - (.send (nrepl-websocket) - (str (assoc payload :id id :session session :ns (str @!last-ns))))) - -(defn handle-nrepl-eval [{:keys [code] :as msg}] - (let [[kind val] (try [::success (eval-string code)] - (catch :default e - [::error (str e)]))] - (case kind - ::success - (do (nrepl-reply msg {:value (pr-str val)}) - (nrepl-reply msg {:status ["done"]})) - ::error - (do - (nrepl-reply msg {:err (pr-str val)}) - (nrepl-reply msg {:ex (pr-str val) - :status ["error" "done"]}))))) - -(defn handle-nrepl-message [msg] - (case (:op msg) - :eval (handle-nrepl-eval msg) - :complete (let [completions (completions (assoc msg :ctx @!sci-ctx))] - (nrepl-reply msg completions)))) - -(when (.-SCITTLE_NREPL_WEBSOCKET_PORT js/window) +(when-let [ws-port (.-SCITTLE_NREPL_WEBSOCKET_PORT js/window)] (set! (.-ws_nrepl js/window) - (new js/WebSocket "ws://localhost:1340/_nrepl"))) + (new js/WebSocket (ws-url (or (.-SCITTLE_NREPL_WEBSOCKET_HOST js/window) + (.-hostname (.-location js/window))) + ws-port "_nrepl")))) -(when-let [ws (nrepl-websocket)] - (prn :ws ws) +(when-let [ws (nrepl-server/nrepl-websocket)] (set! (.-onmessage ws) (fn [event] - (handle-nrepl-message (edn/read-string (.-data event))))) + (nrepl-server/handle-nrepl-message (edn/read-string (.-data event))))) (set! (.-onerror ws) (fn [event] (js/console.log event)))) diff --git a/src/scittle/pprint.cljs b/src/scittle/pprint.cljs new file mode 100644 index 0000000..c4d1209 --- /dev/null +++ b/src/scittle/pprint.cljs @@ -0,0 +1,8 @@ +(ns scittle.pprint + (:require + [sci.configs.cljs.pprint :refer [config]] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::pprint + config) diff --git a/src/scittle/promesa.cljs b/src/scittle/promesa.cljs new file mode 100644 index 0000000..73c6325 --- /dev/null +++ b/src/scittle/promesa.cljs @@ -0,0 +1,8 @@ +(ns scittle.promesa + (:require + [sci.configs.funcool.promesa :as p] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::promesa + p/config) diff --git a/src/scittle/re_frame.cljs b/src/scittle/re_frame.cljs new file mode 100644 index 0000000..39e219e --- /dev/null +++ b/src/scittle/re_frame.cljs @@ -0,0 +1,8 @@ +(ns scittle.re-frame + (:require + [sci.configs.re-frame.re-frame :as rf] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::re-frame + rf/config) diff --git a/src/scittle/replicant.cljs b/src/scittle/replicant.cljs new file mode 100644 index 0000000..b1c9c7a --- /dev/null +++ b/src/scittle/replicant.cljs @@ -0,0 +1,8 @@ +(ns scittle.replicant + (:require + [sci.configs.cjohansen.replicant :refer [config]] + [scittle.core :as scittle])) + +(scittle/register-plugin! + ::replicant + config)