Skip to content

Commit

Permalink
Support :js-libs + sci/add-js-lib! (#873)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Kavalar <[email protected]>
  • Loading branch information
borkdude and mk authored Feb 7, 2023
1 parent f6b42a7 commit 5edf6b5
Show file tree
Hide file tree
Showing 14 changed files with 682 additions and 388 deletions.
581 changes: 314 additions & 267 deletions API.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SCI is used in [babashka](https://github.com/babashka/babashka),

## Unreleased

- Add `sci/add-js-lib!` for adding js libraries including corresponding `:js-libs` init option
- Speed up Java interop around 5x by caching method lookups
- Allow destructucturing in CLJS `defmethod`
- [#862](https://github.com/babashka/sci/issues/862): fix JS constructor from class in CLJS namespace
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,34 @@ In JS hosts, to allow interop with anything, use the following config:
{:classes {'js goog/global :allow :all}}
```

### JavaScript libraries

Adding support for JavaScript libraries is done via the `:js-libs` option:

```clojure
(ns sci.examples.js-libs
(:require ["fs" :as fs]
[sci.core :as sci]))

(sci/eval-string "
(require '[\"fs\" :as fs])
(fs/existsSync \"README.md\")"
{:js-libs {"fs" fs}})
;;=> true
```

Note that JavaScript libraries _must_ be required using a string library name.

[Property notation](You can read about that
[here](https://clojurescript.org/news/2021-04-06-release#_library_property_namespaces).) is also supported:

``` clojure
(require '["fs$readFileSync" :as slurp])
(slurp "README.md" "utf-8")
```

JavaScript libraries can be added to an existing SCI context using `sci/add-js-lib!`.

### State

SCI uses a context (internally implemented using an atom) to keep track of state
Expand Down
4 changes: 1 addition & 3 deletions bb.edn
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{:paths ["libsci/bb"]

:pods {clj-kondo/clj-kondo {:version "2022.02.09"}}
:deps {io.github.borkdude/quickdoc
#_{:local/root "/Users/borkdude/dev/quickdoc"}
{:git/url "https://github.com/borkdude/quickdoc"
:git/sha "b290f60fd68380485b613c711d98bea172f2f221"}}
:git/sha "32e726cd6d785d00e49d4e614a05f7436d3831c0"}}

:tasks
{test:jvm {:doc "Run CLJ tests with leiningen"
Expand Down
1 change: 1 addition & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
clj-commons/conch {:mvn/version "0.9.2"}
funcool/promesa {:mvn/version "8.0.450"}}}
:shadow {:extra-deps {thheller/shadow-cljs {:mvn/version "2.19.4"}}}
:cljs {:extra-deps {org.clojure/clojurescript {:mvn/version "1.11.54"}}}
:clj-test-runner
{:extra-deps {com.cognitect/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner"
Expand Down
2 changes: 2 additions & 0 deletions doc/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ anywhere, as long as the result is visible as a top level value:

## Registering a JS library as a class

> NOTE: the below code can be replaced by using `sci/add-js-lib!` from SCI > 0.6.37. See [docs](../README.md#javascript-libraries).
``` clojure
(ns example
(:require
Expand Down
9 changes: 9 additions & 0 deletions examples/sci/examples/js_libs.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(ns sci.examples.js-libs
(:require ["fs" :as fs]
[sci.core :as sci]))

(sci/eval-string "
(require '[\"fs\" :as fs])
(fs/existsSync \"README.md\")"
{:js-libs {"fs" fs}})
;;=> true
9 changes: 9 additions & 0 deletions scratch.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(require '[sci.impl.load] :reload)
(require '[sci.core :as sci] :reload)

(def ctx (sci/init {}))

(sci/add-js-lib! ctx "fs" js/fs)

(sci/eval-form ctx '(do (require '["fs" :as fs] '[clojure.string :as str])
(first (str/split-lines (fs/readFileSync "README.md" "utf-8")))))
8 changes: 6 additions & 2 deletions src/sci/async.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@
(apply load/load-lib ctx nil libname opts))
(handle-libspecs ctx (rest libspecs)))
(if-let [load-fn (:async-load-fn @env*)]
(let [opts (apply hash-map opts)]
(let [opts (apply hash-map opts)
[libname* path] (if (string? libname)
(load/lib+path libname)
[libname])]
(.then (js/Promise.resolve (load-fn {:ns (sci/ns-name @last-ns)
:ctx ctx
:libname libname
:libname libname*
:property-path path
:opts opts}))
(fn [res]
(let [ctx (or (:ctx res) ctx)]
Expand Down
28 changes: 17 additions & 11 deletions src/sci/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
[sci.impl.callstack :as cs]
[sci.impl.interpreter :as i]
[sci.impl.io :as sio]
[sci.impl.load :as load]
[sci.impl.macros :as macros]
[sci.impl.namespaces :as namespaces]
[sci.impl.opts :as opts]
Expand Down Expand Up @@ -144,7 +145,6 @@

;; REPL variables


(macros/deftime
(defmacro with-in-str
"Evaluates body in a context in which sci's *in* is bound to a fresh
Expand Down Expand Up @@ -359,7 +359,7 @@
(let [publics (if exclude (apply dissoc publics exclude) publics)]
publics))

(defn- exclude-when-meta [publics-map meta-fn key-fn val-fn skip-keys ]
(defn- exclude-when-meta [publics-map meta-fn key-fn val-fn skip-keys]
(reduce (fn [ns-map [var-name var]]
(if-let [m (meta-fn var)]
(if (some m skip-keys)
Expand All @@ -371,14 +371,13 @@

(defn- meta-fn [opts]
(cond (= :all opts) identity
opts #(select-keys % opts)
opts #(select-keys % opts)
:else #(select-keys % [:arglists
:no-doc
:macro
:doc
:dynamic])))


(macros/deftime
(defmacro copy-ns
"Returns map of names to SCI vars as a result of copying public
Expand Down Expand Up @@ -462,6 +461,13 @@
[:no-doc :skip-wiki]))]
`(-copy-ns ~publics-map ~sci-ns)))))))

(defn add-import!
"Adds import of class named by `class-name` (a symbol) to namespace named by `ns-name` (a symbol) under alias `alias` (a symbol). Returns mutated context."
[ctx ns-name class-name alias]
;; This relies on an internal format of the context and may change at any time.
(swap! (:env ctx) assoc-in [:namespaces ns-name :imports alias] class-name)
ctx)

(defn add-class!
"Adds class (JVM class or JS object) to `ctx` as `class-name` (a
symbol). Returns mutated context."
Expand All @@ -474,13 +480,6 @@
(assoc-in [:raw-classes class-name] class))))
ctx))

(defn add-import!
"Adds import of class named by `class-name` (a symbol) to namespace named by `ns-name` (a symbol) under alias `alias` (a symbol). Returns mutated context."
[ctx ns-name class-name alias]
;; This relies on an internal format of the context and may change at any time.
(swap! (:env ctx) assoc-in [:namespaces ns-name :imports alias] class-name)
ctx)

(defn add-namespace!
"Adds namespace map `ns-map` named by the symbol `ns-name` to
`ctx`. Returns mutated context."
Expand Down Expand Up @@ -520,4 +519,11 @@
(defn resolve [ctx sym]
(@utils/eval-resolve-state ctx {} sym))

#?(:cljs
(defn add-js-lib!
"Add js library to context, so it can be used with `require`."
[ctx name-str js-lib]
(swap! (:env ctx) assoc-in [:js-libs name-str] js-lib)
ctx))

;;;; Scratch
Loading

0 comments on commit 5edf6b5

Please sign in to comment.