├── latestBuild.zip
├── public
├── images
│ ├── react-logo-128.png
│ ├── react-logo-16.png
│ ├── react-logo-32.png
│ └── react-logo-48.png
├── background.js
├── index.html
└── manifest.json
├── src
├── index.js
├── index.css
├── App.js
├── App.css
└── logo.svg
├── .gitignore
├── Makefile
├── package.json
└── README.md
/latestBuild.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casendler/react-chrome-extension-mv3/HEAD/latestBuild.zip
--------------------------------------------------------------------------------
/public/images/react-logo-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casendler/react-chrome-extension-mv3/HEAD/public/images/react-logo-128.png
--------------------------------------------------------------------------------
/public/images/react-logo-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casendler/react-chrome-extension-mv3/HEAD/public/images/react-logo-16.png
--------------------------------------------------------------------------------
/public/images/react-logo-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casendler/react-chrome-extension-mv3/HEAD/public/images/react-logo-32.png
--------------------------------------------------------------------------------
/public/images/react-logo-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casendler/react-chrome-extension-mv3/HEAD/public/images/react-logo-48.png
--------------------------------------------------------------------------------
/public/background.js:
--------------------------------------------------------------------------------
1 | /*global chrome*/
2 | chrome.runtime.onInstalled.addListener(() => {
3 | console.log('Chrome extension successfully installed!');
4 | return;
5 | });
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 380px;
3 | height: 400px;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
7 | sans-serif;
8 | -webkit-font-smoothing: antialiased;
9 | -moz-osx-font-smoothing: grayscale;
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React Chrome Extension
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | /*global chrome*/
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
21 | );
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ####################################
2 | # Build command for Chrome Extension
3 | ####################################
4 |
5 | .PHONY: help build
6 |
7 | help:
8 | $(info ${HELP_MESSAGE})
9 | @exit 0
10 |
11 | build:
12 | @echo 'Removing prior build...'
13 | @rm -rf dist/*
14 | @rm -f latestBuild.zip
15 | @echo 'Prior build removed!'
16 | @echo 'Preparing new extension build..'
17 | @export INLINE_RUNTIME_CHUNK=false; \
18 | export GENERATE_SOURCEMAP=false; \
19 | yarn build
20 | @mkdir -p dist
21 | @cp -r build/* dist
22 | @echo 'Renaming files...'
23 | @mv dist/index.html dist/popup.html
24 | @echo 'Zipping up build files for upload...'
25 | @zip -r -X latestBuild.zip dist/*
26 | @echo 'New extension build ready for upload!'
27 | @exit 0
28 |
29 | define HELP_MESSAGE
30 |
31 | --- Run this command to prepare the build for upload ---
32 | $ make build
33 |
34 | endef
35 |
36 |
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-chrome-extension-mv3",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "4.0.3"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject"
18 | },
19 | "eslintConfig": {
20 | "extends": [
21 | "react-app",
22 | "react-app/jest"
23 | ]
24 | },
25 | "browserslist": {
26 | "production": [
27 | ">0.2%",
28 | "not dead",
29 | "not op_mini all"
30 | ],
31 | "development": [
32 | "last 1 chrome version",
33 | "last 1 firefox version",
34 | "last 1 safari version"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "React Chrome Extension",
4 | "version": "0.1",
5 | "description": "Boilerplate manifest for React based Chrome Extension using Google's Manifest v3",
6 | "background": {
7 | "service_worker": "./background.js"
8 | },
9 | "permissions": [],
10 | "author": "Christian Sendler",
11 | "homepage_url": "https://github.com/casendler/",
12 |
13 | "short_name": "React Extension",
14 | "action": {
15 | "default_popup": "popup.html",
16 | "default_title": "React Chrome Extension",
17 | "default_icon": {
18 | "16": "/images/react-logo-16.png",
19 | "32": "/images/react-logo-32.png",
20 | "48": "/images/react-logo-48.png",
21 | "128": "/images/react-logo-128.png"
22 | }
23 | },
24 | "icons": {
25 | "16": "/images/react-logo-16.png",
26 | "32": "/images/react-logo-32.png",
27 | "48": "/images/react-logo-48.png",
28 | "128": "/images/react-logo-128.png"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Chrome Extension (Manifest v3) Boilerplate
2 |
3 | Starting point for creating a Chrome Browser Extension popup and service-worker based background script. Contains a custom Makefile to prepare your build for an unpacked local upload to chrome://extensions and prepares a zip file for direct upload to the Chrome Web Store.
4 |
5 | Traditionally browser extensions are built using vanilla JS. While this is certainly feasible, it is often cumbersome and inefficient when developing more complex extensions with a rich UI.
6 |
7 | ## Available Scripts
8 |
9 | In the root project directory, you can run:
10 |
11 | ### `make build`
12 |
13 | Prepares the app build for upload to chrome://extensions/ and the Google Chrome Webstore \
14 | It correctly bundles your project and optimizes the build for use as a Chrome Extension. \
15 |
16 | This command creates two things:
17 |
18 | 1. A `./dist` directory that contains the files for `chrome://extensions/` -- use this folder to load the unpacked extension
19 | 2. A file called latestBuild.zip, this .zip can be uploaded to the Developer Console when submitting your extension to the Chrome Web Store
20 |
21 | #### Known Limitations
22 |
23 | - Does not currently handle [Options](https://developer.chrome.com/docs/extensions/mv3/options/) or [Content Scripts](https://developer.chrome.com/docs/extensions/mv3/content_scripts/). For most use cases the options.html page from Chrome is unecessary as you can build an options menu into your popup directly. In order to enable content scripts and the options page, I recommend using `react-app-rewired` to replace the standard single entry point with three entry points for the popup, options, and content scripts.
24 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------