About a year ago, I decided to spend a bit more time learning Clojure to be able to contribute more to the Defold Editor. During this journey I struggled a lot, mostly because I didn’t know about different tools and tricks I could use to make my life easier. The biggest headache was constant editor reloading when I needed to test something I had just written, which was totally unnecessary (I wish I had asked about it earlier).
Here I’m trying to collect all the tips I got from @mats.gisselson and @vlaaad which helped me work on Defold Editor tasks with joy.
Build Editor
The main thing you have to know is that you don’t have to build the engine if you want to develop only the editor.
Open this README.md and follow instructions for your platform.
Long story short:
- Clone the Defold repo
- Install JDK and Leiningen
- Use tsese commands, but instead of
<sha1>
, pick the SHA1 of the latest commit from the Defold repo - Set up IntelliJ IDEA CE + Cursive and use this manual for the IDE setup
It’s basically the same info as in the Defold repo, but I just wanted to show that it’s easy. Yes, the full documentation is much bigger than that, but you don’t need it for now. For now, you just need the Repo/JDK/Leiningen/IDE - you are awesome!
REPL
Using the REPL is the very first thing you need to figure out.
Read this instruction one more time. Use the recommended shortcuts or your own, especially for Load File in REPL
and Send function to REPL
. I know that switching the REPL namespace is also very useful, but I’m not an expert and barely use it, though I should.
In the manual, you see suggestions like:
Suggested shortcut: Cmd+R followed by D.
That means you set hotkey Cmd+R, then check the Second stroke:
checkbox and type D.
If you do as suggested, all your communication with the REPL will be Cmd+R and then one of the keys for running a command.
There is a suggestion in the manual “Clear all Test markers: Cmd+R followed by C” - don’t assign it for now, and here is why…
When you reload a function or the whole file, sometimes you see that your changes haven’t been applied. It’s very annoying and I reloaded the editor a thousand times because of that. Even though I knew about some command to clear cache, I barely used it and always forgot, until the tip Mats gave me:
- Open Preferences > Languages & Frameworks > Clojure > REPL Commands
+
to create a new Global Command- Name it “Clear Caches”
- Pick Execute Command
(do
(require 'dev)
(dev/clear-caches!)
:caches-cleared!)
- Save
- Open Keymap and find your command by name “Clear Caches”
- Assign Cmd+R followed by C
Now you can clear cache using a hotkey and then reload the function after that and be sure the changes were applied!
Reflection warnings
Working with REPL, it’s important to reload the whole file from time to time because reloading the file may return reflection warnings. Reflection warnings occur when the compiler can’t determine the type of a Java object at compile time and must use slower runtime reflection to access its methods or fields. To fix them, add type hints (e.g., ^KeyEvent
, ^YourType
) to your code so the compiler knows the exact type and can avoid reflection.
Development session
A regular Editor development session for me looks like:
lein run
in terminal (in IDEA’s terminal) → load needed test project- Connect REPL
- Make fixes in needed functions and reload these functions using Cmd+R followed by D
- Check changes in the editor. Sometimes it requires reopening a file (e.g., if you’re testing an input event which is registered when a tilemap scene is opened, then you need to close and open the tilemap)
- Sometimes I switch namespace to current and run a function I wrote and loaded in REPL with some Editor’s data to check output and make sure I wrote it right, so I can now use it in the needed place
- Repeat 3, 4, 5 until I fix the issue
- When everything works as I like, reload the whole file(s) in REPL to make sure I don’t have reflection warnings. If I have some, I fix them (this step I forget pretty often)
- Add tests if possible
- Close the editor, open it again, and retest the fix (sometimes it’s possible to introduce circular dependencies or similar issues which you can’t catch in REPL, only on reload)
Here @mats.gisselson explains how a typical development session looks for him: How I work on the Defold Editor · GitHub
Edit CSS styles
All changes should be done in scss
files, and it is possible to build and hot-reload scss
.
Run
lein sass auto
in the editor folder. This daemon will detect all changes in scss
files and compile css
files automatically.
Then call Help->Reload Stylesheet
in the Defold Editor itself.
Editor Extensions
There are a few Defold Editor extensions available in the official Defold org: extension-spine, extension-rive, extension-simpledata, extension-texturepacker, etc.
To be able to navigate in extensions, use code formatting the same as in the project, etc., do the following:
- Create
profiles.clj
file in~/.lein/profiles.clj
- Add a list of paths to the extension folders in the :source-paths array
{:user {:source-paths []}}
- Run
lein init <sha1>
again - In IDEA, right-click on
project.clj
and clickRefresh Leiningen Projects
Exampleprofiles.clj
:
{
:user {:source-paths ["/Users/agulev/projects/extension-rive/defold-rive/editor/src"
"/Users/agulev/projects/extension-simpledata/defold-simpledata/editor/src"
"/Users/agulev/projects/extension-spine/defold-spine/editor/src"
"/Users/agulev/projects/extension-texturepacker/texturepacker/editor/src"]}
}
You can also define profiles for local extensions and use them for testing:
{:local-extension-lua-preprocessor {:jvm-opts ["-Ddefold.extension.lua-preprocessor.path=/Users/agulev/projects/extension-lua-preprocessor"]}
:local-extension-rive {:jvm-opts ["-Ddefold.extension.rive.path=/Users/agulev/projects/extension-rive"]}
:local-extension-simpledata {:jvm-opts ["-Ddefold.extension.simpledata.path=/Users/agulev/projects/extension-simpledata"]}
:local-extension-spine {:jvm-opts ["-Ddefold.extension.spine.path=/Users/agulev/projects/extension-spine"]}
:local-extension-teal {:jvm-opts ["-Ddefold.extension.teal.path=/Users/agulev/projects/extension-teal"]}
:local-extension-texturepacker {:jvm-opts ["-Ddefold.extension.texturepacker.path=/Users/agulev/projects/extension-texturepacker"]}
:local-extensions [:local-extension-lua-preprocessor
:local-extension-rive
:local-extension-simpledata
:local-extension-spine
:local-extension-teal
:local-extension-texturepacker]
:user {:source-paths ["/Users/agulev/projects/extension-rive/defold-rive/editor/src"
"/Users/agulev/projects/extension-simpledata/defold-simpledata/editor/src"
"/Users/agulev/projects/extension-spine/defold-spine/editor/src"
"/Users/agulev/projects/extension-texturepacker/texturepacker/editor/src"]}}
Then run:
lein with-profile +local-extension-spine,+local-extension-texturepacker
or
lein with-profile +local-extensions
The way it works: test projects have stuff like dependencies#0 = {{defold.extension.spine.url}}
in game.project
. The editor will replace these with the corresponding JVM defines during development.
Defold Editor Development functions
- Cmd+T in scene view switches render modes. I don’t know the exact order, I just know that the second view (after pressing Cmd+T twice) shows the render target responsible for click detection.
- There is a text field in
Preferences -> Dev
where it’s possible to specify a customdmengine
which will be run on Cmd+B.
Clojure in Defold Editor and LLMs
When I need to find where something happens in the editor’s code, I use Windsurf in Chat mode. I explain with all the details what I’m looking for, usually from the user and interaction with the editor interface point of view. It helps me to find the entry point for my task.
Then I use the macOS ChatGPT app which I connect to IDEA or other editors. It’s possible to connect one or a few editors with their open tabs as context. I open one or a few files needed as context (using split view), select needed pieces of code, and ask what it does. This helped me read and understand code faster, especially in the beginning with Clojure.
Be careful with code generation. It’s not great. For small, clean functions, it might work. But even then, it often goes in a non-optimal way. In agent-like mode, where it needs to trace through many functions and make many changes, it makes a lot of mistakes. Also, it might randomly remove brackets all over the code. All my experiments with agents failed unfortunately.
I don’t recommend LLMs for code generation unless you will (and can) recheck after the LLM and verify it not only works but is also well written. In the case of Clojure, if you’re not a Clojure expert, I recommend using it for education, code reading, and as a personal assistant that can spend as much time as needed answering your questions. Also, as a yellow rubber duck you can throw ideas at and get feedback from (but be careful, it is always super positive about everything, even if the idea isn’t great but works).
Most of the text is just a compilation of answers I got from @mats.gisselson and @vlaaad when I shared my struggles with them.
I’m sure there are more useful things I don’t know about, but I wanted to share what I already have and make sure this knowledge is available to everyone.
The LLMs section is about my personal experience and what helped me get started with the editor code.
Nowadays, LLM tools are better than they were a year ago, when I had to copy and paste functions—and sometimes full files—so the LLM could follow the logic and explain it to me.
I hope it will be useful for someone.