How to create a Chrome extension using Preact, Babel, and Webpack

Support us on Patreon

Photo by Andre Ouellet on Unsplash

Preact is a fast alternative to React, and it can be quickly added to a Chrome extension, allowing you to build a performant extension with all the benefits from Preact.

Preact is an alternative to React that has only 3kb, you could use React itself for an extension, nothing is prohibiting you to do so, but if you can get away with Preact's API, it will reduce your bundle size.

Before we get started

It may help to understand this guide if you know the following concepts:

Knowledge about NPM and YarnBasic understanding of Webpack and Preact/React

But you can still follow along even if you don't master these topics.

Setting up our extension

To make this article easy to catch up on, let's create the extension files from the start.

Create the project

Make a folder anywhere in your systemCD to that directory using cmd or the terminal.

Type yarn init -y to create the package.json file.

Install the project dependencies

As you know, we are going to use Preact and Webpack, so let's install our dependencies.

You can do so with the following commands:

To install our development dependencies, type the following:

yarn add webpack webpack-cli html-webpack-plugin copy-webpack-plugin babel-loader @babel/core @babel/preset-env @babel/plugin-transform-react-jsx --dev

To install our dependencies type yarn add preact

As you can see, the only dependency that is not a dev dependency is Preact, so this will give you a lightweight setup!

Explanation of the dependencies listed above:

  • webpack and webpack-cli: We are using Webpack to build our extension, having a build process lets us automatize, and include features that help our development, without impacting the final bundle for our users
  • html-webpack-plugin: Extensions can have multiple files, we use the HTML Webpack Plugin to generate HTML files based on a template we provide, this way we don't have to create several HTML files with the same content or do something hacky
  • copy-webpack-plugin: This plugin allows us to copy files from our project folder to the final build folder, or any kind of copy you may want to do, we are using it because our extension has files like the manifest.json that need to be on our final build
  • babel-loader @babel/core @babel/preset-env @babel/plugin-transform-react-jsx: We are adding Babel to allow us to write JSX, you also can extend upon it to use all the neat features Babel allows you to setup
  • preact: This is Preact itself, it's the only dependency exposed on our bundle

Adding a hello world

To have some idea of the final result, let's start by adding a simple hello world!

Create a new file called popup.js on the project root, and paste the following content:

import { render, h } from 'preact';

render(
  <div>Hello world</div>,
  document.getElementById('app')
);

I am not using preact/compat, but you can tweak the configurations later if you wish to, as you can see, we can use Preact normally on a Chrome extension!

Adding the configuration files

There is some boilerplate that you need to add to be able to use the previous example.

We need to configure Webpack and Babel.

Create a new file called webpack.config.js, and use the following content:

const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: {
        popup: './popup.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
        clean: true,
    },
    module: {
        rules: [{
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            use: ['babel-loader'],
        }, ],
    },
    plugins: [
        new CopyPlugin({
            patterns: [{
                from: "public"
            }, ],
        }),
        new HtmlWebpackPlugin({
            template: 'template.html',
            filename: '[name].html'
        })
    ],
};

This is a basic boilerplate, and it already gives us all the configuration we need from Webpack, and can be used on multiple projects!

Understanding the Webpack configuration

If you are learning Webpack, I will try to give a brief explanation of what is going on inside that file so you can understand the build process. Feel free to search more about the plugins and settings being used, to know how to further improve your config.

  • mode: We are using the production mode to optimize our final build
  • output: There are 3 settings there, the filenamepath, and clean. The filename is using [name] to generate a file that uses the entry name, the path is saying that we will create this file inside a folder called dist(you can change it if you want), and clean is telling Webpack to clean the dist folder, by removing all contents before creating a new build
  • module: Module is being used to set up babel-loader, this loader will allow us to quickly use Babel to transpile Javascript
  • CopyPlugin: We are telling webpack to copy all files from the public folder to the dist folder, so we can add the manifest.json and any other file that we want on the final build there
  • HtmlWebpackPlugin: We are creating an HTML file for each entry we specified, this HTML file will include the bundled file for the entry. The pattern [name].html will create, for example, a popup.html file for our popup entry, and include the popup.js(the result from this bundle that is [name].js) inside it.

About the entry setting

entry: { popup: './popup.js' }

As in this simple extension, we are only creating a popup, we only have one property inside this object, for anything else you are using, you can add more properties there.

As said above, the HTML Webpack Plugin uses the name of the entry to generate the HTML file, so we don't have to change anything else if we change an entry.

The output of Webpack is also using the name of the entry, so it works out of the box!

On the extension, when we need to specify files inside the manifest.json, we will now use the name of the entry, as we are moving the manifest.json from our public folder, to the dist, and the dist will have a generated file with each entry name.

Creating the manifest.json

We are not done with configurations, but I believe this is the best moment to talk about manifest.json.

The manifest.json is something specific to our extension, it has nothing to do with Webpack, Babel, or anything else, it tells the browser what our extension is about, and what files it should run to make our extension work.

Let's get started with the manifest.json file

Create a folder named public inside your project rootCreate a new file called manifest.json inside the public folder

NOTE: Everything inside your public folder will be made public! Use it for files like the manifest, icons, and so on. Remember that the public folder was configured this way in the Webpack settings.

Add the following content inside the manifest.json file:

{
    "name": "This is my own extension",
    "description": "Hellooo extension world",
    "version": "1.0",
    "manifest_version": 3,
    "action": {
        "default_popup": "popup.html"
    }
  }

In this file, we set the extension name and its description that will be shown in the browser. As you can see in the action property, our extension will provide a popup.html file that will show the popup content.

Remember our Webpack entry?

entry: { popup: './popup.js' },

The entry is named popup, so Webpack will generate an HTML file and include the js file we specified(popup.js).

Defining the template file

The HTMLWebpackPlugin is using a file named template.html to generate our html files, so we need to create it!

Inside the project root, create a new file called template.html

Add the following inside template.html:

<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
  </body>
</html>

This is definitely a small template! Feel free to change it as you wish, but remember that this file will be used for all our entries.

Configuring Babel

We will use Babel to transpile JSX, there are different ways to specify the Babel configuration, for example, we could have done it inside webpack.config.js.

Here we will create a new file to hold the configuration, so create a file called .babelrc inside the project root folder.

Use the following content:

{
    "presets": [
      "@babel/preset-env"
    ],
    "plugins": [
      [
        "@babel/plugin-transform-react-jsx",
        {
          "pragma": "h",
          "pragmaFrag": "Fragment"
        }
      ]
    ]
}

This is using the transform-react-jsx plugin to allow us to write JSX, it is configured to use Preact instead of React. You can learn more about that in the Preact getting started guide.

Besides that, we are using the Babel preset-env preset for some basic configurations.

Building the extension

Open the package.json file and add the following property:

"scripts": {
    "build": "webpack"
 },

This is a quick shorthand to call the webpack command.

And to build the extension type yarn build from the cmd or terminal, while you are CD into the project's root folder.

After that Webpack will build the project, and the files will be inside a dist folder.

You can test your extension on the browser, by opening it using the folder option in the extension developer's tab, but remember that the extension files are inside the dist folder, so select the dist folder when testing and shipping your extension.

Wrapping it up

Preact gives us an awesome API to work with when building frontend applications, due to its minimal size it can be used in every situation possible, and along with Babel and Webpack, you can have an awesome developer experience even when making web extensions.

Feel free to use the code as your initial boilerplate when creating your extension, you could also create a development configuration for Webpack, and many other things! But the goal of this article is to give a base and starting point.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Dead by Daylight: Your session has expired [HOW TO FIX]

What is NOED in Dead by Daylight