How to write a Firefox add-on in ClojureScript
Posted on in Firefox add-on
I found writing Firefox add-ons to be a good way to learn the ropes in ClojureScript in a productive manner.
You can find a sample add-on on my GitHub. I won't go through the code here, but will lay out the quirky development process. I will mostly focus on the plumbing - how ClojureScript hands over the compiled JavaScript code to the add-on.
The sample add-on tries to mirror the functionality of the one found in the official tutorial - wrap a border around the pages at mozilla.org.
Project Layout
Figuring out a clean project layout took some effort. I am satisfied with the following structure:
project-root |-- addon // root for the Firefox add-on | |--- manifest.json | |--- // add-on artefacts like icons | |--- // JS file compiled and placed here by ClojureScript compiler |-- src // ClojureScript code |-- compile-opts.edn // compilation options provided to ClojureScript compiler |-- deps.edn
The goal is to isolate the artefacts of the add-on from the ClojureScript code. The add-on gets only its JavaScript from ClojureScript. So, isolating the rest of its artefacts - icons, manifest, etc. - into its own directory works out well. ClojureScript dominates the rest of the project layout.
The only time the ClojureScript compiler touches the addon folder is to put the compiled JavaScript in it.
ClojureScript to JavaScript
The compilation process by default uses :optimizations :none
, which produces a set of JavaScript
files, with the main file pointing at the others. This does not work here as the add-on does not seem to pick up
the secondary files from the main file.
The rest of the :optimizations
options - :whitespace
, :simple
,
:advanced
- produce a single standalone JavaScript file, which the add-on happily accepts.
However, :optimizations :none
is the default because it compiles the fastest, lending a speedy development
process. I went with the second fastest - :whitespace
- for development purpose, and
:advanced
to compile a production build.
Two other compilation options helps keep the plumbing short and clean: :target
diverts the
temporary files produced during compilation to a tmp folder, while :target-to
puts the
standalone compile JavaScript file directly into the addon folder.
To summarise, we tweak the default compilation process with the following three options to keep life simple:
{:optimizations :whitespace
:output-dir "tmp"
:output-to "addon/main.js"}
Live reload
Basically, we instruct the ClojureScript compiler to watch over the ClojureScript files and recompile them into JavaScript on change.
# in project-root
clj --main cljs.main \
--watch "src" \
--compile-opts "compile-opts.edn" \
--compile demo.core
In turn, Mozilla's web-ext tool by default watches over the add-on folder and reloads the add-on on detecting a change.
# in addon folder
web-ext run --start-url https://www.mozilla.org/en-US/