The Goals
- Build a Chrome extension
- Base the extension on React
Instructions
- git clone https://github.com/ohaddahan/how-to-build-react-chrome-extension
- cd how-to-build-react-chrome-extension
- ./scripts/run.sh
- Load the unpacked extension into Chrome
The details
./scripts/run.sh is a fairly simple shell script to follow , here is how it works:
- Setup and use nvm, this part optional , and you comment this section if you like. I left it since I think it's easier to avoid version mismatches withnvm
- Create the React application by running npx create-react-app ${extName} --use-npmwhereextNameis an environment variable with default valuereact-chrome-extension.
- Install dependencies npm install npm-run-all webpack-cli axios @material-ui/core chalk.
- Copy contentScript.jsandbackground.jssample files from my repo into the new extensionsrcdirectory.
- Copy over the scriptsandicons.
- Run node scripts/prep.js(will elaborate on it later).
- Finally it will build our extension using npm run build
Why all these extra scripts?
Let me elaborate on the various issues I encountered while getting this to work.
- 
package.jsoncompatibility issues:- Changes done to pacakge.jsonby./scripts/prep.js:
 
- Changes done to 
  packageJSON['scripts']  = {
     "start": "react-scripts start",
     "eject": "react-scripts eject",
     "test":  "react-scripts test",
     "prebuild": "rm -fr build ; node scripts/validate.js",
     "build": "npm-run-all build:*",
     "build:app": "INLINE_RUNTIME_CHUNK=false react-scripts build",
     "build:files":  "node ./scripts/build.js",
     "prezip": "rm -fr *.zip",
     "zip": "npm-run-all zip:*",
     "zip:build": "cd build; zip -r ../build.zip * -x '*.DS_Store'",
     "zip:src": "zip -r src.zip src package.json README.md public -x '*.DS_Store'",
     "release": "npm-run-all build zip"
   };
   packageJSON['eslintConfig']  = { "extends": "react-app" };
There were no changes done to start , eject and test.
  prezip , zip , zip:build , zip:src and release are just regular helpers needed for any Chrome extension.                                                                                                 
- prebuildfirst cleans up the previous build and then runs- ./scripts/validate.js
 all this script does is check that- package.jsonand- manifiest.jsonare valid- JSONfiles and that the required
 Chrome settings exist.
 (some of the settings I used aren't really mandatory but are a very common case and you can remove them if you have such a special case
 - iconsand- browser_action)
- The use of - npm-run-allis purely for ease of use to run multiple targets from one target.
- 
Since we're using create-react-appit will usereact-scripts buildwhich doesn't expect to be used
 as an extension. The first thing we need to change is addingINLINE_RUNTIME_CHUNK=falsebeforereact-scripts build.
 This is done insidepackage.json, underscriptskey , you can see the result in the final file create byscripts/run.sh.
 If we won't use this flag Chrome will not run React properly and raise a
 Content Security Policy error since React will try to inline JavaScript code.- 
package.jsonatscriptskey ,"build:app": "INLINE_RUNTIME_CHUNK=false react-scripts build"
- Create React App - Advanced Configuration > By default, Create React App will embed the runtime script into index.html during the production build. When set to false, the script will not be embedded and will be imported as usual. This is normally required when dealing with CSP.
- 
Content Security Policy (CSP)
> Inline JavaScript will not be executed.
This restriction bans both inline <script>blocks and inline event handlers (e.g.<button onclick="...">). The first restriction wipes out a huge class of cross-site scripting attacks by making it impossible for you to accidentally execute script provided by a malicious third-party. It does, however, require you to write your code with a clean separation between content and behavior (which you should of course do anyway, right?). An example might make this clearer. You might try to write a Browser Action's popup as a single popup.html containing
 
- 
- 
script/build.jsis the last helper script we have, it simply runsnpx webpackon thecontentScript.js
 andbackground.js, after that it copies them into the build directory sincereact-scriptswon't do it for us.- 
manifest.jsoncompatibility issues:
 
- 
- We add - versionand- manifest_versionwhich- create-react-appdoesn't add but Chrome demands.
 We also add- iconswhich isn't mandatory but comes as an incompatible format from- create-react-app.
- We remove - background_color,- display,- start_urland- theme_colorwhich come from- create-react-appbut
 aren't compatible with Chrome.
- We also add - browser_action,- content_scripts,- permissions,- content_security_policy,- background
 which aren't mandatory but most likely needed by most and is missing.
Background and Content Scripts
Included are simple background.js and contentScript.js which send a GET request to https://postman-echo.com/get.
The reason I included them is mainly for completeness and as a reference for others on how they work with a React-Chrome-Extension.
Furthermore they are also an example of chrome.runtime 
What does it actually do?
This sample extension will add a div and a button inside it , as seen in the image below.
Once you click on the button it will send  a GET request to https://postman-echo.com/get and print the response.
How to load and debug it?
- Load the unpacked extension , make sure you're in developer mode and you select the - builddirectory.
 For more details see :
 How to use React.js to create a cross-browser extension in 5 minutes
 Chrome - Getting Started Tutorial and
- Enable background scripts , click on - background page (Inactive)and it will turn to- background page.
 And open- DevToolsfor the background scripts, without this you won't see any- console.logor
 any other action done in the background
- Inside your regular Chrome , open - DevToolsand you'll see the prints coming from- contentScript.js.
- The response from - https://postman-echo.com/getinside- DevTools.
- The background - DevToolsas you can see by the title.
- The prints from - background.jsand response from- https://postman-echo.com/getinside- DevTools.
Final thoughts
While trying to do this I was trying to follow a few other examples as shown in the references section.
Unfortunately none had all the elements I needed or explained all the details I elaborate on here.
I hope this will help anyone else who would like to create a React-Chrome-Extension.
(I'm sure my code can get better, open an issue on GitHub and I'll happily fix it)  
References
- How to use React.js to create a cross-browser extension in 5 minutes
- React Chrome Extension Boilerplate
- Create chrome extension with ReactJs using inject page strategy
- Chrome Extension boilerplate with ReactJS and vanilla JS examples
- Content Security Policy (CSP)
- Create React App - Advanced Configuration
- chrome.runtime - Official Docs
- Chrome - Getting Started Tutorial
 





 
    
Top comments (0)