Building a Chrome Extension with the Angular framework / Rafflesia Part 1
Over the last few months I’ve worked on a handful of assorted projects and written short blog posts about them. For the next few months, I want to go deeper into one particular project.
First, I learned and struggled a bit on this one. Second, I need to focus on doing fewer things better. Thirdly, this just took more time so I don’t have much else to write about.
What is it? A set of tools that integrate with Overleaf, which is an online LaTeX doc editor sort of in the vein of Google Docs. The idea started a long time ago, as I used Overleaf to write papers in college and have continued to use it during grad school.
Using LaTeX inherently gives you some advantages. Writing math formulas is a lot easier. A bibliography can be generated automatically instead of managing it yourself. However even with all of that I did find a number of gaps in the user experience.
At work I recently created a Chrome extension for Interactive Canvas for Actions on Google. I ended up learning a lot about building Chrome extensions, so I figured I’d use some of my experience in this project too.
At its core, the idea is very simple: a small panel that appears at the bottom of your page and has a handful of tools. The user clicks on each tab to select a different tool. There’s no context switching needed.
But how do you actually start doing this?
Setting up the Extension
First I created a new Angular project using the ng CLI. This provided me with all of the initial code I’d need for my actual frontend. You don’t need a blog post to know how to build an Angular website.
A Chrome extension requires a manifest.json file that outlines the project metadata. This is what mine looks like.
Note a few things:
- There aren’t actually any special permissions required for this to work. My first release was rejected as I had some unnecessary ones.
content_scriptsonly runs on Overleaf editor pages. Otherwise the extension never has to activate.
Basically this is a small sliver of logic that runs when the page is loaded. Here I can run some code that exists outside of the page context. This means the code here can’t really interact with specific scripts already existing on the page. For that, I add
In our content script you can see that I create the UI, which is just an iframe and a small controller to show or hide it. In order to improve development velocity, I’d rather not have to add all of my UI into the actual webpage. Instead, I’ll just load another website. This means I can continue to update the hosted site or use localhost without needing to go through another extension version and release.
At this point, I’m reasonably confident my code will work, but how exactly am I going to test it?
Building an Angular Chrome Extension
I decided to bundle my extension-specific logic into the Angular bundle itself. I had to do this when building the Interactive Canvas extension to show up in the DevTools window and make everything work in TypeScript.
I updated my
angular.json file to include these assets:
When I run
ng build, the whole process takes upwards of a minute but produces a valid build folder at
Selecting this folder will load the extension.
Now we can see that the tool will load in our Overleaf page, as pictured above.
Angular & DevTools pane
I want to add a brief section about using Angular for DevTools or other sorts of local UI. While not directly used in this extension, I did do it for the Interactive Canvas extension and it bears some explanation.
In the manifest.json I added a
devtools_page with a simple
index.html. Since it’s in the same directory as everything else, it is included in the extension bundle.
Iterating & Debugging
The development flow isn’t all that bad. Since the hosted logic is running in localhost, I can just use the automatic reloading feature of Angular to make quick tweaks and see how they work.
As the iframe is running within the context of the webpage, I can use standard debugging features in DevTools. I can send logs to the browser and even errors will appear.
However, when I do run into a bug with my extension logic, I do need to make the change and do a full rebuild. It’s admittedly tedious as Angular’s build takes longer than just reloading.
Going forward, I might be able to pull out the extension logic into its own module. But if I do, it might make it harder to reuse shared resources to add to future reliability.
Errors in my extension code appear on the Extensions page.
From here you can click the button to get more details. If you don’t clear out all the errors before your next build, the Errors button will remain and you may not know if that indicates further errors.
Either way, these errors also will appear in your DevTools console, so you can easily dive into exactly what’s happening.
At this point we’ve got a Chrome extension that sort of works and a web app that sort of works. That’s just dandy. I’ll definitely go into more detail about improving each in the future.
But one last piece of the extension lifecycle is actually publishing. The Chrome Web Store expects a zip file that includes your bundle. Creating a zip from code isn’t that hard. However, I’ve been doing my development in WSL which meant that I couldn’t find the usual zip tools. I ended up installing bestzip, a Node CLI to create zip files.
With a quick command I end up with a
zip file that I can share around.
The process isn’t necessarily the most seamless but it does work reasonably well. It’d be nice if there were more support for hot reloading of the extension components themselves. This was something to consider even more when building the Interactive Canvas DevTools as I couldn’t use iframes as a shortcut.
I did want to make sure I document this process for anyone else planning to do something similar.
Future posts will look more at some of the tools I built and how I got them to work deeply with Overleaf’s editor.