Can I use arbitrary node modules from clojurescript? - clojurescript

Is it possible to use arbitrary node.js modules in a clojurescript project? If yes, how do I go about including them? If not, why not?

Yes, you can, there is nothing special about it:
(def fs (js/require "fs"))
(println (.readdirSync fs js/__dirname))
Be careful with the externs if you don't use optimizations none.
Edit: Does leiningen play with the various js package managers?:
Nope. Since the language does not have packages, it cannot know. You have to do js dependency management and lein deps too. There is a lein-npm and a lein-bower to help with integrating these two package managers.

Yes, since late 2017. With shadow-cljs or Lumo now it's not problem to import npm modules in ClojureScript code anymore.
(ns app.main
(:require ["dayjs" :as dayjs]
["shortid" :as shortid]
["lodash" :as lodash]
["lodash" :refer [isString]]))
Read this topic for details: Guide on how to use/import npm modules/packages in ClojureScript?

Since ClojureScript 1.9.854, there's better support to declare npm modules as dependencies and to require them from your namespaces.
In order to declare it as a dependency, you need to use the :npm-deps compiler option (together with the :install-deps one, if you want lein/bootto automatically install it).
:npm-deps is a map from keyword to string, where the keyword is the name of the dependency you would use to install it using npm and the string is the version of the dependency.
An example of what you could add to your project.clj (if you use lein-cljsbuild), in order to use left-pad:
:cljsbuild {:builds [{:id "prod"
:source-paths ["src"]
:compiler {:main left-pad-demo.core
:output-to "package/index.js"
:target :nodejs
:output-dir "target"
:optimizations :simple
:install-deps true
:npm-deps {:left-pad "1.2.0"}
:pretty-print true}}]})
And then, from your namespace, you can require it like so:
(ns left-pad-demo.core
(:require left-pad))
Or so:
(ns left-pad-demo.core
(:require ["left-pad" :as lp]))
A full working namespace could look like:
(ns left-pad-demo.core
(:require left-pad))
(defn -main [s length]
(console.log (left-pad s length)))
(set! *main-cli-fn* -main)
References:
Requiring Node.js modules from ClojureScript namespaces
ClojureScript is not an Island: Integrating Node Modules

Related

Requiring javascript with Clojurescript :bundle

Using ClojureScript :bundle
I can require other javascript libraries but react-datepicker is causing the followinf error:
react-dom.development.js:23965 Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
This is how I requires the library
(:require ["react-datepicker" :as DatePicker])
Than I attempt to use it
($ DatePicker {...})
My guess would be that react-datepicker has a default export.
Try ($ DatePicker/default ...) or with the upcoming CLJS release it would be (:require ["react-datepicker$default" :as DatePicker]) but that is not released as of today and would require cloning master.

Failed to construct ‘HTMLElement’: Please use the ‘new’ operator

I am trying to use AWS Amplify Authentication lib in a re-frame app.
The lib provides a higher order component withAuthenticator which is supposed to wrap the main view of your app. I am trying to use reactify-component and adapt-react-class but unfortunately I get the following error:
Uncaught TypeError: Failed to construct ‘HTMLElement’: Please use the ‘new’ operator, this DOM object constructor cannot be called as a function.
(defn main-panel []
[:div
[:h1 "Hello" ]])
(def root-view
(reagent/adapt-react-class
(withAuthenticator
(reagent/reactify-component main-panel))))
(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(aws-config/configure)
(re-frame/dispatch-sync [::events/initialize-db])
(reagent/render [root-view]
(.getElementById js/document "app")))
Any help is appreciated
I had this issue with reagent + amplify.
Solved it with 2 changes, but I'm unsure if both are needed
#1 Change output of the google closure compiler to es6 (or higher). Amplify seems to use es6 features that Cannot be polyfilled.
This is for shadow-cljs (shadow-cljs.edn), but this should be possible for other build systems as well.
{:builds {:app {:compiler-options {:output-feature-set :es6}}}}
Disclaimer: I switched to shadow-cljs from lein-cljsbuild since I could not get lein-cljsbuild to respect the configuration for es6 output.
#2 Use functional components.
In reagent 1.0.0-alpha1 and up you can change the compiler to produce functional components by default.
(ns core
(:require
[reagent.core :as r]))
(def functional-compiler (r/create-compiler {:function-components true}))
(r/set-default-compiler! functional-compiler)
There are other ways to make functional components. Check the documentation if you don't like, or can't use, this approach.

