How to inject a React app into a Chrome Extension as a Content Script
Update (1/28/2022): I updated the source code for this article to work with React 17 since a few people have been interested and asked about it. Find the updated version on GitHub: https://github.com/yosevu/react-content-script.
Chrome Extensions are great tools that give let you interact with the browser and websites in a variety of ways.
- Browser and Page actions which appear as icons in the browser toolbar and can show an optional popup window.
- New Tab pages which override the default new tab
- Options pages for configuring Chrome Extensions
- Chrome context menus which appear with a right click
- Notifications, which slide in at the top right of the screen
- Content scripts, which inject JavaScript into a web page and can alter its user interface
This article is about loading a React app in a content script. Install create-react-app
first if you don’t have it. Here is the repository if you just want to see the code.
Create a new React app
create-react-app react-content-script
Update the manifest file
- Find it in
/public/manifest.json
The manifest.json
file is required to provide important information for the Chrome Extension. The manifest_version
, name
, and version
are required for the Chrome Extension to be valid. The content_scripts
array is what we will inject our React app into https://www.google.com/
.
You can inject a content script into a specific page as we are doing here or you can use the match
key to specify the urls that you want your code to load in. For example ["<all_urls>"]
loads the content script matches any valid url scheme.
Update the App
component
Remove import logo from './logo.svg';
and replace the src
for the image tag with "https://facebook.github.io/react/img/logo.svg"
.
Update App.css
to be more presentable.
Append the app to the DOM
This is where we actually inject the content script into the DOM on https://www.google.com
. First examine the DOM of the website (or websites) that you want to inject a content script into. Then select an element based on where you want you want to append your code to the DOM.
I chose the <div class="ctr-p" id="viewport">
, but you might have to experiment to find what works best for you. You might not even need to inject any HTML at all.
Deploy
- Next build the app for production by running the command
yarn run build
form/package.json
. - Open the Extensions tab under more tools in the Customize and control Google Chrome menu (with the three vertical dots at the far right of the browser toolbar) or navigate to
chrome://extensions
. - Check the Developer mode checkbox.
- Click on Load unpacked extension… then find and select the
build
folder inreact-content-script/src/
to load the extension.
The extension failed to load because the "css"
and "js"
filenames we specified in the manifest are different than the ones generated by Webpack.
Modify the build output filenames
You could just rename main.css
and main.js
every time there are changes in your build e.g. main.cacbacc7.css
and main.f0a6875b.js
in this case, but a better solution is to modify the Webpack production configuration output filenames so you don’t have to update the manually when you want to load your extension. In order to access /config/webpack.config/prod
, you have to eject from Create React App.
- Eject from Create React App with
yarn run eject
and typey
to confirm. - Remove the generated hashes from the the CSS and JavaScript output filenames. Compare ORIGINAL to UPDATED in
/config/webpack.config/prod
.
// ... (Other configuration)// Note: defined here because it will be used more than once.
// ORIGINAL // const cssFilename = 'static/css/[name].[contenthash:8].css';
// UPDATED
const cssFilename = 'static/css/[name].css';// ... (Other configuration)output: {
// The build folder.
path: paths.appBuild,
// Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it.
// ORIGINAL // filename: 'static/js/[name].[chunkhash:8].js',
// UPDATED
filename: 'static/js/[name].js',// ... (Other configuration)
Rebuild and reload and verify that the filenames are correct before loading the build into Chrome.
That’s it. Thanks for reading and let me know if you have any questions.