Setting up Figwheel for Chrome Extensions Content Scripts

Wilker Lucio
4 min readMar 29, 2016

--

If you are into ClojureScript you probably know Figwheel, it’s a great tool that enables very fast development. The hot reload feature makes the development a breeze, we can just save the file and it’s updated.

Today I wanna share with you one way that I manage to get the hot code reload to work into a Chrome Extension Content Script. Let’s do it.

For this tutorial, I’ll assume you are familiar with ClojureScript and Figwheel. Let’s start looking at our project.clj file:

Here we have a basic CLJS compilation setup, nothing special here.

To run Figwheel let’s use a script, you can create this at script/figwheel.clj:

On the next step, we will set up the Chrome Extension manifest file. Create this at browsers/chrome/manifest.json

There are 2 important pieces to note on this configuration:

  1. The injected scripts: note that first we include the overrides.js (we will look more into later). Then we have to include the Google Closure base and deps, so we get the bases going. After loading the dependencies we have another script to start the process.
  2. Web Accessible Resources: we are going to access our files using XMLHTTPRequest, the web_accessible_resources make it available for us from the content script.

Let’s jump into the overrides.js:

We implemented a function to use as CLOSURE_IMPORT_SCRIPT, the function takes the file path and should import it somehow. In order to execute the script into the Content Script context, we download the script via XMLHTTPRequest and evaluate it.

Source maps are something else we have to take care. Usually the browser will get from the code and download the maps, but since we are loading the code with eval some extra work is required. The fix consists in fetching the source map, fetch the original source and generate a base64 encoded source map inline.

Another detail is the CLOSURE_NO_DEPS. The reason we need this flag, is that otherwise the base.js would try to include the deps.js while it’s loading; but since we are using a custom way to load files that would break the setup.

To finish it up the start.js file is pretty simple, just require your main namespace, like this:

goog.require("my.namespace")

Let’s try it out

Now that we have the build ready, the next step is to load the extension on Chrome. Go to your extensions page, enable the “Developer Mode” and click at “Load Unpacked Extension”. You should select the browsers/chrome folder (the folder that contains the manifest.json file).

If you didn’t started the Figwheel process yet, it’s time.

lein run -m clojure.main script/figwheel.clj

With the extension loaded, visit http://clojure.org and check your console, it should connect to Figwheel!

Connecting Figwheel on secure pages

The steps above should work on any HTTP page, but once you try on an HTTPS you may get something like this:

Mixed Content: The page at 'https://...' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://localhost:3449/figwheel-ws/content-script'. This request has been blocked; this endpoint must be available over WSS.

For security reasons, the browser doesn't allow you to connect from a secure page into an insecure socket. The easiest way that I found to get around this issue is by using https://github.com/jugyo/tunnels (thanks to Bruce Hauman for pointing that out).

Install it and then run:

sudo tunnels 443 3449

This will create a tunnel proxy with a secure layer, then we need to change our Figwheel configuration to use the new secure URL (this change is at your project.clj):

:cljsbuild {:builds [{:id           "content-script"
:figwheel {:websocket-url "wss://localhost:443/figwheel-ws"}
...]}

Note the use of wss as the protocol and the new port.

Doing that your connection should work on both secure and insecure pages.

I would like to give a thank you to Antonin Hildebrand for helping me through the process of getting the code loaded since you are doing a chrome extension you should check his project https://github.com/binaryage/chromex.

UPDATE

Hey, I have to do an errata, this was the second attempt and I thought it was working but it was some old cache. Using the instructions above, Figwheel will fail to reload the file when it needs to.

There is a different approach that reloads fine. Instead of loading our scripts at the “content script” context, we can inject then into the page.

We need to change the manifest.json to load a single file:

And this file will load all the base ones at the page:

We now simplify the overrides, to clarify the distinction I used a new name for the file, overrides-simple

This approach doesn’t require us to inline the source maps, they just work. And the hot reloading works as well.

There is one disadvantage, now our script is running inside of the page context, without the “protection” that we had into the content script context. You also lose access to some chrome API’s.

Finding a way to run inside of content script is still preferable, I’ll try to get the hot reload to work there. If I get it working I’ll update this post with more information. If you can make that work I would be glad to hear as well.

--

--