Smelly Code

React starter kit for Chrome Extensions with Live Reloading 🤓

May 07, 20195 min 👓

Software is eating the world. And JavaScript is eating software. Chrome extensions are not an exception here. They are also built using JavaScript. A few days ago, I got an opportunity to work on a small side project which needed a chrome extension. Since I am new to ReactJs and want to tame it, so I decided to build the desired extension with it. Before I narrate the whole story, let’s take a gander on how one develops a chrome extension.

Chrome extension overview

Every chrome extension starts with a manifest.json file which provides an overview of the permissions, background scripts, content scripts, popups and many other things used by the extension. Here’s a sample manifest file.

{
  "name": "Getting Started Example",
  "version": "1.0",
  "description": "Build an Extension!",
  "permissions": ["storage"],
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "page_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "images/get_started16.png",
      "32": "images/get_started32.png",
      "48": "images/get_started48.png",
      "128": "images/get_started128.png"
    }
  },
  "manifest_version": 2
}

Folder/bundle containing a manifest file, scripts, and other assets, is loaded into chrome from chrome://extensions/ page. Chrome reads the manifest file and installs the extension. If any change is made in script or any other file, we reload the extension to install the latest change.

Here’s a link to the official documentation which elborates chrome extensions and their installations.

After getting familiar with the basics of extension development, I put on my developer hat 🤠 and started building an extension with create-react-app. Here are the steps I followed:

  1. Created a react app by runnning
create-react-app hello-extensions
  1. Replaced manifest.json file with below content.
{
  "name": "Hello Extension",
  "description": "My first chrome extension",
  "version": "1.0",
  "manifest_version": 2,
  "browser_action": {
    "default_popup": "index.html"
  },
  "content_security_policy": "script-src 'self' 'sha256-5As4+...'; object-src 'self'"
}
  1. Build the app using
npm run build
  1. Loaded the build in chrome.

…and it worked. Yay!!

My “Hello World” moment with extension made me happy but my happiness didn’t last for long when I decided to change the background color. I found out that I have to build and reload the extension again 😩.

Build changes 🤖

Running an app(created by create-react-app) in development mode with npm run start doesn’t write files to the disk. I needed files to be present on the disk so that they can be loaded in chrome. As we know, the create-react-app utility uses webpack under the hood with a predefined configuration. And to customize that predefined config we must eject it first. So I ejected the config with the command npm run eject. It generated some config files and scripts.

yarn eject files

After ejecting the configuration, I needed to find a way to write files to disk in development mode. I ran to the webpack dev server api document for help and found an option writeToDisk. As its name suggests, setting writeToDisk true will write files to disk. So I set it true in the dev server config. It helped but it took a toll on the dev server. Dev server became sluggish. Its sluggishness convinced me to look for a better alternative.

I thought for a while and realized that I didn’t need a dev server. I needed a “watch server” 😅. A server to watch files and copy them—with the latest changes—to build directory on the change event. I leveraged webpack.watch api and ended up with the watch script below.

// some neccessary setup code
...
...
...

// Webpack watch
webpack(config).watch({}, (err, stats) => {
  if (err) {
    console.error(err);
  } else {
    copyPublicFolder();
  }
  console.error(
    stats.toString({
      chunks: false,
      colors: true
    })
  );
});

function copyPublicFolder() {
  fs.copySync(paths.appPublic, paths.appBuild, {
    dereference: true,
    filter: file => file !== paths.appHtml
  });
}

Reload changes 🔄

After dealing with the build barrier, the next challenge was to automatically reload the extension. I found a supercool webpack plugin webpack-chrome-extension-reloader which reloads chrome extensions out of the box. A million thanks to Rubens for writing it. A sneak peek of config with the plugin.

// Other configuration
...
...
...
...

  plugins: [
    // Other plugins
    ...
    ...
    ...
    // Chrome extension hot reloading.
      isEnvDevelopment &&
        new ChromeExtensionReloader({
          reloadPage: true // Force the reload of the page also
          entries: {
            // The entries used for the content/background scripts
            contentScript: 'content-script',
            background: 'app' // *REQUIRED
          }
        })
  ].filter(Boolean),

Illustration of the starter kit with live reloading.


Here’s the link to complete package/starter kit. I hope it will be helpful. If you have any idea/suggestion to make it better, please do share. Thanks 🙏🏻. Happy Coding!


Hitesh

Hi, I am Hitesh.

|