QML and Clojurescript: Working REPL

469 views
Skip to first unread message

Aaron Craelius

unread,
Jul 3, 2014, 8:34:44 PM7/3/14
to clojur...@googlegroups.com
Hi all,

I have been trying to get Clojurescript working with QML and want to report how I have gotten a QML-based Clojurescript REPL working.

Although some minimal working examples have been posted - https://github.com/fehrenbach/qmcljs and https://github.com/halgari/qt_with_cljs - it has become evident that getting a working REPL is significantly more difficult.

I initially got a somewhat functional REPL working by porting Weasel and using Qt 5.3's Websockets: https://github.com/aaronc/weasel/blob/master/src/cljs/weasel/repl.cljs. Because of QML's unusual behavior with respect to scoping - the root cannot be modified and the value of this for the file from which Clojurescript was loaded cannot be modified after loading the file. As a result, QML usually throws type errors when you try to def most things from the REPL.

My solution to the scoping issue is somewhat of a hack and currently only works with :whitespace optimizations. One nice thing about the hack, however, is that it does allow Clojurescript to modify QML's global scope. Therefore any namespaces defined by Clojurescript will be available to the whole Qml environment. This may or may not be desirable, but it is the only way I've been able to get a REPL working and I would like to ^:export at least some Clojurescript var's globally to QML.

Anyway, this is what I had to do:

in C++:
QQmlApplicationEngine engine;
QObject* global = new QObject();
// reset the root context to my context object
engine.rootContext()->setContextObject(global);
// and expose it globally as "global"
engine.rootContext()->setContextProperty("global", global);

The following preamble is required to the generated javascript:
var goog = goog || {}; // make sure the goog object is created
// before base.js is loaded
goog.global = global; // set goog.global to point to my context property
// which is actually the QML root context!
global.goog = goog; // make sure goog is defined in my global context.


And line 47 in https://github.com/google/closure-library/blob/master/closure/goog/base.js, needs to be changed to:
goog.global = goog.global || this; // only redefine goog.global if it doesn't exist

I should mention that :simple optimizations almost work, but there are issues if you want to modify an already defined namespace because the Closure compiler converts a lot of google.provide("my_ns") into simply var my_ns = {}. This turns out to be an issue if you want to modify that ns from the REPL because of scoping issues. The good news is that, it looks like :simple work if you don't need a full REPL and will only expose the ^:export'ed vars. :advanced mode works partially is probably a matter of declaring the correct externs.

Would any of the Clojurescript maintainers want to comment on this approach... and on the possibility of patching base.js and adding a :qml compilation :target?

Daniel Kersten

unread,
Jul 4, 2014, 5:11:24 AM7/4/14
to clojur...@googlegroups.com
This is really cool!

I've worked with QML a lot in the past, so I've been interested in seeing ClojureScript run in QML!



--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to the Google Groups "ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

Pauli Jaakkola

unread,
Dec 28, 2014, 12:48:47 PM12/28/14
to clojur...@googlegroups.com
Great to hear someone else is also working with ClojureScript on QML!

Although a REPL is cool, what I would really like is getting the :advanced mode working. I did get some trivial examples to work with your root context manipulation trick.

However, it falls apart with "global" scripts (.pragma library), since, according to Qt docs:

"Global code is run in a reduced scope. During startup, if a QML file includes an external JavaScript file with "global" code, it is executed in a scope that contains only the external file itself and the global object."

After experimenting I conclude that even context properties set before loading the .qml file(s) don't exist (I get a "global is not defined" error). Using "this" doesn't work either and apparently its value is neither the root context nor the root object of the script.

I am at a loss here. Maybe the solution involves finding out what actually exists when "global" code is loaded (is the "global object" the root context? If not, what is it then?). If nothing else helps, maybe the script needs to be loaded and evaluated from C++ with engine.evaluate?

Aaron Craelius

unread,
Dec 29, 2014, 2:04:03 PM12/29/14
to clojur...@googlegroups.com
I haven't actually played with the QML Clojurescript REPL I got working for a while. In general, it required a lot of experimentation to get what I got working and as you can see, QML's restriction on global scope really makes it hard to integrate something like Clojurescript cleanly. I think advanced compilation might work if you define externs and only use those as your integration points but can't say for sure.

Pauli Jaakkola

unread,
Dec 30, 2014, 7:41:43 AM12/30/14
to clojur...@googlegroups.com
I am now getting things done with the following workaround:

1. Do as outlined in the first post
2. Don't use .pragma library. Instead, import the compiled js in main.qml:
import "main.js" as LoadCLJS
3. Now your stuff won't be in the LoadCLJS object, but in the global environment. So instead of
LoadCLJS.my_app.core.foo();
you do
my_app.core.foo();
4. Since the stuff is pushed into the global environment you should only do the import in one QML file. QML code loaded after that file will have access to it.

With this method I managed to get my load time to drop from over 10 seconds to ~3 seconds on Android.

perjantai, 4. heinäkuuta 2014 3.34.44 UTC+3 Aaron Craelius kirjoitti:
Reply all
Reply to author
Forward
0 new messages