Sharing code between separate scripts (outputs) - clojurescript

I have a project which is not intended to be a single page application, and I'm trying to figure out the best way to share code for common functionality between different script outputs.
For example I may have a stores.js target, and a items.js target built from cljs/stores and cljs/items respectively, but both have some overlapping functionality (say tables with editable cells).
So far the solutions I've come up with are either to symlink common file(s) into both directories that implement the functionality, which I find aesthetically displeasing, but should actually work fine, or to extract the common functionality into a separate package and make the project depend on it. That however involves either requiring developers to setup both repositories and install the dependency locally, or setup a maven server.
This answer from 2012 seems to suggest I should simply do away with multiple targets and instead initialize the functionality I want on the correct page, but it seems to me that having the functionality for the entire site in a single file would make the resulting javascript much larger than it needs to be, increasing the page load time, especially given that I will have more than 2 pages.
Is there a better way?

OK, it turns out the solution is pretty simple, and actually should have been clear from the cljsbuild docs, but I wasn't seeing it. It took trying to reply to #phtrivier for me to actually see what I was missing.
The solution is to use multiple source paths in my project.clj file. So a simple example would look like so:
{:builds [{:source-paths ["src/cljs/items"
"src/cljs/shared"]
:compiler {:output-to "resources/public/js/items.js"
:optimizations :simple
:pretty-print true}
:jar true}
{:source-paths ["src/cljs/stores"
"src/cljs/shared"]
:compiler {:output-to "resources/public/js/stores.js"
:optimizations :simple
:pretty-print true}}]}
from there it's simple:
src/cljs/shared/shared.cljs
(ns shared)
(defn common []
"Hello World")
src/cljs/items/items.cljs
(ns items
(:require [shared :refer [common]]))
(.log js/console (common))
src/cljs/stores/stores.cljs
(ns stores
(:require [shared :refer [common]]))
(js/alert (common))
items.js and stores.js will be their own separate files both containing the shared code.

Related

How do I duplicate a namespace in clojurescript?

I would like to copy - or create an alias for - the module other.thing in clojurescript.
Obviously, I can create a file thing in the library my:
(ns my.thing
(:require
[other.thing]))
(def a other.thing/a)
(def b other.thing/b)
(def c other.thing/c)
...
But this is rather verbose.
It would be nice to:
be able to re-export without naming each item by hand
do so without using a separate file
What other options are available?
I believe it is unidiomatic to do that. In the answers of this releated SO question, people are recommending importing explicitly the required namespaces when using them. There's also this Reddit thread where a redditor has created a tool for re-exporting namespaces, but you still have to do it manually for every function.

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.

Single Output File for Multiple Pages

As a follow-up to Project organization and cljsbuild config to require namespace. If I only have one output file, how do I specify what code to run on each separate web page?
One approach might be to include a script tag <script>myproject.somepage.init();</script> in each page. Is there a generally accepted approach?
I think the previous question was interpreted in the context of single page applications where it doesn't make sense to have more than one output file.
If you need different behaviors in separate web pages, I suggest you have an entry namespace for each webpage, using the :main option:
:cljsbuild {:build [{:id "login"
:main my-login.entry
:output-to "resources/public/js/login.js"
:optimizations :advanced}
{:id "feed"
:main my-feed.entry
:output-to "resources/public/js/feed.js"
:optimizations :advanced}]}}
And then require those files on each webpage.
This approach is worth the trouble if my-feed.entry and my-login.entry will require different code, and it would be a waste to deliver all the code for feed also to login. If the behavior is almost the same, and is just a matter of an argument, or one function call, your suggestion is fine.

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"]

Compiling external JS files with Cljsbuild in ClojureScript

I'm trying to compile some JS libraries that we have with lein-cljsbuild to integrate them in our ClojureScript code base. First I added some goog.provide in top of each file, and the files are hierarchically organised in a directory tree according to their namespace (like in Java). That is namespace a.b.c is in src-js/libs/a/b/c.js
I have put the JS files in the root directory of the projects in src-js/libs, and I have the following :compiler options for lein-cljsbuild:
{:id "prod",
:source-paths ["src-cljs" "src-js"]
:compiler
{:pretty-print false,
:libs ["libs/"]
:output-to "resources/public/js/compiled-app.js",
:optimizations :simple}}
None of the JS files get compiled into the compiled-app file. What's wrong?
I also tried to put them in resources/closure-js/libs without success.
I'm using lein-cljsbuild 0.3.0.
First, unlike what is suggested in some texts, you do not need to include your private closure library locations in any classpath configuration statement in your project.clj. So unless the "src/js" directory included in your "source-paths:" statement is for some other purpose, you can remove it.
Second, the only thing to add to your project.clj, for the sake of bringing in your private closure code, is the "libs:" reference you have made; BUT unlike what you have entered, that reference must be to a specific *.js file (or files) and not merely a directory. So if the library you want to used is in a file named test.js and that resides in the /src/js directory, your libs: entry would be: "src/js/test.js". See the cljs-build release notes if you want to use that plugin's default :libs directory option.
Third, (and it looks like you know this already, but this is what tripped me up) if you are using a browser-backed REPL (repl-listen option of cljsbuild), you still will not be able to load/reference/use your private library assets from that REPL until you include a :require statement somewhere in the source for your compiled-app.js (e.g. "(ns testing (:require [myprivatelib]))" ), THEN you must re-compile (lein cljsbuild once) and reload your browser page with a link to compiled-app.js. This brings in that code base. Otherwise, your browser REPL will just keep insisting that the namespace provided in your closure library is not defined.
I hope this helps.