Revisiting Chrome Extension Development – Build System Tips

  • report
    Disclaimer
    Click for Disclaimer
    This Post is over a year old (first published about 5 years ago). As such, please keep in mind that some of the information may no longer be accurate, best practice, or a reflection of how I would approach the same thing today.
  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Jan. 02, 2019
    Last Updated:
    Jan. 02, 2019
  • classTags
    classClick for Tags

Yesterday, I realized something not very good. There was a chrome extension I developed years back (this one) that depended on a service outside my control (TimeClockWizard). When that service changed a bunch of its code a year or so ago, it broke all functionality in my extension. When I found out, I posted a notice on my site that the extension was “deprecated”, but forgot to pull the actual extension from the Chrome Webstore. Or maybe I had just assumed that people would stop trying to use and download it when it had stopped working. However, when I was browsing my Google Analytics account yesterday, I saw some pings from the extension and realized that people were still actively trying to install and/or use it. It made me feel bad, so I thought I would give fixing the extension a crack. It actually went really well, and I’m probably going to publish a fix to the webstore soon that should fix everything in the extension for everyone.

However, what I want to talk about in this post is how certain decisions I made earlier, in terms of tooling, made coming back to work on the extension, even years later, a lot easier. I also made some further improvements as I worked on the repairs yesterday and today. Here is some of what was the most helpful to have setup in my dev pipeline:

Auto ZIP Archive Creator for Webstore Upload / Releases

If you want to upload your Chrome extension to the Chrome Webstore, you need to zip it up in order to upload it. Plus, ZIP archives are always decent to have if you want to use them as a way to distribute “releases”. Since the Chrome Webstore requires you to provide a version number in your manifest that gets bumped up on new releases, I setup a script that bundles up all my required assets and archives them into a ZIP that has a filename corresponding to current version # in my manifest.json file. It will also overwrite the zip file every time it runs, so you can keep running it until you have released a new version, than bump your version number up, so the next time it runs it will create a new archive file.

I have this run after the code has been minified, and just the files I want to upload have been moved to “/dist” (see section on package.json further below). This is super simple – basically just a slight modification of the demo code from the npm “archiver” package:


/**
 * @file Archive_Creator.js
 */
var fs = require("fs");
var archiver = require("archiver");

// Get version info
var manifestJSON = require("../manifest.json");
var versionString = manifestJSON.version.toString();

var output = fs.createWriteStream("./builds/build_"+versionString+".zip");
var archive = archiver("zip", {
    zlib : {level : 6} // compression level
});

// listen for all archive data to be written 
output.on("close", function() {
  console.log(archive.pointer() + " total bytes");
  console.log("archiver has been finalized and the output file descriptor has closed.");
});
 
// good practice to catch this error explicitly 
archive.on("error", function(err) {
  throw err;
});
 
// pipe archive data to the file 
archive.pipe(output);
 
// append files from a directory 
archive.directory("./dist/","");
 
// finalize the archive (ie we are done appending files but streams have to finish yet) 
archive.finalize();

Prepping files for zipping / minifying / copying

It is a good idea when creating a zip to upload to the Chrome webstore to only upload A) what is absolutely necessary to run your extension and B) minified versions of your files. This is as a courtesy to users to keep file sizes down, and in case Google ever starts getting restrictive about the maximum size of extension ZIPs.

I have my system setup like this: minify files (“Minifier.js”)-> copy minified files and assets files (PNG, etc.) to /dist (“Prepare_Dist.js”) -> take entire /dist folder and archive it, sending archive to /builds/build_[version].zip (“Archive_Creator.js”). You can see this pipeline reflected in my package.json script section below:


"scripts": {
	"prepare-dist": "node ./npm/Prepare_Dist.js",
	"minify": "node ./npm/Minifier.js",
	"zip": "node ./npm/Archive_Creator.js",
	"stage": "npm run minify && npm run prepare-dist",
	"build": "npm run minify && npm run prepare-dist && npm run zip"
}

Good tip on debugging popup window

This seems like a no-brainer kind of thing, but while working on repairs, I realized that I needed to be able to debug some of my popup’s JavaScript, but the code I needed to debug was running before I could even open Chrome Dev tools after opening the popup. I could use SetTimeout as a quick hack, but there is a much better way – simply right click on the button for your Chrome Extension in the toolbar and click “Inspect Popup”! Thank you Google, and thank you StackOverflow.

localStorage is pretty cool for persisting data

If you need to persist user data across sessions, the built in browser localStorage API is pretty nifty and convenient. It is incredibly simple to use (values are stored as key-pairs) and access is automatically handled by the browser and scoped to the origin of the initiating code (i.e. my extension should not be able to access the localStorage set by another extension or website).

Advice I wish I had followed from the beginning – multiple files are OK

Since so much of a Chrome extension is centered around distribution and bundling of assets, I was tempted from the beginning to keep my source code to a minimal number of files. For example, if you want to give certain script files different privileges (such as running in the background) you have to specify them by filename in your manifest.json, which could be messy if you have dozens of files. This led to some annoyances where it would have been a lot cleaner and easier to look through my own code if I had split certain functional components into their own files.

What is especially silly about this is that I could have just as easily added a step to my build process that concatenates multiple files into one – so the final zip archive file would have stayed around the same size, and my manifest.json could also have stayed (relatively) the same.

Leave a Reply

Your email address will not be published.