How to author agnostic JavaScript library in ClojureScript? - clojurescript

Let's say I have a cljs file containing the following:
(ns foo)
(defn add [x y]
(+ x y))
and wish to make this available as a JavaScript library to non-ClojureScript devs (primarily focused on node.js). I can do this:
clj -m cljs.main -c foo
But the problem is that the output is geared towards google closure's module system (e.g. goog.require). I can set the target to none with the -t flag (as opposed to browser or node), and that... doesn't fix this. Setting it to node also doesn't fix the issue: no index.js (it's called main like in Java), no module.exports = blah blah. Seems like it's geared towards standalone, full node apps, rather than libraries.
I understand that ClojureScript uses google closure for it's own sub-modules, and I'm not necessarily looking to get rid of all of that (I'm not sure you could). And I get that es2015 native JavaScript modules are out because of their static nature.
I could massage the output by hand or by script to play nice with the npm ecosystem, but I'm surprised that there's no compiler option that can actually output a npm-friendly module. Or is there? Am I just reading --help wrong?

This assumes that you already have a working installation of ClojureScript and Node.js
Given:
math101
|-- package.json
|-- src
| `-- com
| `-- example
| `-- math.cljs
package.json
{
"name": "math101",
"version": "1.0.0",
"main": "dist/index.js",
"license": "MIT",
"devDependencies": {
"shadow-cljs": "^2.8.52",
"source-map-support": "^0.5.13"
}
}
Notes:
dist/index.js - This will contain our ClojureScript code converted to JavaScript
shadow-cljs - The build (and dependency management) tool that we need
source-map-support - Required to run ClojureScript on Node.js
📣Please make sure you have installed these two NPM dependencies before you proceed further.
math.cljs
(ns com.example.math)
(defn add [x y]
(+ x y))
1. Setup the build tool
At the root of math101 run yarn shadow-cljs init.
This will create a file called shadow-cljs.edn with some default settings:
;; shadow-cljs configuration
{:source-paths
["src/dev"
"src/main"
"src/test"]
:dependencies
[]
:builds
{}}
Let's make some changes.
First you don't need that many source paths for this:
{:source-paths
["src"]
:dependencies
[]
:builds
{}}
Then let's add a build configuration:
;; shadow-cljs configuration
{:source-paths
["src"]
:dependencies
[]
:builds
{:math101 {:target :node-library
:output-to "dist/index.js"
:exports-var com.example.math/add}}}
Notes:
:math101 - This the build id that we will use later
:target :node-library - This tells shadow-cljs that you intend to author a library
:output-to "dist/index.js" - The code you intend to publish
:exports-var com.example.math/add - "Fully qualified name" of the function you intend to publish. This will produce a default export.
Additional notes:
The :target :node-library emits code that can be used (via require) as a standard node library, and is useful for publishing your code for re-use as a compiled Javascript artifact.
Source
There is a :npm-module target available but so far :node-library has checked all the boxes for me.
2. Let's build this!
Run yarn shadow-cljs compile math101.
The first time you run this, shadow-cljs will download a bunch of stuff. Eventually it will finish and when it does...
$ node
> var add = require('./dist')
> add(40, 2)
42
✨✨✨
Need to export more than just one function? No problemo.
Let's add subtract:
(ns com.example.math)
(defn add [x y]
(+ x y))
(defn subtract [x y]
(- x y))
And now let's update our build config:
;; shadow-cljs configuration
{:source-paths
["src"]
:dependencies
[]
:builds
{:math101 {:target :node-library
:output-to "dist/index.js"
:exports {:add com.example.math/add
:subtract com.example.math/subtract}}}}
Run yarn shadow-cljs compile math101 again and when it finishes:
$ node
> var math101 = require('./dist')
> math101.add(40, 2)
42
> math101.subtract(44, 2)
42
✨✨✨✨✨✨
ADDENDUM
This is only intended to get you started. shadow-cljs compile generates non-production code (i.e. it's not minified, dead code is not removed AFAIK and Google Closure hasn't run any optimisation yet).
The command for generating production-ready code is shadow-cljs release but you may want to tweak your build config before.
I would highly recommend that you invest time reading up the shadow-cljs documentation. It is an amazing tool.

shadow-cljs supports output in a CommonJS format via :target :npm-module which does support exactly what you are asking for. Node and other JS tools (eg. webpack) can consume separate namespaces independently. The default CLJS tools do not support this mode.
ClojureScript however is very much written with the assumption that your whole program will be optimized by the Closure Compiler. This makes it less than ideal for writing libraries to be included in other builds. Each "library" built this way will contain its own version of cljs.core and therefore will be pretty large to begin and including 2 libraries built this way is a recipe for disaster since they won't be compatible with each other.

Related

How to integrate Framework 7 with shadow-cljs and clojurescript

When I try to initialize Framework7 and Framework7-React within a clojurescript project, using shadow-cljs I get errors like this:
f7.js:31 Uncaught TypeError: Framework7 is not a constructor
at Object.init (f7.js:31)
at F7App.value (app.js:162)
at commitLayoutEffects (react-dom.development.js:21965)
at HTMLUnknownElement.callCallback (react-dom.development.js:363)
at Object.invokeGuardedCallbackImpl (react-dom.development.js:412)
at invokeGuardedCallback (react-dom.development.js:467)
at commitRootImpl (react-dom.development.js:25025)
at exports.unstable_runWithPriority (scheduler.development.js:816)
at runWithPriority$2 (react-dom.development.js:12189)
at commitRoot (react-dom.development.js:24866)
The Framework7 initialization normally consists of nothing more than the following
// Import Framework7
import Framework7 from 'framework7/framework7-lite.esm.bundle.js';
// Import Framework7-React Plugin
import Framework7React from 'framework7-react';
// Import Framework7 Styles
import 'framework7/css/framework7.bundle.css';
// Init F7 React Plugin
Framework7.use(Framework7React);
There is a hacky solution, the involves two separate build pipelines in order to get
Framework7 up and running in a simple shadow-cljs sample project.
I created a a test project that shows both versions, the non-working clojurescript version, where the error occurs can be run by:
npx shadow-cljs watch :direct-no-webpack
It uses the client2.cljs
The working version initializes Framework7 using the ES6 import syntax, and is transpiled using webpack.
yarn run build-dev
npx shadow-cljs watch :direct
It produces the above mentioned error.
I do not understand in detail, what exactly is going on in Framework7 as multiple wrappers are involved.
How can I correctly import and use Framework7 with Clojurescript and Shadow-CLJS?
I don't know anything about Framework7 but it is usually best to just follow the most basic instructions. In this case I added no additional build config and just required the packages directly.
(ns foo.bar
(:require
["framework7" :as f7]
["framework7-react" :as f7r]))
;; avoiding top-level side-effects because of hot-reload
(defn init []
(.use f7 f7r))
I can't tell if this actually works but it didn't throw an error. Avoid accessing ".esm.js" files or so directly since may cause conflicts if you access those but "framework7-react" accesses different ones. Mixing CommonJS and ESM files is not well supported at the moment.
.css imports are not supported by shadow-cljs.
Even though I read the shadow-clj guidelines on how to use npm packages. I had to use the direct path to the Javascript-files in the node_modules directory.
The correct imports are:
(ns app.ui.client2
(:require
["framework7/framework7-lite.esm.bundle.js" :default Framework7]
["framework7-react/framework7-react.esm.js" :default Framework7React]))
I modifed the test project mentioned in my question.

How to add shadow cljs watcher on js directory in a re-frame app?

I have created a basic re-frame app using the template lein new re-frame my-proj. This particular project is interfacing with a framework (ecsy) that requires some ES6 modules and ES6 classes e.g code that is generated by the user, not simply called from cljs. Since Clojurescript does not currently generate ES6 code, I have created some wrapper ES6 modules in my project from which I plan to call into cljs code.
After much futzing, I have discovered that it's not necessary to turn these js wrapper modules into full-blown npm modules under 'node_modules', but rather I can simply put them in a sub-directory of my project e.g resources/libs and then add this directory to :js-options in shadow-cljs.edn:
{:lein true
:nrepl {:port 8777}
:builds {:app {:target :browser
:output-dir "resources/public/js/compiled"
:asset-path "/js/compiled"
:modules {:app {:init-fn re-pure-ecs-simple.core/init
:preloads [devtools.preload]}}
:devtools {:http-root "resources/public"
:http-port 8280}
;;add this
:js-options {:js-package-dirs ["node_modules" "resources/libs"]}}}}
So everything works fine now, but the only problem is if I edit any of the js files in 'resources/public' the lein.bat dev compiler doesn't detect the changes. I can go in and make a mock change to a '.cljs' file, which does cause the compiler to re-compile, but it still doesn't pick up on the changes made to the '.js' file (or '.mjs' file). I have to kill, via ctrl-c, the compiler and re-start it to get the change propagated. Unfortunately, this takes about 15 seconds to compile since it's a full compile.
I tried adding 'resources/libs' to my 'project.clj':
:source-paths ["src/clj" "src/cljs" "resources/libs"]
to no avail.
I also tried deleting the compiled js files from <my_proj-dir>/resources/public/js/compiled/cljs-runtime:
rm 'module$node_modules$systems.js' 'module$node_modules$systems.js.map'
In this case, the compiler does re-generate the files (upon doing a mock .cljs change), but it still uses the prior version e.g. it must be using a cached version.
Is there a way I can add a watcher to this js directory so I can do incremental builds? There's obviously a watcher on the 'src/cljs' directory already. I have consulted the shadow-cljs user gd. but honestly, it's a little overwhelming.
You can follow the directions for requiring local .js in the User's Guide.
Basically you put the .js files into the same folder as your .cljs file and then just require it via (:require ["./foo.js" :as foo]). No additional config required.

How to compile and test functions in ocaml?

For my summer programming course I need to compile and test several functions from an ocaml file (a .ml file) our teacher gave us. I've never programmed in ocaml before, but I downloaded it on my ubuntu vm and not I can't figure out how to compile it and test the functions from the terminal.
I'm also curious if I need to add print statements to the code in order to test them, since ocaml doesn't require main methods. If so, how do I print the return value of a function?
Thank you for your help, I apologize if this is a newbie question.
Make sure that you have installed OCaml properly, by running the OCaml interactive toplevel (don't type $ this is a prompt from your shell):
$ ocaml
It should show something like this:
OCaml version 4.07.0
#
The # symbol is a prompt, you can type OCaml definitions there and send them to the interpreter using the ;; terminating sequence (again don't type # it is also a prompt symbol), e.g.,
# print_endline "Hello, world";;
Hello, world
- : unit = ()
#
Hint: to enable history install rlwrap with sudo apt install rlwrap and run the ocaml toplevel as
$ rlwrap ocaml
Now, we are ready to compile our first program. In OCaml, like in Python, all top-level definitions in your program are evaluated in order of their appearance, therefore you don't need to have a special main function. Despite this fact, I'm usually defining one, using the following idiom
let main () =
print_endline "Hello, world"
(* the entry point - just call the main function *)
let () = main ()
Now, create a new folder (make sure it is empty)
$ mkdir my-first-ocaml-program
$ cd my-first-ocaml-program
and put the OCaml code above into a file named test.ml (the filename doesn't have any special meaning for the compiler toolchain, but I will reference this name in the shell commands below).
let's test that everything is correct, by printing the contents of the test.ml file
$ cat test.ml
and the output should be
let main () =
print_endline "Hello, world"
(* the entry point - just call the main function *)
let () = main ()
Now, let's compile and run at the same time,
$ ocamlbuild test.native --
And we should see the "Hello, world",
Finished, 4 targets (4 cached) in 00:00:00.
Hello, world
The first line is the output from the compiler (ignore it, unless it is different). Starting from the second line it is our output. Here are some explanations on the build one-liner, ocamlbuild test.native. It uses ocamlbuild, an easy to use but powerful OCaml build tool. The test.native tells ocamlbuild that you want to build a native (machine code) binary and that test.ml is the main source file. You can also ask to build a bytecode binary, e.g., test.byte. This is called a target in ocamlbuild parlance. The -- is optional, and it tells ocamlbuild to run the built target as soon as it is ready. Any argument past -- is passed to your program as command line arguments.
What about larger programs or programs with dependencies? The good news is that you can put your code in several files in the same folder, and ocamlbuild will be clever enough to build them in the proper order (it will do the dependency analysis, compiling, and linking for you - all seamlessly). If you need to use some external package, then you can specify it via the -pkg option. For example, let's assume that we're using the fileutils package to implement our version of the ls program1. Let's update our test.ml so that it now is having the following contents:
$ cat test.ml
let main () =
FileUtil.ls "." |> List.iter print_endline
(* the entry point - just call the main function *)
let () = main ()
and, as usual, build and print in one
$ ocamlbuild -pkg fileutils test.native --
Finished, 4 targets (4 cached) in 00:00:00.
./test.native
./_build
./test.ml
1) How to install a package is system dependent. E.g., if you're using apt to install OCaml then you can do sudo apt install libfileutils-ocaml-dev. If you're using opam (the recommended way), then it is just opam install fileutils. In any case, package installation is out of the scope of this question, I'm assuming that in your course you would be using some packages pre-installed. We use fileutils here just as an example, which you can easily adapt to your own purposes.

ClojureScript compiler can't find namespace/file in same directory

lein cljsbuild is having trouble finding a namespace/file that's defined right alongside another namespace/file unless I make sure they're compiled in a certain order.
My directory layout looks like this:
project/
project.clj
src/
cljs/
contempo/
common/
defs.cljs
view/
core.cljs
navigation.cljs
navigation.cljs has some stuff to build Om components for navigating around the page, and core.cljs is the main entry point for this particular page. navigation.cljs starts with:
(ns contempo.view.navigation (:require ...))
core.cljs starts with:
(ns contempo.view.core (:require [contempo.view.navigation :as navigation]))
When I run lein cljsbuild, I get:
solace:Groov jfischer$ lein cljsbuild auto
Compiling ClojureScript.
Compiling "war/view/js/app.js" from ["src/cljs/contempo/common" "src/cljs/contempo/view"]...
Compiling "war/view/js/app.js" failed.
clojure.lang.ExceptionInfo: failed compiling file:src/cljs/contempo/view/core.cljs
... snipped stacktrace ...
Caused by: clojure.lang.ExceptionInfo: No such namespace: contempo.view.navigation at line 1 src/cljs/contempo/view/core.cljs
I can get it to work by removing all references to contempo.view.navigation from core.cljs, running lein cljsbuild auto and letting the compile finish, then putting them back and saving (so cljsbuild picks up the changes), but that's silly and shouldn't be necessary.
My project.clj looks like:
(defproject contempo "0.0.0-SNAPSHOT"
:description "Experimenting with ClojureScript and Om"
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2740"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
[org.omcljs/om "0.8.7"]]
:plugins [[lein-cljsbuild "1.0.4"]]
:clean-targets ^{:protect false} ["war/view/js/app.js"
"war/view/js/out"]
:cljsbuild {:builds [{:id "view-dev"
:source-paths ["src/cljs/contempo/common"
"src/cljs/contempo/view"]
:compiler {:output-to "war/view/js/app.js"
:output-dir "war/view/js/out"
:optimizations :none
:cache-analysis true
:source-map true}}]})
Is there anything obvious I'm doing wrong? This looks pretty similar to every ClojureScript project I've looked at.
Update: Tiny skeleton project that shows the error is up here: namespace-error-demo.zip
The issue ended up being: I wasn't obeying the rules for how namespaces are resolved.
With source paths of src/cljs/contempo/common and src/cljs/contempo/view, if I required the contempo.view.whatever namespace, the compiler would look for it at src/cljs/contempo/common/contempo/view/whatever.cljs and src/cljs/contempo/view/contempo/view/whatever.cljs.
I had to use src/cljs as the source directory. What I wanted to pull off (leaving out code for a given page that didn't need it) was kind of silly (since it'd be pulling in all of ClojureScript anyway) and is now properly addressed thanks to proper Google Closure Module support in ClojureScript.
I was having this same problem all of today. In my case, the root cause was having .cljs files with "-" in the name. I only discovered this was the problem after switching to 0.0-3030, which provides better error messages for the strict file path to namespace conventions that more recent versions of the cljs compiler have.
You might want to try changing :source-paths to ["src/cljs"]

Arity of a function

Is there a way in clojurescript to check what the available arity implementations of a given cljs function are?
If you're running Emacs with nREPL, as explained on this gist, when your text cursor is near the function name Emacs will show the signatures of the function in the message area.
Like that:
The only answer from Rodrigo Taboada is dated and now lots of new tools are out.
Light Table and Fighwheel
I think nowadays Light Table is one of the best tools for ClojureScript, especially when coupled with Figwheel. More info for using LT with Figwheel.
In Light Table to check a function signature you can simply put the cursor over the function name and hit Ctrl-D to toggle the inline documentation.
Emacs and Cider + Figwheel
For Emacs Cider coupled with Figwheel is a great choice, interactive repl and debugger all inside Emacs.
For checking a function you can use the shortcut Ctrl-c d d to show the documentation and function signature.
Configure Cider cannot be easy at first but Figwheel makes the task easier, just un-comment the line under the comment ;; for CIDER in your project.clj file of your Figwheel project, like this:
:profiles {:dev {:dependencies [[binaryage/devtools "0.7.2"]
[figwheel-sidecar "0.5.4-7"]
[com.cemerick/piggieback "0.2.1"]]
;; need to add dev source path here to get user.clj loaded
:source-paths ["src" "dev"]
;; for CIDER
:plugins [[cider/cider-nrepl "0.13.0"]]
:repl-options {; for nREPL dev you really need to limit output
:init (set! *print-length* 50)
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}}
Then set Cider to use figwheel as repl:
M-x then launch the command customize-variable, the variable name to customize is: cider-cljs-lein-repl then set the variable to Figwheel-sidecar, save the configuration state and you are done.
It is important that Cider is the same version of the cider-nrepl plugin, at the time of this post they HAVE to be both version 0.13.0.