Building WebExtensions in Typescript

I spent yesterday evening doing something I haven’t done in a while: tinkering. You may have seen the news that there’s a big change coming in Firefox. The short version is that later this year, the old extension model is going to be retired permanently, and extensions using it will no longer work. As someone with an extension on, I’ve received more than a few emails warning me that they’re about to go dark. This isn’t the first time Mozilla has tried to entice folks to move on from XUL Overlays: Jetpack was a similar effort to allow extensions to play better within a sandbox. This time I think it’s going to stick: the performance benefits seems undeniable, and as a developer the prospect of writing a single extension to support multiple browsers is pretty appealing.

Over a year ago I took a stab at porting OpenAttribute to Browser (Web)Extensions. I read the Firefox code and basically understood it, but only because it was the third iteration of something I’d built. The Chrome code — which should be close to a proper WebExtension — was almost inscrutable to me. So naturally I wanted to start with tests. But a year ago I couldn’t quite make the connection for some reason. WebExtensions split your code between the page (content scripts) and the background process. Long running things belong in the background, and the two communicate via message passing. After reading about the coming XUL-pocalypse, I decided to take another run at it.

Last night, though, I focused on something far smaller: just understanding how to put together a WebExtension using the technologies I’m familiar with — react, redux — and the ones I’m interested in — TypeScript. The result is an extension that doesn’t do much, but it is written in TypeScript, and it does work in both Firefox and Chrome from a single code base.

The attribution extensions I’ve written have always had a data flow problem. There’s the question of what triggers parsing, where the extracted data is stored, and how you update the display. Not to mention how do you do that without slowing down overall browser performance. I’ve had good luck with React in other projects: it feels like it forces me to think of things more functionally, making it easier to write tests: does this component do the right thing with the right data? does this other thing send the right signals with the right input? Cool. But how to do that across the process boundary between background and content scripts?

webext-redux is a package that makes it easy to manage a Redux store in both processes and keep it in sync. The only real wrinkle is that the actions you fire on the content side have to be mapped to actions on the background process, which is where the mutations all take place.

So why TypeScript? I’ve been enjoying ES6 and the changes it brings to JavaScript. But I’ve still missed the types you get in Go with MyPy. TypeScript is interesting: it’s duck typed, but the ducks seem to quack louder than they do in Python.

I was particularly intrigued by ambient modules, which is how TypeScript provides type information for third party JavaScript libraries you may want to integrate. Luckily type definitions already exit for the web extension API, and it’s easy to write a (useless) one to quell the compiler warnings.

I think the biggest shift I’ve been trying to make is understanding imports. import * as actions from './actions' feels weird to write, and to be honest I’m not sure how it differs from import actions from './actions' when there’s not a default export.

I like TypeScript enough to try another experiment in the future. The compiler already pointed out a couple of errors that would have been hard to track down.

Up next: figuring out how to test web extensions and build a single code base that runs under Chrome, Firefox, Edge, and Opera.