By Vitaly Gordon November 13, 2016 12:35 PM
Hot Reloading for Chrome Extensions

When I was working on a simple extension for Chrome a couple of days ago I encountered a problem that the browser does not reload the extension’s source code automatically after I change something. This makes the development harder because after every change you have to press the “Reload” button on the extensions list and then you have to refresh the page to restart content scripts.

After a short research, it turned out that Chrome offers all required APIs in order to implement the hot reload functionality for extensions.

The final solution is available on Github and in the remaining part of the post I will explain how it works.

The solution uses File and Directory Entries API to get the list of all files in the extension directory (recursively):

const filesInDirectory = dir => new Promise (resolve =>
  dir.createReader ().readEntries (entries =>
    Promise.all (entries.filter (e => e.name[0] !== '.').map (e =>
        e.isDirectory
          ? filesInDirectory (e)
          : new Promise (resolve => e.file (resolve))
    ))
    .then (files => [].concat (...files))
    .then (resolve)
  )
)

Then it concatenates the file names and lastModifiedDate of all files resulting in a single string:

const timestampForFilesInDirectory = dir =>
  filesInDirectory (dir).then (files =>
    files.map (f => f.name + f.lastModifiedDate).join ())

This way we can detect not only the changes in files but also removal/adding/renaming of files.

Then there is a watchdog that checks for changes every 1000ms:

const watchChanges = (dir, lastTimestamp) => {
  timestampForFilesInDirectory (dir).then (timestamp => {
    if (!lastTimestamp || (lastTimestamp === timestamp)) {
      setTimeout (() => watchChanges (dir, timestamp), 1000) // retry after 1s
    } else {
      reload ()
    }
  })
}

And if there are any changes it reloads the active tab:

const reload = () => {
  chrome.tabs.query ({ active: true, currentWindow: true }, tabs => {
    if (tabs[0]) { chrome.tabs.reload (tabs[0].id) }
    chrome.runtime.reload ()
  })
}

The reloading of the tab happens before runtime.reload. Otherwise it won’t work because runtime.reload stops execution of the script. But due to the fact that tab reloading is asynchronous everything gets reloaded in the right order — although it looks illogical in the code.

And the final step — the extension starts the watchdog targeting the directory with the extension’s code. This has to be done only if the extension is loaded in the developer mode (i.e. via “Load unpacked extension”):

chrome.management.getSelf (self => {
  if (self.installType === 'development') {
    chrome.runtime.getPackageDirectoryEntry (dir => watchChanges (dir))
  }
})

This way there is no need to remove this line from the production build.

How to use crx-hotreload

  1. Drop hot-reload.js to your extension’s directory.

  2. Edit your manifest.json this way:

"background": { "scripts": ["hot-reload.js"] }

You can also simply clone the repository and use it as a boilerplate for your extension.