diff --git a/CHANGELOG.md b/CHANGELOG.md index 738fc2b5..1648356f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ For a list of breaking changes, check [here](#breaking-changes). +## 0.3.x + +- Add initial features support with datascript and datascript-transit +- Include `cognitect.transit` as built-in dependency +- Add `clojure.pprint/print-table` + ## 0.3.4 - Don't load modules more than once diff --git a/bb.edn b/bb.edn index b218752c..050446b6 100644 --- a/bb.edn +++ b/bb.edn @@ -2,20 +2,47 @@ :tasks {:requires ([babashka.fs :as fs] [cheshire.core :as json] + [clojure.edn :as edn] + [clojure.pprint :as pp] [clojure.string :as str]) - :init (do (def ^:dynamic *test* (= "true" (System/getenv "NBB_TESTS"))) - (def windows? (-> (System/getProperty "os.name") - str/lower-case - (str/starts-with? "win"))) - (when *test* (println "Tests are enabled..")) - (defn wrap-cmd [cmd] - (let [cmd (if *test* - (str (str/replace cmd - "-M" "-M:test") - " --config-merge shadow-tests.edn") - cmd)] + :init (do + (def ^:dynamic *test* (= "true" (System/getenv "NBB_TESTS"))) + (def windows? (-> (System/getProperty "os.name") + str/lower-case + (str/starts-with? "win"))) + (def features (some-> (System/getenv "NBB_FEATURES") + (str/split (re-pattern ",")))) + (when *test* (println "Tests are enabled..")) + (defn wrap-cmd [cmd] + (let [cmd (if features + (format "-Sdeps '%s' %s" + {:deps + (into {} + (map (fn [f] + [(symbol (str f "/deps")) + {:local/root (str "features/" f)}]) + features))} + cmd) + cmd) + cmd (if *test* + (str (str/replace cmd + "-M" "-M:test") + " --config-merge shadow-tests.edn") + cmd)] + (if features + (apply str cmd + (map (fn [f] (format " --config-merge features/%s/shadow-cljs.edn" f)) + features)) cmd))) + ;; Build requires map for nbb to require feature namespaces + (when features + (let [m (->> (fs/glob "features" "**/nbb_features.edn") + (mapcat (comp edn/read-string slurp str) ) + (mapcat (fn [{:keys [namespaces js]}] + (mapv (fn [n] [n js]) namespaces))) + (into {}))] + (spit "src/nbb/feature-requires.edn" (with-out-str (pp/pprint m)))))) clean (fs/delete-tree "lib") compile (clojure (wrap-cmd "-M -m shadow.cljs.devtools.cli --force-spawn compile modules")) diff --git a/deps.edn b/deps.edn index c486a780..bf565c82 100644 --- a/deps.edn +++ b/deps.edn @@ -19,4 +19,5 @@ org.babashka/sci-configs {:git/url "https://github.com/babashka/sci-configs" :git/sha "d197da2413bd8e4e5c43159620ddfe4286c731ba"} - org.clojure/tools.cli {:mvn/version "1.0.206"}}} + org.clojure/tools.cli {:mvn/version "1.0.206"} + com.cognitect/transit-cljs {:mvn/version "0.8.269"}}} diff --git a/doc/api.md b/doc/api.md index c86b4aea..8fcafa8e 100644 --- a/doc/api.md +++ b/doc/api.md @@ -59,3 +59,9 @@ See [js-interop docs](https://github.com/applied-science/js-interop). ### clojure.tools.cli - `format-lines`, `summarize`, `get-default-options`, `parse-opts`, `make-summary-part` + +## transit-cljs + +### cognitect.transit + +- `write`, `writer`, `write-handler`, `write-meta`, `read`, `read`, `read-handler`, `tagged-value` diff --git a/doc/dev.md b/doc/dev.md index d3189d0f..3f47f12b 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -48,3 +48,16 @@ Then run `node out/nbb_main.js ` to test the compiled nbb. ## Test To run tests, run `bb run-tests` for unit tests and `bb run-integration-tests` for running integration tests. + +## Features + +`nbb` can optionally bundle additional Clojure(Script) libraries as features. These can be specified with `$NBB_FEATURES` to compilation tasks e.g. `NBB_FEATURES=datascript,datascript-transit bb release`. The following features are provided: + +* [datascript](https://github.com/tonsky/datascript) +* [datascript-transit](https://github.com/tonsky/datascript-transit) + +To add a new feature, add the following under `features/$LIBRARY/`: +- `deps.edn` - Dependencies for library +- `shadow-cljs.edn` - Compiler options to build library in advanced/release mode +- `src/nbb/impl/$LIBRARY.cljs` - Sci mappings +- `src/nbb_features.edn` - Configuration to map namespaces to js assets diff --git a/features/datascript-transit/deps.edn b/features/datascript-transit/deps.edn new file mode 100644 index 00000000..2412f0ac --- /dev/null +++ b/features/datascript-transit/deps.edn @@ -0,0 +1,2 @@ +{:deps + {datascript-transit/datascript-transit {:mvn/version "0.3.0"}}} diff --git a/features/datascript-transit/shadow-cljs.edn b/features/datascript-transit/shadow-cljs.edn new file mode 100644 index 00000000..01fe58f8 --- /dev/null +++ b/features/datascript-transit/shadow-cljs.edn @@ -0,0 +1,3 @@ +{:modules + {:nbb_datascript_transit {:init-fn nbb.impl.datascript-transit/init + :depends-on #{:nbb_core :nbb_datascript :nbb_transit}}}} diff --git a/features/datascript-transit/src/nbb/impl/datascript_transit.cljs b/features/datascript-transit/src/nbb/impl/datascript_transit.cljs new file mode 100644 index 00000000..2759e996 --- /dev/null +++ b/features/datascript-transit/src/nbb/impl/datascript_transit.cljs @@ -0,0 +1,18 @@ +(ns nbb.impl.datascript-transit + {:no-doc true} + (:require [datascript.transit :as dt] + [nbb.core :as nbb] + [sci.core :as sci :refer [copy-var]])) + +(def transit-ns (sci/create-ns 'datascript.transit nil)) + +(def transit-namespace + {'read-handlers (copy-var dt/read-handlers transit-ns) + 'write-handlers (copy-var dt/write-handlers transit-ns) + 'read-transit-str (copy-var dt/read-transit-str transit-ns) + 'write-transit-str (copy-var dt/write-transit-str transit-ns)}) + +(defn init [] + (nbb/register-plugin! + ::datascript-transit + {:namespaces {'datascript.transit transit-namespace}})) diff --git a/features/datascript-transit/src/nbb_features.edn b/features/datascript-transit/src/nbb_features.edn new file mode 100644 index 00000000..890ab900 --- /dev/null +++ b/features/datascript-transit/src/nbb_features.edn @@ -0,0 +1,3 @@ +[{:name logseq/datascript-transit + :namespaces [datascript.transit] + :js "./nbb_datascript_transit.js"}] diff --git a/features/datascript/deps.edn b/features/datascript/deps.edn new file mode 100644 index 00000000..ac545d01 --- /dev/null +++ b/features/datascript/deps.edn @@ -0,0 +1,2 @@ +{:deps + {datascript/datascript {:mvn/version "1.3.12"}}} diff --git a/features/datascript/shadow-cljs.edn b/features/datascript/shadow-cljs.edn new file mode 100644 index 00000000..7d9c996a --- /dev/null +++ b/features/datascript/shadow-cljs.edn @@ -0,0 +1,6 @@ +{:compiler-options {:externs ["datascript/externs.js"]} + :modules + {:nbb_datascript {:init-fn nbb.impl.datascript/init + ;; From https://github.com/tonsky/datascript/issues/298#issuecomment-813790783 + :prepend "if (global) { global.datascript = datascript } else if (window) { window.datascript = datascript } else { var datascript = {}}" + :depends-on #{:nbb_core}}}} diff --git a/features/datascript/src/nbb/impl/datascript.cljs b/features/datascript/src/nbb/impl/datascript.cljs new file mode 100644 index 00000000..44ecd620 --- /dev/null +++ b/features/datascript/src/nbb/impl/datascript.cljs @@ -0,0 +1,49 @@ +(ns nbb.impl.datascript + {:no-doc true} + (:require [datascript.core :as d] + [datascript.db :as db] + [nbb.core :as nbb] + [sci.core :as sci :refer [copy-var]])) + +(def core-ns (sci/create-ns 'datascript.core nil)) +(def db-ns (sci/create-ns 'datascript.db nil)) + +(def core-namespace + {'q (copy-var d/q core-ns) + 'empty-db (copy-var d/empty-db core-ns) + 'db-with (copy-var d/db-with core-ns) + 'filter (copy-var d/filter core-ns) + 'init-db (copy-var d/init-db core-ns) + 'datom (copy-var d/datom core-ns) + 'datoms (copy-var d/datoms core-ns) + 'pull (copy-var d/pull core-ns) + 'pull-many (copy-var d/pull-many core-ns) + 'entity (copy-var d/entity core-ns) + 'tx0 (copy-var d/tx0 core-ns) + 'db (copy-var d/db core-ns) + 'with (copy-var d/with core-ns) + 'touch (copy-var d/touch core-ns) + 'index-range (copy-var d/index-range core-ns) + 'listen! (copy-var d/listen! core-ns) + 'conn-from-db (copy-var d/conn-from-db core-ns) + 'conn-from-datoms (copy-var d/conn-from-datoms core-ns) + 'transact! (copy-var d/transact! core-ns) + 'create-conn (copy-var d/create-conn core-ns) + 'reset-conn! (copy-var d/reset-conn! core-ns) + 'from-serializable (copy-var d/from-serializable core-ns) + 'serializable (copy-var d/serializable core-ns)}) + +(def db-namespace + {'db-from-reader (copy-var db/db-from-reader db-ns) + 'datom-from-reader (copy-var db/datom-from-reader db-ns) + 'datom-added (copy-var db/datom-added db-ns) + 'datom-tx (copy-var db/datom-tx db-ns) + 'datom (copy-var db/datom db-ns) + 'DB (copy-var db/DB db-ns) + 'Datom (copy-var db/Datom db-ns)}) + +(defn init [] + (nbb/register-plugin! + ::datascript + {:namespaces {'datascript.core core-namespace + 'datascript.db db-namespace}})) diff --git a/features/datascript/src/nbb_features.edn b/features/datascript/src/nbb_features.edn new file mode 100644 index 00000000..68a9ed9b --- /dev/null +++ b/features/datascript/src/nbb_features.edn @@ -0,0 +1,3 @@ +[{:name logseq/datascript + :namespaces [datascript.core datascript.db] + :js "./nbb_datascript.js"}] diff --git a/script/nbb_tests.clj b/script/nbb_tests.clj index 4657dcaf..98321e3e 100644 --- a/script/nbb_tests.clj +++ b/script/nbb_tests.clj @@ -103,11 +103,15 @@ "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"))))) (deftest pprint-test - (is (= (str "(0 1 2 3 4 5 6 7 8 9)\n") - (nbb "-e" "(require '[cljs.pprint :as pp]) (with-out-str (pp/pprint (range 10)))"))) + (testing "pprint" + (is (= (str "(0 1 2 3 4 5 6 7 8 9)\n") + (nbb "-e" "(require '[cljs.pprint :as pp]) (with-out-str (pp/pprint (range 10)))")))) (testing "cljs.pprint = clojure.pprint" (is (= (str "(0 1 2 3 4 5 6 7 8 9)\n") - (nbb "-e" "(require '[clojure.pprint :as pp]) (with-out-str (pp/pprint (range 10)))"))))) + (nbb "-e" "(require '[clojure.pprint :as pp]) (with-out-str (pp/pprint (range 10)))")))) + (testing "print-table" + (is (= "\n| :a |\n|----|\n| 1 |\n| 2 |\n" + (nbb* "-e" "(require '[clojure.pprint :as pp]) (do (pp/print-table [{:a 1} {:a 2}]))"))))) (deftest api-test (tasks/shell {:dir "test-scripts/api-test"} (npm "install")) @@ -161,6 +165,10 @@ (is (= expected-cljs-bean-output (normalize-interop-output (nbb* "examples/cljs-bean/example.cljs"))))) +(deftest transit-test + (is (= {:fruits [:apple :banana :pear]} + (nbb "test-scripts/transit.cljs" (pr-str {:fruits [:apple :banana :pear]}))))) + (deftest error-test (let [err (-> (process ["node" "lib/nbb_main.js" "test-scripts/error.cljs"] {:out :string diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 045bd088..fb5f4f7a 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -40,6 +40,8 @@ :depends-on #{:nbb_core}} :nbb_tools_cli {:init-fn nbb.impl.tools-cli/init :depends-on #{:nbb_core :nbb_goog_string}} + :nbb_transit {:init-fn nbb.impl.transit/init + :depends-on #{:nbb_core}} :nbb_nrepl_server {:init-fn nbb.impl.nrepl-server/init :depends-on #{:nbb_core :nbb_api}} :nbb_repl {:init-fn nbb.impl.repl/init diff --git a/src/nbb/core.cljs b/src/nbb/core.cljs index 793b327b..6de859f0 100644 --- a/src/nbb/core.cljs +++ b/src/nbb/core.cljs @@ -5,11 +5,13 @@ ["path" :as path] ["url" :as url] [clojure.string :as str] + [clojure.edn :as edn] [goog.object :as gobj] [nbb.classpath :as cp] [nbb.common :refer [core-ns]] [sci.core :as sci] [sci.impl.vars :as vars] + [shadow.resource :as resource] [shadow.esm :as esm]) (:require-macros [nbb.macros :as macros @@ -105,6 +107,14 @@ (declare old-require) +(defn- get-feature-requires + [] + (edn/read-string (resource/inline "nbb/feature-requires.edn"))) + +;; Lazily build map once to not effect initial load time +(def feature-requires + (memoize get-feature-requires)) + (defn ^:private handle-libspecs [libspecs] (if (seq libspecs) (let [fst (first libspecs) @@ -120,7 +130,7 @@ current-ns (symbol current-ns-str)] (if as-alias (do (old-require fst) - (handle-libspecs (next libspecs))) + (handle-libspecs (next libspecs))) (case libname ;; built-ins (reagent.core) @@ -159,7 +169,12 @@ (load-module "./nbb_tools_cli.js" libname as refer rename libspecs) (goog.string goog.string.format) (load-module "./nbb_goog_string.js" libname as refer rename libspecs) - (if (string? libname) + (cognitect.transit) + (load-module "./nbb_transit.js" libname as refer rename libspecs) + (cond + (get (feature-requires) libname) + (load-module (get (feature-requires) libname) libname as refer rename libspecs) + (string? libname) ;; TODO: parse properties (let [[libname properties] (str/split libname #"\$" 2) properties (when properties (.split properties ".")) @@ -199,11 +214,12 @@ mod))))))] (-> mod (.then after-load))) + :else ;; assume symbol (if (sci/eval-form @sci-ctx (list 'clojure.core/find-ns (list 'quote libname))) ;; built-in namespace (do (old-require fst) - (handle-libspecs (next libspecs))) + (handle-libspecs (next libspecs))) (let [file (str/replace (str munged) #"\." "/") files [(str file ".cljs") (str file ".cljc")] dirs @cp/classpath-entries @@ -234,16 +250,16 @@ (if-let [clazz (get-in @sci-ctx [:class->opts libname :class])] (do (when as (swap! (:env @sci-ctx) assoc-in [:namespaces current-ns :imports as] libname)) - (doseq [field refer] - (let [mod-field (gobj/get clazz (str field)) - internal-subname (str current-ns "$" munged "$" field)] - (swap! sci-ctx sci/merge-opts {:classes {internal-subname mod-field}}) - ;; Repeat hack from above - (let [field (get rename field field)] - (swap! (:env @sci-ctx) - assoc-in - [:namespaces current-ns :imports field] internal-subname)))) - (handle-libspecs (next libspecs))) + (doseq [field refer] + (let [mod-field (gobj/get clazz (str field)) + internal-subname (str current-ns "$" munged "$" field)] + (swap! sci-ctx sci/merge-opts {:classes {internal-subname mod-field}}) + ;; Repeat hack from above + (let [field (get rename field field)] + (swap! (:env @sci-ctx) + assoc-in + [:namespaces current-ns :imports field] internal-subname)))) + (handle-libspecs (next libspecs))) (js/Promise.reject (js/Error. (str "Could not find namespace: " libname))))))))))) (js/Promise.resolve @sci/ns))) @@ -399,7 +415,8 @@ 'array (sci/copy-var array core-ns) 'tap> (sci/copy-var tap> core-ns) 'add-tap (sci/copy-var add-tap core-ns) - 'remove-tap (sci/copy-var remove-tap core-ns)} + 'remove-tap (sci/copy-var remove-tap core-ns) + 'uuid (sci/copy-var uuid core-ns)} 'clojure.main {'repl-requires (sci/copy-var repl-requires @@ -421,6 +438,7 @@ :set gobj/set :getKeys gobj/getKeys :getValueByKeys gobj/getValueByKeys} + 'ExceptionInfo js/Error 'Math js/Math} :disable-arity-checks true})) diff --git a/src/nbb/feature-requires.edn b/src/nbb/feature-requires.edn new file mode 100644 index 00000000..1be5aa9f --- /dev/null +++ b/src/nbb/feature-requires.edn @@ -0,0 +1,2 @@ +;; This is a stub file which is only populated when nbb is built with features +{} diff --git a/src/nbb/impl/transit.cljs b/src/nbb/impl/transit.cljs new file mode 100644 index 00000000..67eb734b --- /dev/null +++ b/src/nbb/impl/transit.cljs @@ -0,0 +1,22 @@ +(ns nbb.impl.transit + (:require [cognitect.transit :as transit] + [nbb.core :as nbb] + [sci.core :as sci :refer [copy-var]])) + +(def transit-ns (sci/create-ns 'cognitect.transit nil)) + +(def transit-namespace + {'write (copy-var transit/write transit-ns) + 'writer (copy-var transit/writer transit-ns) + 'write-handler (copy-var transit/write-handler transit-ns) + 'write-meta (copy-var transit/write-meta transit-ns) + 'read (copy-var transit/read transit-ns) + 'reader (copy-var transit/reader transit-ns) + 'read-handler (copy-var transit/read-handler transit-ns) + 'tagged-value (copy-var transit/tagged-value transit-ns) + 'ListHandler (copy-var transit/ListHandler transit-ns)}) + +(defn init [] + (nbb/register-plugin! + ::transit + {:namespaces {'cognitect.transit transit-namespace}})) diff --git a/src/nbb/pprint.cljs b/src/nbb/pprint.cljs index 31a7a17d..07529b53 100644 --- a/src/nbb/pprint.cljs +++ b/src/nbb/pprint.cljs @@ -9,13 +9,15 @@ (binding [*print-fn* @sci/print-fn] (apply pp/pprint args))) +(defn print-table [& args] + (binding [*print-fn* @sci/print-fn] + (apply pp/print-table args))) + (def pprint-namespace - {'pprint (sci/copy-var pprint pns)}) + {'pprint (sci/copy-var pprint pns) + 'print-table (sci/copy-var print-table pns)}) (defn init [] (nbb/register-plugin! ::pprint {:namespaces {'cljs.pprint pprint-namespace}})) - - - diff --git a/test-scripts/transit.cljs b/test-scripts/transit.cljs new file mode 100644 index 00000000..066e5ef5 --- /dev/null +++ b/test-scripts/transit.cljs @@ -0,0 +1,10 @@ +(ns transit + (:require [cognitect.transit :as t] + [clojure.edn :as edn])) + +(defn roundtrip [x] + (let [w (t/writer :json) + r (t/reader :json)] + (t/read r (t/write w x)))) + +(prn (roundtrip (edn/read-string (first *command-line-args*))))