Posts Developing a browser extension with Create React App and Tailwind CSS
Post
Cancel

Developing a browser extension with Create React App and Tailwind CSS

1. Create new app

Create a new app via create-react-app with the following command.

1
npx create-react-app my-browser-extension --template typescript

Or for Typescrip support run this command:

1
npx create-react-app my-browser-extension --template typescript

2. Setup the manifest

Create React App creates a public dir, delete everything except the manifets.json and index.html. The current manifest is for Web Apps, but we need a Web Extensions manifest. So replace the public/manifest.json content with for example the following one. An extension should have an icon, for the following manifest you would need to add a svg logo in the public dir.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "manifest_version": 2,
  "name": "My-Browser-Extension",
  "version": "0.1",
  "description": "My browser extension",
  "homepage_url": "https://example.org",
  "icons": {
    "48": "logo.svg",
    "96": "logo.svg"
  },
  "permissions": [
  ],
  "web_accessible_resources": [
  ]
}

3. Setup Tailwind CSS

Install the Tailwind and its peer-dependencies using npm. As Create React App doesn’t support PostCSS 8 yet we need to install the Tailwind CSS v2.0 PostCSS 7 compatibility build.

1
npm install tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 @craco/craco

We also installed @craco/craco, because Create React App doesn’t let you override the PostCSS configuration natively. Therefore we need to replace react-scripts with craco in the package.json like so:

1
2
3
4
5
6
7
8
9
10
{
    //...
    "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject",
  },
  //...
}

Then, generate your tailwind config via npx tailwindcss init. In the tailwind.config.js file you need to change the line purge: [] to purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'].

Finally include Tailwind in your src/index.css

1
2
3
4
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

4. Setup for production

Create React App embeds an inline script into index.html in the production build. But this would break the extension usage as it goes against its CSP (Content Security Policy), so we just disable it.

Change the build script in package.json to

1
"build": "INLINE_RUNTIME_CHUNK=false craco build"

Setup for development

The goal is to create a live-reloading environment without ejecting.

First install the Webpack extension reloader plugin.

1
npm install -D webpack-extension-reloader

Then put the following script in scripts/watch.js and make it executable chmod +x ./scripts/watch.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/env node

// Based on: https://mmazzarolo.com/blog/2019-10-19-browser-extension-development/
// Updated for: https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.md#configuration

// Force a "development" environment in watch mode
process.env.BABEL_ENV = "development";
process.env.NODE_ENV = "development";

const fs = require("fs-extra");
const paths = require("react-scripts/config/paths");
const webpack = require("webpack");
const colors = require("colors/safe");
const ExtensionReloader = require("webpack-extension-reloader");

// use Craco to create the Webpack development config
const { createWebpackDevConfig } = require("@craco/craco");
const cracoConfig = require("../craco.config.js");
const config = createWebpackDevConfig(cracoConfig);

// The classic webpack-dev-server can't be used to develop browser extensions,
// so we remove the "webpackHotDevClient" from the config "entry" point.
config.entry = !Array.isArray(config.entry) ? config.entry : config.entry.filter(function(entry) {
  return !entry.includes("webpackHotDevClient");
});

// Edit the Webpack config by setting the output directory to "./build".
config.output.path = paths.appBuild;
paths.publicUrl = paths.appBuild + "/";

// Add the webpack-extension-reloader plugin to the Webpack config.
// It notifies and reloads the extension on code changes.
config.plugins.push(new ExtensionReloader());

// Start Webpack in watch mode.
const compiler = webpack(config);
const watcher = compiler.watch({}, function(err) {
  if (err) {
    console.error(err);
  } else {
    // Every time Webpack finishes recompiling copy all the assets of the
    // "public" dir in the "build" dir (except for the index.html)
    fs.copySync(paths.appPublic, paths.appBuild, {
      dereference: true,
      filter: file => file !== paths.appHtml
    });
    // Report on console the succesfull build
    console.clear();
    console.info(colors.green("Compiled successfully!"));
    console.info("Built at", new Date().toLocaleTimeString());
    console.info();
    console.info("Note that the development build is not optimized.");
    console.info("To create a production build, use yarn build.");
  }
});

Finally, add a watch script to your package.json.

1
"watch": "./scripts/watch.js"

5. Start development

You can now replace the created example with your html that is styled with Tailwind CSS. To develop your extension with live-reloading just run npm run watch or npm run build for production.

To load an extension in Firefox, open about:debugging, click This Firefox then click Load Temporary Add-on... and open your build/manifest.json file.

References

This blog article is mainly based on Matteo’s article Developing a browser extension with Create React App. But as we replaced react-scripts with cargo, we need to make a few adjustments here.

Resources

This post is licensed under CC BY 4.0 by the author.