Creating a Chrome Extension with React and TypeScript
Published: 2022-12-28 13:30:44

In my quest to develop a Chrome Extension using React and TypeScript, I found that existing resources didn't quite meet my needs. This prompted me to embark on a journey of creating my own guide, tailored to my specific requirements.

Here's what I aimed to achieve:

- Develop a Chrome extension featuring an interactive popup screen for enhanced user experience.
- Write the code in TypeScript, leveraging its robust typing system for better maintainability and error prevention.
Generate two distinct outputs for the published code:
- A Popup app
- Content scripts, background scripts, and other necessary components
- Prepare the code for publication, which includes:
- Uglifying the code to minimize its size and improve performance
- Compressing the code into a ZIP file, creating a package ready for Chrome Extension publication.

#Create React app

## Prepare environment
```
npx create-react-app chrome-react --template typescript
```
When created I remove react-script package and copies build dependencies, configuration files and scripts into the app directory. It is needed to modify `webpack.config` file. (Another solution is to use [react-app-rewired](https://github.com/timarney/react-app-rewired)
```
npm run eject
```
Then for be sure everything works I have run a build
```
npm run build
```
which finished successfully 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0flmu620cnnpe51cocys.png)

Now I want to be able to create build for development so I need to add 
```
npm install -g win-node-env
```
I set `$env:NODE_ENV="production"` in Powershell terminal. 

##Prepare project files
I deleted marked files from `/src` directory
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/78xavrklrk57tu786kzd.png)

Then I have updated two left files which are now:
App.tsx
```
import React from 'react';

function App() {
  return (
    <div>
      Chrome extension popup
    </div>
  );
}

export default App;
```
index.tsx
```
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```
Another directory to cleanup is `public`. There are few files to remove:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxeh97h6lqtv3avpym32.png)

And the content of `index.html` needs to be updated so it will looks like that:
```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <title>Chrome Extension </title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
```

The second file to update is the hearth of extension - [manifest.json](https://developer.chrome.com/docs/extensions/mv3/intro/) file.

The current `manifest.json` is file created by `create-react-app` and it content is related to [Web App Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) which mostly is useful for mobile devices. As I am working on Chrome Extension I can removed its contents and put the valid extension manifest 

```
{
  "name": "Chrome Extension",
  "description": "Chrome Extension Description",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "index.html",
    "default_title": "Open the popup"
  }
}
```

Now I build project and check if I can load properly extension into the Chrome by this steps:

Open `chrome://extensions/` then Load Unpacked Extension and point to build directory in the source of project.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gemd36bz3ij63dacjmc.png)

Here we go! Extension works. But this is only beginning.

##Content script
Now I've created directory `extension` where I want to put all files related to work with browsing page.
Then created new file `extension\content.ts` and put sample code `console.log('hello')`

Now it will be good to compile this file as separate in output. 

Edit `config\webpack.config.js` Go to line 205 and edit value of `entry` property
```
  entry: {
      main: paths.appIndexJs,
      contentScript: paths.contentScript
    },
```
Then add in `config\paths.js` property with path to contentScript
```
 contentScript: resolveModule(resolveApp, 'extension/content'),
```
Good... I have content script file in build directory but it contains hash which during development will make difficulties as it need to be statically typed into `manifest.json`

So for development process edit `scripts\build.js` and edit line 50 and set running configFactory with `development` parameter.
```
const config = configFactory('development');
```
REMEMBER if this script will be used to make final build it needs to be switched back to `production` value.

Yes, the right solution is to put parameter with value of environment variable. But I will skip this for now as do t manually is enough for me.

Now I can check if everything is allright...
```
npm run build
```
and the result is:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rf3pyp7gmh5fnsdyyxow.png)

Of course! Webpack in development mode is set to put all files content to bundle.js so it needs to be updated.

Go to `webpack.config.js` line 216-218 and change development value of filename to be dynamic based on name of entries.
```
filename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].js'
        : isEnvDevelopment && 'static/js/[name].js',
```

Ooops next issue

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bslp2145wzvcz4pr10ri.png)

It means I need to turn off fast refresh
Powershell
```
$env:FAST_REFRESH="false"  
```
CMD
```
set FAST_REFRESH=false
```

 
Voilà! Build success and now it is time to make an last update to manifest file to include content script. 

```
{
  "name": "Chrome Extension",
  "description": "Chrome Extension Description",
  "version": "1.0",
  "manifest_version": 3,
  "action": {
    "default_popup": "index.html",
    "default_title": "Open the popup"
  },
  "content_scripts": [
    {
      "matches": [
        "https://*.com/*"
      ],
      "js": [
        "static/js/contentScript.js"
      ],
      "run_at": "document_end"
    }
  ]
}
```
Refresh extension in Chrome and check console in devtools when enter for any page with com domain. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uv21vovai5d4akavvak5.png)

##Preparation to publish
This part will be described later.