├── .gitignore
├── README.md
├── config-overrides.js
├── example
├── README.md
├── config-overrides.js
├── package.json
├── public
│ ├── extension-manifest.json
│ ├── favicon.ico
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon48.png
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── robots.txt
│ └── web-app-manifest.json
├── src
│ ├── App.tsx
│ ├── extension.tsx
│ ├── graphql
│ │ ├── graphql-client.ts
│ │ └── queries.ts
│ ├── helpers.ts
│ ├── index.css
│ ├── index.tsx
│ ├── login
│ │ └── index.tsx
│ ├── potential-reviewers
│ │ └── index.tsx
│ ├── project-setup.ts
│ ├── react-app-env.d.ts
│ ├── reportWebVitals.ts
│ ├── required-token-scopes.png
│ ├── services
│ │ ├── assets-service.ts
│ │ ├── auth-service.ts
│ │ ├── environment-service.ts
│ │ └── local-storage-service.ts
│ └── setupTests.ts
├── tsconfig.json
└── yarn.lock
├── package.json
├── public
├── extension-manifest.json
├── favicon.ico
├── icon128.png
├── icon16.png
├── icon48.png
├── index.html
├── logo192.png
├── logo512.png
├── robots.txt
└── web-app-manifest.json
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── background
│ └── index.ts
├── index.css
├── index.tsx
├── logo.svg
├── project-setup.ts
├── react-app-env.d.ts
├── reportWebVitals.ts
├── services
│ ├── assets-service.ts
│ └── environment-service.ts
└── setupTests.ts
├── tsconfig.json
└── yarn.lock
/.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 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | .eslintcache
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Create React Chrome Extension - TS
2 |
3 | A lightweight boilerplate for building a **Chrome extension** and a standard **web app** with React, TypeScript and Webpack **at the same time**.
4 |
5 | ## How to use the boilerplate
6 |
7 | If you already know React, you can start coding your Chrome extension straight away with no build configuration.
8 |
9 | Watch a short demo on YouTube: [How to Build a Chrome Extension with React and TypeScript in 3 Minutes](https://youtu.be/qIuaHkXU0zM)
10 |
11 | ### Setup options
12 |
13 | You can change the options used in the `setupProject` function in [src/index.tsx](https://github.com/pixochi/create-react-chrome-extension-ts/blob/main/src/index.tsx) to specify your React root component, and where the React root component will be rendered.
14 |
15 | `rootElement` - your React root component that will be either rendered as a standard web app or injected to a web page by your Chrome extension
16 |
17 | `injectExtensionTo` - a CSS selector for an element on a web page to which the extension will be injected to
18 |
19 | `injectWebAppTo` - a CSS selector for an element to which the web app will be rendered if the app runs in development mode with `yarn start` or is built as a standard web app with `yarn build:web-app`
20 |
21 | **Default options:**
22 | ```javascript
23 | setupProject({
24 | rootElement: (
25 |
26 |
27 |
28 | ),
29 | injectExtensionTo: "body",
30 | injectWebAppTo: "#root",
31 | });
32 | ```
33 |
34 | ### Manifest files
35 |
36 | The boilerplate contains 2 manifest files: an extension manifest for Google Chrome and a web app manifest if you want to make a PWA build.
37 |
38 | `extension manifest` - [extension-manifest.json](https://github.com/pixochi/create-react-chrome-extension-ts/blob/main/public/extension-manifest.json)
39 |
40 | `web app manifest` - [web-app-manifest.json](https://github.com/pixochi/create-react-chrome-extension-ts/blob/main/public/web-app-manifest.json)
41 |
42 | The `build` folder will contain either of them depending on which build script you run - `yarn build:extension` or `yarn build:web-app`
43 |
44 | ### Background scripts
45 |
46 | If your Chrome extension needs to use background scripts, add them to `src/background/index.ts`.
47 |
48 | ## Available Scripts
49 |
50 | In the project directory, you can run:
51 |
52 | ### `yarn start`
53 |
54 | Runs the app in the development mode.\
55 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
56 |
57 | The page will reload if you make edits.\
58 | You will also see any lint errors in the console.
59 |
60 | ### `yarn build:extension`
61 |
62 | **Builds the extension** for production to the `build` folder.\
63 | It correctly bundles React in production mode and optimizes the build for the best performance.
64 |
65 | The build is minified and your extension is ready to be used in Developer mode or published to the Google Web Store!.
66 |
67 | #### Open the extension in Developer mode
68 |
69 | 1. Open the Extension Management page by navigating to [chrome://extensions](chrome://extensions).
70 | 2. Enable Developer Mode by clicking the toggle switch next to **Developer mode**.
71 | 3. Click the **LOAD UNPACKED** button and select the extension directory.
72 |
73 | ### `yarn build:web-app`
74 |
75 | **Builds the web app** for production to the `build` folder.\
76 | It correctly bundles React in production mode and optimizes the build for the best performance.
77 |
78 | The build is minified and the filenames include the hashes.\
79 | Your app is ready to be deployed!
80 |
81 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
82 |
83 | ### `yarn test`
84 |
85 | Launches the test runner in the interactive watch mode.\
86 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
87 |
88 | ## Learn More
89 |
90 | You can read more in the Medium article: [How to Build a Chrome Extension with React, TypeScript and Webpack: From creating a boilerplate to publishing a complete extension to Chrome Web Store](https://jakub-kozak.medium.com/how-to-build-a-chrome-extension-with-react-typescript-and-webpack-92e806ce2e16).
91 |
92 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
2 | const FileManagerPlugin = require("filemanager-webpack-plugin");
3 |
4 | // Adds a manifest file to the build according to the current context,
5 | // and deletes files from the build that are not needed in the current context
6 | const getFileManagerPlugin = () => {
7 | const isExtensionBuild = process.env.REACT_APP_BUILD_TARGET === "extension";
8 | const webAppBuildFiles = [
9 | "index.html",
10 | "favicon.ico",
11 | "logo192.png",
12 | "logo512.png",
13 | "robots.txt",
14 | "asset-manifest.json",
15 | ];
16 | const extensionBuildFiles = ["icon16.png", "icon48.png", "icon128.png"];
17 |
18 | const manifestFiles = {
19 | webApp: "build/web-app-manifest.json",
20 | extension: "build/extension-manifest.json",
21 | };
22 |
23 | return new FileManagerPlugin({
24 | events: {
25 | onEnd: {
26 | copy: [
27 | {
28 | source: isExtensionBuild
29 | ? manifestFiles.extension
30 | : manifestFiles.webApp,
31 | destination: "build/manifest.json",
32 | },
33 | ],
34 | delete: Object.values(manifestFiles).concat(
35 | (isExtensionBuild ? webAppBuildFiles : extensionBuildFiles).map(
36 | (filename) => `build/${filename}`
37 | )
38 | ),
39 | },
40 | },
41 | });
42 | };
43 |
44 | module.exports = {
45 | webpack: function (config) {
46 | const isExtensionBuild = process.env.REACT_APP_BUILD_TARGET === "extension";
47 |
48 | // The default webpack configuration from `Create React App` can be used
49 | // if the app is not built as a chrome extension with the `build:extension` script.
50 | if (!isExtensionBuild) {
51 | config.plugins = config.plugins.concat(getFileManagerPlugin());
52 | return config;
53 | }
54 | // The webpack configuration will be updated
55 | // for the production build of the extension.
56 | else {
57 | // Disable bundle splitting,
58 | // a single bundle file has to loaded as `content_script`.
59 | config.optimization.splitChunks = {
60 | cacheGroups: {
61 | default: false,
62 | },
63 | };
64 |
65 | // `false`: each entry chunk embeds runtime.
66 | // The extension is built with a single entry including all JS.
67 | // https://symfonycasts.com/screencast/webpack-encore/single-runtime-chunk
68 | config.optimization.runtimeChunk = false;
69 |
70 | config.entry = {
71 | // web extension
72 | main: "./src/index.tsx",
73 | // background script that has to be referenced in the extension manifest
74 | background: "./src/background/index.ts",
75 | };
76 |
77 | // Filenames of bundles must not include `[contenthash]`, so that they can be referenced in `extension-manifest.json`.
78 | // The `[name]` is taken from `config.entry` properties, so if we have `main` and `background` as properties, we get 2 output files - main.js and background.js.
79 | config.output.filename = "[name].js";
80 |
81 | // `MiniCssExtractPlugin` is used by the default CRA webpack configuration for
82 | // extracting CSS into separate files. The plugin has to be removed because it
83 | // uses `[contenthash]` in filenames of the separate CSS files.
84 | config.plugins = config.plugins
85 | .filter((plugin) => !(plugin instanceof MiniCssExtractPlugin))
86 | .concat(
87 | // `MiniCssExtractPlugin` is used with its default config instead,
88 | // which doesn't contain `[contenthash]`.
89 | new MiniCssExtractPlugin(),
90 | getFileManagerPlugin()
91 | );
92 |
93 | return config;
94 | }
95 | },
96 | };
97 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Extension built with the Create React Chrome Extension boilerplate
2 |
3 | The extension is also published in the Google Web Store:
4 | [GitHub PR reviewers](https://chrome.google.com/webstore/detail/github-pr-reviewers/lfhipcniiclmedbnbmkgdpoamecaheii)
5 |
6 | ## About
7 | The extension was bootstraped with [Create React Chrome Extension](https://github.com/pixochi/create-react-chrome-extension-ts) and adds :
8 | - local storage to keep users logged in
9 | - [Apollo Client](https://www.apollographql.com/docs/react/) for using the [GitHub GraphQL API](https://docs.github.com/en/free-pro-team@latest/graphql)
10 | - [Semantic UI React](https://react.semantic-ui.com/) for styling
11 |
12 | ## Available Scripts
13 |
14 | In the project directory, you can run:
15 |
16 | ### `yarn start`
17 |
18 | Runs the app in the development mode.\
19 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
20 |
21 | The page will reload if you make edits.\
22 | You will also see any lint errors in the console.
23 |
24 | ### `yarn build:extension`
25 |
26 | **Builds the extension** for production to the `build` folder.\
27 | It correctly bundles React in production mode and optimizes the build for the best performance.
28 |
29 | The build is minified and your extension is ready to be used in Developer mode or published to the Google Web Store!.\
30 |
31 | #### Open the extension in Developer mode
32 |
33 | 1. Open the Extension Management page by navigating to [chrome://extensions](chrome://extensions).
34 | 2. Enable Developer Mode by clicking the toggle switch next to **Developer mode**.
35 | 3. Click the **LOAD UNPACKED** button and select the extension directory.
36 |
37 | ### `yarn build:web-app`
38 |
39 | **Builds the web app** for production to the `build` folder.\
40 | It correctly bundles React in production mode and optimizes the build for the best performance.
41 |
42 | The build is minified and the filenames include the hashes.\
43 | Your app is ready to be deployed!
44 |
45 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
46 |
47 | ### `yarn test`
48 |
49 | Launches the test runner in the interactive watch mode.\
50 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
51 |
52 | ## Learn More
53 |
54 | You can learn more in the [Create React Chrome Extension documentation](https://github.com/pixochi/create-react-chrome-extension-ts).
55 |
--------------------------------------------------------------------------------
/example/config-overrides.js:
--------------------------------------------------------------------------------
1 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
2 | const FileManagerPlugin = require("filemanager-webpack-plugin");
3 |
4 | // Adds a manifest file to the build according to the current context,
5 | // and deletes files from the build that are not needed in the current context
6 | const getFileManagerPlugin = () => {
7 | const isExtensionBuild = process.env.REACT_APP_BUILD_TARGET === "extension";
8 | const webAppBuildFiles = [
9 | "index.html",
10 | "favicon.ico",
11 | "logo192.png",
12 | "logo512.png",
13 | "robots.txt",
14 | "asset-manifest.json",
15 | ];
16 | const extensionBuildFiles = ["icon16.png", "icon48.png", "icon128.png"];
17 |
18 | const manifestFiles = {
19 | webApp: "build/web-app-manifest.json",
20 | extension: "build/extension-manifest.json",
21 | };
22 |
23 | return new FileManagerPlugin({
24 | events: {
25 | onEnd: {
26 | copy: [
27 | {
28 | source: isExtensionBuild
29 | ? manifestFiles.extension
30 | : manifestFiles.webApp,
31 | destination: "build/manifest.json",
32 | },
33 | ],
34 | delete: Object.values(manifestFiles).concat(
35 | (isExtensionBuild ? webAppBuildFiles : extensionBuildFiles).map(
36 | (filename) => `build/${filename}`
37 | )
38 | ),
39 | },
40 | },
41 | });
42 | };
43 |
44 | module.exports = {
45 | webpack: function (config) {
46 | const isExtensionBuild = process.env.REACT_APP_BUILD_TARGET === "extension";
47 |
48 | // The default webpack configuration from `Create React App` can be used
49 | // if the app is not built as a chrome extension with the `build:extension` script.
50 | if (!isExtensionBuild) {
51 | config.plugins = config.plugins.concat(getFileManagerPlugin());
52 | return config;
53 | }
54 | // The webpack configuration will be updated
55 | // for the production build of the extension.
56 | else {
57 | // Disable bundle splitting,
58 | // a single bundle file has to loaded as `content_script`.
59 | config.optimization.splitChunks = {
60 | cacheGroups: {
61 | default: false,
62 | },
63 | };
64 |
65 | // `false`: each entry chunk embeds runtime.
66 | // The extension is built with a single entry including all JS.
67 | // https://symfonycasts.com/screencast/webpack-encore/single-runtime-chunk
68 | config.optimization.runtimeChunk = false;
69 |
70 | // The name of the extension bundle must not include `[contenthash]`,
71 | // so it can be referenced in `manifest.json` as `content_script`.
72 | config.output.filename = "main.js";
73 |
74 | // `MiniCssExtractPlugin` is used by the default CRA webpack configuration for
75 | // extracting CSS into separate files. The plugin has to be removed because it
76 | // uses `[contenthash]` in filenames of the separate CSS files.
77 | config.plugins = config.plugins
78 | .filter((plugin) => !(plugin instanceof MiniCssExtractPlugin))
79 | .concat(
80 | // `MiniCssExtractPlugin` is used with its default config instead,
81 | // which doesn't contain `[contenthash]`.
82 | new MiniCssExtractPlugin(),
83 | getFileManagerPlugin()
84 | );
85 |
86 | return config;
87 | }
88 | },
89 | };
90 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-react-chrome-extension-ts",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@apollo/client": "3.3.4",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "@types/chrome": "0.0.126",
11 | "@types/jest": "^26.0.15",
12 | "@types/node": "^12.0.0",
13 | "@types/react": "^16.9.53",
14 | "@types/react-dom": "^16.9.8",
15 | "filemanager-webpack-plugin": "3.0.0-beta.0",
16 | "graphql": "15.4.0",
17 | "react": "^17.0.1",
18 | "react-app-rewired": "2.1.6",
19 | "react-dom": "^17.0.1",
20 | "react-scripts": "4.0.1",
21 | "semantic-ui-css": "2.4.1",
22 | "semantic-ui-react": "2.0.1",
23 | "typescript": "4.1.2",
24 | "web-vitals": "^0.2.4"
25 | },
26 | "scripts": {
27 | "start": "react-app-rewired start",
28 | "build:extension": "REACT_APP_BUILD_TARGET=extension react-app-rewired build",
29 | "build:web-app": "REACT_APP_BUILD_TARGET=web-app react-app-rewired build",
30 | "test": "react-app-rewired test"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/example/public/extension-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Create React Chrome Extension - TypeScript",
4 | "version": "0.1.0",
5 | "description": "A lightweight boilerplate for building a Chrome extension with React, TypeScript and Webpack.",
6 | "author": "Jakub Pixochi Kozak",
7 | "icons": {
8 | "16": "icon16.png",
9 | "48": "icon48.png",
10 | "128": "icon128.png"
11 | },
12 | "content_scripts": [
13 | {
14 | "matches": [
15 | "https://github.com/*/*/pull/*",
16 | "https://github.com/*/*/compare/*"
17 | ],
18 | "js": ["main.js"],
19 | "css": ["main.css"]
20 | }
21 | ],
22 | "web_accessible_resources": ["static/*"],
23 | "permissions": ["storage"]
24 | }
25 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixochi/create-react-chrome-extension-ts/db05e81d73d5a955cd000862beb92342202e1a7a/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixochi/create-react-chrome-extension-ts/db05e81d73d5a955cd000862beb92342202e1a7a/example/public/icon128.png
--------------------------------------------------------------------------------
/example/public/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixochi/create-react-chrome-extension-ts/db05e81d73d5a955cd000862beb92342202e1a7a/example/public/icon16.png
--------------------------------------------------------------------------------
/example/public/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pixochi/create-react-chrome-extension-ts/db05e81d73d5a955cd000862beb92342202e1a7a/example/public/icon48.png
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |