Calling cljs from js in a cljs project

clojurescript

#1

Hello,

At my workplace I’ve instigated a ClojureScript project (running on node and browser); to encourage acceptance I’ve promoted the fact that incorporating plain old JS is not a problem. I now find myself with a mix of cljs and js each calling functions from one to the other. It appeared to be working up to 1.9.1032 but the update to the Closure Compiler (72e2ab6e) appears to have broken my build (I used git bisect to pinpoint the breakage). I haven’t been able to exactly reproduce the problem in a minimal reproducible case, but this is my attempt: au-phiware/cljs-in-js-in-cljs. It exhibits some interesting behaviour between 1.9.1032 and 1.9.1033.

First off I’m not even sure my approach is the most sensible or if I’m pushing things in the wrong direction. The majority of the project is written in cljs, some of which calls upon some functionality in a es6 module, e.g.

(ns hello.core
 (:require [hello.es-module :refer [greet]]))

(greet "test")

And some of those es6 modules need to call upon some core functionality that is back in the cljs code. To get access to that functionality I have used goog.require, e.g.

goog.require("hello.say");
export var greet = function(m) {
    document.write(hello.say.it(m));
};

Thus that functionality must be exported, e.g.

(ns hello.say)
(defn ^:export it [m]
  (str "Hello, " m))

Now, proir to 1.9.1033, the compiler would emit the expected goog.provide and transform the export statement. With 1.9.1033, the compiler emits goog.require("goog"); and an extra goog.require for the cljs ns, e.g.

goog.provide("module$usr$src$hello$es_module");
goog.require("goog");
goog.require("hello.say");
goog.require("hello.say");var greet$$module$usr$src$hello$es_module=function(m){document.write(hello.say.it(m))};var module$usr$src$hello$es_module={};module$usr$src$hello$es_module.greet=greet$$module$usr$src$hello$es_module

Compiling with simple optimizations, the goog.require("goog"); appears to confuse the cljs compiler because it adds it itself (src/main/clojure/cljs/closure.clj line 2716) and winds up with two. I notice that there’s already an issue for this: CLJS-1677 but is a low priority.

I guess I’m looking for advice and/or workarounds. Thanks!


#2

Now sure what are those cljs versions, but I’d recommend to test it with the latest stable version 1.9.946.


#3

Those versions are builds made from 72e2ab6e (1.9.1033) and 2f9e50c2 (1.9.1032).


#4

I guess, I want to know: am I doing something that’s unsupported?

It seems that ‘edge’ versions of cljs have broken my project.


#5

I don’t think so? Thanks for sharing it. I haven’t seen a lot of cljs/js interop examples like this.

There’s been a lot of work on JS module support/interop. I would have made the same suggestion as @roman01la but if it doesn’t make sense you might try going forward to the latest (or really close). I’m pretty sure a few bugs in this area have been fixed along the way. Sorry I can’t be more helpful/specific.


#6

This was probably caused by big Closure-compiler update, which changed how provides and requires are handled for module-processed JS (:module-type :es6). Previously Closure generated those goog.provide/require calls itself. We now generate them ourselves because Cljs compiler is written to read those.

My guess is that we should remove goog require here: https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/closure.clj#L1738-L1740

Did you create issue at Cljs jira about this yet?

(CLJS-1677 is different issue)


#7

Hmm, thinking about this, I’m not sure if using goog.require from random CJS or ES6 module should work.

Alternative would be to write JS code as Closure JS:

goog.provide("hello.es-module");
goog.require("hello.say");

hello.es_module.greet = function(m) {
    document.write(hello.say.it(m));
};

And remove :foreign-libs and add :libs ["src"]. In this case ES6 wouldn’t be rewritten to ES3/5 so you can’t use all features.


#8

This should fix the duplicate goog base input error: https://dev.clojure.org/jira/browse/CLJS-2691

Duplicate goog.requires to the cljs file don’t seem to cause problems. It might be easy to fix later, as I think the next Closure-compiler will add more information about require calls I can use to remove duplicates.

@thheller Mentioned that the correct way to require Closure or Cljs namespaces from ES6 is with goog: prefix:

import it from "goog:hello.say";

This should work after https://dev.clojure.org/jira/browse/CLJS-2689 and in this case, no duplicate goog.require calls are written to the file.

There is still another problem. If hello.say is only used by Closure JS (:libs) or module-processed JS, the Cljs file is not compiled, due to how Cljs compiler checks for required Cljs files. But this didn’t work in previous versions either. To workaround this, you can require hello.say in your hello.core ns.


#9

(Apologies for taking so long to get back to this issue.)

Thank you for taking the time to look into this and for your advice. I did not know about the goog: prefix (nor can I find it documented anywhere, I assume it’s a GCC thing?) and I can live with requiring the transitive dependencies (i.e. hello.say from hello.core). I have tried compiling my minimal project with the latest cljs (1.10.271) and it works with both simple and advanced optimisations: au-phiware/cljs-in-js-in-cljs#7f427e2

I haven’t had a chance to try it in my work project yet, which was vastly more complicated but I consider this set of issues solved!


#10

Thanks again for the help, @juhoteperi . I wonder if you have any insight/workaround to a related problem that I’ve encountered, which is caused by that big Closure update? I’ve reported the bug: https://dev.clojure.org/jira/browse/CLJS-2746