Require ClojureScript's Analyzer API

I'm having trouble requiring ClojureScript's analyzer API:
(ns triangle.core
(:require
[cljs.analyzer.api :as ana-api]))
yields:
clojure.lang.ExceptionInfo : No such namespace: cljs.analyzer.api, could not locate cljs/analyzer/api.cljs, cljs/analyzer/api.cljc, or Closure namespace "cljs.analyzer.api" in file ...
Anybody knows, what could be the reason? Requiring cljs.analyzer works without problems.
Works for me, are you using the latest version?

clj-http/get url {:as :json} doesn't work in script but in REPL

I'm experimenting with Clojure and Leiningen.
I was successful in executing to following line in the REPL:
(print (:body (client/get "https://coinbase.com/api/v1/prices/spot_rate?currency=CAD" {:as :json}))
I created a project with lein new http. When I run the following lines witn lein run then the coercion to JSON doesn't work. It simply prints a correct JSON string.
(ns http.core
(:require [clj-http.client :as client])
(:use clojure.pprint))
(defn -main
[]
(print
(:body
(client/get "https://coinbase.com/api/v1/prices/spot_rate?currency=CAD" {:as :json}))
the output of the script is
{"amount":"306.89","currency":"CAD"}
Any idea what's wrong?
As it turned out there was a breaking change with clj-http version 2.0.0.
Now one has to list explicitly the optional dependencies in project.clj.
After I added
[cheshire "5.5.0"]
to my list of dependencies the program worked as expected.
Please see the documentation for the change here.
I don't know exactly what changed, but [clj-http "1.1.2"] has the behavior you want.

Serving data with Ring and Compojure

I am configuring and setting up a web application to serve the JSON data http://www.ericrochester.com/clj-data-analysis/data/census-race.json file statically.
My dependencies:
:dependencies [[org.clojure/clojure "1.5.1"]
[org.clojure/clojurescript "0.0-2197"]
[ring/ring-core "1.1.7"]
[ring/ring-jetty-adapter "1.1.7"]
[compojure "1.1.3"]
[hiccup "1.0.2"]
[lein-cljsbuild "0.2.10"]]
As the title says I'm using Ring as a development plugin, i.e.
:plugins [[lein-ring "0.8.3"]]
The Leiningen project is
(ns test-app.core
(:require [compojure.route :as route]
[compojure.handler :as handler]
[clojure.string :as str])
(:use compojure.core
ring.adapter.jetty
[ring.middleware.content-type :only
(wrap-content-type)]
[ring.middleware.file :only (wrap-file)]
[ring.middleware.file-info :only
(wrap-file-info)]
[ring.middleware.stacktrace :only
(wrap-stacktrace)]
[ring.util.response :only (redirect)]))
and in the project.clj
:ring {:handler test-app.core/app}
which I am not sure if this will tell Ring where the web application
is.
Moreover, I am not sure how to serve the JSON data file statically. I have read that "Ring serves static files out of the /resources directory of your project. In this case, create the directory /resources/data and put the data file that you downloaded from http://www.ericrochester.com/clj-data-analysis/data/census-race.json into it."
It is mostly this last part about creating the /resources/data directory where I am lost in the implementation. Could someone show how this part is done?
If I can learn to get past this I am looking to build routes and handlers, i.e.
(defroutes site-routes
(GET "/" [] (redirect "/data/census-race.json"))
(route/resources "/")
(route/not-found "Page not found"))
and
(def app
(-> (handler/site site-routes)
(wrap-file "resources")
(wrap-file-info)
(wrap-content-type)))
and in the project.clj
:ring {:handler test-app.core/app}
which I am not sure if this will tell Ring where the web application
is.
When you run $ lein ring server-headless it will look for app in the test-app.core namespace. You should have something like the following in your core.clj file:
(def app
(handler/site app-routes))
It is mostly this last part about creating the /resources/data
directory where I am lost in the implementation. Could someone show
how this part is done?
In the root of your project directory you should have a resources folder. Create a data folder inside the resources folder.
I'm not sure what problem you're having?
If your project looks like this:
project.clj
src/test_app/core.clj
resources/public/data/census-race.json
Then the your site-routes handler will serve up that JSON file when you request the path /data/census-race.json.
You don't need any of the extra middleware like wrap-file, wrap-file-info, or wrap-content-type, since compojure.route/resources already does everything you need.